[PHP-WEBMASTER] [web-php] master: Create UserNoteService class

[email protected] (Kamil Tekiela via Derick Rethans)
Newsgroups php.webmaster
Message-ID <[email protected]>
Author: Kamil Tekiela (kamil-tekiela)
Committer: Derick Rethans (derickr)
Date: 2026-01-13T14:00:36+01:00

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

Create UserNoteService class

Changed paths:
  A  src/UserNotes/UserNoteService.php
  M  include/layout.inc
  M  include/shared-manual.inc
  M  manual/add-note.php
  M  manual/vote-note.php


Diff:

diff --git a/include/layout.inc b/include/layout.inc
index 02b6f733ca..98d8c845c1 100644
--- a/include/layout.inc
+++ b/include/layout.inc
@@ -164,21 +164,6 @@ function make_link(string $url, string $linktext = ''): string
     return sprintf("<a href=\"%s\">%s</a>", $url, $linktext ?: $url);
 }
 
-// make_popup_link()
-// return a hyperlink to something, within the site, that pops up a new window
-//
-function make_popup_link($url, $linktext = false, $target = false, $windowprops = "", $extras = false) {
-    return sprintf("<a href=\"%s\" target=\"%s\" onclick=\"window.open('%s','%s','%s');return false;\"%s>%s</a>",
-        htmlspecialchars($url, ENT_QUOTES | ENT_IGNORE),
-        ($target ?: "_new"),
-        htmlspecialchars($url, ENT_QUOTES | ENT_IGNORE),
-        ($target ?: "_new"),
-                $windowprops,
-        ($extras ? ' ' . $extras : ''),
-        ($linktext ?: $url),
-    );
-}
-
 // Print a link for a downloadable file (including filesize)
 function download_link($file, $title): void
 {
@@ -220,20 +205,6 @@ function clean($var) {
   return htmlspecialchars($var, ENT_QUOTES);
 }
 
-// Clean out the content of one user note for printing to HTML
-function clean_note($text)
-{
-    // Highlight PHP source
-    $text = highlight_php(trim($text), true);
-
-    // Turn urls into links
-    return preg_replace(
-        '!((mailto:|(https?|ftp|nntp|news)://).*?)(\s|<|\)|"|\\\\|\'|$)!',
-        '<a href="\1" rel="nofollow" target="_blank">\1</a>\4',
-        $text,
-    );
-}
-
 function display_errors($errors): void
 {
     echo '<div class="errors">';
diff --git a/include/shared-manual.inc b/include/shared-manual.inc
index 5d10ea5df0..f14d63b7f1 100644
--- a/include/shared-manual.inc
+++ b/include/shared-manual.inc
@@ -23,185 +23,7 @@ $PGI = []; $SIDEBAR_DATA = '';
 // =============================================================================
 
 use phpweb\I18n\Languages;
-use phpweb\UserNotes\Sorter;
-use phpweb\UserNotes\UserNote;
-
-/**
- * Print out all user notes for this manual page
- *
- * @param array<string, UserNote> $notes
- */
-function manual_notes($notes):void {
-    global $LANG;
-
-    // Get needed values
-    list($filename) = $GLOBALS['PGI']['this'];
-
-    // Drop file extension from the name
-    if (substr($filename, -4) == '.php') {
-        $filename = substr($filename, 0, -4);
-    }
-
-    $sorter = new Sorter();
-    $sorter->sort($notes);
-
-    $repo = strtolower($LANG);
-    $addNote = autogen('add_a_note', $LANG);
-    // Link target to add a note to the current manual page,
-    // and it's extended form with a [+] image
-    $addnotelink = '/manual/add-note.php?sect=' . $filename .
-                   '&amp;repo=' . $repo .
-                   '&amp;redirect=' . $_SERVER['BASE_HREF'];
-    $addnotesnippet = make_link(
-        $addnotelink,
-        "+<small>$addNote</small>",
-    );
-
-    $num_notes = count($notes);
-    $noteCountHtml = '';
-    if ($num_notes) {
-        $noteCountHtml = "<span class=\"count\">$num_notes note" . ($num_notes == 1 ? '' : 's') . "</span>";
-    }
-
-    $userContributedNotes = autogen('user_contributed_notes', $LANG);
-    echo <<<END_USERNOTE_HEADER
-<section id="usernotes">
- <div class="head">
-  <span class="action">{$addnotesnippet}</span>
-  <h3 class="title">$userContributedNotes {$noteCountHtml}</h3>
- </div>
-END_USERNOTE_HEADER;
-
-    // If we have no notes, then inform the user
-    if ($num_notes === 0) {
-        $noUserContributedNotes = autogen('no_user_notes', $LANG);
-        echo "\n <div class=\"note\">$noUserContributedNotes</div>";
-    } else {
-        // If we have notes, print them out
-        echo '<div id="allnotes">';
-        foreach ($notes as $note) {
-            manual_note_display($note);
-        }
-        echo "</div>\n";
-        echo "<div class=\"foot\">$addnotesnippet</div>\n";
-    }
-    echo "</section>";
-}
-
-/**
- * Get user notes from the appropriate text dump
- *
- * @return array<string, UserNote>
- */
-function manual_notes_load(string $id): array
-{
-    $hash = substr(md5($id), 0, 16);
-    $notes_file = $_SERVER['DOCUMENT_ROOT'] . "/backend/notes/" .
-                  substr($hash, 0, 2) . "/$hash";
-
-    // Open the note file for reading and get the data (12KB)
-    // ..if it exists
-    if (!file_exists($notes_file)) {
-        return [];
-    }
-    $notes = [];
-    if ($fp = @fopen($notes_file, "r")) {
-        while (!feof($fp)) {
-            $line = chop(fgets($fp, 12288));
-            if ($line == "") { continue; }
-            @list($id, $sect, $rate, $ts, $user, $note, $up, $down) = explode("|", $line);
-            $notes[$id] = new UserNote($id, $sect, $rate, $ts, $user, base64_decode($note, true), (int) $up, (int) $down);
-        }
-        fclose($fp);
-    }
-    return $notes;
-}
-
-// Print out one user note entry
-function manual_note_display(UserNote $note, $voteOption = true): void
-{
-    if ($note->user) {
-        $name = "\n  <strong class=\"user\"><em>" . htmlspecialchars($note->user) . "</em></strong>";
-    } else {
-        $name = "<strong class=\"user\"><em>Anonymous</em></strong>";
-    }
-    $name = ($note->id ? "\n  <a href=\"#{$note->id}\" class=\"name\">$name</a><a class=\"genanchor\" href=\"#{$note->id}\"> &para;</a>" : "\n  $name");
-
-    // New date style will be relative time
-    $date = new DateTime("@{$note->ts}");
-    $datestr = relTime($date);
-    $fdatestr = $date->format("Y-m-d h:i");
-    $text = clean_note($note->text);
-
-    // Calculate note rating by up/down votes
-    $vote = $note->upvotes - $note->downvotes;
-    $p = floor(($note->upvotes / (($note->upvotes + $note->downvotes) ?: 1)) * 100);
-    $rate = !$p && !($note->upvotes + $note->downvotes) ? "no votes..." : "$p% like this...";
-
-    // Vote User Notes Div
-    if ($voteOption) {
-        list($redir_filename) = $GLOBALS['PGI']['this'];
-        if (substr($redir_filename, -4) == '.php') {
-            $redir_filename = substr($redir_filename, 0, -4);
-        }
-        $rredir_filename = urlencode($redir_filename);
-        $votediv = <<<VOTEDIV
-  <div class="votes">
-    <div id="Vu{$note->id}">
-    <a href="/manual/vote-note.php?id={$note->id}&amp;page={$rredir_filename}&amp;vote=up" title="Vote up!" class="usernotes-voteu">up</a>
-    </div>
-    <div id="Vd{$note->id}">
-    <a href="/manual/vote-note.php?id={$note->id}&amp;page={$rredir_filename}&amp;vote=down" title="Vote down!" class="usernotes-voted">down</a>
-    </div>
-    <div class="tally" id="V{$note->id}" title="{$rate}">
-    {$vote}
-    </div>
-  </div>
-VOTEDIV;
-    } else {
-        $votediv = null;
-    }
-
-    // If the viewer is logged in, show admin options
-    if (isset($_COOKIE['IS_DEV']) && $note->id) {
-
-        $admin = "\n  <span class=\"admin\">\n   " .
-
-        make_popup_link(
-            'https://main.php.net/manage/user-notes.php?action=edit+' . $note->id,
-            '<img src="/images/[email protected]" height="12" width="12" alt="edit note">',
-            'admin',
-            'scrollbars=yes,width=650,height=400',
-        ) . "\n   " .
-
-        make_popup_link(
-            'https://main.php.net/manage/user-notes.php?action=reject+' . $note->id,
-            '<img src="/images/[email protected]" height="12" width="12" alt="reject note">',
-            'admin',
-            'scrollbars=no,width=300,height=200',
-        ) . "\n   " .
-
-        make_popup_link(
-            'https://main.php.net/manage/user-notes.php?action=delete+' . $note->id,
-            '<img src="/images/[email protected]" height="12" width="12" alt="delete note">',
-            'admin',
-            'scrollbars=no,width=300,height=200',
-        ) . "\n  </span>";
-
-    } else {
-        $admin = '';
-    }
-
-    echo <<<USER_NOTE_TEXT
-
-  <div class="note" id="{$note->id}">{$votediv}{$name}{$admin}<div class="date" title="$fdatestr"><strong>{$datestr}</strong></div>
-  <div class="text" id="Hcom{$note->id}">
-{$text}
-  </div>
- </div>
-USER_NOTE_TEXT;
-
-}
+use phpweb\UserNotes\UserNoteService;
 
 function manual_navigation_breadcrumbs(array $setup) {
     $menu = [];
@@ -298,7 +120,9 @@ function manual_setup($setup): void {
     if (substr($filename, -4) == '.php') {
         $filename = substr($filename, 0, -4);
     }
-    $USERNOTES = manual_notes_load($filename);
+
+    $userNoteService = new UserNoteService();
+    $USERNOTES = $userNoteService->load($filename);
     if ($USERNOTES) {
         $note = current($USERNOTES);
         $timestamps[] = $note->ts;
@@ -422,62 +246,18 @@ function manual_footer($setup): void {
     </div>
 CONTRIBUTE;
 
-    manual_notes($USERNOTES);
+    $userNoteService = new UserNoteService();
+    $userNoteService->display($USERNOTES);
     site_footer([
         'related_menu' => $__RELATED['toc'],
         'related_menu_deprecated' => $__RELATED['toc_deprecated'],
     ]);
 }
 
-// This function takes a DateTime object and returns a formated string of the time difference relative to now
-function relTime(DateTime $date) {
-    $current = new DateTime();
-    $diff = $current->diff($date);
-    $units = ["year" => $diff->format("%y"),
-        "month" => $diff->format("%m"),
-        "day" => $diff->format("%d"),
-        "hour" => $diff->format("%h"),
-        "minute" => $diff->format("%i"),
-        "second" => $diff->format("%s"),
-    ];
-    $out = "just now...";
-    foreach ($units as $unit => $amount) {
-        if (empty($amount)) {
-            continue;
-        }
-        $out = $amount . " " . ($amount == 1 ? $unit : $unit . "s") . " ago";
-        break;
-    }
-    return $out;
-}
-
-function contributors($setup) {
-    if (!isset($_GET["contributors"])
-        || !isset($setup["history"]["contributors"])
-        || count($setup["history"]["contributors"]) < 1) {
-        return;
-    }
-
-    $contributorList = "<li>" . implode("</li><li>", $setup["history"]["contributors"]) . "</li>";
-
-    echo <<<CONTRIBUTORS
-<div class="book">
-  <h1 class="title">Output Buffering Control</h1>
-  The following have authored commits that contributed to this page:
-  <ul>
-   $contributorList
-  </ul>
-</div>
-CONTRIBUTORS;
-    manual_footer($setup);
-    exit;
-}
-
 function autogen(string $text, string $lang) {
     static $translations = [];
 
     $lang = ($lang === "") ? "en" : $lang;
-    $lang = strtolower($lang);
     if (isset($translations[$lang])) {
         if (isset($translations[$lang][$text]) && $translations[$lang][$text] !== "") {
             return $translations[$lang][$text];
diff --git a/manual/add-note.php b/manual/add-note.php
index 4cb6651366..522c4b509e 100644
--- a/manual/add-note.php
+++ b/manual/add-note.php
@@ -8,6 +8,7 @@
 include __DIR__ . '/spam_challenge.php';
 
 use phpweb\UserNotes\UserNote;
+use phpweb\UserNotes\UserNoteService;
 
 site_header("Add Manual Note", ['css' => 'add-note.css']);
 
@@ -146,9 +147,10 @@
     if ($error) { echo "<p class=\"formerror\">$error</p>\n"; }
 
     // Print out preview of note
+    $userNoteService = new UserNoteService();
     echo '<p>This is what your entry will look like, roughly:</p>';
     echo '<div id="usernotes">';
-    manual_note_display(new UserNote('', '', '', time(), $user, $note));
+    $userNoteService->displaySingle(new UserNote('', '', '', time(), $user, $note));
     echo '</div><br><br>';
 }
 
diff --git a/manual/vote-note.php b/manual/vote-note.php
index d07689e8ed..94b4f69e80 100644
--- a/manual/vote-note.php
+++ b/manual/vote-note.php
@@ -1,4 +1,7 @@
 <?php
+
+use phpweb\UserNotes\UserNoteService;
+
 $_SERVER['BASE_PAGE'] = 'manual/vote-note.php';
 include_once __DIR__ . '/../include/prepend.inc';
 include_once __DIR__ . '/../include/posttohost.inc';
@@ -13,9 +16,10 @@
 $BACKid = htmlspecialchars($_REQUEST['id'] ?? '');
 $link = "/{$BACKpage}#{$BACKid}";
 $master_url = "https://main.php.net/entry/user-notes-vote.php";
+$notes = new UserNoteService();
 
 if ($_SERVER['REQUEST_METHOD'] == 'POST') {
-  if (isset($_SERVER['HTTP_X_JSON']) && $_SERVER['HTTP_X_JSON'] == 'On' && !empty($_REQUEST['id']) && !empty($_REQUEST['page']) && ($N = manual_notes_load($_REQUEST['page'])) && array_key_exists($_REQUEST['id'], $N) && !empty($_REQUEST['vote']) && ($_REQUEST['vote'] === 'up' || $_REQUEST['vote'] === 'down')) {
+  if (isset($_SERVER['HTTP_X_JSON']) && $_SERVER['HTTP_X_JSON'] == 'On' && !empty($_REQUEST['id']) && !empty($_REQUEST['page']) && ($N = $notes->load($_REQUEST['page'])) && array_key_exists($_REQUEST['id'], $N) && !empty($_REQUEST['vote']) && ($_REQUEST['vote'] === 'up' || $_REQUEST['vote'] === 'down')) {
     $response = [];
     $hash = substr(md5($_REQUEST['page']), 0, 16);
     $notes_file = $_SERVER['DOCUMENT_ROOT'] . "/backend/notes/" . substr($hash, 0, 2) . "/$hash";
@@ -51,7 +55,7 @@
     echo json_encode($response);
     exit;
   }
-  if (!empty($_REQUEST['id']) && !empty($_REQUEST['page']) && ($N = manual_notes_load($_REQUEST['page'])) && array_key_exists($_REQUEST['id'], $N) && !empty($_REQUEST['vote']) && ($_REQUEST['vote'] === 'up' || $_REQUEST['vote'] === 'down')) {
+  if (!empty($_REQUEST['id']) && !empty($_REQUEST['page']) && ($N = $notes->load($_REQUEST['page'])) && array_key_exists($_REQUEST['id'], $N) && !empty($_REQUEST['vote']) && ($_REQUEST['vote'] === 'up' || $_REQUEST['vote'] === 'down')) {
     if (!empty($_POST['challenge']) && !empty($_POST['func']) || empty($_POST['arga']) || empty($_POST['argb'])) {
       if (!test_answer($_POST['func'], $_POST['arga'], $_POST['argb'], $_POST['challenge'])) {
         $error = "Incorrect answer! Please try again.";
@@ -96,7 +100,7 @@
   site_header("Vote On User Notes");
   $headerset = true;
 
-  if (!empty($_REQUEST['id']) && !empty($_REQUEST['page']) && ($N = manual_notes_load($_REQUEST['page'])) && array_key_exists($_REQUEST['id'], $N) && !empty($_REQUEST['vote']) && ($_REQUEST['vote'] === 'up' || $_REQUEST['vote'] === 'down')) {
+  if (!empty($_REQUEST['id']) && !empty($_REQUEST['page']) && ($N = $notes->load($_REQUEST['page'])) && array_key_exists($_REQUEST['id'], $N) && !empty($_REQUEST['vote']) && ($_REQUEST['vote'] === 'up' || $_REQUEST['vote'] === 'down')) {
 ?>
  <div class="container" id="notes-dialog" style="width: 100%; padding-bottom: 15px; margin: auto;">
   <div style="width: 100%; margin: auto;"><h1>Voting</h1></div>
@@ -118,7 +122,7 @@
 <?php
   $backID = htmlspecialchars($_REQUEST['id']);
   $backPAGE = htmlspecialchars($_REQUEST['page']);
-  manual_note_display($N[$_REQUEST['id']], false);
+  $notes->displaySingle($N[$_REQUEST['id']], false);
 ?>
  </div>
  <div style="width: 90%; margin: auto;"><p><a href="<?php echo "/{$backPAGE}#{$backID}"; ?>">&lt;&lt; Back to user notes page</a></p></div>
@@ -171,7 +175,7 @@
 <?php
   $backID = htmlspecialchars($_REQUEST['id']);
   $backPAGE = htmlspecialchars($_REQUEST['page']);
-  manual_note_display($N[$_REQUEST['id']], false);
+  $notes->displaySingle($N[$_REQUEST['id']], false);
 ?>
  </div>
  <div style="width: 90%; margin: auto;"><p><a href="<?php echo "/{$backPAGE}#{$backID}"; ?>">&lt;&lt; Back to user notes page</a></p></div>
diff --git a/src/UserNotes/UserNoteService.php b/src/UserNotes/UserNoteService.php
new file mode 100644
index 0000000000..5e49ad9b55
--- /dev/null
+++ b/src/UserNotes/UserNoteService.php
@@ -0,0 +1,237 @@
+<?php
+
+namespace phpweb\UserNotes;
+
+final class UserNoteService
+{
+    /**
+     * Get user notes from the appropriate text dump
+     *
+     * @return array<string, UserNote>
+     */
+    public function load(string $id): array
+    {
+        $hash = substr(md5($id), 0, 16);
+        $notes_file = $_SERVER['DOCUMENT_ROOT'] . "/backend/notes/" . substr($hash, 0, 2) . "/$hash";
+
+        // Open the note file for reading and get the data (12KB)
+        // ..if it exists
+        if (!file_exists($notes_file)) {
+            return [];
+        }
+        $notes = [];
+        if ($fp = @fopen($notes_file, "r")) {
+            while (!feof($fp)) {
+                $line = chop(fgets($fp, 12288));
+                if ($line == "") { continue; }
+                @list($id, $sect, $rate, $ts, $user, $note, $up, $down) = explode("|", $line);
+                $notes[$id] = new UserNote($id, $sect, $rate, $ts, $user, base64_decode($note, true), (int) $up, (int) $down);
+            }
+            fclose($fp);
+        }
+        return $notes;
+    }
+
+    /**
+     * Print out all user notes for this manual page
+     *
+     * @param array<string, UserNote> $notes
+     */
+    public function display($notes):void {
+        global $LANG;
+
+        // Get needed values
+        list($filename) = $GLOBALS['PGI']['this'];
+
+        // Drop file extension from the name
+        if (substr($filename, -4) == '.php') {
+            $filename = substr($filename, 0, -4);
+        }
+
+        $sorter = new Sorter();
+        $sorter->sort($notes);
+
+        $addNote = autogen('add_a_note', $LANG);
+        $repo = strtolower($LANG);
+        // Link target to add a note to the current manual page,
+        // and it's extended form with a [+] image
+            $addnotelink = '/manual/add-note.php?sect=' . $filename .
+                   '&amp;repo=' . $repo .
+                   '&amp;redirect=' . $_SERVER['BASE_HREF'];
+        $addnotesnippet = make_link(
+            $addnotelink,
+            "+<small>$addNote</small>",
+        );
+
+        $num_notes = count($notes);
+        $noteCountHtml = '';
+        if ($num_notes) {
+            $noteCountHtml = "<span class=\"count\">$num_notes note" . ($num_notes == 1 ? '' : 's') . "</span>";
+        }
+
+        $userContributedNotes = autogen('user_contributed_notes', $LANG);
+        echo <<<END_USERNOTE_HEADER
+<section id="usernotes">
+ <div class="head">
+  <span class="action">{$addnotesnippet}</span>
+  <h3 class="title">$userContributedNotes {$noteCountHtml}</h3>
+ </div>
+END_USERNOTE_HEADER;
+
+        // If we have no notes, then inform the user
+        if ($num_notes === 0) {
+            $noUserContributedNotes = autogen('no_user_notes', $LANG);
+            echo "\n <div class=\"note\">$noUserContributedNotes</div>";
+        } else {
+            // If we have notes, print them out
+            echo '<div id="allnotes">';
+            foreach ($notes as $note) {
+                $this->displaySingle($note);
+            }
+            echo "</div>\n";
+            echo "<div class=\"foot\">$addnotesnippet</div>\n";
+        }
+        echo "</section>";
+    }
+
+    /**
+     * Print out one user note entry
+     */
+    public function displaySingle(UserNote $note, $voteOption = true): void
+    {
+        if ($note->user) {
+            $name = "\n  <strong class=\"user\"><em>" . htmlspecialchars($note->user) . "</em></strong>";
+        } else {
+            $name = "<strong class=\"user\"><em>Anonymous</em></strong>";
+        }
+        $name = ($note->id ? "\n  <a href=\"#{$note->id}\" class=\"name\">$name</a><a class=\"genanchor\" href=\"#{$note->id}\"> &para;</a>" : "\n  $name");
+
+        // New date style will be relative time
+        $date = new \DateTime("@{$note->ts}");
+        $datestr = $this->relTime($date);
+        $fdatestr = $date->format("Y-m-d h:i");
+        $text = $this->cleanContent($note->text);
+
+        // Calculate note rating by up/down votes
+        $vote = $note->upvotes - $note->downvotes;
+        $p = floor(($note->upvotes / (($note->upvotes + $note->downvotes) ?: 1)) * 100);
+        $rate = !$p && !($note->upvotes + $note->downvotes) ? "no votes..." : "$p% like this...";
+
+        // Vote User Notes Div
+        if ($voteOption) {
+            list($redir_filename) = $GLOBALS['PGI']['this'];
+            if (substr($redir_filename, -4) == '.php') {
+                $redir_filename = substr($redir_filename, 0, -4);
+            }
+            $rredir_filename = urlencode($redir_filename);
+            $votediv = <<<VOTEDIV
+  <div class="votes">
+    <div id="Vu{$note->id}">
+    <a href="/manual/vote-note.php?id={$note->id}&amp;page={$rredir_filename}&amp;vote=up" title="Vote up!" class="usernotes-voteu">up</a>
+    </div>
+    <div id="Vd{$note->id}">
+    <a href="/manual/vote-note.php?id={$note->id}&amp;page={$rredir_filename}&amp;vote=down" title="Vote down!" class="usernotes-voted">down</a>
+    </div>
+    <div class="tally" id="V{$note->id}" title="{$rate}">
+    {$vote}
+    </div>
+  </div>
+VOTEDIV;
+        } else {
+            $votediv = null;
+        }
+
+        // If the viewer is logged in, show admin options
+        if (isset($_COOKIE['IS_DEV']) && $note->id) {
+
+            $admin = "\n  <span class=\"admin\">\n   " .
+
+            $this->makePopupLink(
+                'https://main.php.net/manage/user-notes.php?action=edit+' . $note->id,
+                '<img src="/images/[email protected]" height="12" width="12" alt="edit note">',
+                'admin',
+                'scrollbars=yes,width=650,height=400',
+            ) . "\n   " .
+
+            $this->makePopupLink(
+                'https://main.php.net/manage/user-notes.php?action=reject+' . $note->id,
+                '<img src="/images/[email protected]" height="12" width="12" alt="reject note">',
+                'admin',
+                'scrollbars=no,width=300,height=200',
+            ) . "\n   " .
+
+            $this->makePopupLink(
+                'https://main.php.net/manage/user-notes.php?action=delete+' . $note->id,
+                '<img src="/images/[email protected]" height="12" width="12" alt="delete note">',
+                'admin',
+                'scrollbars=no,width=300,height=200',
+            ) . "\n  </span>";
+
+        } else {
+            $admin = '';
+        }
+
+        echo <<<USER_NOTE_TEXT
+
+  <div class="note" id="{$note->id}">{$votediv}{$name}{$admin}<div class="date" title="$fdatestr"><strong>{$datestr}</strong></div>
+  <div class="text" id="Hcom{$note->id}">
+{$text}
+  </div>
+ </div>
+USER_NOTE_TEXT;
+    }
+
+    // Clean out the content of one user note for printing to HTML
+    private function cleanContent(string $text): string
+    {
+        // Highlight PHP source
+        $text = highlight_php(trim($text), true);
+
+        // Turn urls into links
+        return preg_replace(
+            '!((mailto:|(https?|ftp|nntp|news)://).*?)(\s|<|\)|"|\\\\|\'|$)!',
+            '<a href="\1" rel="nofollow" target="_blank">\1</a>\4',
+            $text,
+        );
+    }
+
+    /**
+     * This function takes a DateTime object and returns a formated string of the time difference relative to now
+     */
+    private function relTime(\DateTime $date): string
+    {
+        $current = new \DateTime();
+        $diff = $current->diff($date);
+        $units = ["year" => $diff->format("%y"),
+            "month" => $diff->format("%m"),
+            "day" => $diff->format("%d"),
+            "hour" => $diff->format("%h"),
+            "minute" => $diff->format("%i"),
+            "second" => $diff->format("%s"),
+        ];
+        $out = "just now...";
+        foreach ($units as $unit => $amount) {
+            if (empty($amount)) {
+                continue;
+            }
+            $out = $amount . " " . ($amount == 1 ? $unit : $unit . "s") . " ago";
+            break;
+        }
+        return $out;
+    }
+
+    /**
+     * Return a hyperlink to something, within the site, that pops up a new window
+     */
+    private function makePopupLink(string $url, string $linktext = '', string $target = '', string $windowprops = ''): string
+    {
+        return sprintf("<a href=\"%s\" target=\"%s\" onclick=\"window.open('%s','%s','%s');return false;\"%s>%s</a>",
+            htmlspecialchars($url, ENT_QUOTES | ENT_IGNORE),
+            ($target ?: "_new"),
+            htmlspecialchars($url, ENT_QUOTES | ENT_IGNORE),
+            ($target ?: "_new"),
+                    $windowprops,
+            ($linktext ?: $url),
+        );
+    }
+}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.