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

Commit: 
https://github.com/php/web-news/commit/b7d1acc70bd8e2cbdfd8ea2c4b16fd319fe3b70a
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)
      *

Reply via email to