<!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>[13529] sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events: Translate: Sync "Translation Events" from GitHub</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/13529">13529</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/13529","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>amieiro</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2024-04-15 13:37:55 +0000 (Mon, 15 Apr 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: Sync "Translation Events" from GitHub</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_htmlwpcontentpluginswporggptranslationeventsassetsjstranslationeventsjs">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/assets/js/translation-events.js</a></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_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>
<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_htmlwpcontentpluginswporggptranslationeventstemplateseventsformphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/templates/events-form.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventstemplateseventsheaderphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/templates/events-header.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventstemplateseventslistphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/templates/events-list.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventstemplateseventsmyeventsphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/templates/events-my-events.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><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsautoloadphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/autoload.php</a></li>
<li>sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/attendee/</li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludesattendeeattendeerepositoryphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/attendee/attendee-repository.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludesattendeeattendeephp">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/attendee/attendee.php</a></li>
<li>sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/event/</li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludeseventeventdatephp">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/event/event-date.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludeseventeventformhandlerphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/event/event-form-handler.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludeseventeventrepositorycachedphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/event/event-repository-cached.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludeseventeventrepositoryinterfacephp">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/event/event-repository-interface.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludeseventeventrepositoryphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/event/event-repository.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludeseventeventphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/event/event.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludeseventtextsnippetphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/event-text-snippet.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludesroutesuserhosteventphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/user/host-event.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludesupgradephp">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/upgrade.php</a></li>
</ul>

<h3>Removed Paths</h3>
<ul>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludesactiveeventscachephp">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/active-events-cache.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludeseventphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/event.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-04-15 08:28:54 UTC (rev 13528)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/assets/css/translation-events.css      2024-04-15 13:37:55 UTC (rev 13529)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3,17 +3,21 @@
</span><span class="cx" style="display: block; padding: 0 10px">        width: 140px;
</span><span class="cx" style="display: block; padding: 0 10px">        vertical-align: top;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> .translation-event-form #event-title,
</span><span class="cx" style="display: block; padding: 0 10px"> .translation-event-form #event-description {
</span><span class="cx" style="display: block; padding: 0 10px">        width: 30%;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> .translation-event-form input[type="text"],
</span><span class="cx" style="display: block; padding: 0 10px"> .translation-event-form input[type="date"] {
</span><span class="cx" style="display: block; padding: 0 10px">        width: 13%;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> .translation-event-form div {
</span><span class="cx" style="display: block; padding: 0 10px">        margin-top: 1em;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> .translation-event-form #submit-event {
</span><span class="cx" style="display: block; padding: 0 10px">        margin-left: 10%;
</span><span class="cx" style="display: block; padding: 0 10px">        width: 30%;
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -20,15 +24,13 @@
</span><span class="cx" style="display: block; padding: 0 10px">        margin-top: 1em;
</span><span class="cx" style="display: block; padding: 0 10px">        display: block;
</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-page-title {
-       border-bottom: #d9d8d8 thin solid;
-       padding-bottom: .5em;
-}
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> .event-details-page {
</span><span class="cx" style="display: block; padding: 0 10px">        border-bottom: #cfd4d4 thin solid;
</span><span class="cx" style="display: block; padding: 0 10px">        width: 60%;
</span><span class="cx" style="display: block; padding: 0 10px">        margin: 0 auto;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> .event-details-left {
</span><span class="cx" style="display: block; padding: 0 10px">        width: 77%;
</span><span class="cx" style="display: block; padding: 0 10px">        float: left;
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -36,39 +38,52 @@
</span><span class="cx" style="display: block; padding: 0 10px">        margin-right: 1%;
</span><span class="cx" style="display: block; padding: 0 10px">        padding-right: 1em;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> .event-details-right {
</span><span class="cx" style="display: block; padding: 0 10px">        width: 22%;
</span><span class="cx" style="display: block; padding: 0 10px">        float: right;
</span><span class="cx" style="display: block; padding: 0 10px">        padding-top: 1em;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> .event-details-stats {
</span><span class="cx" style="display: block; padding: 0 10px">        clear: both;
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        margin: 1em;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ margin: 0;
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> .event-details-stats table {
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        margin: 1rem;
+}
+.event-details-stats table {
</ins><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">+
</ins><span class="cx" style="display: block; padding: 0 10px"> .event-details-stats table td:first-child {
</span><span class="cx" style="display: block; padding: 0 10px">        text-align: left;
</span><span class="cx" style="display: block; padding: 0 10px">        word-break: break-all;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</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><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><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">        /* allow breaking the word anywhere so that it doesn't overflow */
</span><span class="cx" style="display: block; padding: 0 10px">        word-break: break-all;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> .event-details-stats-totals td {
</span><span class="cx" style="display: block; padding: 0 10px">        font-weight: bold;
</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-contributors li {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+.event-contributors li, .event-attendees li {
</ins><span class="cx" style="display: block; padding: 0 10px">         display: inline-block;
</span><span class="cx" style="display: block; padding: 0 10px">        list-style-type: none;
</span><span class="cx" style="display: block; padding: 0 10px">        margin-right: 1em;
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -75,21 +90,36 @@
</span><span class="cx" style="display: block; padding: 0 10px">        margin-bottom: .5em;
</span><span class="cx" style="display: block; padding: 0 10px">        width: 15em;
</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-contributors li .avatar {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+.event-contributors li .avatar, .event-attendees li .avatar {
</ins><span class="cx" style="display: block; padding: 0 10px">         border-radius: 50%;
</span><span class="cx" style="display: block; padding: 0 10px">        vertical-align: middle;
</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-attendees h2 {
+       margin-bottom: 0;
+}
+
+span.event-creator, span.event-you {
+       display: block;
+       margin-left: 55px;
+       margin-top: 1em;
+       font-size: smaller;
+}
</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><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> .translation-event-form button.save-draft {
</span><span class="cx" style="display: block; padding: 0 10px">        background: #fff;
</span><span class="cx" style="display: block; padding: 0 10px">        color: var(--gp-color-btn-primary-bg);
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> #event-url span {
</span><span class="cx" style="display: block; padding: 0 10px">        width: 10%;
</span><span class="cx" style="display: block; padding: 0 10px">        display: inline-block;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> span.event-list-date {
</span><span class="cx" style="display: block; padding: 0 10px">        display: block;
</span><span class="cx" style="display: block; padding: 0 10px">        color: #939393;
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -96,72 +126,94 @@
</span><span class="cx" style="display: block; padding: 0 10px">        font-size: .8em;
</span><span class="cx" style="display: block; padding: 0 10px">        font-weight: normal;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> span.event-list-date.events-i-am-attending {
</span><span class="cx" style="display: block; padding: 0 10px">        font-size: .8em;
</span><span class="cx" style="display: block; padding: 0 10px">        font-weight: normal;
</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-list-item a{
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+.event-list-item a {
</ins><span class="cx" style="display: block; padding: 0 10px">         font-weight: bold;
</span><span class="cx" style="display: block; padding: 0 10px">        font-size: 1.2em;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> li.event-list-item {
</span><span class="cx" style="display: block; padding: 0 10px">        list-style-type: none;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        margin-bottom: .5em;
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> li.event-list-item p {
</span><span class="cx" style="display: block; padding: 0 10px">        margin-top: 0;
</span><span class="cx" style="display: block; padding: 0 10px">        color: #5a5a5a;
</span><span class="cx" style="display: block; padding: 0 10px">        font-size: .95em;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> h2.event_page_title {
</span><span class="cx" style="display: block; padding: 0 10px">        border-bottom: #e0e0e0 thin solid;
</span><span class="cx" style="display: block; padding: 0 10px">        padding-bottom: 0.5em;
</span><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">+
</ins><span class="cx" style="display: block; padding: 0 10px"> .event-list-top-bar {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        display: block;
</del><span class="cx" style="display: block; padding: 0 10px">         width: 80%;
</span><span class="cx" style="display: block; padding: 0 10px">        margin: 0 auto;
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        text-align: right;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ display: grid;
+       grid-template-columns: 1fr auto;
+       align-items: center;
+       border-bottom: #d9d8d8 thin solid;
+       padding-bottom: .5em;
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> .event-list-nav li {
</span><span class="cx" style="display: block; padding: 0 10px">        display: inline;
</span><span class="cx" style="display: block; padding: 0 10px">        margin: 0 0.1rem;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> .event-list-nav li a {
</span><span class="cx" style="display: block; padding: 0 10px">        text-decoration: none;
</span><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">+
</ins><span class="cx" style="display: block; padding: 0 10px"> .event-list-nav li:not(:first-child):not(:last-child):before {
</span><span class="cx" style="display: block; padding: 0 10px">        content: " | ";
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> .event-left-col {
</span><span class="cx" style="display: block; padding: 0 10px">        width: 75%;
</span><span class="cx" style="display: block; padding: 0 10px">        float: left;
</span><span class="cx" style="display: block; padding: 0 10px">        border-right: var(--gp-color-secondary-400) thin solid;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> .event-right-col {
</span><span class="cx" style="display: block; padding: 0 10px">        width: 25%;
</span><span class="cx" style="display: block; padding: 0 10px">        float: left;
</span><span class="cx" style="display: block; padding: 0 10px">        padding-left: 1em;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> .event-attending-list {
</span><span class="cx" style="display: block; padding: 0 10px">        list-style-type: none;
</span><span class="cx" style="display: block; padding: 0 10px">        padding: 0;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> .event-attending-list li {
</span><span class="cx" style="display: block; padding: 0 10px">        margin-bottom: 1em;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> .event-list-nav li a.button {
</span><span class="cx" style="display: block; padding: 0 10px">        background-color: var(--gp-color-btn-primary-bg) !important;
</span><span class="cx" style="display: block; padding: 0 10px">        color: #fff !important;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> a.event-link-draft {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        color:#80807f;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ color: #80807f;
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-span.event-label-draft, span.event-details-join-expired, span.active-events-before-translation-table {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+span.event-label-draft,
+span.event-details-join-expired,
+span.active-events-before-translation-table {
</ins><span class="cx" style="display: block; padding: 0 10px">         font-weight: 500;
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        color: var( --gp-color-bubble-inactive-project-text );
-       border: 1px solid var( --gp-color-bubble-inactive-project-text );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ color: var(--gp-color-bubble-inactive-project-text);
+       border: 1px solid var(--gp-color-bubble-inactive-project-text);
</ins><span class="cx" style="display: block; padding: 0 10px">         font-size: .7em;
</span><span class="cx" style="display: block; padding: 0 10px">        margin-right: 0.3em;
</span><span class="cx" style="display: block; padding: 0 10px">        width: 6em;
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -170,28 +222,37 @@
</span><span class="cx" style="display: block; padding: 0 10px">        border-radius: 1em;
</span><span class="cx" style="display: block; padding: 0 10px">        text-transform: capitalize;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> span.active-events-before-translation-table a {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        color: var( --gp-color-bubble-inactive-project-text );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ color: var(--gp-color-bubble-inactive-project-text);
</ins><span class="cx" style="display: block; padding: 0 10px">         text-decoration: none;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> .active-events-before-translation-table {
</span><span class="cx" style="display: block; padding: 0 10px">        width: 100%;
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        border: 1px solid var( --gp-color-border-default );
-       background: var( --gp-color-status-waiting-subtle );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ border: 1px solid var(--gp-color-border-default);
+       background: var(--gp-color-status-waiting-subtle);
</ins><span class="cx" style="display: block; padding: 0 10px">         margin: 1rem 0;
</span><span class="cx" style="display: block; padding: 0 10px">        padding: 0.3rem 0.8rem;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> .event-page-wrapper {
</span><span class="cx" style="display: block; padding: 0 10px">        margin: 0 auto;
</span><span class="cx" style="display: block; padding: 0 10px">        width: 80%;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> .event-list {
</span><span class="cx" style="display: block; padding: 0 10px">        margin: 0;
</span><span class="cx" style="display: block; padding: 0 10px">        padding: 0;
</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-head {
-       border-bottom: var(--gp-color-secondary-400) thin solid;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+input[type="submit"].remove-as-host, input[type="submit"].convert-to-host {
+       text-align: center;
+       display: inline;
+       margin-top: 1em;
+       font-weight: bold;
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> input[type="submit"].attend-btn {
</span><span class="cx" style="display: block; padding: 0 10px">        width: 100%;
</span><span class="cx" style="display: block; padding: 0 10px">        text-align: center;
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -199,27 +260,36 @@
</span><span class="cx" style="display: block; padding: 0 10px">        margin-top: 1em;
</span><span class="cx" style="display: block; padding: 0 10px">        font-weight: bold;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-input[type="submit"].attending-btn, a.button.is-primary.attend-btn {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+input[type="submit"].attending-btn,
+a.button.is-primary.attend-btn {
</ins><span class="cx" style="display: block; padding: 0 10px">         width: 100%;
</span><span class="cx" style="display: block; padding: 0 10px">        text-align: center;
</span><span class="cx" style="display: block; padding: 0 10px">        display: block;
</span><span class="cx" style="display: block; padding: 0 10px">        margin-top: 1em;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> .event-details-date {
</span><span class="cx" style="display: block; padding: 0 10px">        background: #f4f4f4;
</span><span class="cx" style="display: block; padding: 0 10px">        color: #606161;
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        padding: .5em;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ padding: 0.1em .5em;
</ins><span class="cx" style="display: block; padding: 0 10px">         font-size: .9em;
</span><span class="cx" style="display: block; padding: 0 10px">        line-height: 1.8em;
</span><span class="cx" style="display: block; padding: 0 10px">        font-weight: 500;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        margin-bottom: 1em;
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> .event-page-edit-link {
</span><span class="cx" style="display: block; padding: 0 10px">        float: right;
</span><span class="cx" style="display: block; padding: 0 10px">        text-decoration: none;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        justify-self: end;
+       grid-column: 2;
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> .event-stats-summary {
</span><span class="cx" style="display: block; padding: 0 10px">        margin-top: 1em;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> .event-stats-summary summary {
</span><span class="cx" style="display: block; padding: 0 10px">        background: #f8f8f8;
</span><span class="cx" style="display: block; padding: 0 10px">        padding: 0.4em;
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -228,6 +298,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">        font-weight: 500;
</span><span class="cx" style="display: block; padding: 0 10px">        font-size: .9em;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> .event-stats-summary p.event-stats-text {
</span><span class="cx" style="display: block; padding: 0 10px">        margin: 0;
</span><span class="cx" style="display: block; padding: 0 10px">        background: #f8f8f8;
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -235,15 +306,23 @@
</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><span class="cx" style="display: block; padding: 0 10px"> }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-time.event-utc-time {
-       display: block;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+.event-details-date time {
</ins><span class="cx" style="display: block; padding: 0 10px">         font-size: .95em;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        font-weight: normal;
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-.event-utc-time:first-of-type{
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+.event-details-date time:last-child {
+       margin-bottom: 0;
+}
+
+.event-details-date time.full-time {
+       display: block;
</ins><span class="cx" style="display: block; padding: 0 10px">         border-bottom: #cdcdcd thin solid;
</span><span class="cx" style="display: block; padding: 0 10px">        padding-bottom: 0.5em;
</span><span class="cx" style="display: block; padding: 0 10px">        margin-bottom: 0.5em;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> span.event-details-date-label {
</span><span class="cx" style="display: block; padding: 0 10px">        font-weight: bold;
</span><span class="cx" style="display: block; padding: 0 10px">        color: #5a5a5a;
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -250,19 +329,46 @@
</span><span class="cx" style="display: block; padding: 0 10px">        display: block;
</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">+.event-host {
+       display: flex;
+       grid-column: 1;
+}
+
+p.event-sub-head {
+       grid-column: 1 / -1;
+       margin-top: 0;
+}
+
+a.event-page-edit-link {
+       font-size: .9em;
+       text-decoration: none;
+}
+
+a.event-page-edit-link:hover {
+       border-bottom: var(--gp-color-btn-primary-bg) thin solid;
+       text-decoration: none;
+}
+ul.text-snippets {
+       padding: 0;
+       margin-left: 160px;
+}
+.first-time-contributor-tada::after {
+       content: ' ðŸŽ‰';
+}
+
</ins><span class="cx" style="display: block; padding: 0 10px"> /* show the event-details-right below instead of on the right on mobile */
</span><span class="cx" style="display: block; padding: 0 10px"> @media (max-width: 768px) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        .event-page-wrapper, .event-details-right, .event-details-left {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       .event-page-wrapper,
+       .event-details-right,
+       .event-details-left {
</ins><span class="cx" style="display: block; padding: 0 10px">                 width: 100%;
</span><span class="cx" style="display: block; padding: 0 10px">                float: none;
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px">         .event-details-left {
</span><span class="cx" style="display: block; padding: 0 10px">                border-right: none;
</span><span class="cx" style="display: block; padding: 0 10px">                margin: 0;
</span><span class="cx" style="display: block; padding: 0 10px">                padding: 0;
</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 {
-               margin: 0;
-       }
</del><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsassetsjstranslationeventsjs"></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/js/translation-events.js</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/js/translation-events.js      2024-04-15 08:28:54 UTC (rev 13528)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/assets/js/translation-events.js        2024-04-15 13:37:55 UTC (rev 13529)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -9,6 +9,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                }
</span><span class="cx" style="display: block; padding: 0 10px">                                validateEventDates();
</span><span class="cx" style="display: block; padding: 0 10px">                                convertToUserLocalTime();
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                setInterval( convertToUserLocalTime, 10000 );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                                $( '.submit-event' ).on(
</span><span class="cx" style="display: block; padding: 0 10px">                                        'click',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -27,6 +28,16 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                                handleDelete()
</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">+
+                               $( '.text-snippet' ).on(
+                                       'click',
+                                       function ( e ) {
+                                               e.preventDefault();
+                                               var textArea        = $( this ).closest( 'div' ).find( 'textarea' );
+                                               var textAreaContent = textArea.val();
+                                               textArea.val( textAreaContent + $( this ).data( 'snippet' ) );
+                                       }
+                               );
</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">@@ -37,6 +48,15 @@
</span><span class="cx" style="display: block; padding: 0 10px">                 * @param isDraft         Whether the current event status is a draft or not
</span><span class="cx" style="display: block; padding: 0 10px">                 */
</span><span class="cx" style="display: block; padding: 0 10px">                function handleSubmit( eventStatus, isDraft ) {
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        const $form = $( '.translation-event-form' );
+                       if ( ! $form[0].reportValidity() ) {
+                               return;
+                       }
+
+                       if ( '' === $( '#event-title' ).val() ) {
+                               $gp.notices.error( 'Event title must be set.' );
+                               return;
+                       }
</ins><span class="cx" style="display: block; padding: 0 10px">                         if ( '' === $( '#event-start' ).val() ) {
</span><span class="cx" style="display: block; padding: 0 10px">                                $gp.notices.error( 'Event start date and time must be set.' );
</span><span class="cx" style="display: block; padding: 0 10px">                                return;
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -56,7 +76,6 @@
</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">                        $( '#event-form-action' ).val( eventStatus );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        const $form        = $( '.translation-event-form' );
</del><span class="cx" style="display: block; padding: 0 10px">                         const $is_creation = $( '#form-name' ).val() === 'create_event';
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        $.ajax(
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -148,12 +167,73 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><span class="cx" style="display: block; padding: 0 10px">                        timeElements.forEach(
</span><span class="cx" style="display: block; padding: 0 10px">                                function ( timeEl ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        const eventDateObj         = new Date( timeEl.getAttribute( 'datetime' ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 const datetime = timeEl.getAttribute( 'datetime' );
+                                       if ( ! datetime ) {
+                                               return;
+                                       }
+                                       const eventDateObj = new Date( datetime );
+                                       timeEl.title       = eventDateObj.toUTCString();
+
</ins><span class="cx" style="display: block; padding: 0 10px">                                         const userTimezoneOffset   = new Date().getTimezoneOffset();
</span><span class="cx" style="display: block; padding: 0 10px">                                        const userTimezoneOffsetMs = userTimezoneOffset * 60 * 1000;
</span><span class="cx" style="display: block; padding: 0 10px">                                        const userLocalDateTime    = new Date( eventDateObj.getTime() - userTimezoneOffsetMs );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        const options      = {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 if ( timeEl.classList.contains( 'relative' ) ) {
+                                               // Display the relative time.
+                                               const now    = new Date();
+                                               let diff     = userLocalDateTime - now;
+                                               let in_text  = 'in ';
+                                               let ago_text = '';
+                                               if ( diff < 0 ) {
+                                                       if ( timeEl.classList.contains( 'future' ) ) {
+                                                               // If an event transitions from future to past, reload the page to move it from active to past events and vice versa.
+                                                               location.reload();
+                                                       }
+                                                       in_text  = '';
+                                                       ago_text = ' ago';
+                                                       diff     = - diff;
+                                               }
+
+                                               const seconds    = Math.floor( diff / 1000 );
+                                               const minutes    = Math.floor( seconds / 60 );
+                                               const hours      = Math.floor( minutes / 60 );
+                                               const days       = Math.floor( hours / 24 );
+                                               const weeks      = Math.floor( days / 7 );
+                                               const months     = Math.floor( days / 30 );
+                                               const years      = Math.floor( days / 365.25 );
+                                               let relativeTime = '';
+                                               if ( years > 1 ) {
+                                                       if ( ! timeEl.classList.contains( 'hide-if-too-far' ) ) {
+                                                               relativeTime = years + ' year' + ( years > 1 ? 's' : '' );
+                                                       } else {
+                                                               in_text = '';
+                                                       }
+                                               } else if ( months > 1 ) {
+                                                       if ( ! timeEl.classList.contains( 'hide-if-too-far' ) ) {
+                                                               relativeTime = months + ' month' + ( months > 1 ? 's' : '' );
+                                                       } else {
+                                                               in_text = '';
+                                                       }
+                                               } else if ( weeks > 1 ) {
+                                                       if ( ! timeEl.classList.contains( 'hide-if-too-far' ) || weeks < 3 ) {
+                                                               relativeTime = weeks + ' week' + ( weeks > 1 ? 's' : '' );
+                                                       } else {
+                                                               in_text = '';
+                                                       }
+                                               } else if ( days > 0 ) {
+                                                       relativeTime = days + ' day' + ( days > 1 ? 's' : '' );
+                                               } else if ( hours > 0 ) {
+                                                       relativeTime = hours + ' hour' + ( hours > 1 ? 's' : '' );
+                                               } else if ( minutes > 0 ) {
+                                                       relativeTime = minutes + ' minute' + ( minutes > 1 ? 's' : '' );
+                                               } else {
+                                                       relativeTime = 'less than a minute';
+                                               }
+                                               timeEl.textContent = in_text + relativeTime + ago_text;
+                                               return;
+                                       }
+
+                                       const options = {
</ins><span class="cx" style="display: block; padding: 0 10px">                                                 weekday: 'short',
</span><span class="cx" style="display: block; padding: 0 10px">                                                year: 'numeric',
</span><span class="cx" style="display: block; padding: 0 10px">                                                month: 'short',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -162,6 +242,18 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                                minute: 'numeric',
</span><span class="cx" style="display: block; padding: 0 10px">                                                timeZoneName: 'short'
</span><span class="cx" style="display: block; padding: 0 10px">                                        };
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                        if ( timeEl.dataset.format ) {
+                                               if ( timeEl.dataset.format.includes( 'l' ) ) {
+                                                       options.weekday = 'long';
+                                               } else if ( ! timeEl.dataset.format.includes( 'D' ) ) {
+                                                       delete options.weekday;
+                                               }
+                                               if ( timeEl.dataset.format.includes( 'F' ) ) {
+                                                       options.month = 'long';
+                                               } else if ( timeEl.dataset.format.includes( 'm' ) || timeEl.dataset.format.includes( 'n' ) ) {
+                                                       options.month = 'numeric';
+                                               }
+                                       }
</ins><span class="cx" style="display: block; padding: 0 10px">                                         timeEl.textContent = userLocalDateTime.toLocaleTimeString( navigator.language, options );
</span><span class="cx" style="display: block; padding: 0 10px">                                }
</span><span class="cx" style="display: block; padding: 0 10px">                        );
</span></span></pre></div>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsautoloadphp"></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/autoload.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/autoload.php                         (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/autoload.php   2024-04-15 13:37:55 UTC (rev 13529)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,23 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+require_once __DIR__ . '/includes/upgrade.php';
+require_once __DIR__ . '/templates/helper-functions.php';
+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/host-event.php';
+require_once __DIR__ . '/includes/routes/user/my-events.php';
+require_once __DIR__ . '/includes/attendee/attendee.php';
+require_once __DIR__ . '/includes/attendee/attendee-repository.php';
+require_once __DIR__ . '/includes/event/event-date.php';
+require_once __DIR__ . '/includes/event/event.php';
+require_once __DIR__ . '/includes/event/event-repository-interface.php';
+require_once __DIR__ . '/includes/event/event-repository.php';
+require_once __DIR__ . '/includes/event/event-repository-cached.php';
+require_once __DIR__ . '/includes/event/event-form-handler.php';
+require_once __DIR__ . '/includes/stats-calculator.php';
+require_once __DIR__ . '/includes/stats-listener.php';
+require_once __DIR__ . '/includes/event-text-snippet.php';
</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/autoload.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_htmlwpcontentpluginswporggptranslationeventsincludesactiveeventscachephp"></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/active-events-cache.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/active-events-cache.php     2024-04-15 08:28:54 UTC (rev 13528)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/active-events-cache.php       2024-04-15 13:37:55 UTC (rev 13529)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1,53 +0,0 @@
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-<?php
-
-namespace Wporg\TranslationEvents;
-
-use Exception;
-
-class Active_Events_Cache {
-       public const CACHE_DURATION = 60 * 60 * 24; // 24 hours.
-       private const KEY           = 'translation-events-active-events';
-
-       /**
-        * Cache active events.
-        *
-        * @param Event[] $events Events to cache.
-        *
-        * @throws Exception When it fails to cache events.
-        */
-       public function cache( array $events ): void {
-               if ( ! wp_cache_set( self::KEY, $events, '', self::CACHE_DURATION ) ) {
-                       throw new Exception( 'Failed to cache active events' );
-               }
-       }
-
-       /**
-        * Returns the cached events, or null if nothing is cached.
-        *
-        * @return Event[]|null
-        * @throws Exception When it fails to retrieve cached events.
-        */
-       public function get(): ?array {
-               $result = wp_cache_get( self::KEY, '', false, $found );
-               if ( ! $found ) {
-                       return null;
-               }
-
-               if ( ! is_array( $result ) ) {
-                       throw new Exception( 'Cached events is not an array, something is wrong' );
-               }
-
-               return $result;
-       }
-
-       /**
-        * Invalidates the active events cache.
-        *
-        * @throws Exception When it fails to invalidate the cache.
-        */
-       public static function invalidate(): void {
-               if ( ! wp_cache_delete( self::KEY ) ) {
-                       throw new Exception( 'Failed to invalidate cached events' );
-               }
-       }
-}
</del></span></pre></div>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludesattendeeattendeerepositoryphp"></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/attendee/attendee-repository.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/attendee/attendee-repository.php                            (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/attendee/attendee-repository.php      2024-04-15 13:37:55 UTC (rev 13529)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,187 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+namespace Wporg\TranslationEvents\Attendee;
+
+use Exception;
+use WP_User;
+
+class Attendee_Repository {
+       /**
+        * @throws Exception
+        */
+       public function insert_attendee( Attendee $attendee ): void {
+               global $wpdb, $gp_table_prefix;
+               // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+               // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
+               // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
+               $wpdb->query(
+                       $wpdb->prepare(
+                               "insert ignore into {$gp_table_prefix}event_attendees (event_id, user_id, is_host) values (%d, %d, %d)",
+                               array(
+                                       'event_id' => $attendee->event_id(),
+                                       'user_id'  => $attendee->user_id(),
+                                       'is_host'  => $attendee->is_host() ? 1 : 0,
+                               ),
+                       ),
+               );
+               // phpcs:enable
+       }
+
+       /**
+        * Update an attendee.
+        *
+        * @param Attendee $attendee The attendee to update.
+        * @return void
+        */
+       public function update_attendee( Attendee $attendee ): void {
+               global $wpdb, $gp_table_prefix;
+
+               // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
+               // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
+               $wpdb->update(
+                       "{$gp_table_prefix}event_attendees",
+                       array( 'is_host' => $attendee->is_host() ? 1 : 0 ),
+                       array(
+                               'event_id' => $attendee->event_id(),
+                               'user_id'  => $attendee->user_id(),
+                       )
+               );
+               // phpcs:enable
+       }
+
+       /**
+        * @throws Exception
+        */
+       public function remove_attendee( int $event_id, int $user_id ): void {
+               global $wpdb, $gp_table_prefix;
+               // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
+               // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
+               $wpdb->delete(
+                       "{$gp_table_prefix}event_attendees",
+                       array(
+                               'event_id' => $event_id,
+                               'user_id'  => $user_id,
+                       ),
+                       array(
+                               '%d',
+                               '%d',
+                       ),
+               );
+               // phpcs:enable
+       }
+
+       /**
+        * @throws Exception
+        */
+       public function get_attendee( int $event_id, int $user_id ): ?Attendee {
+               global $wpdb, $gp_table_prefix;
+
+               // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+               // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
+               // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
+               $row = $wpdb->get_row(
+                       $wpdb->prepare(
+                               "
+                               select *
+                               from {$gp_table_prefix}event_attendees
+                               where event_id = %d
+                                 and user_id = %d
+                       ",
+                               array(
+                                       $event_id,
+                                       $user_id,
+                               ),
+                       )
+               );
+               // phpcs:enable
+
+               if ( ! $row ) {
+                       return null;
+               }
+
+               $attendee = new Attendee( $row->event_id, $row->user_id );
+               if ( '1' === $row->is_host ) {
+                       $attendee->mark_as_host();
+               }
+
+               return $attendee;
+       }
+
+       /**
+        * @return Attendee[] Attendees of the event.
+        */
+       public function get_attendees( int $event_id ): array { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
+               // TODO.
+               return array();
+       }
+
+       /**
+        * Get the hosts' users for an event.
+        *
+        * @param int $event_id The id of the event.
+        * @return array[Attendee] The hosts of the event.
+        */
+       public function get_hosts( int $event_id ): array {
+               global $wpdb, $gp_table_prefix;
+
+               // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+               // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
+               // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
+               $host_ids = $wpdb->get_col(
+                       $wpdb->prepare(
+                               "
+                               select user_id
+                               from {$gp_table_prefix}event_attendees
+                               where event_id = %d and is_host = 1
+                       ",
+                               array(
+                                       $event_id,
+                               )
+                       )
+               );
+               // phpcs:enable
+
+               $hosts = array();
+               foreach ( $host_ids as $host_id ) {
+                       $hosts[] = $this->get_attendee( $event_id, $host_id );
+               }
+               return $hosts;
+       }
+
+       /**
+        * @deprecated
+        * TODO: This method should be moved out of this class because it's not about attendance,
+        *       it returns events that match a condition (have a user as attendee), so it belongs in an event repository.
+        *       However, since we don't have an event repository yet, the method is placed here for now.
+        *       When the method is moved to an event repository, it should return Event instances instead of event ids.
+        *
+        * @return int[] Event ids.
+        */
+       public function get_events_for_user( int $user_id ): array {
+               global $wpdb, $gp_table_prefix;
+
+               // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+               // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
+               // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
+               $rows = $wpdb->get_results(
+                       $wpdb->prepare(
+                               "
+                               select event_id
+                               from {$gp_table_prefix}event_attendees
+                               where user_id = %d
+                       ",
+                               array(
+                                       $user_id,
+                               )
+                       )
+               );
+               // phpcs:enable
+
+               return array_map(
+                       function ( object $row ) {
+                               return intval( $row->event_id );
+                       },
+                       $rows
+               );
+       }
+}
</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/attendee/attendee-repository.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_htmlwpcontentpluginswporggptranslationeventsincludesattendeeattendeephp"></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/attendee/attendee.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/attendee/attendee.php                               (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/attendee/attendee.php 2024-04-15 13:37:55 UTC (rev 13529)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,47 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+namespace Wporg\TranslationEvents\Attendee;
+
+use Exception;
+
+class Attendee {
+       private int $event_id;
+       private int $user_id;
+       private bool $is_host;
+
+       /**
+        * @throws Exception
+        */
+       public function __construct( int $event_id, int $user_id ) {
+               if ( $event_id < 1 ) {
+                       throw new Exception( 'invalid event id' );
+               }
+               if ( $user_id < 1 ) {
+                       throw new Exception( 'invalid user id' );
+               }
+
+               $this->event_id = $event_id;
+               $this->user_id  = $user_id;
+               $this->is_host  = false;
+       }
+
+       public function event_id(): int {
+               return $this->event_id;
+       }
+
+       public function user_id(): int {
+               return $this->user_id;
+       }
+
+       public function is_host(): bool {
+               return $this->is_host;
+       }
+
+       public function mark_as_host(): void {
+               $this->is_host = true;
+       }
+
+       public function mark_as_non_host(): void {
+               $this->is_host = false;
+       }
+}
</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/attendee/attendee.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_htmlwpcontentpluginswporggptranslationeventsincludeseventeventdatephp"></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/event/event-date.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/event/event-date.php                                (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/event/event-date.php  2024-04-15 13:37:55 UTC (rev 13529)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,173 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+namespace Wporg\TranslationEvents\Event;
+
+use DateTime;
+use DateTimeImmutable;
+use DateTimeZone;
+use Exception;
+
+/**
+ * Event_Date
+ *
+ * The event date is in local time, get the UTC time via the utc() method.
+ *
+ * @package Wporg\TranslationEvents
+ */
+abstract class Event_Date extends DateTimeImmutable {
+       protected $event_timezone;
+       public function __construct( string $date, DateTimeZone $timezone = null ) {
+               if ( ! $timezone ) {
+                       $timezone = new DateTimeZone( 'UTC' );
+               }
+
+               try {
+                       $utc_date = new DateTime( $date, new DateTimeZone( 'UTC' ) );
+                       $utc_date->setTimezone( $timezone );
+               } catch ( Exception $e ) {
+                       $utc_date = new DateTime( 'now', new DateTimeZone( 'UTC' ) );
+               }
+
+               parent::__construct( $utc_date->format( 'Y-m-d H:i:s' ), $timezone );
+               $this->event_timezone = $timezone;
+       }
+
+       public function timezone() {
+               return $this->event_timezone;
+       }
+
+       /**
+        * Get the standard formatted text for the date in UTC.
+        *
+        * @return string The date text.
+        */
+       public function __toString(): string {
+               return $this->utc()->format( 'Y-m-d H:i:s' );
+       }
+       /**
+        * Get the local formatted text for the date in UTC.
+        *
+        * @return DateTimeImmutable The date text.
+        */
+       public function utc(): DateTimeImmutable {
+               return $this->setTimeZone( new DateTimeZone( 'UTC' ) );
+       }
+
+       public function is_in_the_past() {
+               $current_date_time = new DateTimeImmutable( 'now', new DateTimeZone( 'UTC' ) );
+               return $this->utc() < $current_date_time;
+       }
+
+       public function print_relative_time_html( $format = false ) {
+               echo wp_kses(
+                       '<time
+                               class="event-utc-time relative' . ( $this->is_in_the_past() ? '' : ' future' ) . '"
+                               datetime="' . esc_attr( $this ) . '">' . esc_html( $format ? $this->format( $format ) : $this->get_variable_text() ) . '</time>',
+                       array(
+                               'time' => array(
+                                       'class'    => array(),
+                                       'datetime' => array(),
+                               ),
+                       )
+               );
+       }
+
+       public function print_time_html( $format = 'D, F j, Y H:i T' ) {
+               echo wp_kses(
+                       '<time
+                               class="event-utc-time full-time"
+                               data-format="' . esc_attr( $format ) . '"
+                               datetime="' . esc_attr( $this ) . '">' . esc_html( $this->format( $format ) ) . '</time>',
+                       array(
+                               'time' => array(
+                                       'class'       => array(),
+                                       'datetime'    => array(),
+                                       'data-format' => array(),
+                               ),
+                       )
+               );
+       }
+
+       /**
+        * Generate variable text depending on when the event starts or ends.
+        *
+        * @return string The end date text.
+        */
+       abstract public function get_variable_text(): string;
+}
+
+class Event_Start_Date extends Event_Date {
+       public function get_variable_text(): string {
+               $interval       = $this->diff( new DateTimeImmutable( 'now', new DateTimeZone( 'UTC' ) ) );
+               $hours_left     = ( $interval->d * 24 ) + $interval->h;
+               $hours_in_a_day = 24;
+
+               if ( $this->is_in_the_past() ) {
+                       if ( 0 === $hours_left ) {
+                               /* translators: %s: Number of minutes left. */
+                               return sprintf( _n( 'started %s minute ago', 'started %s minutes ago', $interval->i ), $interval->i );
+                       }
+
+                       if ( $hours_left >= $hours_in_a_day ) {
+                               /* translators: %s: Number of hours left. */
+                               return sprintf( _n( 'started %s hour ago', 'started %s hours ago', $hours_left ), $hours_left );
+                       }
+
+                       // translators: %s: A date.
+                       return sprintf( __( 'started %s', 'gp-translation-events' ), $this->format( 'D, F j, Y H:i T' ) );
+               }
+
+               if ( 0 === $hours_left ) {
+                       if ( ! $interval->i ) {
+                               return __( 'starts in less than a minute', 'gp-translation-events' );
+                       }
+                       /* translators: %s: Number of minutes left. */
+                       return sprintf( _n( 'starts in %s minute', 'starts in %s minutes', $interval->i, 'gp-translation-events' ), $interval->i );
+               }
+
+               if ( $hours_left <= $hours_in_a_day ) {
+                       /* translators: %s: Number of hours left. */
+                       $out = sprintf( _n( 'starts in %s hour', 'starts in %s hours', $hours_left, 'gp-translation-events' ), $hours_left );
+                       if ( $interval->i ) {
+                               /* translators: %s: Number of minutes left. */
+                               $out .= sprintf( _n( ' and %s minute', ' and %s minutes', $interval->i, 'gp-translation-events' ), $interval->i );
+                       }
+                       return $out;
+               }
+
+               // translators: %s: A date.
+               return sprintf( __( 'started %s', 'gp-translation-events' ), $this->format( 'D, F j, Y H:i T' ) );
+       }
+}
+
+class Event_End_Date extends Event_Date {
+       public function get_variable_text(): string {
+               if ( $this->is_in_the_past() ) {
+                       return sprintf( 'ended %s', $this->format( 'l, F j, Y' ) );
+               }
+
+               $interval       = $this->diff( new DateTimeImmutable( 'now', new DateTimeZone( 'UTC' ) ) );
+               $hours_left     = ( $interval->d * 24 ) + $interval->h;
+               $hours_in_a_day = 24;
+
+               if ( 0 === $hours_left ) {
+                       if ( ! $interval->i ) {
+                               return __( 'ends in less than a minute', 'gp-translation-events' );
+                       }
+                       /* translators: %s: Number of minutes left. */
+                       return sprintf( _n( 'ends in %s minute', 'ends in %s minutes', $interval->i, 'gp-translation-events' ), $interval->i );
+               }
+
+               if ( $hours_left <= $hours_in_a_day ) {
+                       /* translators: %s: Number of hours left. */
+                       $out = sprintf( _n( 'ends in %s hour', 'ends in %s hours', $hours_left, 'gp-translation-events' ), $hours_left );
+                       if ( $interval->i ) {
+                               /* translators: %s: Number of minutes left. */
+                               $out .= sprintf( _n( ' and %s minute', ' and %s minutes', $interval->i, 'gp-translation-events' ), $interval->i );
+                       }
+                       return $out;
+               }
+
+               return sprintf( 'until %s', $this->format( 'l, F j, Y H:i' ) );
+       }
+}
</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/event/event-date.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_htmlwpcontentpluginswporggptranslationeventsincludeseventeventformhandlerphp"></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/event/event-form-handler.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/event/event-form-handler.php                                (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/event/event-form-handler.php  2024-04-15 13:37:55 UTC (rev 13529)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,237 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+namespace Wporg\TranslationEvents\Event;
+
+use DateTime;
+use DateTimeZone;
+use Exception;
+use GP;
+use WP_Error;
+use Wporg\TranslationEvents\Attendee\Attendee;
+use Wporg\TranslationEvents\Attendee\Attendee_Repository;
+use Wporg\TranslationEvents\Stats_Calculator;
+
+class Event_Form_Handler {
+       private Event_Repository_Interface $event_repository;
+       private Attendee_Repository $attendee_repository;
+
+       public function __construct( Event_Repository_Interface $event_repository, Attendee_Repository $attendee_repository ) {
+               $this->event_repository    = $event_repository;
+               $this->attendee_repository = $attendee_repository;
+       }
+
+       public function handle( array $form_data ): void {
+               if ( ! is_user_logged_in() ) {
+                       wp_send_json_error( esc_html__( 'The user must be logged in.', 'gp-translation-events' ), 403 );
+               }
+               $action           = isset( $form_data['form_name'] ) ? sanitize_text_field( wp_unslash( $form_data['form_name'] ) ) : '';
+               $response_message = '';
+               $is_nonce_valid   = false;
+               $nonce_name       = '_event_nonce';
+               if ( ! in_array( $action, array( 'create_event', 'edit_event', 'delete_event' ), true ) ) {
+                       wp_send_json_error( esc_html__( 'Invalid form name.', 'gp-translation-events' ), 403 );
+               }
+               /**
+                * 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 ( 'create_event' === $action && ( ! $can_crud_event ) ) {
+                       wp_send_json_error( esc_html__( 'The user does not have permission to create an event.', 'gp-translation-events' ), 403 );
+               }
+               if ( 'edit_event' === $action ) {
+                       $event_id = isset( $form_data['event_id'] ) ? sanitize_text_field( wp_unslash( $form_data['event_id'] ) ) : '';
+                       $event    = $this->event_repository->get_event( $event_id );
+                       $attendee = $this->attendee_repository->get_attendee( $event->id(), get_current_user_id() );
+                       if ( ! ( $can_crud_event || ( $attendee instanceof Attendee && $attendee->is_host() ) || current_user_can( 'edit_post', $event_id ) || $event->author_id() === get_current_user_id() ) ) {
+                               wp_send_json_error( esc_html__( 'The user does not have permission to edit or delete the event.', 'gp-translation-events' ), 403 );
+                       }
+               }
+               if ( 'delete_event' === $action ) {
+                       $event_id         = isset( $form_data['event_id'] ) ? sanitize_text_field( wp_unslash( $form_data['event_id'] ) ) : '';
+                       $event            = $this->event_repository->get_event( $event_id );
+                       $attendee         = $this->attendee_repository->get_attendee( $event->id(), get_current_user_id() );
+                       $stats_calculator = new Stats_Calculator();
+                       if ( $stats_calculator->event_has_stats( $event->id() ) ) {
+                               wp_send_json_error( esc_html__( 'The event has stats so it cannot be deleted.', 'gp-translation-events' ), 422 );
+                       }
+                       if ( ! ( $can_crud_event || ( $attendee instanceof Attendee && $attendee->is_host() ) || current_user_can( 'delete_post', $event_id ) || get_current_user_id() === $event->author_id() ) ) {
+                               wp_send_json_error( esc_html__( 'You do not have permission to delete this event.', 'gp-translation-events' ), 403 );
+                       }
+               }
+               if ( isset( $form_data[ $nonce_name ] ) ) {
+                       $nonce_value = sanitize_text_field( wp_unslash( $form_data[ $nonce_name ] ) );
+                       if ( wp_verify_nonce( $nonce_value, $nonce_name ) ) {
+                               $is_nonce_valid = true;
+                       }
+               }
+               if ( ! $is_nonce_valid ) {
+                       wp_send_json_error( esc_html__( 'Nonce verification failed.', 'gp-translation-events' ), 403 );
+               }
+
+               if ( 'delete_event' === $action ) {
+                       // Delete event.
+                       $event_id = intval( sanitize_text_field( wp_unslash( $form_data['event_id'] ) ) );
+                       $event    = $this->event_repository->get_event( $event_id );
+                       if ( ! $event ) {
+                               wp_send_json_error( esc_html__( 'Event does not exist.', 'gp-translation-events' ), 404 );
+                       }
+
+                       $stats_calculator = new Stats_Calculator();
+                       try {
+                               $event_stats = $stats_calculator->for_event( $event->id() );
+                       } catch ( Exception $e ) {
+                               wp_send_json_error( esc_html__( 'Failed to calculate event stats.', 'gp-translation-events' ), 500 );
+                       }
+                       if ( ! empty( $event_stats->rows() ) ) {
+                               wp_send_json_error( esc_html__( 'Event has stats so it cannot be deleted.', 'gp-translation-events' ), 422 );
+                       }
+
+                       if ( false === $this->event_repository->delete_event( $event ) ) {
+                               $response_message = esc_html__( 'Failed to delete event.', 'gp-translation-events' );
+                               $event_status     = $event->status();
+                       } else {
+                               $response_message = esc_html__( 'Event deleted successfully.', 'gp-translation-events' );
+                               $event_status     = 'deleted';
+                       }
+               } else {
+                       // Create or update event.
+
+                       try {
+                               $new_event = $this->parse_form_data( $form_data );
+                       } catch ( InvalidTimeZone $e ) {
+                               wp_send_json_error( esc_html__( 'Invalid time zone.', 'gp-translation-events' ), 422 );
+                               return;
+                       } catch ( InvalidStart $e ) {
+                               wp_send_json_error( esc_html__( 'Invalid start date.', 'gp-translation-events' ), 422 );
+                               return;
+                       } catch ( InvalidEnd $e ) {
+                               wp_send_json_error( esc_html__( 'Invalid end date.', 'gp-translation-events' ), 422 );
+                               return;
+                       } catch ( InvalidStatus $e ) {
+                               wp_send_json_error( esc_html__( 'Invalid status.', 'gp-translation-events' ), 422 );
+                               return;
+                       } catch ( InvalidTitle $e ) {
+                               wp_send_json_error( esc_html__( 'Invalid title.', 'gp-translation-events' ), 422 );
+                               return;
+                       }
+                       if ( $new_event->end() < new DateTime( 'now', new DateTimeZone( 'UTC' ) ) ) {
+                               wp_send_json_error( esc_html__( 'Past events cannot be created or edited.', 'gp-translation-events' ), 422 );
+                               return;
+                       }
+
+                       // This is a list of slugs that are not allowed, as they conflict with the event URLs.
+                       $invalid_slugs = array( 'new', 'edit', 'attend', 'my-events' );
+                       if ( in_array( sanitize_title( $new_event->title() ), $invalid_slugs, true ) ) {
+                               wp_send_json_error( esc_html__( 'Invalid slug.', 'gp-translation-events' ), 422 );
+                       }
+
+                       if ( 'create_event' === $action ) {
+                               $result = $this->event_repository->insert_event( $new_event );
+                               if ( $result instanceof WP_Error ) {
+                                       wp_send_json_error( esc_html__( 'Failed to create event.', 'gp-translation-events' ), 422 );
+                                       return;
+                               }
+                               $response_message = esc_html__( 'Event created successfully.', 'gp-translation-events' );
+                       }
+                       if ( 'edit_event' === $action ) {
+                               $event = $this->event_repository->get_event( $new_event->id() );
+                               if ( ! $event ) {
+                                       wp_send_json_error( esc_html__( 'Event does not exist.', 'gp-translation-events' ), 404 );
+                               }
+
+                               try {
+                                       $event->set_status( $new_event->status() );
+                                       $event->set_title( $new_event->title() );
+                                       $event->set_description( $new_event->description() );
+                                       $event->set_timezone( $new_event->timezone() );
+                                       $event->set_times( $new_event->start(), $new_event->end() );
+                               } catch ( Exception $e ) {
+                                       wp_send_json_error( esc_html__( 'Failed to update event.', 'gp-translation-events' ), 422 );
+                                       return;
+                               }
+
+                               $result = $this->event_repository->update_event( $event );
+                               if ( $result instanceof WP_Error ) {
+                                       wp_send_json_error( esc_html__( 'Failed to update event.', 'gp-translation-events' ), 422 );
+                                       return;
+                               }
+                               $response_message = esc_html__( 'Event updated successfully', 'gp-translation-events' );
+                       }
+
+                       $event_id     = $new_event->id();
+                       $event_status = $new_event->status();
+               }
+
+               list( $permalink, $post_name ) = get_sample_permalink( $event_id );
+               $permalink                     = str_replace( '%pagename%', $post_name, $permalink );
+               wp_send_json_success(
+                       array(
+                               'message'        => $response_message,
+                               'eventId'        => $event_id,
+                               'eventUrl'       => str_replace( '%pagename%', $post_name, $permalink ),
+                               'eventStatus'    => $event_status,
+                               'eventEditUrl'   => esc_url( gp_url( '/events/edit/' . $event_id ) ),
+                               'eventDeleteUrl' => esc_url( gp_url( '/events/my-events/' ) ),
+                       )
+               );
+       }
+
+       // PHPCS erroneously thinks there should be only two throw tags.
+       // phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
+       /**
+        * @throws InvalidStart
+        * @throws InvalidEnd
+        * @throws InvalidTimeZone
+        * @throws InvalidTitle
+        * @throws InvalidStatus
+        */
+       // phpcs:enable
+       private function parse_form_data( array $data ): Event {
+               $event_id = isset( $data['event_id'] ) ? sanitize_text_field( wp_unslash( $data['event_id'] ) ) : 0;
+               $title    = isset( $data['event_title'] ) ? sanitize_text_field( wp_unslash( $data['event_title'] ) ) : '';
+
+               // This will be sanitized by sanitize_post which is called in wp_insert_post.
+               // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
+               $description    = isset( $data['event_description'] ) ? force_balance_tags( wp_unslash( $data['event_description'] ) ) : '';
+               $event_start    = isset( $data['event_start'] ) ? sanitize_text_field( wp_unslash( $data['event_start'] ) ) : '';
+               $event_end      = isset( $data['event_end'] ) ? sanitize_text_field( wp_unslash( $data['event_end'] ) ) : '';
+               $event_timezone = isset( $data['event_timezone'] ) ? sanitize_text_field( wp_unslash( $data['event_timezone'] ) ) : '';
+
+               $event_status = '';
+               if ( isset( $data['event_form_action'] ) && in_array( $data['event_form_action'], array( 'draft', 'publish', 'delete' ), true ) ) {
+                       $event_status = sanitize_text_field( wp_unslash( $data['event_form_action'] ) );
+               }
+
+               try {
+                       $timezone = new DateTimeZone( $event_timezone );
+               } catch ( Exception $e ) {
+                       throw new InvalidTimeZone();
+               }
+
+               try {
+                       $start = new Event_Start_Date( $event_start, $timezone );
+               } catch ( Exception $e ) {
+                       throw new InvalidStart();
+               }
+
+               try {
+                       $end = new Event_End_Date( $event_end, $timezone );
+               } catch ( Exception $e ) {
+                       throw new InvalidEnd();
+               }
+
+               $event = new Event(
+                       get_current_user_id(),
+                       $start->setTimezone( new DateTimeZone( 'UTC' ) ),
+                       $end->setTimezone( new DateTimeZone( 'UTC' ) ),
+                       $timezone,
+                       $event_status,
+                       $title,
+                       $description,
+               );
+               $event->set_id( intval( $event_id ) );
+               return $event;
+       }
+}
</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/event/event-form-handler.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_htmlwpcontentpluginswporggptranslationeventsincludeseventeventrepositorycachedphp"></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/event/event-repository-cached.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/event/event-repository-cached.php                           (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/event/event-repository-cached.php     2024-04-15 13:37:55 UTC (rev 13529)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,95 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+namespace Wporg\TranslationEvents\Event;
+
+use DateTimeImmutable;
+use DateTimeZone;
+use Exception;
+use WP_Error;
+
+class Event_Repository_Cached extends Event_Repository {
+       private const CACHE_DURATION    = DAY_IN_SECONDS;
+       private const ACTIVE_EVENTS_KEY = 'translation-events-active-events';
+
+       public function insert_event( Event $event ) {
+               $event_id_or_error = parent::insert_event( $event );
+               if ( $event_id_or_error instanceof WP_Error ) {
+                       return $event_id_or_error;
+               }
+
+               $this->invalidate_cache();
+               return $event_id_or_error;
+       }
+
+       public function update_event( Event $event ) {
+               $event_id_or_error = parent::update_event( $event );
+               if ( $event_id_or_error instanceof WP_Error ) {
+                       return $event_id_or_error;
+               }
+
+               $this->invalidate_cache();
+               return $event_id_or_error;
+       }
+
+       public function delete_event( Event $event ) {
+               parent::delete_event( $event );
+               $this->invalidate_cache();
+       }
+
+       public function get_current_events( int $page = -1, int $page_size = -1 ): Events_Query_Result {
+               $this->assert_pagination_arguments( $page, $page_size );
+
+               $cache_duration = self::CACHE_DURATION;
+               $now            = new DateTimeImmutable( 'now', new DateTimeZone( 'UTC' ) );
+               $boundary_start = $now;
+               $boundary_end   = $now->modify( "+$cache_duration seconds" );
+
+               $events = wp_cache_get( self::ACTIVE_EVENTS_KEY, '', false, $found );
+               if ( ! $found ) {
+                       $events = $this->get_events_active_between( $boundary_start, $boundary_end )->events;
+                       wp_cache_set( self::ACTIVE_EVENTS_KEY, $events, '', self::CACHE_DURATION );
+               } elseif ( ! is_array( $events ) ) {
+                       throw new Exception( 'Cached events is not an array, something is wrong' );
+               }
+
+               // Filter out events that aren't actually active at $at.
+               $events = array_values(
+                       array_filter(
+                               $events,
+                               function ( $event ) use ( $now ) {
+                                       return $event->start() <= $now && $now <= $event->end();
+                               }
+                       )
+               );
+
+               if ( empty( $events ) ) {
+                       return new Events_Query_Result( $events, $page, 0 );
+               }
+
+               // Split the list of all current events into pages.
+               // If no pagination parameters were supplied, we return the full list of events as a single page.
+
+               if ( $page >= 1 ) {
+                       // Pagination parameters were supplied.
+                       // Convert from 1-indexed to 0-indexed.
+                       --$page;
+               } else {
+                       // No pagination parameters were supplied.
+                       $page      = 0;
+                       $page_size = count( $events );
+               }
+
+               $pages = array_chunk( $events, $page_size );
+               if ( ! empty( $pages ) && isset( $pages[ $page ] ) ) {
+                       $events = $pages[ $page ];
+               } else {
+                       $events = array();
+               }
+
+               return new Events_Query_Result( $events, $page, count( $pages ) );
+       }
+
+       private function invalidate_cache(): void {
+               wp_cache_delete( self::ACTIVE_EVENTS_KEY );
+       }
+}
</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/event/event-repository-cached.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_htmlwpcontentpluginswporggptranslationeventsincludeseventeventrepositoryinterfacephp"></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/event/event-repository-interface.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/event/event-repository-interface.php                                (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/event/event-repository-interface.php  2024-04-15 13:37:55 UTC (rev 13529)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,151 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+namespace Wporg\TranslationEvents\Event;
+
+use Exception;
+use WP_Error;
+
+interface Event_Repository_Interface {
+       /**
+        * Insert a new Event.
+        *
+        * @param Event $event Event to insert.
+        *
+        * @return int|WP_Error The id of the inserted event, or an error.
+        */
+       public function insert_event( Event $event );
+
+       /**
+        * Update an Event.
+        *
+        * @param Event $event Event to update.
+        *
+        * @return int|WP_Error The id of the updated event, or an error.
+        */
+       public function update_event( Event $event );
+
+       /**
+        * Delete an Event.
+        *
+        * @param Event $event Event to delete.
+        *
+        * @return Event|false Deleted event or false on error.
+        */
+       public function delete_event( Event $event );
+
+       /**
+        * Get an Event.
+        *
+        * @param int $id Event id.
+        *
+        * @return Event|null
+        */
+       public function get_event( int $id ): ?Event;
+
+       /**
+        * @throws Exception
+        */
+
+       /**
+        * Get events that are currently active.
+        *
+        * @param int $page      Index of the page to return.
+        * @param int $page_size Page size.
+        *
+        * @return Events_Query_Result
+        * @throws Exception
+        */
+       public function get_current_events( int $page = -1, int $page_size = -1 ): Events_Query_Result;
+
+       /**
+        * Get events that will be active in the future.
+        *
+        * @param int $page      Index of the page to return.
+        * @param int $page_size Page size.
+        *
+        * @return Events_Query_Result
+        * @throws Exception
+        */
+       public function get_upcoming_events( int $page = -1, int $page_size = -1 ): Events_Query_Result;
+
+       /**
+        * Get events that were active in the past.
+        *
+        * @param int $page      Index of the page to return.
+        * @param int $page_size Page size.
+        *
+        * @return Events_Query_Result
+        * @throws Exception
+        */
+       public function get_past_events( int $page = -1, int $page_size = -1 ): Events_Query_Result;
+
+       /**
+        * Get events that are currently active for a given user.
+        *
+        * @param int $user_id   Id of the user.
+        * @param int $page      Index of the page to return.
+        * @param int $page_size Page size.
+        *
+        * @return Events_Query_Result
+        * @throws Exception
+        */
+       public function get_current_and_upcoming_events_for_user( int $user_id, int $page = -1, int $page_size = -1 ): Events_Query_Result;
+
+       /**
+        * Get events that are no longer active for a given user.
+        *
+        * @param int $user_id   Id of the user.
+        * @param int $page      Index of the page to return.
+        * @param int $page_size Page size.
+        *
+        * @return Events_Query_Result
+        * @throws Exception
+        */
+       public function get_past_events_for_user( int $user_id, int $page = -1, int $page_size = -1 ): Events_Query_Result;
+
+       /**
+        * Get events created by a given user.
+        *
+        * @param int $user_id   Id of the user.
+        * @param int $page      Index of the page to return.
+        * @param int $page_size Page size.
+        *
+        * @return Events_Query_Result
+        * @throws Exception
+        */
+       public function get_events_created_by_user( int $user_id, int $page = -1, int $page_size = -1 ): Events_Query_Result;
+
+       /**
+        * Get events hosted by a given user.
+        *
+        * @param int $user_id   Id of the user.
+        * @param int $page      Index of the page to return.
+        * @param int $page_size Page size.
+        *
+        * @return Events_Query_Result
+        * @throws Exception
+        */
+       public function get_events_hosted_by_user( int $user_id, int $page = -1, int $page_size = -1 ): Events_Query_Result;
+}
+
+class Events_Query_Result {
+       /**
+        * @var Event[]
+        */
+       public array $events;
+
+       public int $page_count;
+
+       /**
+        * @var int The current page (starts at 1).
+        */
+       public int $current_page;
+
+       public function __construct( array $events, int $current_page, int $page_count ) {
+               $this->events = $events;
+
+               // The call to intval() is required because WP_Query::max_num_pages is sometimes a float, despite being type-hinted as int.
+               $this->page_count   = intval( $page_count );
+               $this->current_page = intval( $current_page );
+       }
+}
</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/event/event-repository-interface.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_htmlwpcontentpluginswporggptranslationeventsincludeseventeventrepositoryphp"></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/event/event-repository.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/event/event-repository.php                          (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/event/event-repository.php    2024-04-15 13:37:55 UTC (rev 13529)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,420 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+namespace Wporg\TranslationEvents\Event;
+
+use DateTimeImmutable;
+use DateTimeZone;
+use Exception;
+use WP_Error;
+use WP_Post;
+use WP_Query;
+use Wporg\TranslationEvents\Attendee\Attendee_Repository;
+use Wporg\TranslationEvents\Translation_Events;
+
+class Event_Repository implements Event_Repository_Interface {
+       private const POST_TYPE = Translation_Events::CPT;
+
+       private Attendee_Repository $attendee_repository;
+
+       public function __construct( Attendee_Repository $attendee_repository ) {
+               $this->attendee_repository = $attendee_repository;
+       }
+
+       public function insert_event( Event $event ) {
+               $event_id_or_error = wp_insert_post(
+                       array(
+                               'post_type'    => self::POST_TYPE,
+                               'post_name'    => $event->slug(),
+                               'post_title'   => $event->title(),
+                               'post_content' => $event->description(),
+                               'post_status'  => $event->status(),
+                       )
+               );
+               if ( $event_id_or_error instanceof WP_Error ) {
+                       return $event_id_or_error;
+               }
+
+               $event->set_id( $event_id_or_error );
+               $this->update_event_meta( $event );
+               return $event->id();
+       }
+
+       public function update_event( Event $event ) {
+               $event_id_or_error = wp_update_post(
+                       array(
+                               'ID'           => $event->id(),
+                               'post_name'    => $event->slug(),
+                               'post_title'   => $event->title(),
+                               'post_content' => $event->description(),
+                               'post_status'  => $event->status(),
+                       )
+               );
+               if ( $event_id_or_error instanceof WP_Error ) {
+                       return $event_id_or_error;
+               }
+
+               $this->update_event_meta( $event );
+               return $event->id();
+       }
+
+       public function delete_event( Event $event ) {
+               $result = wp_trash_post( $event->id() );
+               if ( ! $result ) {
+                       return false;
+               }
+               return $event;
+       }
+
+       public function get_event( int $id ): ?Event {
+               $post = $this->get_event_post( $id );
+               if ( ! $post ) {
+                       return null;
+               }
+
+               try {
+                       $meta  = $this->get_event_meta( $id );
+                       $event = new Event(
+                               intval( $post->post_author ),
+                               $meta['start'],
+                               $meta['end'],
+                               $meta['timezone'],
+                               $post->post_status,
+                               $post->post_title,
+                               $post->post_content,
+                       );
+                       $event->set_id( $post->ID );
+                       $event->set_slug( $post->post_name );
+                       return $event;
+               } catch ( Exception $e ) {
+                       // This should not be possible as it means data in the database is invalid.
+                       // So we consider an invalid event to be not found.
+                       return null;
+               }
+       }
+
+       public function get_current_events( int $page = -1, int $page_size = -1 ): Events_Query_Result {
+               $now = new DateTimeImmutable( 'now', new DateTimeZone( 'UTC' ) );
+
+               return $this->get_events_active_between(
+                       $now,
+                       $now,
+                       array(),
+                       $page,
+                       $page_size
+               );
+       }
+
+       public function get_upcoming_events( int $page = - 1, int $page_size = - 1 ): Events_Query_Result {
+               $now = new DateTimeImmutable( 'now', new DateTimeZone( 'UTC' ) );
+
+               // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_query
+               // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_key
+               return $this->execute_events_query(
+                       $page,
+                       $page_size,
+                       array(
+                               'meta_query' => array(
+                                       array(
+                                               'key'     => '_event_start',
+                                               'value'   => $now->format( 'Y-m-d H:i:s' ),
+                                               'compare' => '>=',
+                                               'type'    => 'DATETIME',
+                                       ),
+                               ),
+                               'orderby'    => array( 'meta_value', 'ID' ),
+                               'order'      => 'ASC',
+                       )
+               );
+               // phpcs:enable
+       }
+
+       public function get_past_events( int $page = - 1, int $page_size = - 1 ): Events_Query_Result {
+               $now = new DateTimeImmutable( 'now', new DateTimeZone( 'UTC' ) );
+
+               // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_query
+               // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_key
+               return $this->execute_events_query(
+                       $page,
+                       $page_size,
+                       array(
+                               'meta_query' => array(
+                                       array(
+                                               'key'     => '_event_end',
+                                               'value'   => $now->format( 'Y-m-d H:i:s' ),
+                                               'compare' => '<',
+                                               'type'    => 'DATETIME',
+                                       ),
+                               ),
+                               'orderby'    => array( 'meta_value', 'ID' ),
+                               'order'      => 'DESC',
+                       )
+               );
+               // phpcs:enable
+       }
+
+       public function get_current_and_upcoming_events_for_user( int $user_id, int $page = -1, int $page_size = -1 ): Events_Query_Result {
+               $now = new DateTimeImmutable( 'now', new DateTimeZone( 'UTC' ) );
+
+               // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_query
+               // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_key
+               return $this->execute_events_query(
+                       $page,
+                       $page_size,
+                       array(
+                               'meta_query' => array(
+                                       array(
+                                               'key'     => '_event_end',
+                                               'value'   => $now->format( 'Y-m-d H:i:s' ),
+                                               'compare' => '>',
+                                               'type'    => 'DATETIME',
+                                       ),
+                               ),
+                               'meta_key'   => '_event_start',
+                               'orderby'    => 'meta_value',
+                               'order'      => 'ASC',
+                       ),
+                       $this->attendee_repository->get_events_for_user( $user_id ),
+               );
+               // phpcs:enable
+       }
+
+       public function get_past_events_for_user( int $user_id, int $page = -1, int $page_size = -1 ): Events_Query_Result {
+               $now = new DateTimeImmutable( 'now', new DateTimeZone( 'UTC' ) );
+
+               $user_events = $this->attendee_repository->get_events_for_user( $user_id );
+               if ( empty( $user_events ) ) {
+                       return new Events_Query_Result( array(), 1, 1 );
+               }
+
+               // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_query
+               // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_key
+               return $this->execute_events_query(
+                       $page,
+                       $page_size,
+                       array(
+                               'meta_query' => array(
+                                       array(
+                                               'key'     => '_event_end',
+                                               'value'   => $now->format( 'Y-m-d H:i:s' ),
+                                               'compare' => '<',
+                                               'type'    => 'DATETIME',
+                                       ),
+                               ),
+                               'meta_key'   => '_event_start',
+                               'meta_type'  => 'DATETIME',
+                               'orderby'    => 'meta_value',
+                               'order'      => 'DESC',
+                       ),
+                       $user_events
+               );
+               // phpcs:enable
+       }
+
+       public function get_events_created_by_user( int $user_id, int $page = -1, int $page_size = -1 ): Events_Query_Result {
+               // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_query
+               // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_key
+               return $this->execute_events_query(
+                       $page,
+                       $page_size,
+                       array(
+                               'post_status' => array( 'publish', 'draft' ),
+                               'author'      => $user_id,
+                               'meta_key'    => '_event_start',
+                               'orderby'     => 'meta_value',
+                               'order'       => 'DESC',
+                       )
+               );
+               // phpcs:enable
+       }
+
+       public function get_events_hosted_by_user( int $user_id, int $page = -1, int $page_size = -1 ): Events_Query_Result {
+               global $wpdb, $gp_table_prefix;
+
+               // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+               // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
+               // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
+               $events_user_is_hosting_ids = $wpdb->get_col(
+                       $wpdb->prepare(
+                               "
+                               select distinct event_id
+                               from {$gp_table_prefix}event_attendees
+                               where user_id = %d
+                               and is_host = 1
+                       ",
+                               array(
+                                       $user_id,
+                               )
+                       ),
+               );
+
+               if ( empty( $events_user_is_hosting_ids ) ) {
+                       return new Events_Query_Result( array(), 1, 1 );
+               }
+
+               // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_query
+               // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_key
+               return $this->execute_events_query(
+                       $page,
+                       $page_size,
+                       array(
+                               'post_status' => array( 'publish', 'draft' ),
+                               'meta_key'    => '_event_start',
+                               'orderby'     => 'meta_value',
+                               'order'       => 'DESC',
+                       ),
+                       $events_user_is_hosting_ids
+               );
+               // phpcs:enable
+       }
+
+       /**
+        * @throws Exception
+        */
+       protected function get_events_active_between(
+               DateTimeImmutable $boundary_start,
+               DateTimeImmutable $boundary_end,
+               array $filter_by_ids = array(),
+               int $page = -1,
+               int $page_size = -1
+       ): Events_Query_Result {
+               if ( $boundary_end < $boundary_start ) {
+                       throw new Exception( 'boundary end must not be before boundary start' );
+               }
+
+               // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_query
+               // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_key
+               $query_args = array(
+                       'meta_query' => array(
+                               array(
+                                       'key'     => '_event_start',
+                                       'value'   => $boundary_end->format( 'Y-m-d H:i:s' ),
+                                       'compare' => '<',
+                                       'type'    => 'DATETIME',
+                               ),
+                               array(
+                                       'key'     => '_event_end',
+                                       'value'   => $boundary_start->format( 'Y-m-d H:i:s' ),
+                                       'compare' => '>',
+                                       'type'    => 'DATETIME',
+                               ),
+                       ),
+                       'meta_key'   => '_event_start',
+                       'meta_type'  => 'DATETIME',
+                       'orderby'    => array( 'meta_value', 'ID' ),
+               );
+               // phpcs:enable
+
+               return $this->execute_events_query( $page, $page_size, $query_args, $filter_by_ids );
+       }
+
+       /**
+        * @throws Exception
+        */
+       protected function assert_pagination_arguments( int $page, int $page_size ) {
+               if ( -1 !== $page && $page <= 0 ) {
+                       throw new Exception( 'page must be greater than 0' );
+               }
+               if ( -1 !== $page_size && $page_size <= 0 ) {
+                       throw new Exception( 'page size must be greater than 0' );
+               }
+               if ( $page > 0 && -1 === $page_size ) {
+                       throw new Exception( 'if page is specified, page size must also be' );
+               }
+               if ( $page_size > 0 && -1 === $page ) {
+                       throw new Exception( 'if page size is specified, page must also be' );
+               }
+       }
+
+       /**
+        * @throws InvalidStart
+        * @throws InvalidEnd
+        * @throws InvalidTitle
+        * @throws InvalidStatus
+        * @throws Exception
+        */
+       private function execute_events_query( int $page, int $page_size, array $args, array $filter_by_ids = array() ): Events_Query_Result {
+               $this->assert_pagination_arguments( $page, $page_size );
+
+               $args = array_replace_recursive(
+                       $args,
+                       array(
+                               'post_type'      => self::POST_TYPE,
+                               'paged'          => $page,
+                               'posts_per_page' => $page_size,
+                       ),
+               );
+
+               if ( ! isset( $args['post_status'] ) ) {
+                       $args['post_status'] = 'publish';
+               }
+
+               if ( ! empty( $filter_by_ids ) ) {
+                       $args['post__in'] = $filter_by_ids;
+               }
+
+               $query  = new WP_Query( $args );
+               $posts  = $query->get_posts();
+               $events = array();
+
+               foreach ( $posts as $post ) {
+                       $meta = $this->get_event_meta( $post->ID );
+
+                       $title = $post->post_title;
+                       if ( empty( $title ) ) {
+                               // Previously, it was possible for events to not have a title, so there can be events in the database
+                               // that do not have a title. To work around that, we set the title of those events to a single space.
+                               $title = ' ';
+                       }
+
+                       $event = new Event(
+                               intval( $post->post_author ),
+                               $meta['start'],
+                               $meta['end'],
+                               $meta['timezone'],
+                               $post->post_status,
+                               $title,
+                               $post->post_content,
+                       );
+                       $event->set_id( $post->ID );
+                       $event->set_slug( $post->post_name );
+                       $events[] = $event;
+               }
+
+               return new Events_Query_Result( $events, $page, $query->max_num_pages );
+       }
+
+       private function get_event_post( int $event_id ): ?WP_Post {
+               if ( 0 === $event_id ) {
+                       return null;
+               }
+               $post = get_post( $event_id );
+               if ( ! ( $post instanceof WP_Post ) ) {
+                       return null;
+               }
+               if ( self::POST_TYPE !== $post->post_type ) {
+                       return null;
+               }
+
+               return $post;
+       }
+
+       /**
+        * @throws Exception
+        */
+       private function get_event_meta( int $event_id ): array {
+               $meta = get_post_meta( $event_id );
+               $utc  = new DateTimeZone( 'UTC' );
+
+               return array(
+                       'start'    => new Event_Start_Date( $meta['_event_start'][0], $utc ),
+                       'end'      => new Event_End_Date( $meta['_event_end'][0], $utc ),
+                       'timezone' => new DateTimeZone( $meta['_event_timezone'][0] ),
+               );
+       }
+
+       private function update_event_meta( Event $event ) {
+               update_post_meta( $event->id(), '_event_start', $event->start()->utc()->format( 'Y-m-d H:i:s' ) );
+               update_post_meta( $event->id(), '_event_end', $event->end()->utc()->format( 'Y-m-d H:i:s' ) );
+               update_post_meta( $event->id(), '_event_timezone', $event->timezone()->getName() );
+       }
+}
</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/event/event-repository.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_htmlwpcontentpluginswporggptranslationeventsincludeseventeventphp"></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/event/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/event/event.php                             (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/event/event.php       2024-04-15 13:37:55 UTC (rev 13529)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,169 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+namespace Wporg\TranslationEvents\Event;
+
+use DateTimeZone;
+use Exception;
+use Throwable;
+
+class InvalidTimeZone extends Exception {
+       public function __construct( Throwable $previous = null ) {
+               parent::__construct( 'Event time zone is invalid', 0, $previous );
+       }
+}
+
+class InvalidStart extends Exception {
+       public function __construct( Throwable $previous = null ) {
+               parent::__construct( 'Event start is invalid', 0, $previous );
+       }
+}
+
+class InvalidEnd extends Exception {
+       public function __construct( Throwable $previous = null ) {
+               parent::__construct( 'Event end is invalid', 0, $previous );
+       }
+}
+
+class InvalidTitle extends Exception {
+       public function __construct( Throwable $previous = null ) {
+               parent::__construct( 'Event title is invalid', 0, $previous );
+       }
+}
+
+class InvalidStatus extends Exception {
+       public function __construct( Throwable $previous = null ) {
+               parent::__construct( 'Event status is invalid', 0, $previous );
+       }
+}
+
+class Event {
+       private int $id = 0;
+       private int $author_id;
+       private Event_Start_Date $start;
+       private Event_End_Date $end;
+       private DateTimeZone $timezone;
+       private string $slug = '';
+       private string $status;
+       private string $title;
+       private string $description;
+
+       /**
+        * @throws InvalidStart
+        * @throws InvalidEnd
+        * @throws InvalidStatus
+        * @throws InvalidTitle
+        */
+       public function __construct(
+               int $author_id,
+               Event_Start_Date $start,
+               Event_End_Date $end,
+               DateTimeZone $timezone,
+               string $status,
+               string $title,
+               string $description
+       ) {
+               $this->author_id = $author_id;
+               $this->set_times( $start, $end );
+               $this->set_timezone( $timezone );
+               $this->set_status( $status );
+               $this->set_title( $title );
+               $this->set_description( $description );
+       }
+
+       public function id(): int {
+               return $this->id;
+       }
+
+       public function author_id(): int {
+               return $this->author_id;
+       }
+
+       public function start(): Event_Start_Date {
+               return $this->start;
+       }
+
+       public function end(): Event_End_Date {
+               return $this->end;
+       }
+
+       public function timezone(): DateTimeZone {
+               return $this->timezone;
+       }
+
+       public function slug(): string {
+               return $this->slug;
+       }
+
+       public function status(): string {
+               return $this->status;
+       }
+
+       public function title(): string {
+               return $this->title;
+       }
+
+       public function description(): string {
+               return $this->description;
+       }
+
+       public function set_id( int $id ): void {
+               $this->id = $id;
+       }
+
+       public function set_slug( string $slug ): void {
+               $this->slug = $slug;
+       }
+
+       /**
+        * @throws InvalidStart|InvalidEnd
+        */
+       public function set_times( Event_Start_Date $start, Event_End_Date $end ): void {
+               $this->validate_times( $start, $end );
+               $this->start = $start;
+               $this->end   = $end;
+       }
+
+       public function set_timezone( DateTimeZone $timezone ): void {
+               $this->timezone = $timezone;
+       }
+
+       /**
+        * @throws InvalidStatus
+        */
+       public function set_status( string $status ): void {
+               if ( ! in_array( $status, array( 'draft', 'publish' ), true ) ) {
+                       throw new InvalidStatus();
+               }
+               $this->status = $status;
+       }
+
+       /**
+        * @throws InvalidTitle
+        */
+       public function set_title( string $title ): void {
+               if ( ! $title ) {
+                       throw new InvalidTitle();
+               }
+               $this->title = $title;
+       }
+
+       public function set_description( string $description ): void {
+               $this->description = $description;
+       }
+
+       /**
+        * @throws InvalidStart
+        * @throws InvalidEnd
+        */
+       private function validate_times( Event_Start_Date $start, Event_End_Date $end ) {
+               if ( $end <= $start ) {
+                       throw new InvalidEnd();
+               }
+               if ( ! $start->getTimezone() || 'UTC' !== $start->getTimezone()->getName() ) {
+                       throw new InvalidStart();
+               }
+               if ( ! $end->getTimezone() || 'UTC' !== $end->getTimezone()->getName() ) {
+                       throw new InvalidEnd();
+               }
+       }
+}
</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/event/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_htmlwpcontentpluginswporggptranslationeventsincludeseventtextsnippetphp"></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/event-text-snippet.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/event-text-snippet.php                              (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/event-text-snippet.php        2024-04-15 13:37:55 UTC (rev 13529)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,21 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+namespace Wporg\TranslationEvents;
+
+class Event_Text_Snippet {
+
+       /**
+        * Generate links for text snippets.
+        *
+        * @return string The snippet links in a list.
+        */
+       public static function get_snippet_links(): string {
+               $snippets           = apply_filters( 'wporg_translation_events_snippets', array() );
+               $snippets_link_list = '<ul class="text-snippets">';
+               foreach ( $snippets as $snippet ) {
+                       $snippets_link_list .= sprintf( '<li><a href="#" class="text-snippet" data-snippet="%s">%s</a></li>', esc_html( $snippet['snippet'] ), esc_html( $snippet['title'] ) );
+               }
+               $snippets_link_list .= '</ul>';
+               return $snippets_link_list;
+       }
+}
</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/event-text-snippet.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_htmlwpcontentpluginswporggptranslationeventsincludeseventphp"></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/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/event.php   2024-04-15 08:28:54 UTC (rev 13528)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/event.php     2024-04-15 13:37:55 UTC (rev 13529)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1,80 +0,0 @@
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-<?php
-
-namespace Wporg\TranslationEvents;
-
-use DateTimeImmutable;
-use DateTimeZone;
-use Exception;
-
-class Event {
-       private int $id;
-       private DateTimeImmutable $start;
-       private DateTimeImmutable $end;
-       private DateTimeZone $timezone;
-
-       /**
-        * Make an Event from post meta.
-        *
-        * @throws Exception When dates are invalid.
-        */
-       public static function from_post_meta( int $id, array $meta ): Event {
-               if ( ! isset( $meta['_event_start'][0] ) || ! isset( $meta['_event_end'][0] ) || ! isset( $meta['_event_timezone'][0] ) ) {
-                       throw new Exception( 'Invalid event meta' );
-               }
-
-               return new Event(
-                       $id,
-                       DateTimeImmutable::createFromFormat( 'Y-m-d H:i:s', $meta['_event_start'][0], new DateTimeZone( 'UTC' ) ),
-                       DateTimeImmutable::createFromFormat( 'Y-m-d H:i:s', $meta['_event_end'][0], new DateTimeZone( 'UTC' ) ),
-                       new DateTimeZone( $meta['_event_timezone'][0] ),
-               );
-       }
-
-       private function __construct( int $id, DateTimeImmutable $start, DateTimeImmutable $end, DateTimeZone $timezone ) {
-               $this->id       = $id;
-               $this->start    = $start;
-               $this->end      = $end;
-               $this->timezone = $timezone;
-       }
-
-       public function id(): int {
-               return $this->id;
-       }
-
-       public function start(): DateTimeImmutable {
-               return $this->start;
-       }
-
-       public function end(): DateTimeImmutable {
-               return $this->end;
-       }
-
-       public function timezone(): DateTimeZone {
-               return $this->timezone;
-       }
-
-       /**
-        * Generate text for the end date.
-        *
-        * @param string $event_end The end date.
-        *
-        * @return string The end date text.
-        */
-       public static function get_end_date_text( string $event_end ): string {
-               $end_date_time     = new DateTimeImmutable( $event_end );
-               $current_date_time = new DateTimeImmutable( 'now', new DateTimeZone( 'UTC' ) );
-
-               $interval       = $end_date_time->diff( $current_date_time );
-               $hours_left     = ( $interval->d * 24 ) + $interval->h;
-               $hours_in_a_day = 24;
-
-               if ( 0 === $hours_left ) {
-                       /* translators: %s: Number of minutes left. */
-                       return sprintf( _n( 'ends in %s minute', 'ends in %s minutes', $interval->i ), $interval->i );
-               } elseif ( $hours_left <= $hours_in_a_day ) {
-                       /* translators: %s: Number of hours left. */
-                       return sprintf( _n( 'ends in %s hour', 'ends in %s hours', $hours_left ), $hours_left );
-               }
-               return sprintf( 'until %s', $end_date_time->format( 'M j, Y' ) );
-       }
-}
</del></span></pre></div>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludesrouteseventcreatephp"></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/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     2024-04-15 08:28:54 UTC (rev 13528)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/event/create.php       2024-04-15 13:37:55 UTC (rev 13529)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2,6 +2,8 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> namespace Wporg\TranslationEvents\Routes\Event;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+use Wporg\TranslationEvents\Event\Event_End_Date;
+use Wporg\TranslationEvents\Event\Event_Start_Date;
</ins><span class="cx" style="display: block; padding: 0 10px"> use Wporg\TranslationEvents\Routes\Route;
</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">@@ -14,18 +16,18 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        wp_safe_redirect( wp_login_url( home_url( $wp->request ) ) );
</span><span class="cx" style="display: block; padding: 0 10px">                        exit;
</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_form_title         = 'Create Event';
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $event_page_title         = 'Create Event';
</ins><span class="cx" style="display: block; padding: 0 10px">                 $event_form_name          = 'create_event';
</span><span class="cx" style="display: block; padding: 0 10px">                $css_show_url             = 'hide-event-url';
</span><span class="cx" style="display: block; padding: 0 10px">                $event_id                 = null;
</span><span class="cx" style="display: block; padding: 0 10px">                $event_title              = '';
</span><span class="cx" style="display: block; padding: 0 10px">                $event_description        = '';
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $event_timezone           = '';
-               $event_start              = '';
-               $event_end                = '';
</del><span class="cx" style="display: block; padding: 0 10px">                 $event_url                = '';
</span><span class="cx" style="display: block; padding: 0 10px">                $create_delete_button     = true;
</span><span class="cx" style="display: block; padding: 0 10px">                $visibility_delete_button = 'none';
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                $event_timezone           = null;
+               $event_start              = new Event_Start_Date( date_i18n( 'Y - m - d H:i' ) );
+               $event_end                = new Event_End_Date( date_i18n( 'Y - m - d H:i' ) );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $this->tmpl( 'events-form', get_defined_vars() );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span></span></pre></div>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludesrouteseventdetailsphp"></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/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    2024-04-15 08:28:54 UTC (rev 13528)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/event/details.php      2024-04-15 13:37:55 UTC (rev 13529)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -4,6 +4,9 @@
</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 GP;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+use Wporg\TranslationEvents\Attendee\Attendee;
+use Wporg\TranslationEvents\Attendee\Attendee_Repository;
+use Wporg\TranslationEvents\Event\Event_Repository_Interface;
</ins><span class="cx" style="display: block; padding: 0 10px"> use Wporg\TranslationEvents\Routes\Route;
</span><span class="cx" style="display: block; padding: 0 10px"> use Wporg\TranslationEvents\Stats_Calculator;
</span><span class="cx" style="display: block; padding: 0 10px"> use Wporg\TranslationEvents\Translation_Events;
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -12,6 +15,15 @@
</span><span class="cx" style="display: block; padding: 0 10px">  * Displays the event details page.
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span><span class="cx" style="display: block; padding: 0 10px"> class Details_Route extends Route {
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        private Event_Repository_Interface $event_repository;
+       private Attendee_Repository $attendee_repository;
+
+       public function __construct() {
+               parent::__construct();
+               $this->event_repository    = Translation_Events::get_event_repository();
+               $this->attendee_repository = Translation_Events::get_attendee_repository();
+       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">         public function handle( string $event_slug ): void {
</span><span class="cx" style="display: block; padding: 0 10px">                $user  = wp_get_current_user();
</span><span class="cx" style="display: block; padding: 0 10px">                $event = get_page_by_path( $event_slug, OBJECT, Translation_Events::CPT );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -18,6 +30,10 @@
</span><span class="cx" style="display: block; padding: 0 10px">                if ( ! $event ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $this->die_with_404();
</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 = $this->event_repository->get_event( $event->ID );
+               if ( ! $event ) {
+                       $this->die_with_404();
+               }
</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">                 * Filter the ability to create, edit, or delete an event.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -25,22 +41,27 @@
</span><span class="cx" style="display: block; padding: 0 10px">                 * @param bool $can_crud_event Whether the user can create, edit, or delete an event.
</span><span class="cx" style="display: block; padding: 0 10px">                 */
</span><span class="cx" style="display: block; padding: 0 10px">                $can_crud_event = apply_filters( 'gp_translation_events_can_crud_event', GP::$permission->current_user_can( 'admin' ) );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( 'publish' !== $event->post_status && ! $can_crud_event ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( 'publish' !== $event->status() && ! $can_crud_event ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                         $this->die_with_error( esc_html__( 'You are not authorized to view this page.', 'gp-translation-events' ), 403 );
</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">-                $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 ] );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $event_id          = $event->id();
+               $event_title       = $event->title();
+               $event_description = $event->description();
+               $event_start       = $event->start();
+               $event_end         = $event->end();
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                $attendee          = $this->attendee_repository->get_attendee( $event->id(), $user->ID );
+               $user_is_attending = $attendee instanceof Attendee;
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 $stats_calculator = new Stats_Calculator();
</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">-                        $event_stats  = $stats_calculator->for_event( $event );
-                       $contributors = $stats_calculator->get_contributors( $event );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $event_stats   = $stats_calculator->for_event( $event->id() );
+                       $contributors  = $stats_calculator->get_contributors( $event->id() );
+                       $attendees     = $stats_calculator->get_attendees_not_contributing( $event->id() );
+                       $attendee_repo = $this->attendee_repository;
+                       $hosts         = $this->attendee_repository->get_hosts( $event->id() );
+                       $projects      = $stats_calculator->get_projects( $event->id() );
</ins><span class="cx" style="display: block; padding: 0 10px">                 } catch ( Exception $e ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
</span><span class="cx" style="display: block; padding: 0 10px">                        error_log( $e );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -47,6 +68,11 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        $this->die_with_error( esc_html__( 'Failed to calculate event stats', 'gp-translation-events' ) );
</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">+                $is_editable_event = true;
+               if ( $event_end->is_in_the_past() || $stats_calculator->event_has_stats( $event->id() ) ) {
+                       $is_editable_event = false;
+               }
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 $this->tmpl( 'event', get_defined_vars() );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludesrouteseventeditphp"></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/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       2024-04-15 08:28:54 UTC (rev 13528)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/event/edit.php 2024-04-15 13:37:55 UTC (rev 13529)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2,9 +2,9 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> namespace Wporg\TranslationEvents\Routes\Event;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-use DateTime;
-use DateTimeZone;
-use Exception;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+use Wporg\TranslationEvents\Attendee\Attendee;
+use Wporg\TranslationEvents\Attendee\Attendee_Repository;
+use Wporg\TranslationEvents\Event\Event_Repository_Interface;
</ins><span class="cx" style="display: block; padding: 0 10px"> use Wporg\TranslationEvents\Routes\Route;
</span><span class="cx" style="display: block; padding: 0 10px"> use Wporg\TranslationEvents\Stats_Calculator;
</span><span class="cx" style="display: block; padding: 0 10px"> use Wporg\TranslationEvents\Translation_Events;
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -13,6 +13,15 @@
</span><span class="cx" style="display: block; padding: 0 10px">  * Displays the event edit page.
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span><span class="cx" style="display: block; padding: 0 10px"> class Edit_Route extends Route {
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        private Event_Repository_Interface $event_repository;
+       private Attendee_Repository $attendee_repository;
+
+       public function __construct() {
+               parent::__construct();
+               $this->event_repository    = Translation_Events::get_event_repository();
+               $this->attendee_repository = Translation_Events::get_attendee_repository();
+       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">         public function handle( int $event_id ): void {
</span><span class="cx" style="display: block; padding: 0 10px">                global $wp;
</span><span class="cx" style="display: block; padding: 0 10px">                if ( ! is_user_logged_in() ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -19,58 +28,49 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        wp_safe_redirect( wp_login_url( home_url( $wp->request ) ) );
</span><span class="cx" style="display: block; padding: 0 10px">                        exit;
</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 = 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() ) ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $event    = $this->event_repository->get_event( $event_id );
+               $attendee = $this->attendee_repository->get_attendee( $event->id(), get_current_user_id() );
+
+               if ( ! $event || ! ( ( $attendee instanceof Attendee && $attendee->is_host() ) || current_user_can( 'edit_post', $event->id() ) || $event->author_id() === get_current_user_id() ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                         $this->die_with_error( esc_html__( 'Event does not exist, or you do not have permission to edit it.', 'gp-translation-events' ), 403 );
</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 ( 'trash' === $event->post_status ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( 'trash' === $event->status() ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                         $this->die_with_error( esc_html__( 'You cannot edit a trashed event', 'gp-translation-events' ), 403 );
</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">                include ABSPATH . 'wp-admin/includes/post.php';
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $event_form_title              = 'Edit Event';
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $event_page_title              = 'Edit Event';
</ins><span class="cx" style="display: block; padding: 0 10px">                 $event_form_name               = 'edit_event';
</span><span class="cx" style="display: block; padding: 0 10px">                $css_show_url                  = '';
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $event_title                   = $event->post_title;
-               $event_description             = $event->post_content;
-               $event_status                  = $event->post_status;
-               list( $permalink, $post_name ) = get_sample_permalink( $event_id );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $event_title                   = $event->title();
+               $event_description             = $event->description();
+               $event_status                  = $event->status();
+               list( $permalink, $post_name ) = get_sample_permalink( $event->id() );
</ins><span class="cx" style="display: block; padding: 0 10px">                 $permalink                     = str_replace( '%pagename%', $post_name, $permalink );
</span><span class="cx" style="display: block; padding: 0 10px">                $event_url                     = get_site_url() . gp_url( wp_make_link_relative( $permalink ) );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $event_timezone                = get_post_meta( $event_id, '_event_timezone', true ) ?: '';
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $event_timezone                = $event->timezone();
+               $event_start                   = $event->start();
+               $event_end                     = $event->end();
</ins><span class="cx" style="display: block; padding: 0 10px">                 $create_delete_button          = false;
</span><span class="cx" style="display: block; padding: 0 10px">                $visibility_delete_button      = 'inline-flex';
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                if ( $event->end()->is_in_the_past() ) {
+                       $this->die_with_error( esc_html__( 'You cannot edit a past event.', 'gp-translation-events' ), 403 );
+               }
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 $stats_calculator = new Stats_Calculator();
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( ! $stats_calculator->event_has_stats( $event ) ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+               if ( $stats_calculator->event_has_stats( $event->id() ) ) {
+                       $this->die_with_error( esc_html__( 'You cannot edit an event with translations.', 'gp-translation-events' ), 403 );
+               }
+
+               if ( ! $stats_calculator->event_has_stats( $event->id() ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                         $current_user = wp_get_current_user();
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        if ( $current_user->ID === $event->post_author || current_user_can( 'manage_options' ) ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 if ( ( $current_user->ID === $event->author_id() || ( $attendee instanceof Attendee && $attendee->is_host() ) || current_user_can( 'manage_options' ) ) && ! $event->end()->is_in_the_past() ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                                 $create_delete_button = true;
</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><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                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' ) );
-               }
-
</del><span class="cx" style="display: block; padding: 0 10px">                 $this->tmpl( 'events-form', get_defined_vars() );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-
-       /**
-        * 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' );
-       }
</del><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludesrouteseventlistphp"></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/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       2024-04-15 08:28:54 UTC (rev 13528)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/event/list.php 2024-04-15 13:37:55 UTC (rev 13529)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -6,6 +6,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> use DateTimeZone;
</span><span class="cx" style="display: block; padding: 0 10px"> use Exception;
</span><span class="cx" style="display: block; padding: 0 10px"> use WP_Query;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+use Wporg\TranslationEvents\Event\Event_Repository_Interface;
</ins><span class="cx" style="display: block; padding: 0 10px"> use Wporg\TranslationEvents\Routes\Route;
</span><span class="cx" style="display: block; padding: 0 10px"> use Wporg\TranslationEvents\Translation_Events;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -13,6 +14,13 @@
</span><span class="cx" style="display: block; padding: 0 10px">  * Displays the event list page.
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span><span class="cx" style="display: block; padding: 0 10px"> class List_Route extends Route {
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        private Event_Repository_Interface $event_repository;
+
+       public function __construct() {
+               parent::__construct();
+               $this->event_repository = Translation_Events::get_event_repository();
+       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">         public function handle(): void {
</span><span class="cx" style="display: block; padding: 0 10px">                $current_datetime_utc = null;
</span><span class="cx" style="display: block; padding: 0 10px">                try {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -55,92 +63,11 @@
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px">                // phpcs:enable
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $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 );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $current_events_query        = $this->event_repository->get_current_events( $_current_events_paged, 10 );
+               $upcoming_events_query       = $this->event_repository->get_upcoming_events( $_upcoming_events_paged, 10 );
+               $past_events_query           = $this->event_repository->get_past_events( $_past_events_paged, 10 );
+               $user_attending_events_query = $this->event_repository->get_current_and_upcoming_events_for_user( get_current_user_id(), $_user_attending_events_paged, 10 );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $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 );
-
</del><span class="cx" style="display: block; padding: 0 10px">                 $this->tmpl( 'events-list', get_defined_vars() );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludesroutesuserattendeventphp"></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/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        2024-04-15 08:28:54 UTC (rev 13528)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/user/attend-event.php  2024-04-15 13:37:55 UTC (rev 13529)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2,6 +2,9 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> namespace Wporg\TranslationEvents\Routes\User;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+use Wporg\TranslationEvents\Attendee\Attendee;
+use Wporg\TranslationEvents\Attendee\Attendee_Repository;
+use Wporg\TranslationEvents\Event\Event_Repository_Interface;
</ins><span class="cx" style="display: block; padding: 0 10px"> use Wporg\TranslationEvents\Routes\Route;
</span><span class="cx" style="display: block; padding: 0 10px"> use Wporg\TranslationEvents\Translation_Events;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -11,6 +14,15 @@
</span><span class="cx" style="display: block; padding: 0 10px">  * If the user is currently marked as attending, they will be marked as not attending.
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span><span class="cx" style="display: block; padding: 0 10px"> class Attend_Event_Route extends Route {
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        private Event_Repository_Interface $event_repository;
+       private Attendee_Repository $attendee_repository;
+
+       public function __construct() {
+               parent::__construct();
+               $this->event_repository    = Translation_Events::get_event_repository();
+               $this->attendee_repository = Translation_Events::get_attendee_repository();
+       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">         public function handle( int $event_id ): void {
</span><span class="cx" style="display: block; padding: 0 10px">                $user = wp_get_current_user();
</span><span class="cx" style="display: block; padding: 0 10px">                if ( ! $user ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -17,28 +29,23 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        $this->die_with_error( esc_html__( 'Only logged-in users can attend events', 'gp-translation-events' ), 403 );
</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">-                $event = get_post( $event_id );
-
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $event = $this->event_repository->get_event( $event_id );
</ins><span class="cx" style="display: block; padding: 0 10px">                 if ( ! $event ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $this->die_with_404();
</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">-                $event_ids = get_user_meta( $user->ID, Translation_Events::USER_META_KEY_ATTENDING, true ) ?? array();
-               if ( ! $event_ids ) {
-                       $event_ids = array();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $attendee = $this->attendee_repository->get_attendee( $event->id(), $user->ID );
+               if ( $attendee instanceof Attendee && $attendee->is_host() && ( 1 === count( $this->attendee_repository->get_hosts( $event_id ) ) ) ) {
+                       $this->die_with_error( esc_html__( 'The event needs a host. Add a new host before stopping to attend the event.', 'gp-translation-events' ), 403 );
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-
-               if ( ! isset( $event_ids[ $event_id ] ) ) {
-                       // Not yet attending, mark as attending.
-                       $event_ids[ $event_id ] = true;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( $attendee instanceof Attendee ) {
+                       $this->attendee_repository->remove_attendee( $event->id(), $user->ID );
</ins><span class="cx" style="display: block; padding: 0 10px">                 } else {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        // Currently attending, mark as not attending.
-                       unset( $event_ids[ $event_id ] );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $attendee = new Attendee( $event->id(), $user->ID );
+                       $this->attendee_repository->insert_attendee( $attendee );
</ins><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">-                update_user_meta( $user->ID, Translation_Events::USER_META_KEY_ATTENDING, $event_ids );
-
-               wp_safe_redirect( gp_url( "/events/$event->post_name" ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         wp_safe_redirect( gp_url( "/events/{$event->slug()}" ) );
</ins><span class="cx" style="display: block; padding: 0 10px">                 exit;
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludesroutesuserhosteventphp"></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/host-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/host-event.php                          (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/user/host-event.php    2024-04-15 13:37:55 UTC (rev 13529)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,70 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+namespace Wporg\TranslationEvents\Routes\User;
+
+use Wporg\TranslationEvents\Attendee\Attendee;
+use Wporg\TranslationEvents\Attendee\Attendee_Repository;
+use Wporg\TranslationEvents\Event\Event_Repository_Interface;
+use Wporg\TranslationEvents\Routes\Route;
+use Wporg\TranslationEvents\Translation_Events;
+
+/**
+ * Toggle whether the current user is hosting an event.
+ * If the user is not currently marked as host, they will be marked as host.
+ * If the user is currently marked as host, they will be marked as not host.
+ */
+class Host_Event_Route extends Route {
+       private Event_Repository_Interface $event_repository;
+       private Attendee_Repository $attendee_repository;
+
+       /**
+        * Host_Event_Route constructor.
+        */
+       public function __construct() {
+               parent::__construct();
+               $this->event_repository    = Translation_Events::get_event_repository();
+               $this->attendee_repository = Translation_Events::get_attendee_repository();
+       }
+
+       /**
+        * Handle the request to toggle whether the current user is hosting an event.
+        *
+        * @param int $event_id The event ID.
+        * @param int $user_id  The user ID.
+        * @return void
+        */
+       public function handle( int $event_id, int $user_id ): void {
+               $current_user = wp_get_current_user();
+               if ( ! $current_user->exists() ) {
+                       $this->die_with_error( esc_html__( "Only logged-in users can manage event's hosts.", 'gp-translation-events' ), 403 );
+               }
+
+               $current_user_attendee = $this->attendee_repository->get_attendee( $event_id, $current_user->ID );
+               if ( ! current_user_can( 'manage_options' ) && ! $current_user_attendee->is_host() ) {
+                       $this->die_with_error( esc_html__( "This user does not have permissions to manage event's hosts.", 'gp-translation-events' ), 403 );
+               }
+
+               $event = $this->event_repository->get_event( $event_id );
+               if ( ! $event ) {
+                       $this->die_with_404();
+               }
+
+               $affected_attendee = $this->attendee_repository->get_attendee( $event_id, $user_id );
+               if ( $affected_attendee instanceof Attendee && $affected_attendee->is_host() && ( 1 === count( $this->attendee_repository->get_hosts( $event_id ) ) ) ) {
+                       $this->die_with_error( esc_html__( 'The event needs a host. Add a new host before stopping to attend the event.', 'gp-translation-events' ), 403 );
+               }
+               // The user is attending to the event, so if I don't find the attendee, I won't create it.
+               if ( $affected_attendee instanceof Attendee ) {
+                       if ( $affected_attendee->is_host() ) {
+                               $affected_attendee->mark_as_non_host();
+                       } else {
+                               $affected_attendee->mark_as_host();
+                       }
+
+                       $this->attendee_repository->update_attendee( $affected_attendee );
+               }
+
+               wp_safe_redirect( gp_url( "/events/{$event->slug()}" ) );
+               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/host-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="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/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   2024-04-15 08:28:54 UTC (rev 13528)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/user/my-events.php     2024-04-15 13:37:55 UTC (rev 13529)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2,9 +2,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> namespace Wporg\TranslationEvents\Routes\User;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-use DateTime;
-use DateTimeZone;
-use WP_Query;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+use Wporg\TranslationEvents\Event\Event_Repository_Interface;
</ins><span class="cx" style="display: block; padding: 0 10px"> use Wporg\TranslationEvents\Routes\Route;
</span><span class="cx" style="display: block; padding: 0 10px"> use Wporg\TranslationEvents\Translation_Events;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -12,6 +10,13 @@
</span><span class="cx" style="display: block; padding: 0 10px">  * Displays the My Events page for a user.
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span><span class="cx" style="display: block; padding: 0 10px"> class My_Events_Route extends Route {
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        private Event_Repository_Interface $event_repository;
+
+       public function __construct() {
+               parent::__construct();
+               $this->event_repository = Translation_Events::get_event_repository();
+       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">         public function handle(): void {
</span><span class="cx" style="display: block; padding: 0 10px">                global $wp;
</span><span class="cx" style="display: block; padding: 0 10px">                if ( ! is_user_logged_in() ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -21,6 +26,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                include ABSPATH . 'wp-admin/includes/post.php';
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $_events_i_created_paged  = 1;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                $_events_i_hosted_paged   = 1;
</ins><span class="cx" style="display: block; padding: 0 10px">                 $_events_i_attended_paged = 1;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // phpcs:disable WordPress.Security.NonceVerification.Recommended
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -30,6 +36,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                $_events_i_created_paged = (int) $value;
</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">+                if ( isset( $_GET['events_i_hosted_paged'] ) ) {
+                       $value = sanitize_text_field( wp_unslash( $_GET['events_i_hosted_paged'] ) );
+                       if ( is_numeric( $value ) ) {
+                               $_events_i_hosted_paged = (int) $value;
+                       }
+               }
</ins><span class="cx" style="display: block; padding: 0 10px">                 if ( isset( $_GET['events_i_attended_paged'] ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $value = sanitize_text_field( wp_unslash( $_GET['events_i_attended_paged'] ) );
</span><span class="cx" style="display: block; padding: 0 10px">                        if ( is_numeric( $value ) ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -38,47 +50,10 @@
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px">                // phpcs:enable
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $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 );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $events_i_created_query  = $this->event_repository->get_events_created_by_user( get_current_user_id(), $_events_i_created_paged, 10 );
+               $events_i_host_query     = $this->event_repository->get_events_hosted_by_user( get_current_user_id(), $_events_i_hosted_paged, 10 );
+               $events_i_attended_query = $this->event_repository->get_past_events_for_user( get_current_user_id(), $_events_i_attended_paged, 10 );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $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 );
-
</del><span class="cx" style="display: block; padding: 0 10px">                 $this->tmpl( 'events-my-events', get_defined_vars() );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<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-04-15 08:28:54 UTC (rev 13528)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/stats-calculator.php  2024-04-15 13:37:55 UTC (rev 13529)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -5,8 +5,11 @@
</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><span class="cx" style="display: block; padding: 0 10px"> use WP_User;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+use GP;
</ins><span class="cx" style="display: block; padding: 0 10px"> use GP_Locale;
</span><span class="cx" style="display: block; padding: 0 10px"> use GP_Locales;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+use DateTimeImmutable;
+use DateTimeZone;
</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="lines" style="display: block; padding: 0 10px; color: #888">@@ -85,7 +88,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @throws Exception When stats calculation failed.
</span><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 for_event( WP_Post $event ): Event_Stats {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ public function for_event( int $event_id ): Event_Stats {
</ins><span class="cx" style="display: block; padding: 0 10px">                 $stats = new Event_Stats();
</span><span class="cx" style="display: block; padding: 0 10px">                global $wpdb, $gp_table_prefix;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -98,15 +101,15 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        $wpdb->prepare(
</span><span class="cx" style="display: block; padding: 0 10px">                                "
</span><span class="cx" style="display: block; padding: 0 10px">                                select locale,
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                           sum(action = 'create') as created,
-                                          count(*) as total,
-                                          count(distinct user_id) as users
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 sum(action = 'create') as created,
+                                       count(*) as total,
+                                       count(distinct user_id) as users
</ins><span class="cx" style="display: block; padding: 0 10px">                                 from {$gp_table_prefix}event_actions
</span><span class="cx" style="display: block; padding: 0 10px">                                where event_id = %d
</span><span class="cx" style="display: block; padding: 0 10px">                                group by locale with rollup
</span><span class="cx" style="display: block; padding: 0 10px">                        ",
</span><span class="cx" style="display: block; padding: 0 10px">                                array(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        $event->ID,
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 $event_id,
</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">@@ -147,7 +150,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="cx" style="display: block; padding: 0 10px">         * Get contributors for an event.
</span><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 get_contributors( WP_Post $event ): array {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ public function get_contributors( int $event_id ): array {
</ins><span class="cx" style="display: block; padding: 0 10px">                 global $wpdb, $gp_table_prefix;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -164,7 +167,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                group by user_id
</span><span class="cx" style="display: block; padding: 0 10px">                        ",
</span><span class="cx" style="display: block; padding: 0 10px">                                array(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        $event->ID,
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 $event_id,
</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">@@ -188,15 +191,109 @@
</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 attendees without contributions for an event.
+        */
+       public function get_attendees_not_contributing( int $event_id ): array {
+               global $wpdb, $gp_table_prefix;
+
+               // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+               // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
+               // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
+               $all_attendees_ids = $wpdb->get_col(
+                       $wpdb->prepare(
+                               "
+                               select distinct user_id
+                               from {$gp_table_prefix}event_attendees
+                               where event_id = %d
+                       ",
+                               array(
+                                       $event_id,
+                               )
+                       ),
+               );
+
+               $contributing_ids = $wpdb->get_col(
+                       $wpdb->prepare(
+                               "
+                               select distinct user_id
+                               from {$gp_table_prefix}event_actions
+                               where event_id = %d
+                       ",
+                               array(
+                                       $event_id,
+                               )
+                       )
+               );
+
+               $attendees_not_contributing_ids = array_diff( $all_attendees_ids, $contributing_ids );
+
+               $attendees_not_contributing = array();
+               foreach ( $attendees_not_contributing_ids as $user_id ) {
+                       $attendees_not_contributing[] = new WP_User( $user_id );
+               }
+
+               return $attendees_not_contributing;
+       }
+
+       /**
+        * Get projects for an event.
+        */
+       public function get_projects( int $event_id ): 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
+                                       o.project_id as project,
+                                       group_concat( distinct e.locale ) as locales,
+                                       sum(action = 'create') as created,
+                                       count(*) as total,
+                                       count(distinct user_id) as users
+                               from {$gp_table_prefix}event_actions e, {$gp_table_prefix}originals o
+                               where e.event_id = %d and e.original_id = o.id
+                               group by o.project_id
+                       ",
+                               array(
+                                       $event_id,
+                               )
+                       )
+               );
+               // phpcs:enable
+
+               $projects = array();
+               foreach ( $rows as $row ) {
+                       $row->project      = GP::$project->get( $row->project );
+                       $project_name      = $row->project->name;
+                       $parent_project_id = $row->project->parent_project_id;
+                       while ( $parent_project_id ) {
+                               $parent_project    = GP::$project->get( $parent_project_id );
+                               $parent_project_id = $parent_project->parent_project_id;
+                               $project_name      = substr( htmlspecialchars_decode( $parent_project->name ), 0, 35 ) . ' - ' . $project_name;
+                       }
+                       $projects[ $project_name ] = $row;
+               }
+
+               ksort( $projects );
+
+               return $projects;
+       }
+
+       /**
</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><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * @param WP_Post $event The event to check.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * @param int $event_id The id of the event to check.
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><span class="cx" style="display: block; padding: 0 10px">         * @return bool True if the event has stats, false otherwise.
</span><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 event_has_stats( WP_Post $event ): bool {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ public function event_has_stats( int $event_id ): bool {
</ins><span class="cx" style="display: block; padding: 0 10px">                 try {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $stats = $this->for_event( $event );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $stats = $this->for_event( $event_id );
</ins><span class="cx" style="display: block; padding: 0 10px">                 } catch ( Exception $e ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        return false;
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -203,4 +300,40 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                return ! empty( $stats->rows() );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       /**
+        * Check if a user is a first time contributor.
+        *
+        * @param Event_Start_Date $event_start The event start date.
+        * @param int              $user_id      The user ID.
+        *
+        * @return bool True if the user is a first time contributor, false otherwise.
+        */
+       public function is_first_time_contributor( $event_start, $user_id ) {
+               global $wpdb, $gp_table_prefix;
+
+               // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+               // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
+               // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
+               // phpcs:disable WordPress.DB.DirectDatabaseQuery.SchemaChange
+               $users_first_translation_date = $wpdb->get_var(
+                       $wpdb->prepare(
+                               "
+                       select min(date_added) from {$gp_table_prefix}translations where user_id = %d
+               ",
+                               array(
+                                       $user_id,
+                               )
+                       )
+               );
+
+               if ( get_userdata( $user_id ) && ! $users_first_translation_date ) {
+                       return true;
+               }
+               $event_start_date_time  = new DateTimeImmutable( $event_start->__toString(), new DateTimeZone( 'UTC' ) );
+               $first_translation_date = new DateTimeImmutable( $users_first_translation_date, new DateTimeZone( 'UTC' ) );
+               // A first time contributor is someone whose first translation was made not earlier than 24 hours before the event.
+               $event_start_date_time = $event_start_date_time->modify( '-1 day' );
+               return $event_start_date_time <= $first_translation_date;
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</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-04-15 08:28:54 UTC (rev 13528)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/stats-listener.php    2024-04-15 13:37:55 UTC (rev 13529)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -7,6 +7,9 @@
</span><span class="cx" style="display: block; padding: 0 10px"> use Exception;
</span><span class="cx" style="display: block; padding: 0 10px"> use GP_Translation;
</span><span class="cx" style="display: block; padding: 0 10px"> use GP_Translation_Set;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+use Wporg\TranslationEvents\Attendee\Attendee_Repository;
+use Wporg\TranslationEvents\Event\Event;
+use Wporg\TranslationEvents\Event\Event_Repository_Interface;
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> class Stats_Listener {
</span><span class="cx" style="display: block; padding: 0 10px">        const ACTION_CREATE          = 'create';
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -14,10 +17,15 @@
</span><span class="cx" style="display: block; padding: 0 10px">        const ACTION_REJECT          = 'reject';
</span><span class="cx" style="display: block; padding: 0 10px">        const ACTION_REQUEST_CHANGES = 'request_changes';
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        private Active_Events_Cache $active_events_cache;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ private Attendee_Repository $attendee_repository;
+       private Event_Repository_Interface $event_repository;
</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( Active_Events_Cache $active_events_cache ) {
-               $this->active_events_cache = $active_events_cache;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ public function __construct(
+               Event_Repository_Interface $event_repository,
+               Attendee_Repository $attendee_repository
+       ) {
+               $this->event_repository    = $event_repository;
+               $this->attendee_repository = $attendee_repository;
</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">        public function start(): void {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -69,8 +77,8 @@
</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><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><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $active_events = $this->get_active_events( $happened_at );
-                       $events        = $this->select_events_user_is_registered_for( $active_events, $user_id );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $active_events = $this->event_repository->get_current_events();
+                       $events        = $this->select_events_user_is_registered_for( $active_events->events, $user_id );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        // phpcs:ignore Generic.Commenting.DocComment.MissingShort
</span><span class="cx" style="display: block; padding: 0 10px">                        /** @var GP_Translation_Set $translation_set Translation set */
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -107,61 +115,6 @@
</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><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * Get active events at a given time.
-        *
-        * @return Event[]
-        * @throws Exception When it fails to get active events.
-        */
-       private function get_active_events( DateTimeImmutable $at ): array {
-               $events = $this->active_events_cache->get();
-               if ( null === $events ) {
-                       $cache_duration = Active_Events_Cache::CACHE_DURATION;
-                       $boundary_start = $at;
-                       $boundary_end   = $at->modify( "+$cache_duration seconds" );
-
-                       // Get events for which start is before $boundary_end AND end is after $boundary_start.
-                       $event_ids = get_posts(
-                               array(
-                                       'post_type'      => Translation_Events::CPT,
-                                       'post_status'    => 'publish',
-                                       'posts_per_page' => - 1,
-                                       'fields'         => 'ids',
-                                       'meta_query'     => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
-                                               array(
-                                                       'key'     => '_event_start',
-                                                       'value'   => $boundary_end->format( 'Y-m-d H:i:s' ),
-                                                       'compare' => '<',
-                                                       'type'    => 'DATETIME',
-                                               ),
-                                               array(
-                                                       'key'     => '_event_end',
-                                                       'value'   => $boundary_start->format( 'Y-m-d H:i:s' ),
-                                                       'compare' => '>',
-                                                       'type'    => 'DATETIME',
-                                               ),
-                                       ),
-                               ),
-                       );
-
-                       $events = array();
-                       foreach ( $event_ids as $event_id ) {
-                               $meta     = get_post_meta( $event_id );
-                               $events[] = Event::from_post_meta( $event_id, $meta );
-                       }
-
-                       $this->active_events_cache->cache( $events );
-               }
-
-               // Filter out events that aren't actually active at $at.
-               return array_filter(
-                       $events,
-                       function ( $event ) use ( $at ) {
-                               return $event->start() <= $at && $at <= $event->end();
-                       }
-               );
-       }
-
-       /**
</del><span class="cx" style="display: block; padding: 0 10px">          * Filter an array of events so that it only includes events the given user is attending.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @param Event[] $events Events.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -171,11 +124,11 @@
</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, Translation_Events::USER_META_KEY_ATTENDING, true );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $attending_event_ids = $this->attendee_repository->get_events_for_user( $user_id );
</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><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                return isset( $attending_event_ids[ $event->id() ] );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         return in_array( $event->id(), $attending_event_ids, true );
</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></pre></div>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludesupgradephp"></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/upgrade.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/upgrade.php                         (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/upgrade.php   2024-04-15 13:37:55 UTC (rev 13529)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,105 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+namespace Wporg\TranslationEvents;
+
+use Exception;
+use WP_Query;
+use Wporg\TranslationEvents\Attendee\Attendee;
+
+class Upgrade {
+       private const VERSION        = 2;
+       private const VERSION_OPTION = 'wporg_gp_translations_events_version';
+
+       public static function upgrade_if_needed(): void {
+               $previous_version = get_option( self::VERSION_OPTION );
+
+               // If previous version is not set yet, set it to version 1.
+               if ( false === $previous_version ) {
+                       $previous_version = 1;
+               }
+
+               if ( self::VERSION === $previous_version ) {
+                       // Nothing to do, we're already at the latest version.
+                       return;
+               }
+
+               // Upgrade database schema.
+               require_once ABSPATH . 'wp-admin/includes/upgrade.php';
+               dbDelta( self::get_database_schema_sql() );
+
+               // Run version-specific upgrades.
+               $is_running_tests = 'yes' === getenv( 'WPORG_TRANSLATION_EVENTS_TESTS' );
+               if ( $previous_version < 2 && ! $is_running_tests ) {
+                       try {
+                               self::v2_import_legacy_attendees();
+                       } catch ( Exception $e ) {
+                               // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
+                               error_log( $e );
+                       }
+               }
+
+               update_option( self::VERSION_OPTION, self::VERSION );
+       }
+
+       private static function get_database_schema_sql(): string {
+               global $gp_table_prefix;
+
+               return "
+                       CREATE TABLE `{$gp_table_prefix}event_actions` (
+                               `translate_event_actions_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+                               `event_id` int(10) NOT NULL COMMENT 'Post_ID of the translation_event post in the wp_posts table',
+                               `original_id` int(10) NOT NULL COMMENT 'ID of the translation',
+                               `user_id` int(10) NOT NULL COMMENT 'ID of the user who made the action',
+                               `action` enum('approve','create','reject','request_changes') NOT NULL COMMENT 'The action that the user made (create, reject, etc)',
+                               `locale` varchar(10) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL COMMENT 'Locale of the translation',
+                               `happened_at` datetime NOT NULL COMMENT 'When the action happened, in UTC',
+                       PRIMARY KEY (`translate_event_actions_id`),
+                       UNIQUE KEY `event_per_translated_original_per_user` (`event_id`,`locale`,`original_id`,`user_id`)
+                       ) COMMENT='Tracks translation actions that happened during a translation event';
+
+                       CREATE TABLE `{$gp_table_prefix}event_attendees` (
+                               `translate_event_attendees_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+                               `event_id` int(10) NOT NULL COMMENT 'Post_ID of the translation_event post in the wp_posts table',
+                               `user_id` int(10) NOT NULL COMMENT 'ID of the user who is attending the event',
+                               `is_host` tinyint(1) default 0 not null comment 'Whether the user is a host of the event',
+                       PRIMARY KEY (`translate_event_attendees_id`),
+                       UNIQUE KEY `event_per_user` (`event_id`,`user_id`),
+                       INDEX `user` (`user_id`)
+                       ) COMMENT='Attendees of events';
+               ";
+       }
+
+       /**
+        * Previously, event attendance was tracked through user_meta.
+        * This function imports this legacy attendance information into the attendees table.
+        *
+        * Instead of looping through all users, we consider only users who have contributed to an event.
+        *
+        * @throws Exception
+        */
+       private static function v2_import_legacy_attendees(): void {
+               $query = new WP_Query(
+                       array(
+                               'post_type'   => Translation_Events::CPT,
+                               'post_status' => 'publish',
+                       )
+               );
+
+               $events              = $query->get_posts();
+               $stats_calculator    = new Stats_Calculator();
+               $attendee_repository = Translation_Events::get_attendee_repository();
+               foreach ( $events as $event ) {
+                       $host_attendee = new Attendee( $event->ID, intval( $event->post_author ) );
+                       $host_attendee->mark_as_host();
+                       $attendee_repository->insert_attendee( $host_attendee );
+
+                       foreach ( $stats_calculator->get_contributors( $event->ID ) as $user ) {
+                               $attendee = $attendee_repository->get_attendee( $event->ID, $user->id );
+                               if ( ! $attendee ) {
+                                       $attendee = new Attendee( $event->ID, $user->ID );
+                                       $attendee_repository->insert_attendee( $attendee );
+                               }
+                       }
+               }
+       }
+}
</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/upgrade.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_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-04-15 08:28:54 UTC (rev 13528)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/templates/event.php    2024-04-15 13:37:55 UTC (rev 13529)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -5,39 +5,34 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> namespace Wporg\TranslationEvents;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-use WP_Post;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+use WP_User;
+use Wporg\TranslationEvents\Attendee\Attendee;
+use Wporg\TranslationEvents\Attendee\Attendee_Repository;
+use Wporg\TranslationEvents\Event\Event;
+use Wporg\TranslationEvents\Event\Event_End_Date;
+use Wporg\TranslationEvents\Event\Event_Start_Date;
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-/** @var WP_Post $event */
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+/** @var Attendee_Repository $attendee_repo */
+/** @var Attendee $attendee */
+/** @var Event $event */
</ins><span class="cx" style="display: block; padding: 0 10px"> /** @var int $event_id */
</span><span class="cx" style="display: block; padding: 0 10px"> /** @var string $event_title */
</span><span class="cx" style="display: block; padding: 0 10px"> /** @var string $event_description */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-/** @var string $event_start */
-/** @var string $event_end */
-/** @var bool $user_is_attending */
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+/** @var Event_Start_Date $event_start */
+/** @var Event_End_Date $event_end */
</ins><span class="cx" style="display: block; padding: 0 10px"> /** @var Event_Stats $event_stats */
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+/** @var array $projects */
+/** @var WP_User $user */
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /* translators: %s: Event title. */
</span><span class="cx" style="display: block; padding: 0 10px"> gp_title( sprintf( __( 'Translation Events - %s' ), esc_html( $event_title ) ) );
</span><span class="cx" style="display: block; padding: 0 10px"> gp_breadcrumb_translation_events( array( esc_html( $event_title ) ) );
</span><span class="cx" style="display: block; padding: 0 10px"> gp_tmpl_header();
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+$event_page_title = $event_title;
</ins><span class="cx" style="display: block; padding: 0 10px"> gp_tmpl_load( 'events-header', get_defined_vars(), __DIR__ );
</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"> <div class="event-page-wrapper">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        <div class="event-details-head">
-               <h1>
-                       <?php echo esc_html( $event_title ); ?>
-                       <?php if ( 'draft' === $event->post_status ) : ?>
-                               <span class="event-label-draft"><?php echo esc_html( $event->post_status ); ?></span>
-                       <?php endif; ?>
-               </h1>
-               <p>
-                       Host: <a href="<?php echo esc_attr( get_author_posts_url( $event->post_author ) ); ?>"><?php echo esc_html( get_the_author_meta( 'display_name', $event->post_author ) ); ?></a>
-                       <?php if ( current_user_can( 'edit_post', $event_id ) ) : ?>
-                               <a class="event-page-edit-link button" href="<?php echo esc_url( gp_url( 'events/edit/' . $event_id ) ); ?>"><span class="dashicons dashicons-edit"></span>Edit event</a>
-                       <?php endif ?>
-               </p>
-       </div>
</del><span class="cx" style="display: block; padding: 0 10px">         <div class="event-details-left">
</span><span class="cx" style="display: block; padding: 0 10px">                <div class="event-page-content">
</span><span class="cx" style="display: block; padding: 0 10px">                        <?php
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -44,133 +39,243 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                echo wp_kses_post( wpautop( make_clickable( $event_description ) ) );
</span><span class="cx" style="display: block; padding: 0 10px">                        ?>
</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">+                <?php if ( ! empty( $contributors ) ) : ?>
+                       <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>
+                                                       <?php if ( $stats_calculator->is_first_time_contributor( $event_start, $contributor->ID ) ) : ?>
+                                                               <span class="first-time-contributor-tada"></span>
+                                                       <?php endif; ?>
+                                                       <?php
+                                                       if ( ! $event->end()->is_in_the_past() ) :
+                                                               if ( ( $attendee instanceof Attendee && $attendee->is_host() ) || current_user_can( 'manage_options' ) ) :
+                                                                       if ( $user->ID !== $contributor->ID ) :
+                                                                               $_attendee = $attendee_repo->get_attendee( $event_id, $contributor->ID );
+                                                                               if ( $_attendee instanceof Attendee ) :
+                                                                                       echo '<form class="add-remove-user-as-host" method="post" action="' . esc_url( gp_url( "/events/host/$event_id/$contributor->ID" ) ) . '">';
+                                                                                       if ( $_attendee->is_host() ) :
+                                                                                               if ( 1 === count( $attendee_repo->get_hosts( $event_id ) ) ) :
+                                                                                                       echo '<input type="submit" class="button is-primary remove-as-host" disabled value="Remove as host"/>';
+                                                                                               else :
+                                                                                                       echo '<input type="submit" class="button is-primary remove-as-host" value="Remove as host"/>';
+                                                                                               endif;
+                                                                                       else :
+                                                                                               echo '<input type="submit" class="button is-secondary convert-to-host" value="Make co-host"/>';
+                                                                                       endif;
+                                                                                       echo '</form>';
+                                                                               endif;
+                                                                       elseif ( ( $attendee instanceof Attendee && $attendee->is_host() ) ) :
+                                                                                       echo '<span class="event-you">' . esc_html__( 'You (host)', 'gp-translation-events' ) . '</span>';
+                                                                               else :
+                                                                                       echo '<span class="event-you">' . esc_html__( 'You (event creator)', 'gp-translation-events' ) . '</span>';
+                                                                       endif;
+                                                               endif;
+                                                       endif;
+                                                       ?>
+                                               </li>
+                                       <?php endforeach; ?>
+                               </ul>
+                       </div>
+               <?php endif; ?>
+               <?php if ( ! empty( $attendees ) && ( ! $event->end()->is_in_the_past() || ( ( $attendee instanceof Attendee && $attendee->is_host() ) || current_user_can( 'manage_options' ) ) ) ) : ?>
+                       <div class="event-attendees">
+                               <h2><?php esc_html_e( 'Attendees', 'gp-translation-events' ); ?></h2>
+                               <ul>
+                                       <?php foreach ( $attendees as $_user ) : ?>
+                                               <li class="event-attendee">
+                                                       <a href="<?php echo esc_url( get_author_posts_url( $_user->ID ) ); ?>"><?php echo get_avatar( $_user->ID, 48 ); ?></a>
+                                                       <a href="<?php echo esc_url( get_author_posts_url( $_user->ID ) ); ?>"><?php echo esc_html( get_the_author_meta( 'display_name', $_user->ID ) ); ?></a>
+                                                       <?php if ( $stats_calculator->is_first_time_contributor( $event_start, $_user->ID ) ) : ?>
+                                                               <span class="first-time-contributor-tada"></span>
+                                                       <?php endif; ?>
+                                                       <?php
+                                                       if ( ! $event->end()->is_in_the_past() ) :
+                                                               if ( ( $attendee instanceof Attendee && $attendee->is_host() ) || current_user_can( 'manage_options' ) ) :
+                                                                       if ( $user->ID !== $_user->ID ) :
+                                                                               $_attendee = $attendee_repo->get_attendee( $event_id, $_user->ID );
+                                                                               if ( $_attendee instanceof Attendee ) :
+                                                                                       echo '<form class="add-remove-user-as-host" method="post" action="' . esc_url( gp_url( "/events/host/$event_id/$_user->ID" ) ) . '">';
+                                                                                       if ( $_attendee->is_host() ) :
+                                                                                               if ( 1 === count( $attendee_repo->get_hosts( $event_id ) ) ) :
+                                                                                                       echo '<input type="submit" class="button is-primary remove-as-host" disabled value="Remove as host"/>';
+                                                                                               else :
+                                                                                                       echo '<input type="submit" class="button is-primary remove-as-host" value="Remove as host"/>';
+                                                                                               endif;
+                                                                                       else :
+                                                                                               echo '<input type="submit" class="button is-secondary convert-to-host" value="Make co-host"/>';
+                                                                                       endif;
+                                                                                       echo '</form>';
+                                                                               endif;
+                                                                       elseif ( ( $attendee instanceof Attendee && $attendee->is_host() ) ) :
+                                                                                       echo '<span class="event-you">' . esc_html__( 'You (host)', 'gp-translation-events' ) . '</span>';
+                                                                               else :
+                                                                                       echo '<span class="event-you">' . esc_html__( 'You (event creator)', 'gp-translation-events' ) . '</span>';
+                                                                       endif;
+                                                               endif;
+                                                       endif;
+                                                       ?>
+                                               </li>
+                                       <?php endforeach; ?>
+                               </ul>
+                       </div>
+               <?php endif; ?>
</ins><span class="cx" style="display: block; padding: 0 10px">                 <?php if ( ! empty( $event_stats->rows() ) ) : ?>
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        <div class="event-details-stats">
-               <h2><?php esc_html_e( 'Stats', 'gp-translation-events' ); ?></h2>
-               <table>
-                       <thead>
-                       <tr>
-                               <th scope="col">Locale</th>
-                               <th scope="col">Translations created</th>
-                               <th scope="col">Translations reviewed</th>
-                               <th scope="col">Contributors</th>
-                       </tr>
-                       </thead>
-                       <tbody>
-                       <?php /** @var $row Stats_Row */ ?>
-                       <?php foreach ( $event_stats->rows() as $_locale => $row ) : ?>
-                       <tr>
-                               <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>
-                               <td><?php echo esc_html( $row->created ); ?></td>
-                               <td><?php echo esc_html( $row->reviewed ); ?></td>
-                               <td><?php echo esc_html( $row->users ); ?></td>
-                       </tr>
-               <?php endforeach ?>
-                       <tr class="event-details-stats-totals">
-                               <td>Total</td>
-                               <td><?php echo esc_html( $event_stats->totals()->created ); ?></td>
-                               <td><?php echo esc_html( $event_stats->totals()->reviewed ); ?></td>
-                               <td><?php echo esc_html( $event_stats->totals()->users ); ?></td>
-                       </tr>
-                       </tbody>
-               </table>
-       </div>
-       <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>
-       <details class="event-stats-summary">
-               <summary>View stats summary in text </summary>
-               <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()
-                                                       )
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 <div class="event-details-stats">
+                               <h2><?php esc_html_e( 'Stats', 'gp-translation-events' ); ?></h2>
+                               <table>
+                                       <thead>
+                                       <tr>
+                                               <th scope="col">Locale</th>
+                                               <th scope="col">Translations created</th>
+                                               <th scope="col">Translations reviewed</th>
+                                               <th scope="col">Contributors</th>
+                                       </tr>
+                                       </thead>
+                                       <tbody>
+                                       <?php /** @var $row Stats_Row */ ?>
+                                       <?php foreach ( $event_stats->rows() as $_locale => $row ) : ?>
+                                       <tr>
+                                               <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>
+                                               <td><?php echo esc_html( $row->created ); ?></td>
+                                               <td><?php echo esc_html( $row->reviewed ); ?></td>
+                                               <td><?php echo esc_html( $row->users ); ?></td>
+                                       </tr>
+                               <?php endforeach ?>
+                                       <tr class="event-details-stats-totals">
+                                               <td>Total</td>
+                                               <td><?php echo esc_html( $event_stats->totals()->created ); ?></td>
+                                               <td><?php echo esc_html( $event_stats->totals()->reviewed ); ?></td>
+                                               <td><?php echo esc_html( $event_stats->totals()->users ); ?></td>
+                                       </tr>
+                                       </tbody>
+                               </table>
+                       </div>
+                       <div class="event-projects">
+                               <h2><?php esc_html_e( 'Projects', 'gp-translation-events' ); ?></h2>
+                               <ul>
+                                       <?php foreach ( $projects as $project_name => $row ) : ?>
+                                       <li class="event-project" title="<?php echo esc_html( str_replace( ',', ', ', $row->locales ) ); ?>">
+                                               <a href="<?php echo esc_url( gp_url_project( $row->project ) ); ?>"><?php echo esc_html( $project_name ); ?></a> <small> to
+                                               <?php
+                                               foreach ( explode( ',', $row->locales ) as $_locale ) {
+                                                       $_locale = \GP_Locales::by_slug( $_locale );
+                                                       ?>
+                                                       <a href="<?php echo esc_url( gp_url_project_locale( $row->project, $_locale, 'default' ) ); ?>"><?php echo esc_html( $_locale->english_name ); ?></a>
+                                                       <?php
+                                               }
+                                               // translators: %d: Number of contributors.
+                                               echo esc_html( sprintf( _n( 'by %d contributor', 'by %d contributors', $row->users, 'gp-translation-events' ), $row->users ) );
+                                               ?>
+                                               </small>
+                                       </li>
+                               <?php endforeach; ?>
+                               </ul>
+                       </div>
+                       <details class="event-stats-summary">
+                               <summary>View stats summary in text </summary>
+                               <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(),
</ins><span class="cx" style="display: block; padding: 0 10px">                                                 )
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        ),
-                                       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
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 );
+                                       ?>
+                                       <?php
+                                       echo wp_kses(
+                                               sprintf(
+                                               // translators: %s the contributors.
+                                                       esc_html__( 'Contributors were %s.', 'gp-translation-events' ),
+                                                       implode(
+                                                               ', ',
+                                                               array_map(
+                                                                       function ( $contributor ) use ( $stats_calculator, $event_start ) {
+                                                                               $append_tada = $stats_calculator->is_first_time_contributor( $event_start, $contributor->ID ) ? '<span class="first-time-contributor-tada"></span>' : '';
+                                                                               return '@' . $contributor->user_login . $append_tada;
+                                                                       },
+                                                                       $contributors
+                                                               )
</ins><span class="cx" style="display: block; padding: 0 10px">                                                         )
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                                ),
+                                               array(
+                                                       'span' => array(
+                                                               'class' => array(),
+                                                       ),
</ins><span class="cx" style="display: block; padding: 0 10px">                                                 )
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        )
-                               )
-                       );
-                       ?>
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 );
+                                       ?>
</ins><span class="cx" style="display: block; padding: 0 10px">                         </p>
</span><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 class="cx" style="display: block; padding: 0 10px">                        <p>
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                <span class="event-details-date-label">Starts:</span> <time class="event-utc-time" datetime="<?php echo esc_attr( $event_start ); ?>"></time>
-                               <span class="event-details-date-label">Ends:</span><time class="event-utc-time" datetime="<?php echo esc_attr( $event_end ); ?>"></time>
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         <span class="event-details-date-label">
+                                       <?php echo esc_html( $event_start->is_in_the_past() ? __( 'Started', 'gp-translation-events' ) : __( 'Starts', 'gp-translation-events' ) ); ?>:
+                                       <?php $event_start->print_relative_time_html(); ?>
+                               </span>
+                               <?php $event_start->print_time_html(); ?>
+                               <span class="event-details-date-label">
+                                       <?php echo esc_html( $event_end->is_in_the_past() ? __( 'Ended', 'gp-translation-events' ) : __( 'Ends', 'gp-translation-events' ) ); ?>:
+                                       <?php $event_end->print_relative_time_html(); ?>
+
+                               </span>
+                               <?php $event_end->print_time_html(); ?>
</ins><span class="cx" style="display: block; padding: 0 10px">                         </p>
</span><span class="cx" style="display: block; padding: 0 10px">                </div>
</span><span class="cx" style="display: block; padding: 0 10px">                <?php if ( is_user_logged_in() ) : ?>
</span><span class="cx" style="display: block; padding: 0 10px">                <div class="event-details-join">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        <?php
-                       $current_time = gmdate( 'Y-m-d H:i:s' );
-                       if ( strtotime( $current_time ) > strtotime( $event_end ) ) :
-                               ?>
-                               <?php if ( $user_is_attending ) : ?>
-                                       <span class="event-details-join-expired"><?php esc_html_e( 'You attended', 'gp-translation-events' ); ?></span>
-                               <?php endif ?>
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 <?php if ( $event_end->is_in_the_past() ) : ?>
+                               <?php if ( $attendee instanceof Attendee ) : ?>
+                                       <button disabled="disabled" class="button is-primary attend-btn"><?php esc_html_e( 'You attended', 'gp-translation-events' ); ?></button>
+                               <?php endif; ?>
</ins><span class="cx" style="display: block; padding: 0 10px">                         <?php else : ?>
</span><span class="cx" style="display: block; padding: 0 10px">                                <form class="event-details-attend" method="post" action="<?php echo esc_url( gp_url( "/events/attend/$event_id" ) ); ?>">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        <?php if ( ! $user_is_attending ) : ?>
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 <?php if ( $attendee instanceof Attendee ) : ?>
+                                               <?php if ( $attendee->is_host() && ( 1 === count( $attendee_repo->get_hosts( $event_id ) ) ) ) : ?>
+                                                       <input type="submit" class="button is-secondary attending-btn" disabled value="You're attending" />
+                                               <?php else : ?>
+                                                       <input type="submit" class="button is-secondary attending-btn" value="You're attending" />
+                                               <?php endif; ?>
+                                       <?php else : ?>
</ins><span class="cx" style="display: block; padding: 0 10px">                                                 <input type="submit" class="button is-primary attend-btn" value="Attend Event"/>
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        <?php else : ?>
-                                               <input type="submit" class="button is-secondary attending-btn" value="You're attending"/>
-                                       <?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">                                 </form>
</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">                <?php else : ?>
</span><span class="cx" style="display: block; padding: 0 10px">                <div class="event-details-join">
</span><span class="cx" style="display: block; padding: 0 10px">                        <p>
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                <?php global $wp; ?>
-                               <a href="<?php echo esc_url( wp_login_url( home_url( $wp->request ) ) ); ?>" class="button is-primary attend-btn"><?php esc_html_e( 'Login to attend', 'gp-translation-events' ); ?></a>
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         <?php if ( ! $event_end->is_in_the_past() ) : ?>
+                                       <a href="<?php echo esc_url( wp_login_url() ); ?>" class="button is-primary attend-btn"><?php esc_html_e( 'Login to attend', 'gp-translation-events' ); ?></a>
+                               <?php else : ?>
+                                       <button disabled="disabled" class="button is-primary attend-btn"><?php esc_html_e( 'Event is over', 'gp-translation-events' ); ?></button>
+                               <?php endif; ?>
</ins><span class="cx" style="display: block; padding: 0 10px">                         </p>
</span><span class="cx" style="display: block; padding: 0 10px">                </div>
</span><span class="cx" style="display: block; padding: 0 10px">                <?php endif; ?>
</span></span></pre></div>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventstemplateseventsformphp"></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/events-form.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/events-form.php    2024-04-15 08:28:54 UTC (rev 13528)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/templates/events-form.php      2024-04-15 13:37:55 UTC (rev 13529)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -5,24 +5,27 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> namespace Wporg\TranslationEvents;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-/** @var string $event_form_title */
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+use DateTimeZone;
+use Wporg\TranslationEvents\Event\Event_End_Date;
+use Wporg\TranslationEvents\Event\Event_Start_Date;
+
+/** @var string $event_page_title */
</ins><span class="cx" style="display: block; padding: 0 10px"> /** @var string $event_form_name */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-/** @var int $event_id */
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+/** @var int    $event_id */
</ins><span class="cx" style="display: block; padding: 0 10px"> /** @var string $event_title */
</span><span class="cx" style="display: block; padding: 0 10px"> /** @var string $event_description */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-/** @var string $event_start */
-/** @var string $event_end */
-/** @var string $event_timezone */
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+/** @var Event_Start_Date $event_start */
+/** @var Event_End_Date $event_end */
+/** @var DateTimeZone|null $event_timezone */
</ins><span class="cx" style="display: block; padding: 0 10px"> /** @var string $event_url */
</span><span class="cx" style="display: block; padding: 0 10px"> /** @var string $css_show_url */
</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_title( __( 'Translation Events' ) . ' - ' . esc_html( $event_form_title . ' - ' . $event_title ) );
-gp_breadcrumb_translation_events( array( esc_html( $event_form_title ) ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+gp_title( __( 'Translation Events' ) . ' - ' . esc_html( $event_page_title . ' - ' . $event_title ) );
+gp_breadcrumb_translation_events( array( esc_html( $event_page_title ) ) );
</ins><span class="cx" style="display: block; padding: 0 10px"> gp_tmpl_header();
</span><span class="cx" style="display: block; padding: 0 10px"> gp_tmpl_load( 'events-header', get_defined_vars(), __DIR__ );
</span><span class="cx" style="display: block; padding: 0 10px"> ?>
</span><span class="cx" style="display: block; padding: 0 10px"> <div class="event-page-wrapper">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-<h2 class="event-page-title"><?php echo esc_html( $event_form_title ); ?></h2>
</del><span class="cx" style="display: block; padding: 0 10px"> <form class="translation-event-form" action="" method="post">
</span><span class="cx" style="display: block; padding: 0 10px">        <?php wp_nonce_field( '_event_nonce', '_event_nonce' ); ?>
</span><span class="cx" style="display: block; padding: 0 10px">        <input type="hidden" name="action" value="submit_event_ajax">
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -40,21 +43,34 @@
</span><span class="cx" style="display: block; padding: 0 10px">        <div>
</span><span class="cx" style="display: block; padding: 0 10px">                <label for="event-description">Event Description</label>
</span><span class="cx" style="display: block; padding: 0 10px">                <textarea id="event-description" name="event_description" rows="4" required><?php echo esc_html( $event_description ); ?></textarea>
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        </div>
-       <div>
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         <?php
+               echo wp_kses(
+                       Event_Text_Snippet::get_snippet_links(),
+                       array(
+                               'a'  => array(
+                                       'href'         => array(),
+                                       'data-snippet' => array(),
+                                       'class'        => array(),
+                               ),
+                               'ul' => array( 'class' => array() ),
+                               'li' => array(),
+                       )
+               );
+               ?>
+                       <div>
</ins><span class="cx" style="display: block; padding: 0 10px">                 <label for="event-start">Start Date</label>
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                <input type="datetime-local" id="event-start" name="event_start" value="<?php echo esc_attr( $event_start ); ?>" required>
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         <input type="datetime-local" id="event-start" name="event_start" value="<?php echo esc_attr( $event_start->format( 'Y-m-d H:i' ) ); ?>" required>
</ins><span class="cx" style="display: block; padding: 0 10px">         </div>
</span><span class="cx" style="display: block; padding: 0 10px">        <div>
</span><span class="cx" style="display: block; padding: 0 10px">                <label for="event-end">End Date</label>
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                <input type="datetime-local" id="event-end" name="event_end" value="<?php echo esc_attr( $event_end ); ?>" required>
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         <input type="datetime-local" id="event-end" name="event_end" value="<?php echo esc_attr( $event_end->format( 'Y-m-d H:i' ) ); ?>" required>
</ins><span class="cx" style="display: block; padding: 0 10px">         </div>
</span><span class="cx" style="display: block; padding: 0 10px">        <div>
</span><span class="cx" style="display: block; padding: 0 10px">                <label for="event-timezone">Event Timezone</label>
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                <select id="event-timezone" name="event_timezone"  required>
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         <select id="event-timezone" name="event_timezone" required>
</ins><span class="cx" style="display: block; padding: 0 10px">                         <?php
</span><span class="cx" style="display: block; padding: 0 10px">                        echo wp_kses(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                wp_timezone_choice( $event_timezone, get_user_locale() ),
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         wp_timezone_choice( $event_timezone ? $event_timezone->getName() : null, get_user_locale() ),
</ins><span class="cx" style="display: block; padding: 0 10px">                                 array(
</span><span class="cx" style="display: block; padding: 0 10px">                                        'optgroup' => array( 'label' => array() ),
</span><span class="cx" style="display: block; padding: 0 10px">                                        'option'   => array(
</span></span></pre></div>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventstemplateseventsheaderphp"></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/events-header.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/events-header.php  2024-04-15 08:28:54 UTC (rev 13528)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/templates/events-header.php    2024-04-15 13:37:55 UTC (rev 13529)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1,10 +1,24 @@
</span><span class="cx" style="display: block; padding: 0 10px"> <?php
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> namespace Wporg\TranslationEvents;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> use GP;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+use Wporg\TranslationEvents\Attendee\Attendee;
+use Wporg\TranslationEvents\Event\Event;
+
+/** @var Attendee $attendee */
+/** @var Event  $event */
+/** @var string $event_page_title */
+/** @var bool   $is_editable_event */
</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"> <div class="event-list-top-bar">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<h2 class="event-page-title">
+       <?php echo esc_html( $event_page_title ); ?>
+       <?php if ( isset( $event ) && 'draft' === $event->status() ) : ?>
+                               <span class="event-label-draft"><?php echo esc_html( $event->status() ); ?></span>
+                       <?php endif; ?>
+</h2>
</ins><span class="cx" style="display: block; padding: 0 10px">         <ul class="event-list-nav">
</span><span class="cx" style="display: block; padding: 0 10px">                <?php if ( is_user_logged_in() ) : ?>
</span><span class="cx" style="display: block; padding: 0 10px">                        <li><a href="<?php echo esc_url( gp_url( '/events/my-events/' ) ); ?>">My Events</a></li>
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -21,4 +35,29 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        <?php endif; ?>
</span><span class="cx" style="display: block; padding: 0 10px">                <?php endif; ?>
</span><span class="cx" style="display: block; padding: 0 10px">        </ul>
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        <?php if ( isset( $event ) && ! isset( $event_form_name ) ) : ?>
+       <p class="event-sub-head">
+                       <span class="event-host">
+                               <?php
+                               if ( 1 === count( $hosts ) ) :
+                                       esc_html_e( 'Host:', 'gp-translation-events' );
+                               else :
+                                       esc_html_e( 'Hosts:', 'gp-translation-events' );
+                               endif;
+                               ?>
+                               <?php foreach ( $hosts as $host ) : ?>
+                                       <?php $user = get_userdata( $host->user_id() ); ?>
+                                       &nbsp;<a href="<?php echo esc_attr( get_author_posts_url( $user->ID ) ); ?>"><?php echo esc_html( get_the_author_meta( 'display_name', $user->ID ) ); ?></a>
+                                       <?php if ( end( $hosts ) !== $host ) : ?>
+                                               ,
+                                       <?php endif; ?>
+                               <?php endforeach; ?>
+                       .</span>
+                       <?php $show_edit_button = ( ( $attendee instanceof Attendee && $attendee->is_host() ) || current_user_can( 'edit_post', $event->id() ) ) && $is_editable_event; ?>
+                       <?php if ( $show_edit_button ) : ?>
+                               <a class="event-page-edit-link" href="<?php echo esc_url( gp_url( 'events/edit/' . $event->id() ) ); ?>"><span class="dashicons dashicons-edit"></span><?php esc_html_e( 'Edit event', 'gp-translation-events' ); ?></a>
+                       <?php endif ?>
+               </p>
+               <?php endif; ?>
+
</ins><span class="cx" style="display: block; padding: 0 10px"> </div>
</span></span></pre></div>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventstemplateseventslistphp"></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/events-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/templates/events-list.php    2024-04-15 08:28:54 UTC (rev 13528)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/templates/events-list.php      2024-04-15 13:37:55 UTC (rev 13529)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -7,38 +7,39 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> use DateTime;
</span><span class="cx" style="display: block; padding: 0 10px"> use WP_Query;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+use Wporg\TranslationEvents\Event\Event;
+use Wporg\TranslationEvents\Event\Events_Query_Result;
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-/** @var WP_Query $current_events_query */
-/** @var WP_Query $upcoming_events_query */
-/** @var WP_Query $past_events_query */
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+/** @var Events_Query_Result $current_events_query */
+/** @var Events_Query_Result $upcoming_events_query */
+/** @var Events_Query_Result $past_events_query */
+/** @var Events_Query_Result $user_attending_events_query */
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> gp_title( __( 'Translation Events', 'gp-translation-events' ) );
</span><span class="cx" style="display: block; padding: 0 10px"> gp_breadcrumb_translation_events();
</span><span class="cx" style="display: block; padding: 0 10px"> gp_tmpl_header();
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+$event_page_title = __( 'Translation Events', 'gp-translation-events' );
</ins><span class="cx" style="display: block; padding: 0 10px"> gp_tmpl_load( 'events-header', get_defined_vars(), __DIR__ );
</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"> <div class="event-page-wrapper">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        <h1 class="event_page_title"><?php esc_html_e( 'Translation Events', 'gp-translation-events' ); ?></h1>
</del><span class="cx" style="display: block; padding: 0 10px"> <div class="event-left-col">
</span><span class="cx" style="display: block; padding: 0 10px"> <?php
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-if ( $current_events_query->have_posts() ) :
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+if ( ! empty( $current_events_query->events ) ) :
</ins><span class="cx" style="display: block; padding: 0 10px">         ?>
</span><span class="cx" style="display: block; padding: 0 10px">        <h2><?php esc_html_e( 'Current events', 'gp-translation-events' ); ?></h2>
</span><span class="cx" style="display: block; padding: 0 10px">        <ul class="event-list">
</span><span class="cx" style="display: block; padding: 0 10px">                <?php
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                while ( $current_events_query->have_posts() ) :
-                       $current_events_query->the_post();
-                       $event_end = Event::get_end_date_text( get_post_meta( get_the_ID(), '_event_end', true ) );
-                       $event_url = gp_url( wp_make_link_relative( get_the_permalink() ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         foreach ( $current_events_query->events as $event ) :
+                       $event_url = gp_url( wp_make_link_relative( get_the_permalink( $event->id() ) ) );
</ins><span class="cx" style="display: block; padding: 0 10px">                         ?>
</span><span class="cx" style="display: block; padding: 0 10px">                        <li class="event-list-item">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                <a href="<?php echo esc_url( $event_url ); ?>"><?php the_title(); ?></a>
-                               <span class="event-list-date"><?php echo esc_html( $event_end ); ?></span>
-                               <?php the_excerpt(); ?>
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         <a href="<?php echo esc_url( $event_url ); ?>"><?php echo esc_html( $event->title() ); ?></a>
+                               <span class="event-list-date">ends <?php $event->end()->print_relative_time_html(); ?></time></span>
+                               <?php echo esc_html( get_the_excerpt( $event->id() ) ); ?>
</ins><span class="cx" style="display: block; padding: 0 10px">                         </li>
</span><span class="cx" style="display: block; padding: 0 10px">                        <?php
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                endwhile;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         endforeach;
</ins><span class="cx" style="display: block; padding: 0 10px">                 ?>
</span><span class="cx" style="display: block; padding: 0 10px">        </ul>
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -46,8 +47,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">        echo wp_kses_post(
</span><span class="cx" style="display: block; padding: 0 10px">                paginate_links(
</span><span class="cx" style="display: block; padding: 0 10px">                        array(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                'total'     => $current_events_query->max_num_pages,
-                               'current'   => max( 1, $current_events_query->query_vars['paged'] ),
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         'total'     => $current_events_query->page_count,
+                               'current'   => $current_events_query->current_page,
</ins><span class="cx" style="display: block; padding: 0 10px">                                 'format'    => '?current_events_paged=%#%',
</span><span class="cx" style="display: block; padding: 0 10px">                                'prev_text' => '&laquo; Previous',
</span><span class="cx" style="display: block; padding: 0 10px">                                'next_text' => 'Next &raquo;',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -57,22 +58,22 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        wp_reset_postdata();
</span><span class="cx" style="display: block; padding: 0 10px"> endif;
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-if ( $upcoming_events_query->have_posts() ) :
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+if ( ! empty( $upcoming_events_query->events ) ) :
</ins><span class="cx" style="display: block; padding: 0 10px">         ?>
</span><span class="cx" style="display: block; padding: 0 10px">        <h2><?php esc_html_e( 'Upcoming events', 'gp-translation-events' ); ?></h2>
</span><span class="cx" style="display: block; padding: 0 10px">        <ul class="event-list">
</span><span class="cx" style="display: block; padding: 0 10px">                <?php
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                while ( $upcoming_events_query->have_posts() ) :
-                       $upcoming_events_query->the_post();
-                       $event_start = ( new DateTime( get_post_meta( get_the_ID(), '_event_start', true ) ) )->format( 'l, F j, Y' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         foreach ( $upcoming_events_query->events as $event ) :
+                       $event_url = gp_url( wp_make_link_relative( get_the_permalink( $event->id() ) ) );
</ins><span class="cx" style="display: block; padding: 0 10px">                         ?>
</span><span class="cx" style="display: block; padding: 0 10px">                        <li class="event-list-item">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                <a href="<?php echo esc_url( gp_url( wp_make_link_relative( get_the_permalink() ) ) ); ?>"><?php the_title(); ?></a>
-                               <span class="event-list-date"><?php echo esc_html( $event_start ); ?></span>
-                               <?php the_excerpt(); ?>
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         <a href="<?php echo esc_url( $event_url ); ?>"><?php echo esc_html( $event->title() ); ?></a>
+                               <span class="event-list-date">starts <?php $event->start()->print_relative_time_html(); ?></span>
+                               <?php echo esc_html( get_the_excerpt( $event->id() ) ); ?>
</ins><span class="cx" style="display: block; padding: 0 10px">                         </li>
</span><span class="cx" style="display: block; padding: 0 10px">                        <?php
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                endwhile;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         endforeach;
</ins><span class="cx" style="display: block; padding: 0 10px">                 ?>
</span><span class="cx" style="display: block; padding: 0 10px">        </ul>
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -80,8 +81,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">        echo wp_kses_post(
</span><span class="cx" style="display: block; padding: 0 10px">                paginate_links(
</span><span class="cx" style="display: block; padding: 0 10px">                        array(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                'total'     => $upcoming_events_query->max_num_pages,
-                               'current'   => max( 1, $upcoming_events_query->query_vars['paged'] ),
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         'total'     => $upcoming_events_query->page_count,
+                               'current'   => $upcoming_events_query->current_page,
</ins><span class="cx" style="display: block; padding: 0 10px">                                 'format'    => '?upcoming_events_paged=%#%',
</span><span class="cx" style="display: block; padding: 0 10px">                                'prev_text' => '&laquo; Previous',
</span><span class="cx" style="display: block; padding: 0 10px">                                'next_text' => 'Next &raquo;',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -91,27 +92,21 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        wp_reset_postdata();
</span><span class="cx" style="display: block; padding: 0 10px"> endif;
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-if ( $past_events_query->have_posts() ) :
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+if ( ! empty( $past_events_query->events ) ) :
</ins><span class="cx" style="display: block; padding: 0 10px">         ?>
</span><span class="cx" style="display: block; padding: 0 10px">        <h2><?php esc_html_e( 'Past events', 'gp-translation-events' ); ?></h2>
</span><span class="cx" style="display: block; padding: 0 10px">        <ul class="event-list">
</span><span class="cx" style="display: block; padding: 0 10px">                <?php
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                while ( $past_events_query->have_posts() ) :
-                       $past_events_query->the_post();
-                       $event_start = ( new DateTime( get_post_meta( get_the_ID(), '_event_start', true ) ) )->format( 'M j, Y' );
-                       $event_end   = ( new DateTime( get_post_meta( get_the_ID(), '_event_end', true ) ) )->format( 'M j, Y' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         foreach ( $past_events_query->events as $event ) :
+                       $event_url = gp_url( wp_make_link_relative( get_the_permalink( $event->id() ) ) );
</ins><span class="cx" style="display: block; padding: 0 10px">                         ?>
</span><span class="cx" style="display: block; padding: 0 10px">                        <li class="event-list-item">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                <a href="<?php echo esc_url( gp_url( wp_make_link_relative( get_the_permalink() ) ) ); ?>"><?php the_title(); ?></a>
-                               <?php if ( $event_start === $event_end ) : ?>
-                                       <span class="event-list-date"><?php echo esc_html( $event_start ); ?></span>
-                               <?php else : ?>
-                                       <span class="event-list-date"><?php echo esc_html( $event_start ); ?> - <?php echo esc_html( $event_end ); ?></span>
-                               <?php endif; ?>
-                               <?php the_excerpt(); ?>
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         <a href="<?php echo esc_url( $event_url ); ?>"><?php echo esc_html( $event->title() ); ?></a>
+                               <span class="event-list-date">ended <?php $event->end()->print_relative_time_html( 'F j, Y H:i T' ); ?></span>
+                               <?php esc_html( get_the_excerpt( $event->id() ) ); ?>
</ins><span class="cx" style="display: block; padding: 0 10px">                         </li>
</span><span class="cx" style="display: block; padding: 0 10px">                        <?php
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                endwhile;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         endforeach;
</ins><span class="cx" style="display: block; padding: 0 10px">                 ?>
</span><span class="cx" style="display: block; padding: 0 10px">        </ul>
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -119,8 +114,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">        echo wp_kses_post(
</span><span class="cx" style="display: block; padding: 0 10px">                paginate_links(
</span><span class="cx" style="display: block; padding: 0 10px">                        array(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                'total'     => $past_events_query->max_num_pages,
-                               'current'   => max( 1, $past_events_query->query_vars['paged'] ),
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         'total'     => $past_events_query->page_count,
+                               'current'   => $past_events_query->current_page,
</ins><span class="cx" style="display: block; padding: 0 10px">                                 'format'    => '?past_events_paged=%#%',
</span><span class="cx" style="display: block; padding: 0 10px">                                'prev_text' => '&laquo; Previous',
</span><span class="cx" style="display: block; padding: 0 10px">                                'next_text' => 'Next &raquo;',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -131,7 +126,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">        wp_reset_postdata();
</span><span class="cx" style="display: block; padding: 0 10px"> endif;
</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 ( 0 === $current_events_query->post_count && 0 === $upcoming_events_query->post_count && 0 === $past_events_query->post_count ) :
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+if ( empty( $current_events_query->events ) && empty( $upcoming_events_query->events ) && empty( $past_events_query->post_count ) ) :
</ins><span class="cx" style="display: block; padding: 0 10px">         esc_html_e( 'No events found.', 'gp-translation-events' );
</span><span class="cx" style="display: block; padding: 0 10px"> endif;
</span><span class="cx" style="display: block; padding: 0 10px"> ?>
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -138,27 +133,25 @@
</span><span class="cx" style="display: block; padding: 0 10px"> </div>
</span><span class="cx" style="display: block; padding: 0 10px"> <?php if ( is_user_logged_in() ) : ?>
</span><span class="cx" style="display: block; padding: 0 10px">        <div class="event-right-col">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                <h3 class="">Events I'm Attending</h3>
-               <?php if ( ! $user_attending_events_query->have_posts() ) : ?>
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         <h2>Events I'm Attending</h2>
+               <?php if ( empty( $user_attending_events_query->events ) ) : ?>
</ins><span class="cx" style="display: block; padding: 0 10px">                         <p>You don't have any events to attend.</p>
</span><span class="cx" style="display: block; padding: 0 10px">                <?php else : ?>
</span><span class="cx" style="display: block; padding: 0 10px">                        <ul class="event-attending-list">
</span><span class="cx" style="display: block; padding: 0 10px">                                <?php
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                while ( $user_attending_events_query->have_posts() ) :
-                                       $user_attending_events_query->the_post();
-                                       $event_start = ( new DateTime( get_post_meta( get_the_ID(), '_event_start', true ) ) )->format( 'M j, Y' );
-                                       $event_end   = ( new DateTime( get_post_meta( get_the_ID(), '_event_end', true ) ) )->format( 'M j, Y' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         foreach ( $user_attending_events_query->events as $event ) :
+                                       $event_url = gp_url( wp_make_link_relative( get_the_permalink( $event->id() ) ) );
</ins><span class="cx" style="display: block; padding: 0 10px">                                         ?>
</span><span class="cx" style="display: block; padding: 0 10px">                                        <li class="event-list-item">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                                <a href="<?php echo esc_url( gp_url( wp_make_link_relative( get_the_permalink() ) ) ); ?>"><?php the_title(); ?></a>
-                                               <?php if ( $event_start === $event_end ) : ?>
-                                                       <span class="event-list-date events-i-am-attending"><?php echo esc_html( $event_start ); ?></span>
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                         <a href="<?php echo esc_url( $event_url ); ?>"><?php echo esc_html( $event->title() ); ?></a>
+                                               <?php if ( $event->start() === $event->end() ) : ?>
+                                                       <span class="event-list-date events-i-am-attending"><?php $event->start()->print_time_html( 'F j, Y H:i T' ); ?></span>
</ins><span class="cx" style="display: block; padding: 0 10px">                                                 <?php else : ?>
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                                        <span class="event-list-date events-i-am-attending"><?php echo esc_html( $event_start ); ?> - <?php echo esc_html( $event_end ); ?></span>
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                                 <span class="event-list-date events-i-am-attending"><?php $event->start()->print_time_html( 'F j, Y H:i T' ); ?> - <?php $event->end()->print_time_html( 'F j, Y H:i T' ); ?></span>
</ins><span class="cx" style="display: block; padding: 0 10px">                                                 <?php endif; ?>
</span><span class="cx" style="display: block; padding: 0 10px">                                        </li>
</span><span class="cx" style="display: block; padding: 0 10px">                                        <?php
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                endwhile;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         endforeach;
</ins><span class="cx" style="display: block; padding: 0 10px">                                 ?>
</span><span class="cx" style="display: block; padding: 0 10px">                        </ul>
</span><span class="cx" style="display: block; padding: 0 10px">                        <?php
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -165,8 +158,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                echo wp_kses_post(
</span><span class="cx" style="display: block; padding: 0 10px">                                        paginate_links(
</span><span class="cx" style="display: block; padding: 0 10px">                                                array(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                                        'total'     => $user_attending_events_query->max_num_pages,
-                                                       'current'   => max( 1, $user_attending_events_query->query_vars['paged'] ),
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                                 'total'     => $user_attending_events_query->page_count,
+                                                       'current'   => $user_attending_events_query->current_page,
</ins><span class="cx" style="display: block; padding: 0 10px">                                                         'format'    => '?user_attending_events_paged=%#%',
</span><span class="cx" style="display: block; padding: 0 10px">                                                        'prev_text' => '&laquo; Previous',
</span><span class="cx" style="display: block; padding: 0 10px">                                                        'next_text' => 'Next &raquo;',
</span></span></pre></div>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventstemplateseventsmyeventsphp"></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/events-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/templates/events-my-events.php       2024-04-15 08:28:54 UTC (rev 13528)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/templates/events-my-events.php 2024-04-15 13:37:55 UTC (rev 13529)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -5,50 +5,48 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> namespace Wporg\TranslationEvents;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-use DateTime;
-use WP_Query;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+use Wporg\TranslationEvents\Event\Events_Query_Result;
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-/** @var WP_Query $events_i_created_query */
-/** @var WP_Query $events_i_attended_query */
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+/** @var Events_Query_Result $events_i_created_query */
+/** @var Events_Query_Result $events_i_host_query */
+/** @var Events_Query_Result $events_i_attended_query */
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> gp_title( esc_html__( 'Translation Events', 'gp-translation-events' ) . ' - ' . esc_html__( 'My Events', 'gp-translation-events' ) );
</span><span class="cx" style="display: block; padding: 0 10px"> gp_breadcrumb_translation_events( array( esc_html__( 'My Events', 'gp-translation-events' ) ) );
</span><span class="cx" style="display: block; padding: 0 10px"> gp_tmpl_header();
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+$event_page_title = __( 'My Events', 'gp-translation-events' );
</ins><span class="cx" style="display: block; padding: 0 10px"> gp_tmpl_load( 'events-header', get_defined_vars(), __DIR__ );
</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"> <div class="event-page-wrapper">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        <h1 class="event_page_title"><?php esc_html_e( 'My Events', 'gp-translation-events' ); ?> </h1>
-       <h2 class="event_page_title"><?php esc_html_e( 'Events I have created', 'gp-translation-events' ); ?> </h2>
-       <?php if ( $events_i_created_query->have_posts() ) : ?>
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ <?php if ( ! empty( $events_i_host_query->events ) ) : ?>
+               <h2><?php esc_html_e( 'Events I host', 'gp-translation-events' ); ?> </h2>
</ins><span class="cx" style="display: block; padding: 0 10px">                 <ul>
</span><span class="cx" style="display: block; padding: 0 10px">                <?php
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                while ( $events_i_created_query->have_posts() ) :
-                       $events_i_created_query->the_post();
-                       $event_id                      = get_the_ID();
-                       $event_start                   = get_post_meta( $event_id, '_event_start', true );
-                       list( $permalink, $post_name ) = get_sample_permalink( $event_id );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         foreach ( $events_i_host_query->events as $event ) :
+                       list( $permalink, $post_name ) = get_sample_permalink( $event->id() );
</ins><span class="cx" style="display: block; padding: 0 10px">                         $permalink                     = str_replace( '%pagename%', $post_name, $permalink );
</span><span class="cx" style="display: block; padding: 0 10px">                        $event_url                     = gp_url( wp_make_link_relative( $permalink ) );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $event_edit_url                = gp_url( 'events/edit/' . $event_id );
-                       $event_status                  = get_post_status( $event_id );
-                       $event_start                   = ( new DateTime( get_post_meta( get_the_ID(), '_event_start', true ) ) )->format( 'M j, Y' );
-                       $event_end                     = ( new DateTime( get_post_meta( get_the_ID(), '_event_end', true ) ) )->format( 'M j, Y' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $event_edit_url                = gp_url( 'events/edit/' . $event->id() );
+                       $stats_calculator              = new Stats_Calculator();
+                       $has_stats                     = $stats_calculator->event_has_stats( $event->id() );
</ins><span class="cx" style="display: block; padding: 0 10px">                         ?>
</span><span class="cx" style="display: block; padding: 0 10px">                        <li class="event-list-item">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                <a class="event-link-<?php echo esc_attr( $event_status ); ?>" href="<?php echo esc_url( $event_url ); ?>"><?php the_title(); ?></a>
-                               <a href="<?php echo esc_url( $event_edit_url ); ?>" class="button is-small action edit">Edit</a>
-                               <?php if ( 'draft' === $event_status ) : ?>
-                                       <span class="event-label-<?php echo esc_attr( $event_status ); ?>"><?php echo esc_html( $event_status ); ?></span>
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         <a class="event-link-<?php echo esc_attr( $event->status() ); ?>" href="<?php echo esc_url( $event_url ); ?>"><?php echo esc_html( $event->title() ); ?></a>
+                               <?php if ( ! $event->end()->is_in_the_past() && ! $has_stats ) : ?>
+                                       <a href="<?php echo esc_url( $event_edit_url ); ?>" class="button is-small action edit">Edit</a>
</ins><span class="cx" style="display: block; padding: 0 10px">                                 <?php endif; ?>
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                <?php if ( $event_start === $event_end ) : ?>
-                                       <span class="event-list-date events-i-am-attending"><?php echo esc_html( $event_start ); ?></span>
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         <?php if ( 'draft' === $event->status() ) : ?>
+                                       <span class="event-label-<?php echo esc_attr( $event->status() ); ?>"><?php echo esc_html( $event->status() ); ?></span>
+                               <?php endif; ?>
+                               <?php if ( $event->start()->format( 'Y-m-d' ) === $event->end()->format( 'Y-m-d' ) ) : ?>
+                                       <span class="event-list-date events-i-am-attending"><?php $event->start()->print_time_html(); ?></span>
</ins><span class="cx" style="display: block; padding: 0 10px">                                 <?php else : ?>
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        <span class="event-list-date events-i-am-attending"><?php echo esc_html( $event_start ); ?> - <?php echo esc_html( $event_end ); ?></span>
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 <span class="event-list-date events-i-am-attending"><?php $event->start()->print_time_html(); ?> - <?php $event->end()->print_time_html(); ?></span>
</ins><span class="cx" style="display: block; padding: 0 10px">                                 <?php endif; ?>
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                <p><?php the_excerpt(); ?></p>
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         <p><?php echo esc_html( get_the_excerpt( $event->id() ) ); ?></p>
</ins><span class="cx" style="display: block; padding: 0 10px">                         </li>
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                <?php endwhile; ?>
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         <?php endforeach; ?>
</ins><span class="cx" style="display: block; padding: 0 10px">                 </ul>
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                <?php
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -55,8 +53,55 @@
</span><span class="cx" style="display: block; padding: 0 10px">                echo wp_kses_post(
</span><span class="cx" style="display: block; padding: 0 10px">                        paginate_links(
</span><span class="cx" style="display: block; padding: 0 10px">                                array(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        'total'     => $events_i_created_query->max_num_pages,
-                                       'current'   => max( 1, $events_i_created_query->query_vars['events_i_created_paged'] ),
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 'total'     => $events_i_host_query->page_count,
+                                       'current'   => $events_i_host_query->current_page,
+                                       'format'    => '?events_i_hosted_paged=%#%',
+                                       'prev_text' => '&laquo; Previous',
+                                       'next_text' => 'Next &raquo;',
+                               )
+                       ) ?? ''
+               );
+
+               wp_reset_postdata();
+       endif;
+       ?>
+
+       <?php if ( ! empty( $events_i_created_query->events ) ) : ?>
+               <h2><?php esc_html_e( 'Events I have created', 'gp-translation-events' ); ?> </h2>
+               <ul>
+                       <?php
+                       foreach ( $events_i_created_query->events as $event ) :
+                               list( $permalink, $post_name ) = get_sample_permalink( $event->id() );
+                               $permalink                     = str_replace( '%pagename%', $post_name, $permalink );
+                               $event_url                     = gp_url( wp_make_link_relative( $permalink ) );
+                               $event_edit_url                = gp_url( 'events/edit/' . $event->id() );
+                               $stats_calculator              = new Stats_Calculator();
+                               $has_stats                     = $stats_calculator->event_has_stats( $event->id() );
+                               ?>
+                               <li class="event-list-item">
+                                       <a class="event-link-<?php echo esc_attr( $event->status() ); ?>" href="<?php echo esc_url( $event_url ); ?>"><?php echo esc_html( $event->title() ); ?></a>
+                                       <?php if ( ! $event->end()->is_in_the_past() && ! $has_stats ) : ?>
+                                               <a href="<?php echo esc_url( $event_edit_url ); ?>" class="button is-small action edit">Edit</a>
+                                       <?php endif; ?>
+                                       <?php if ( 'draft' === $event->status() ) : ?>
+                                               <span class="event-label-<?php echo esc_attr( $event->status() ); ?>"><?php echo esc_html( $event->status() ); ?></span>
+                                       <?php endif; ?>
+                                       <?php if ( $event->start()->format( 'Y-m-d' ) === $event->end()->format( 'Y-m-d' ) ) : ?>
+                                               <span class="event-list-date events-i-am-attending"><?php $event->start()->print_time_html(); ?></span>
+                                       <?php else : ?>
+                                               <span class="event-list-date events-i-am-attending"><?php $event->start()->print_time_html(); ?> - <?php $event->end()->print_time_html(); ?></span>
+                                       <?php endif; ?>
+                                       <p><?php echo esc_html( get_the_excerpt( $event->id() ) ); ?></p>
+                               </li>
+                       <?php endforeach; ?>
+               </ul>
+
+               <?php
+               echo wp_kses_post(
+                       paginate_links(
+                               array(
+                                       'total'     => $events_i_created_query->page_count,
+                                       'current'   => $events_i_created_query->current_page,
</ins><span class="cx" style="display: block; padding: 0 10px">                                         'format'    => '?events_i_created_paged=%#%',
</span><span class="cx" style="display: block; padding: 0 10px">                                        'prev_text' => '&laquo; Previous',
</span><span class="cx" style="display: block; padding: 0 10px">                                        'next_text' => 'Next &raquo;',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -65,37 +110,28 @@
</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">                wp_reset_postdata();
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        else :
-               echo 'No events found.';
</del><span class="cx" style="display: block; padding: 0 10px">         endif;
</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">-        <h2 class="event_page_title"><?php esc_html_e( 'Events I attended', 'gp-translation-events' ); ?> </h2>
-       <?php if ( $events_i_attended_query->have_posts() ) : ?>
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ <h2><?php esc_html_e( 'Events I attended', 'gp-translation-events' ); ?> </h2>
+       <?php if ( ! empty( $events_i_attended_query->events ) ) : ?>
</ins><span class="cx" style="display: block; padding: 0 10px">                 <ul>
</span><span class="cx" style="display: block; padding: 0 10px">                <?php
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                while ( $events_i_attended_query->have_posts() ) :
-                       $events_i_attended_query->the_post();
-                       $event_id                      = get_the_ID();
-                       $event_start                   = get_post_meta( $event_id, '_event_start', true );
-                       list( $permalink, $post_name ) = get_sample_permalink( $event_id );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         foreach ( $events_i_attended_query->events as $event ) :
+                       list( $permalink, $post_name ) = get_sample_permalink( $event->id() );
</ins><span class="cx" style="display: block; padding: 0 10px">                         $permalink                     = str_replace( '%pagename%', $post_name, $permalink );
</span><span class="cx" style="display: block; padding: 0 10px">                        $event_url                     = gp_url( wp_make_link_relative( $permalink ) );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $event_edit_url                = gp_url( 'events/edit/' . $event_id );
-                       $event_status                  = get_post_status( $event_id );
-                       $event_start                   = ( new DateTime( get_post_meta( get_the_ID(), '_event_start', true ) ) )->format( 'M j, Y' );
-                       $event_end                     = ( new DateTime( get_post_meta( get_the_ID(), '_event_end', true ) ) )->format( 'M j, Y' );
</del><span class="cx" style="display: block; padding: 0 10px">                         ?>
</span><span class="cx" style="display: block; padding: 0 10px">                        <li class="event-list-item">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                <a class="event-link-<?php echo esc_attr( $event_status ); ?>" href="<?php echo esc_url( $event_url ); ?>"><?php the_title(); ?></a>
-                               <?php if ( $event_start === $event_end ) : ?>
-                                       <span class="event-list-date events-i-am-attending"><?php echo esc_html( $event_start ); ?></span>
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         <a class="event-link-<?php echo esc_attr( $event->status() ); ?>" href="<?php echo esc_url( $event_url ); ?>"><?php echo esc_html( $event->title() ); ?></a>
+                               <?php if ( $event->start() === $event->end() ) : ?>
+                                       <span class="event-list-date events-i-am-attending"><?php $event->start()->print_time_html(); ?></span>
</ins><span class="cx" style="display: block; padding: 0 10px">                                 <?php else : ?>
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        <span class="event-list-date events-i-am-attending"><?php echo esc_html( $event_start ); ?> - <?php echo esc_html( $event_end ); ?></span>
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 <span class="event-list-date events-i-am-attending"><?php $event->start()->print_time_html(); ?> - <?php $event->end()->print_time_html(); ?></span>
</ins><span class="cx" style="display: block; padding: 0 10px">                                 <?php endif; ?>
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                <p><?php the_excerpt(); ?></p>
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         <p><?php echo esc_html( get_the_excerpt( $event->id() ) ); ?></p>
</ins><span class="cx" style="display: block; padding: 0 10px">                         </li>
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                <?php endwhile; ?>
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         <?php endforeach; ?>
</ins><span class="cx" style="display: block; padding: 0 10px">                 </ul>
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                <?php
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -102,8 +138,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">                echo wp_kses_post(
</span><span class="cx" style="display: block; padding: 0 10px">                        paginate_links(
</span><span class="cx" style="display: block; padding: 0 10px">                                array(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        'total'     => $events_i_attended_query->max_num_pages,
-                                       'current'   => max( 1, $events_i_attended_query->query_vars['events_i_attended_paged'] ),
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 'total'     => $events_i_attended_query->page_count,
+                                       'current'   => $events_i_attended_query->current_page,
</ins><span class="cx" style="display: block; padding: 0 10px">                                         'format'    => '?events_i_attended_paged=%#%',
</span><span class="cx" style="display: block; padding: 0 10px">                                        'prev_text' => '&laquo; Previous',
</span><span class="cx" style="display: block; padding: 0 10px">                                        'next_text' => 'Next &raquo;',
</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-04-15 08:28:54 UTC (rev 13528)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/wporg-gp-translation-events.php        2024-04-15 13:37:55 UTC (rev 13529)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -24,78 +24,74 @@
</span><span class="cx" style="display: block; padding: 0 10px"> use GP;
</span><span class="cx" style="display: block; padding: 0 10px"> use WP_Post;
</span><span class="cx" style="display: block; padding: 0 10px"> use WP_Query;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+use Wporg\TranslationEvents\Attendee\Attendee;
+use Wporg\TranslationEvents\Attendee\Attendee_Repository;
+use Wporg\TranslationEvents\Event\Event_Form_Handler;
+use Wporg\TranslationEvents\Event\Event_Repository_Cached;
+use Wporg\TranslationEvents\Event\Event_Repository_Interface;
</ins><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">-        public const CPT                     = 'translation_event';
-       public const USER_META_KEY_ATTENDING = 'translation-events-attending';
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ public const CPT = 'translation_event';
</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 static function get_instance() {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ public static function get_instance(): Translation_Events {
</ins><span class="cx" style="display: block; padding: 0 10px">                 static $instance = null;
</span><span class="cx" style="display: block; padding: 0 10px">                if ( null === $instance ) {
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        require_once __DIR__ . '/autoload.php';
</ins><span class="cx" style="display: block; padding: 0 10px">                         $instance = new self();
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px">                return $instance;
</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">+        public static function get_event_repository(): Event_Repository_Interface {
+               static $event_repository = null;
+               if ( null === $event_repository ) {
+                       $event_repository = new Event_Repository_Cached( self::get_attendee_repository() );
+               }
+               return $event_repository;
+       }
+
+       public static function get_attendee_repository(): Attendee_Repository {
+               static $attendee_repository = null;
+               if ( null === $attendee_repository ) {
+                       $attendee_repository = new Attendee_Repository();
+               }
+               return $attendee_repository;
+       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">         public function __construct() {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                \add_action( 'wp_ajax_submit_event_ajax', array( $this, 'submit_event_ajax' ) );
-               \add_action( 'wp_ajax_nopriv_submit_event_ajax', array( $this, 'submit_event_ajax' ) );
-               \add_action( 'wp_enqueue_scripts', array( $this, 'register_translation_event_js' ) );
-               \add_action( 'init', array( $this, 'register_event_post_type' ) );
-               \add_action( 'add_meta_boxes', array( $this, 'event_meta_boxes' ) );
-               \add_action( 'save_post', array( $this, 'save_event_meta_boxes' ) );
-               \add_action( 'transition_post_status', array( $this, 'event_status_transition' ), 10, 3 );
-               \add_filter( 'gp_nav_menu_items', array( $this, 'gp_event_nav_menu_items' ), 10, 2 );
-               \add_filter( 'wp_insert_post_data', array( $this, 'generate_event_slug' ), 10, 2 );
-               \add_action( 'gp_init', array( $this, 'gp_init' ) );
-               \add_action( 'gp_before_translation_table', array( $this, 'add_active_events_current_user' ) );
-               \register_activation_hook( __FILE__, array( $this, 'activate' ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         add_action( 'wp_ajax_submit_event_ajax', array( $this, 'submit_event_ajax' ) );
+               add_action( 'wp_ajax_nopriv_submit_event_ajax', array( $this, 'submit_event_ajax' ) );
+               add_action( 'wp_enqueue_scripts', array( $this, 'register_translation_event_js' ) );
+               add_action( 'init', array( $this, 'register_event_post_type' ) );
+               add_action( 'add_meta_boxes', array( $this, 'event_meta_boxes' ) );
+               add_action( 'save_post', array( $this, 'save_event_meta_boxes' ) );
+               add_action( 'transition_post_status', array( $this, 'event_status_transition' ), 10, 3 );
+               add_filter( 'gp_nav_menu_items', array( $this, 'gp_event_nav_menu_items' ), 10, 2 );
+               add_filter( 'wp_insert_post_data', array( $this, 'generate_event_slug' ), 10, 2 );
+               add_action( 'gp_init', array( $this, 'gp_init' ) );
+               add_action( 'gp_before_translation_table', array( $this, 'add_active_events_current_user' ) );
+
+               if ( is_admin() ) {
+                       Upgrade::upgrade_if_needed();
+               }
</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">        public function gp_init() {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                require_once __DIR__ . '/templates/helper-functions.php';
-               require_once __DIR__ . '/includes/active-events-cache.php';
-               require_once __DIR__ . '/includes/event.php';
-               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';
-               require_once __DIR__ . '/includes/stats-calculator.php';
-               require_once __DIR__ . '/includes/stats-listener.php';
-
</del><span class="cx" style="display: block; padding: 0 10px">                 GP::$router->add( '/events?', array( 'Wporg\TranslationEvents\Routes\Event\List_Route', 'handle' ) );
</span><span class="cx" style="display: block; padding: 0 10px">                GP::$router->add( '/events/new', array( 'Wporg\TranslationEvents\Routes\Event\Create_Route', 'handle' ) );
</span><span class="cx" style="display: block; padding: 0 10px">                GP::$router->add( '/events/edit/(\d+)', array( 'Wporg\TranslationEvents\Routes\Event\Edit_Route', 'handle' ) );
</span><span class="cx" style="display: block; padding: 0 10px">                GP::$router->add( '/events/attend/(\d+)', array( 'Wporg\TranslationEvents\Routes\User\Attend_Event_Route', 'handle' ), 'post' );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                GP::$router->add( '/events/host/(\d+)/(\d+)', array( 'Wporg\TranslationEvents\Routes\User\Host_Event_Route', 'handle' ), 'post' );
</ins><span class="cx" style="display: block; padding: 0 10px">                 GP::$router->add( '/events/my-events', array( 'Wporg\TranslationEvents\Routes\User\My_Events_Route', 'handle' ) );
</span><span class="cx" style="display: block; padding: 0 10px">                GP::$router->add( '/events/([a-z0-9_-]+)', array( 'Wporg\TranslationEvents\Routes\Event\Details_Route', 'handle' ) );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $active_events_cache = new Active_Events_Cache();
-               $stats_listener      = new Stats_Listener( $active_events_cache );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $stats_listener = new Stats_Listener(
+                       self::get_event_repository(),
+                       self::get_attendee_repository(),
+               );
</ins><span class="cx" style="display: block; padding: 0 10px">                 $stats_listener->start();
</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">-        public function activate() {
-               global $gp_table_prefix;
-               $create_table = "
-               CREATE TABLE `{$gp_table_prefix}event_actions` (
-                       `translate_event_actions_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
-                       `event_id` int(10) NOT NULL COMMENT 'Post_ID of the translation_event post in the wp_posts table',
-                       `original_id` int(10) NOT NULL COMMENT 'ID of the translation',
-                       `user_id` int(10) NOT NULL COMMENT 'ID of the user who made the action',
-                       `action` enum('approve','create','reject','request_changes') NOT NULL COMMENT 'The action that the user made (create, reject, etc)',
-                       `locale` varchar(10) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL COMMENT 'Locale of the translation',
-                       `happened_at` datetime NOT NULL COMMENT 'When the action happened, in UTC',
-               PRIMARY KEY (`translate_event_actions_id`),
-               UNIQUE KEY `event_per_translated_original_per_user` (`event_id`,`locale`,`original_id`,`user_id`)
-               ) COMMENT='Tracks translation actions that happened during a translation event'";
-               require_once ABSPATH . 'wp-admin/includes/upgrade.php';
-               dbDelta( $create_table );
-       }
-
</del><span class="cx" style="display: block; padding: 0 10px">         /**
</span><span class="cx" style="display: block; padding: 0 10px">         * Register the event post type.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -130,7 +126,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * Add meta boxes for the event post type.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function event_meta_boxes() {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                \add_meta_box( 'event_dates', 'Event Dates', array( $this, 'event_dates_meta_box' ), self::CPT, 'normal', 'high' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         add_meta_box( 'event_dates', 'Event Dates', array( $this, 'event_dates_meta_box' ), self::CPT, 'normal', 'high' );
</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">@@ -175,207 +171,15 @@
</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><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * Validate the event dates.
-        *
-        * @param string $event_start The event start date.
-        * @param string $event_end The event end date.
-        * @return bool Whether the event dates are valid.
-        * @throws Exception When dates are invalid.
-        */
-       public function validate_event_dates( string $event_start, string $event_end ): bool {
-               if ( ! $event_start || ! $event_end ) {
-                       return false;
-               }
-               $event_start = new DateTime( $event_start );
-               $event_end   = new DateTime( $event_end );
-               if ( $event_start < $event_end ) {
-                       return true;
-               }
-               return false;
-       }
-
-       /**
</del><span class="cx" style="display: block; padding: 0 10px">          * Handle the event form submission for the creation, editing, and deletion of events. This function is called via AJAX.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function submit_event_ajax() {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( ! is_user_logged_in() ) {
-                       wp_send_json_error( esc_html__( 'The user must be logged in.', 'gp-translation-events' ), 403 );
-               }
-               $action           = isset( $_POST['form_name'] ) ? sanitize_text_field( wp_unslash( $_POST['form_name'] ) ) : '';
-               $event_id         = null;
-               $event            = null;
-               $response_message = '';
-               $form_actions     = array( 'draft', 'publish', 'delete' );
-               $is_nonce_valid   = false;
-               $nonce_name       = '_event_nonce';
-               if ( ! in_array( $action, array( 'create_event', 'edit_event', 'delete_event' ), true ) ) {
-                       wp_send_json_error( esc_html__( 'Invalid form name.', 'gp-translation-events' ), 403 );
-               }
-               /**
-                * 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 ( 'create_event' === $action && ( ! $can_crud_event ) ) {
-                       wp_send_json_error( esc_html__( 'The user does not have permission to create an event.', 'gp-translation-events' ), 403 );
-               }
-               if ( 'edit_event' === $action ) {
-                       $event_id = isset( $_POST['event_id'] ) ? sanitize_text_field( wp_unslash( $_POST['event_id'] ) ) : '';
-                       $event    = get_post( $event_id );
-                       if ( ! ( $can_crud_event || current_user_can( 'edit_post', $event_id ) || intval( $event->post_author ) === get_current_user_id() ) ) {
-                               wp_send_json_error( esc_html__( 'The user does not have permission to edit or delete the event.', 'gp-translation-events' ), 403 );
-                       }
-               }
-               if ( 'delete_event' === $action ) {
-                       $event_id = isset( $_POST['event_id'] ) ? sanitize_text_field( wp_unslash( $_POST['event_id'] ) ) : '';
-                       $event    = get_post( $event_id );
-                       if ( ! ( $can_crud_event || current_user_can( 'delete_post', $event->ID ) || get_current_user_id() === $event->post_author ) ) {
-                               wp_send_json_error( esc_html__( 'You do not have permission to delete this event.', 'gp-translation-events' ), 403 );
-                       }
-               }
-               if ( isset( $_POST[ $nonce_name ] ) ) {
-                       $nonce_value = sanitize_text_field( wp_unslash( $_POST[ $nonce_name ] ) );
-                       if ( wp_verify_nonce( $nonce_value, $nonce_name ) ) {
-                               $is_nonce_valid = true;
-                       }
-               }
-               if ( ! $is_nonce_valid ) {
-                       wp_send_json_error( esc_html__( 'Nonce verification failed.', 'gp-translation-events' ), 403 );
-               }
-               // This is a list of slugs that are not allowed, as they conflict with the event URLs.
-               $invalid_slugs = array( 'new', 'edit', 'attend', 'my-events' );
-               $title         = isset( $_POST['event_title'] ) ? sanitize_text_field( wp_unslash( $_POST['event_title'] ) ) : '';
-               // This will be sanitized by santitize_post which is called in wp_insert_post.
-               $description    = isset( $_POST['event_description'] ) ? force_balance_tags( wp_unslash( $_POST['event_description'] ) ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
-               $event_start    = isset( $_POST['event_start'] ) ? sanitize_text_field( wp_unslash( $_POST['event_start'] ) ) : '';
-               $event_end      = isset( $_POST['event_end'] ) ? sanitize_text_field( wp_unslash( $_POST['event_end'] ) ) : '';
-               $event_timezone = isset( $_POST['event_timezone'] ) ? sanitize_text_field( wp_unslash( $_POST['event_timezone'] ) ) : '';
-               if ( isset( $title ) && in_array( sanitize_title( $title ), $invalid_slugs, true ) ) {
-                       wp_send_json_error( esc_html__( 'Invalid slug.', 'gp-translation-events' ), 422 );
-               }
-
-               $is_valid_event_date = false;
-               try {
-                       $is_valid_event_date = $this->validate_event_dates( $event_start, $event_end );
-               } catch ( Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
-                       // Deliberately ignored, handled below.
-               }
-               if ( ! $is_valid_event_date ) {
-                       wp_send_json_error( esc_html__( 'Invalid event dates.', 'gp-translation-events' ), 422 );
-               }
-
-               $event_status = '';
-               if ( isset( $_POST['event_form_action'] ) && in_array( $_POST['event_form_action'], $form_actions, true ) ) {
-                       $event_status = sanitize_text_field( wp_unslash( $_POST['event_form_action'] ) );
-               }
-
-               if ( ! isset( $_POST['form_name'] ) ) {
-                       wp_send_json_error( esc_html__( 'Form name must be set.', 'gp-translation-events' ), 422 );
-               }
-
-               if ( 'create_event' === $action ) {
-                       $event_id         = wp_insert_post(
-                               array(
-                                       'post_type'    => self::CPT,
-                                       'post_title'   => $title,
-                                       'post_content' => $description,
-                                       'post_status'  => $event_status,
-                               )
-                       );
-                       $response_message = esc_html__( 'Event created successfully!', 'gp-translation-events' );
-               }
-               if ( 'edit_event' === $action ) {
-                       if ( ! isset( $_POST['event_id'] ) ) {
-                               wp_send_json_error( esc_html__( 'Event id is required.', 'gp-translation-events' ), 422 );
-                       }
-                       $event_id = sanitize_text_field( wp_unslash( $_POST['event_id'] ) );
-                       $event    = get_post( $event_id );
-                       if ( ! $event || self::CPT !== $event->post_type || ! ( current_user_can( 'edit_post', $event->ID ) || intval( $event->post_author ) === get_current_user_id() ) ) {
-                               wp_send_json_error( esc_html__( 'Event does not exist.', 'gp-translation-events' ), 404 );
-                       }
-                       wp_update_post(
-                               array(
-                                       'ID'           => $event_id,
-                                       'post_title'   => $title,
-                                       'post_content' => $description,
-                                       'post_status'  => $event_status,
-                               )
-                       );
-                       $response_message = esc_html__( 'Event updated successfully!', 'gp-translation-events' );
-               }
-               if ( 'delete_event' === $action ) {
-                       $event_id = sanitize_text_field( wp_unslash( $_POST['event_id'] ) );
-                       $event    = get_post( $event_id );
-                       if ( ! $event || self::CPT !== $event->post_type ) {
-                               wp_send_json_error( esc_html__( 'Event does not exist.', 'gp-translation-events' ), 404 );
-                       }
-                       if ( ! ( current_user_can( 'delete_post', $event->ID ) || get_current_user_id() === $event->post_author ) ) {
-                               wp_send_json_error( 'You do not have permission to delete this event' );
-                       }
-                       $stats_calculator = new Stats_Calculator();
-                       try {
-                               $event_stats = $stats_calculator->for_event( $event );
-                       } catch ( Exception $e ) {
-                               wp_send_json_error( esc_html__( 'Failed to calculate event stats.', 'gp-translation-events' ), 500 );
-                       }
-                       if ( ! empty( $event_stats->rows() ) ) {
-                               wp_send_json_error( esc_html__( 'Event has translations and cannot be deleted.', 'gp-translation-events' ), 422 );
-                       }
-                       wp_trash_post( $event_id );
-                       $response_message = esc_html__( 'Event deleted successfully!', 'gp-translation-events' );
-               }
-               if ( ! $event_id ) {
-                       wp_send_json_error( esc_html__( 'Event could not be created or updated.', 'gp-translation-events' ), 422 );
-               }
-               if ( 'delete_event' !== $_POST['form_name'] ) {
-                       try {
-                               update_post_meta( $event_id, '_event_start', $this->convert_to_utc( $event_start, $event_timezone ) );
-                               update_post_meta( $event_id, '_event_end', $this->convert_to_utc( $event_end, $event_timezone ) );
-                       } catch ( Exception $e ) {
-                               wp_send_json_error( esc_html__( 'Invalid start or end', 'gp-translation-events' ), 422 );
-                       }
-
-                       update_post_meta( $event_id, '_event_timezone', $event_timezone );
-               }
-               try {
-                       Active_Events_Cache::invalidate();
-               } catch ( Exception $e ) {
-                       // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
-                       error_log( $e );
-               }
-
-               list( $permalink, $post_name ) = get_sample_permalink( $event_id );
-               $permalink                     = str_replace( '%pagename%', $post_name, $permalink );
-               wp_send_json_success(
-                       array(
-                               'message'        => $response_message,
-                               'eventId'        => $event_id,
-                               'eventUrl'       => str_replace( '%pagename%', $post_name, $permalink ),
-                               'eventStatus'    => $event_status,
-                               'eventEditUrl'   => esc_url( gp_url( '/events/edit/' . $event_id ) ),
-                               'eventDeleteUrl' => esc_url( gp_url( '/events/my-events/' ) ),
-                       )
-               );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $form_handler = new Event_Form_Handler( self::get_event_repository(), self::get_attendee_repository() );
+               // Nonce verification is done by the form handler.
+               // phpcs:ignore WordPress.Security.NonceVerification.Missing
+               $form_handler->handle( $_POST );
</ins><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">-
-
-
-       /**
-        * Convert a date time in a time zone to UTC.
-        *
-        * @param string $date_time The date time in the time zone.
-        * @param string $time_zone The time zone.
-        * @return string The date time in UTC.
-        * @throws Exception When dates are invalid.
-        */
-       public function convert_to_utc( string $date_time, string $time_zone ): string {
-               $date_time = new DateTime( $date_time, new DateTimeZone( $time_zone ) );
-               $date_time->setTimezone( new DateTimeZone( 'UTC' ) );
-               return $date_time->format( 'Y-m-d H:i:s' );
-       }
-
</del><span class="cx" style="display: block; padding: 0 10px">         public function register_translation_event_js() {
</span><span class="cx" style="display: block; padding: 0 10px">                wp_register_style( 'translation-events-css', plugins_url( 'assets/css/translation-events.css', __FILE__ ), array(), filemtime( __DIR__ . '/assets/css/translation-events.css' ) );
</span><span class="cx" style="display: block; padding: 0 10px">                gp_enqueue_style( 'translation-events-css' );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -399,6 +203,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * @param string  $new_status The new post status.
</span><span class="cx" style="display: block; padding: 0 10px">         * @param string  $old_status The old post status.
</span><span class="cx" style="display: block; padding: 0 10px">         * @param WP_Post $post       The post object.
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         *
+        * @throws Exception
</ins><span class="cx" style="display: block; padding: 0 10px">          */
</span><span class="cx" style="display: block; padding: 0 10px">        public function event_status_transition( string $new_status, string $old_status, WP_Post $post ): void {
</span><span class="cx" style="display: block; padding: 0 10px">                if ( self::CPT !== $post->post_type ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -405,13 +211,15 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        return;
</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><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $current_user_id         = get_current_user_id();
-                       $user_attending_events   = get_user_meta( $current_user_id, self::USER_META_KEY_ATTENDING, true ) ?: array();
-                       $is_user_attending_event = in_array( $post->ID, $user_attending_events, true );
-                       if ( ! $is_user_attending_event ) {
-                               $new_user_attending_events              = $user_attending_events;
-                               $new_user_attending_events[ $post->ID ] = true;
-                               update_user_meta( $current_user_id, self::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">+                 $event_id            = $post->ID;
+                       $user_id             = $post->post_author;
+                       $attendee_repository = self::get_attendee_repository();
+                       $attendee            = $attendee_repository->get_attendee( $event_id, $user_id );
+
+                       if ( null === $attendee ) {
+                               $attendee = new Attendee( $event_id, $user_id );
+                               $attendee->mark_as_host();
+                               $attendee_repository->insert_attendee( $attendee );
</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">@@ -465,11 +273,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="cx" style="display: block; padding: 0 10px">         * Add the active events for the current user before the translation table.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * @return void
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * @throws Exception
</ins><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(), self::USER_META_KEY_ATTENDING, true ) ?: array();
-               if ( empty( $user_attending_events ) ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $attendee_repository      = new Attendee_Repository();
+               $user_attending_event_ids = $attendee_repository->get_events_for_user( get_current_user_id() );
+               if ( empty( $user_attending_event_ids ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                         return;
</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">@@ -476,7 +285,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $current_datetime_utc       = ( new DateTime( 'now', new DateTimeZone( 'UTC' ) ) )->format( 'Y-m-d H:i:s' );
</span><span class="cx" style="display: block; padding: 0 10px">                $user_attending_events_args = array(
</span><span class="cx" style="display: block; padding: 0 10px">                        'post_type'   => self::CPT,
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'post__in'    => array_keys( $user_attending_events ),
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'post__in'    => $user_attending_event_ids,
</ins><span class="cx" style="display: block; padding: 0 10px">                         'post_status' => 'publish',
</span><span class="cx" style="display: block; padding: 0 10px">                        // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
</span><span class="cx" style="display: block; padding: 0 10px">                        'meta_query'  => array(
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -498,6 +307,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'orderby'     => 'meta_value',
</span><span class="cx" style="display: block; padding: 0 10px">                        'order'       => 'ASC',
</span><span class="cx" style="display: block; padding: 0 10px">                );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px">                 $user_attending_events_query = new WP_Query( $user_attending_events_args );
</span><span class="cx" style="display: block; padding: 0 10px">                $number_of_events            = $user_attending_events_query->post_count;
</span><span class="cx" style="display: block; padding: 0 10px">                if ( 0 === $number_of_events ) {
</span></span></pre>
</div>
</div>

</body>
</html>