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
137 changes: 123 additions & 14 deletions public/conferences/index.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?php

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

$_SERVER['BASE_PAGE'] = 'conferences/index.php';
Expand All @@ -14,33 +15,141 @@
'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) === '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 = '<p class="prepend"><a href="https://wiki.php.net/conferences">Want to see your conference appear here?</a></p>';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the word "conference" in "your conference appear here?" also switch to "call for paper", for the cfps view?


$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;
}
115 changes: 115 additions & 0 deletions src/News/ConferenceSchedule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<?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 = [];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The preview URL breaks on this one, because it is still using PHP 8.2. The actual server runs PHP 8.4, where this is okay.

For reference, the error is (with 8.2):

( ! ) Fatal error: Multiple access type modifiers are not allowed in /storage1/php-web-php/src/News/ConferenceSchedule.php on line 26


/** @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): ?string

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I would prefer if the return value of this would be an Enum —with I believe, the four cases, conferences, cfp, frontpage, and releases

{
$found = null;
foreach ($entry['category'] as $category) {
if ($category['term'] === 'conferences') {
return 'conferences';
}

if ($category['term'] === 'cfp') {
$found = 'cfp';
}
}

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) === '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;
}
}
7 changes: 6 additions & 1 deletion src/News/Entry.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,12 @@ public function save(): self {
$dom->preserveWhiteSpace = false;
$item = $dom->createElementNs("http://www.w3.org/2005/Atom", "entry");

$href = self::WEBROOT . ($this->isConference() ? '/conferences/index.php' : '/index.php');
// Build the appropriate deep link depending on whether it's news, conference, or CFP
$href = self::WEBROOT . match (true) {
in_array('conferences', $this->categories, true) => '/conferences/index.php',
in_array('cfp', $this->categories, true) => '/conferences/index.php?view=cfps',
default => '/index.php',
};
$archive = self::WEBROOT . "/archive/" . date('Y', $_SERVER['REQUEST_TIME']) . ".php#{$this->id}";
$link = ($this->image['link'] ?? null) ?: $archive;

Expand Down
Loading
Loading