Author: Mark Randall
Committer: Derick Rethans (derickr)
Date: 2026-06-24T09:48:49+01:00

Commit: 
https://github.com/php/web-php/commit/1c98aa654b831d543178d64f3041d9e623b82975
Raw diff: 
https://github.com/php/web-php/commit/1c98aa654b831d543178d64f3041d9e623b82975.diff

Update NewsHandler with docblock type, reusable filtering. Add reliable 
fallback for 'updated' => 'published'.

Changed paths:
  M  include/layout.inc
  M  phpstan-baseline.neon
  M  public/index.php
  M  src/News/NewsHandler.php


Diff:

diff --git a/include/layout.inc b/include/layout.inc
index f065310020..857128d64c 100644
--- a/include/layout.inc
+++ b/include/layout.inc
@@ -542,7 +542,7 @@ function get_news_changes()
         return false;
     }
 
-    $date = date_create($lastNews["updated"]);
+    $date = date_create($lastNews["updated"] ?? $lastNews["published"]);
     if (isset($_COOKIE["LAST_NEWS"]) && $_COOKIE["LAST_NEWS"] >= 
$date->getTimestamp()) {
         return false;
     }
@@ -557,6 +557,8 @@ function get_news_changes()
 
     $date->modify("+1 week");
     if ($date->getTimestamp() > $_SERVER["REQUEST_TIME"]) {
+        assert(isset($lastNews["link"][0]["href"]));
+
         $link = preg_replace('~^(http://php.net/|https://www.php.net/)~', '/', 
$lastNews["link"][0]["href"]);
         $title = $lastNews["title"];
         return "<a href='{$link}'>{$title}</a>";
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
index eba441413e..ed6401d99c 100644
--- a/phpstan-baseline.neon
+++ b/phpstan-baseline.neon
@@ -2532,24 +2532,6 @@ parameters:
                        count: 1
                        path: src/News/Entry.php
 
-               -
-                       message: '#^Call to function is_array\(\) with null 
will always evaluate to false\.$#'
-                       identifier: function.impossibleType
-                       count: 1
-                       path: src/News/NewsHandler.php
-
-               -
-                       message: '#^Method 
phpweb\\News\\NewsHandler\:\:getLastestNews\(\) return type has no value type 
specified in iterable type array\.$#'
-                       identifier: missingType.iterableValue
-                       count: 1
-                       path: src/News/NewsHandler.php
-
-               -
-                       message: '#^Method 
phpweb\\News\\NewsHandler\:\:getPregeneratedNews\(\) return type has no value 
type specified in iterable type array\.$#'
-                       identifier: missingType.iterableValue
-                       count: 1
-                       path: src/News/NewsHandler.php
-
                -
                        message: '#^Method 
phpweb\\Themes\\FeatureComparison\:\:__construct\(\) has parameter \$links with 
no value type specified in iterable type array\.$#'
                        identifier: missingType.iterableValue
diff --git a/public/index.php b/public/index.php
index 6758a83781..1694e3a246 100644
--- a/public/index.php
+++ b/public/index.php
@@ -61,7 +61,7 @@
 foreach ((new NewsHandler())->getFrontPageNews() as $entry) {
     $link = preg_replace('~^(http://php.net/|https://www.php.net/)~', '', 
$entry["id"]);
     $id = parse_url($entry["id"], PHP_URL_FRAGMENT);
-    $date = date_create($entry['updated']);
+    $date = date_create($entry['updated'] ?? $entry['published']);
     $date_human = date_format($date, 'd M Y');
     $date_w3c = date_format($date, DATE_W3C);
     $content .= <<<NEWSENTRY
diff --git a/src/News/NewsHandler.php b/src/News/NewsHandler.php
index a7e97f766e..c22c7e3e2f 100644
--- a/src/News/NewsHandler.php
+++ b/src/News/NewsHandler.php
@@ -5,15 +5,42 @@
 namespace phpweb\News;
 
 use DateTimeImmutable;
-
 use function array_filter;
 use function array_values;
+use function count;
 use function is_array;
 
+/**
+ * @phpstan-type NewsEntryStruct array{
+ *     title: string,
+ *     id: string,
+ *     published: string,
+ *     updated?: string,
+ *     link?: list<array{
+ *        href: string,
+ *        rel: string,
+ *        type?: string,
+ *     }>,
+ *     category: list<array{
+ *         term: string,
+ *         label: string,
+ *     }>,
+ *     newsImage?: array{
+ *         link: string,
+ *         content: string,
+ *     },
+ *     content: string,
+ *     intro?: string,
+ *     finalTeaserDate?: string,
+ * }
+ */
 final class NewsHandler
 {
     private const MAX_FRONT_PAGE_NEWS = 25;
 
+    /**
+     * @return NewsEntryStruct|null
+     */
     public function getLastestNews(): array|null
     {
         $news = $this->getPregeneratedNews();
@@ -24,45 +51,48 @@ public function getLastestNews(): array|null
         return $news[0];
     }
 
-    /** @return list<array> */
-    public function getFrontPageNews(): array
+    /**
+     * @param list<string> $tags
+     * @return list<NewsEntryStruct>
+     */
+    public function getTaggedEntries(array $tags, ?int $limit = null): array
     {
-        $frontPage = [];
+        $entries = [];
         foreach ($this->getPregeneratedNews() as $entry) {
-            foreach ($entry['category'] as $category) {
-                if ($category['term'] !== 'frontpage') {
-                    continue;
-                }
-
-                $frontPage[] = $entry;
-                if (count($frontPage) >= self::MAX_FRONT_PAGE_NEWS) {
-                    break 2;
-                }
+            if (!self::isTagged($entry, $tags)) {
+                continue;
+            }
+
+            $entries[] = $entry;
+            if ($limit !== null && count($entries) >= $limit) {
+                break;
             }
         }
 
-        return $frontPage;
+        return $entries;
     }
 
-    /** @return list<array> */
-    public function getConferences(): array
+    /**
+     *  Looks up generated news with frontpage tags.
+     *
+     * @return list<NewsEntryStruct>
+     */
+    public function getFrontPageNews(int $limit = self::MAX_FRONT_PAGE_NEWS): 
array
     {
-        $conferences = [];
-        foreach ($this->getPregeneratedNews() as $entry) {
-            foreach ($entry['category'] as $category) {
-                if ($category['term'] !== 'cfp' && $category['term'] !== 
'conferences') {
-                    continue;
-                }
-
-                $conferences[] = $entry;
-                break;
-            }
-        }
+        return $this->getTaggedEntries(['frontpage'], $limit);
+    }
 
-        return $conferences;
+    /**
+     * @return list<NewsEntryStruct>
+     */
+    public function getConferences(?int $limit = null): array
+    {
+        return $this->getTaggedEntries(['cfp', 'conferences'], $limit);
     }
 
-    /** @return list<array> */
+    /**
+     * @return list<NewsEntryStruct>
+     */
     public function getNewsByYear(int $year): array
     {
         return array_values(array_filter(
@@ -71,11 +101,32 @@ public function getNewsByYear(int $year): array
         ));
     }
 
+    /**
+     * @return list<NewsEntryStruct>
+     */
     public function getPregeneratedNews(): array
     {
         $NEWS_ENTRIES = null;
         include __DIR__ . '/../../include/pregen-news.inc';
 
+        /** @phpstan-ignore-next-line - pregen-news sets global variable */
         return is_array($NEWS_ENTRIES) ? $NEWS_ENTRIES : [];
     }
+
+    /**
+     * @param NewsEntryStruct $data
+     * @param list<string>|string $tags
+     */
+    public static function isTagged(array $data, array|string $tags): bool
+    {
+        $tags = is_array($tags) ? $tags : [$tags];
+
+        foreach ($data['category'] as $category) {
+            if (in_array($category['term'], $tags, true)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
 }

Reply via email to