Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 127 additions & 14 deletions public/conferences/index.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

use phpweb\News\Category;
use phpweb\News\ConferenceSchedule;
use phpweb\News\NewsHandler;

$_SERVER['BASE_PAGE'] = 'conferences/index.php';
Expand All @@ -14,33 +16,144 @@
'css' => ['home.css'],
]);

$content = "<div class='home-content'>";
$panels = '<p class="prepend"><a href="https://wiki.php.net/conferences">Want to see your conference appear here?</a></p>';
$today = gmdate('Y-m-d');
$schedule = new ConferenceSchedule((new NewsHandler())->getConferences(), $today);

// The page has two views, switched via ?view=: the default view, which lists
// conferences only (targeted at attendees/sponsors), and the call-for-papers
// view, which lists CFPs only, split into open and closed (targeting speakers).
$view = ($_GET['view'] ?? null) === 'cfps' ? 'cfps' : 'conferences';

// Build nav for switching between views
$viewLinks = [
'conferences' => ['label' => 'Conferences', 'href' => '/conferences/'],
'cfps' => ['label' => 'Calls for papers', 'href' => '/conferences/?view=cfps'],
];

$viewSwitcher = '<nav class="conference-views" aria-label="Conference views">';
foreach ($viewLinks as $key => $viewLink) {
$isActive = $key === $view;
$viewSwitcher .= sprintf(
'<a class="conference-view%s" href="%s"%s>%s</a>',
$isActive ? ' is-active' : '',
$viewLink['href'],
$isActive ? ' aria-current="page"' : '',
$viewLink['label'],
);
}
$viewSwitcher .= '</nav>';

foreach ((new NewsHandler())->getConferences() as $entry) {
$renderEntry = static function (array $entry) use ($MYSITE, $today): string {
$link = preg_replace('~^(http://php.net/|https://www.php.net/)~', '', $entry["id"]);
$id = parse_url($entry["id"], PHP_URL_FRAGMENT);
$date = date_format(date_create($entry["updated"] ?? $entry["published"]), 'Y-m-d');
$content .= '<div class="newsentry">';
$content .= '<h3 class="newstitle title"><a href="' . $MYSITE . $link . '" id="' . $id . '">' . $entry["title"] . '</a></h3>';
$content .= '<div class="newsimage">';

$html = '<div class="newsentry">';
$html .= '<h3 class="newstitle title"><a href="' . $MYSITE . $link . '" id="' . $id . '">' . $entry["title"] . '</a></h3>';

if (isset($entry["finalTeaserDate"])) {
$isPast = $entry["finalTeaserDate"] < $today;
if (ConferenceSchedule::termFor($entry) === Category::Cfp) {
$label = $isPast ? 'CFP closed' : 'CFP closes';
} else {
$label = $isPast ? 'Started' : 'Starts';
}
$html .= '<p class="newsdate">' . $label . ' <time datetime="' . $entry["finalTeaserDate"] . '">' . date_format(date_create($entry["finalTeaserDate"]), 'd M Y') . '</time></p>';
}

$html .= '<div class="newsimage">';

if (isset($entry["newsImage"])) {
$content .= sprintf('<a href="%s"><img src="/images/news/%s"></a>', $entry["newsImage"]["link"], $entry["newsImage"]["content"]);
$html .= sprintf('<a href="%s"><img src="/images/news/%s"></a>', $entry["newsImage"]["link"], $entry["newsImage"]["content"]);
}

$content .= '</div>';
$content .= '<div class="newscontent">';
$content .= $entry["content"];
$content .= '</div>';
$content .= '</div>';
$html .= '</div>';
$html .= '<div class="newscontent">';
$html .= $entry["content"];
$html .= '</div>';
$html .= '</div>';

return $html;
};

$content = "<div class='home-content'>";
$content .= $viewSwitcher;

if ($view === 'cfps') {
$content .= '<h2>Open calls for papers</h2>';

if ($schedule->openCallsForPapers === []) {
$content .= '<p>No open calls for papers right now.</p>';
} else {
foreach ($schedule->openCallsForPapers as $entry) {
$content .= $renderEntry($entry);
}
}

if ($schedule->closedCallsForPapers !== []) {
$content .= '<h2 class="conferences-divider">Closed calls for papers</h2>';

foreach ($schedule->closedCallsForPapers as $entry) {
$content .= $renderEntry($entry);
}
}
} else {
$content .= '<h2>Upcoming conferences</h2>';

$panels .= sprintf('<p class="panel"><a href="%s">%s</a></p>', $entry["newsImage"]["link"] ?? $link, $entry["title"]);
if ($schedule->upcomingConferences === []) {
$content .= '<p>No upcoming conferences right now.</p>';
} else {
foreach ($schedule->upcomingConferences as $entry) {
$content .= $renderEntry($entry);
}
}

if ($schedule->pastConferences !== []) {
$content .= '<h2 class="conferences-divider">Past conferences</h2>';

foreach ($schedule->pastConferences as $entry) {
$content .= $renderEntry($entry);
}
}
}

$content .= "</div>";

echo $content;

// Build sidebar mirroring the active view: conferences grouped into upcoming/past, or CFPs into open/closed.
$panels = sprintf(
'<p class="prepend"><a href="https://wiki.php.net/conferences">Want to see your %s appear here?</a></p>',
$view === 'cfps' ? 'call for papers' : 'conference',
);

$renderPanels = static function (array $entries): string {
$html = '';
foreach ($entries as $entry) {
$link = preg_replace('~^(http://php.net/|https://www.php.net/)~', '', $entry["id"]);
$html .= sprintf('<p class="panel"><a href="%s">%s</a></p>', $entry["newsImage"]["link"] ?? $link, $entry["title"]);
}

return $html;
};

if ($view === 'cfps') {
if ($schedule->openCallsForPapers !== []) {
$panels .= '<h3>Open</h3>' . $renderPanels($schedule->openCallsForPapers);
}

if ($schedule->closedCallsForPapers !== []) {
$panels .= '<h3>Closed</h3>' . $renderPanels($schedule->closedCallsForPapers);
}
} else {
if ($schedule->upcomingConferences !== []) {
$panels .= '<h3>Upcoming</h3>' . $renderPanels($schedule->upcomingConferences);
}

if ($schedule->pastConferences !== []) {
$panels .= '<h3>Past</h3>' . $renderPanels($schedule->pastConferences);
}
}

site_footer([
"atom" => "/feed.atom",
"elephpants" => true,
Expand Down
57 changes: 48 additions & 9 deletions public/styles/home.css
Original file line number Diff line number Diff line change
Expand Up @@ -90,22 +90,22 @@
}

.hero__versions a.notes {
font-size:.875rem;
white-space:nowrap;
font-size: .875rem;
white-space: nowrap;
}

.hero__versions a {
color: var(--background-text-color);
border:0;
border: 0;
}

.hero__versions a:hover,
.hero__versions a:focus {
border-bottom:1px dotted;
border-bottom: 1px dotted;
}

.hero__version-link {
color:#fff !important;
color: #fff !important;
display: inline-block;
}

Expand Down Expand Up @@ -151,11 +151,12 @@
/* Layout */

#layout-content {
border-right:.25rem solid #666;
border-right: .25rem solid #666;
}

.home .newsentry .newstitle a:after {
content:"\20 \00bb";
color:#666;
content: "\20 \00bb";
color: #666;
}

p.archive {
Expand All @@ -180,7 +181,7 @@ p.archive {
}

#layout-content {
width:70%;
width: 70%;
}
}

Expand Down Expand Up @@ -216,3 +217,41 @@ aside.tips .social-media .icon-twitter {
background-color: var(--dark-magenta-color) !important;
border-color: var(--dark-magenta-color) !important;
}

/* Conference pages */
.conference-views {
display: flex;
justify-content: flex-end;
gap: 1.5rem;
margin: 0 0 1.5rem;
border-bottom: 1px solid #ccc;
}

.conference-views .conference-view {
padding: .5rem .25rem;
margin-bottom: -1px;
border-bottom: 3px solid transparent;
color: #666;
text-decoration: none;
}

.conference-views .conference-view.is-active {
border-bottom-color: var(--dark-blue-color);
color: var(--dark-grey-color);
font-weight: 600;
}

.newsentry .newsdate {
margin: -.75rem 0 .75rem;
color: #666;
font-size: .75rem;
font-weight: 600;
letter-spacing: .04em;
text-transform: uppercase;
}

h2.conferences-divider {
margin-top: 2rem;
padding-top: 1rem;
border-top: 1px solid #ccc;
}
16 changes: 16 additions & 0 deletions src/News/Category.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace phpweb\News;

/**
* The category terms a news entry can carry.
*/
enum Category: string
{
case Frontpage = 'frontpage';
case Releases = 'releases';
case Conferences = 'conferences';
case Cfp = 'cfp';
}
117 changes: 117 additions & 0 deletions src/News/ConferenceSchedule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<?php

declare(strict_types=1);

namespace phpweb\News;

use function usort;

/**
* Groups and orders conference/CFP news entries for the /conferences page and its CFP view.
*
* Conferences and calls for papers are separate, unlinked news entries, each with a single
* finalTeaserDate that means different things: for a conference it's the event's start date,
* for a CFP it's the submission deadline. They live on different pages:
*
* - The conferences view shows conferences only, split by start date into upcoming (soonest
* first) and past (most recent first). A conference with no finalTeaserDate falls into past.
* - The CFP view shows CFPs only, split by deadline into open (deadline today or later, soonest
* first) and closed (deadline passed, or no deadline, most recently closed first).
*
* finalTeaserDate nuance: for a conference it's start date, for a CFP it's end date
*/
final class ConferenceSchedule
{
/** @var list<array> Conferences starting today or later, soonest first. */
public private(set) array $upcomingConferences = [];
Comment thread
mattstauffer marked this conversation as resolved.
Resolved

/** @var list<array> Conferences already started (or undated), most recent first. */
public private(set) array $pastConferences = [];

/** @var list<array> CFPs whose submission deadline has not passed, soonest deadline first. */
public private(set) array $openCallsForPapers = [];

/** @var list<array> CFPs whose deadline has passed (or is unknown), most recently closed first. */
public private(set) array $closedCallsForPapers = [];

/**
* @param list<array> $entries conference/CFP entries, e.g. from NewsHandler::getConferences()
* @param string $today today's date as 'Y-m-d'
*/
public function __construct(array $entries, string $today)
{
foreach ($entries as $entry) {
$this->assignConferenceOrCfp($entry, $today);
}

$this->upcomingConferences = self::sortedByDate($this->upcomingConferences, ascending: true);
$this->openCallsForPapers = self::sortedByDate($this->openCallsForPapers, ascending: true);
$this->pastConferences = self::sortedByDate($this->pastConferences, ascending: false);
$this->closedCallsForPapers = self::sortedByDate($this->closedCallsForPapers, ascending: false);
}

/**
* The entry's primary term. A few posts announce a conference and open its CFP at once,
* carrying both terms; anything that is a conference is treated as a conference (and so
* placed by its date), leaving Cfp for CFP-only posts.
*
* @param array<array-key, mixed> $entry
*/
public static function termFor(array $entry): ?Category
{
$found = null;
foreach ($entry['category'] as $category) {
$term = Category::tryFrom($category['term']);

if ($term === Category::Conferences) {
return $term;
}

if ($term === Category::Cfp) {
$found = $term;
}
}

return $found;
}

/**
* @param array<array-key, mixed> $conferenceOrCfp
*/
private function assignConferenceOrCfp(array $conferenceOrCfp, string $today): void
{
$isFuture = isset($conferenceOrCfp['finalTeaserDate']) && $conferenceOrCfp['finalTeaserDate'] >= $today;
$isCfp = self::termFor($conferenceOrCfp) === Category::Cfp;

match (true) {
$isCfp && $isFuture => $this->openCallsForPapers[] = $conferenceOrCfp,
$isCfp => $this->closedCallsForPapers[] = $conferenceOrCfp,
$isFuture => $this->upcomingConferences[] = $conferenceOrCfp,
default => $this->pastConferences[] = $conferenceOrCfp,
};
}

/**
* Sort entries by finalTeaserDate ('Y-m-d'), keeping undated entries last.
*
* @param list<array> $entries
*
* @return list<array>
*/
private static function sortedByDate(array $entries, bool $ascending): array
{
usort($entries, static function (array $a, array $b) use ($ascending): int {
$aDate = $a['finalTeaserDate'] ?? '';
$bDate = $b['finalTeaserDate'] ?? '';

// Push undated entries to the end whichever way the dated ones are ordered.
if ($aDate === '' || $bDate === '') {
return ($aDate === '' ? 1 : 0) <=> ($bDate === '' ? 1 : 0);
}

return $ascending ? $aDate <=> $bDate : $bDate <=> $aDate;
});

return $entries;
}
}
Loading
Loading