[PHP-WEBMASTER] [web-news] master: Show thread information for each article (#24)

Author: Jim Winstead (jimwins)
Committer: GitHub (web-flow)
Pusher: jimwins
Date: 2024-09-02T12:19:19-07:00

Commit: Show thread information for each article (#24) · php/web-news@b7d1acc · GitHub
Raw diff: https://github.com/php/web-news/commit/b7d1acc70bd8e2cbdfd8ea2c4b16fd319fe3b70a.diff

Show thread information for each article (#24)

Changed paths:
  A lib/ThreadTree.php
  M article.php
  M lib/Web/News/Nntp.php

Diff:

diff --git a/article.php b/article.php
index 244f93b..a617e58 100644
--- a/article.php
+++ b/article.php
@@ -1,6 +1,7 @@
<?php

require 'common.php';
+require 'lib/ThreadTree.php';

if (isset($_GET['article'])) {
     $article = (int)$_GET['article'];
@@ -227,6 +228,36 @@
echo " </pre>\n";
echo " </blockquote>\n";

+try {
+ $overview = $nntpClient->getThreadOverview($group, $article);
+
+ $threads = new \PhpWeb\ThreadTree($overview['articles']);
+ ?>
+ <blockquote>
+ <h2>
+ Thread (<?= sprintf("%d message%s", $count = $threads->count(), $count > 1 ? 's' : '') ?>)
+ </h2>
+ <div class="responsive-table">
+ <table class="standard">
+ <thead>
+ <tr>
+ <th>#</th>
+ <th>Subject</th>
+ <th>Author</th>
+ <th>Date</th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php $threads->printRows($group, 'utf8'); ?>
+ </tbody>
+ </table>
+ </div>
+ </blockquote>
+ <?php
+} catch (\Throwable $t) {
+ // We don't care if there's no thread. (There should be, though.)
+}
+
// Does not check existence of next, so consider this the super duper fast [broken] version
// Based off navbar() in group.php
$group = htmlspecialchars($group, ENT_QUOTES, "UTF-8");
diff --git a/lib/ThreadTree.php b/lib/ThreadTree.php
new file mode 100644
index 0000000..f9c09c3
--- /dev/null
+++ b/lib/ThreadTree.php
@@ -0,0 +1,97 @@
+<?php
+
+namespace PhpWeb;
+
+class ThreadTree
+{
+ public $articles;
+ public $root;
+ public $tree = ;
+ public $articleNumbers = ;
+ public $extraRootChildren = ;
+
+ public function __construct(array $articles)
+ {
+ $this->articles = $articles;
+
+ /*
+ * We need to build a tree of the articles. We know they are in article
+ * number order, we assume that this means that parents come before
+ * children. There may end up being some posts that appear unattached
+ * and we just assume they are replies to the root of the tree.
+ */
+ foreach ($this->articles as $articleNumber => $details) {
+ $messageId = $details['messageId'];
+
+ if (!isset($this->root)) {
+ $this->root = $messageId;
+ }
+
+ $this->articleNumbers[$messageId] = $articleNumber;
+
+ if ($details['references']) {
+ /* Parent is the last reference. */
+ if (preg_match('/.*(<.+?>)$/', $details['references'], $matches)) {
+ $parent = $matches[1];
+ $this->tree[$parent] = $messageId;
+ if (!array_key_exists($parent, $this->articleNumbers)) {
+ $this->extraRootChildren = $parent;
+ }
+ }
+ } else {
+ if ($this->root && $this->root != $messageId) {
+ $this->extraRootChildren = $messageId;
+ }
+ }
+ }
+ }
+
+ public function count()
+ {
+ return count($this->articleNumbers);
+ }
+
+ protected function printArticleAndChildren($messageId, $group, $charset, $depth = 0)
+ {
+ if (array_key_exists($messageId, $this->articleNumbers)) {
+ $articleNumber = $this->articleNumbers[$messageId];
+
+ # for debugging that we've actually handled all articles
+ #unset($this->articleNumbers[$messageId]);
+
+ $details = $this->articles[$articleNumber];
+
+ echo " <tr>\n";
+ echo " <td align=\"center\"><a href=\"/$group/$articleNumber\">$articleNumber</a></td>\n";
+ echo " <td>";
+ echo str_repeat("&nbsp; &nbsp;", $depth ?? 0);
+ echo "<a href=\"/$group/$articleNumber\">";
+ echo format_subject($details['subject'], $charset);
+ echo "</a></td>\n";
+ echo " <td class=\"vcard\">" . format_author($details['author'], $charset) . "</td>\n";
+ echo " <td class=\"align-center\"><span class='monospace mod-small'>" .
+ format_date($details['date']) . "</span></td>\n";
+ echo " </tr>\n";
+ }
+
+ // bail out if things are too deep
+ if ($depth > 40) {
+ error_log("Tree was too deep, didn't print children of {$messageId})");
+ return;
+ }
+
+ if (array_key_exists($messageId, $this->tree)) {
+ foreach ($this->tree[$messageId] as $child) {
+ $this->printArticleAndChildren($child, $group, $charset, $depth + 1);
+ }
+ }
+ }
+
+ public function printRows($group, $charset = 'utf8')
+ {
+ $this->printArticleAndChildren($this->root, $group, $charset);
+ foreach ($this->extraRootChildren as $root) {
+ $this->printArticleAndChildren($root, $group, $charset, 1);
+ }
+ }
+}
diff --git a/lib/Web/News/Nntp.php b/lib/Web/News/Nntp.php
index e76902d..b6587f3 100644
--- a/lib/Web/News/Nntp.php
+++ b/lib/Web/News/Nntp.php
@@ -189,6 +189,48 @@ public function getArticlesOverview($group, $start, $pageSize = 20)
         return $overview;
     }

+ /**
+ * Returns an overview of the articles in the same thread as the specified
+ * message
+ *
+ * @param string $group The name of the group to select
+ * @param int $article The number of an article in the thread
+ * @return array
+ */
+ public function getThreadOverview($group, $article)
+ {
+ $groupDetails = $this->selectGroup($group);
+
+ $overview = [
+ 'group' => $groupDetails,
+ 'articles' => ,
+ ];
+
+ $response = $this->sendCommand("XTHREAD {$article}", 224);
+
+ while ($line = fgets($this->connection)) {
+ if ($line == ".\r\n") {
+ break;
+ }
+
+ $line = rtrim($line);
+ list($n, $subject, $author, $date, $messageId, $references, $lines, $extra) = explode("\t", $line, 9);
+
+ $overview['articles'][$n] = [
+ 'subject' => $subject,
+ 'author' => $author,
+ 'date' => $date,
+ 'messageId' => $messageId,
+ 'references' => $references,
+ 'lines' => $lines,
+ 'extra' => $extra,
+ ];
+
+ }
+
+ return $overview;
+ }
+
     /**
      * Returns the full content of the specified article (headers and body)
      *