Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce UserNote DTO #594

Merged
merged 7 commits into from
Sep 16, 2022
Merged
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
85 changes: 43 additions & 42 deletions include/shared-manual.inc
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,16 @@ $PGI = []; $SIDEBAR_DATA = '';
// =============================================================================

require_once __DIR__ . '/../autoload.php';
use phpweb\UserNotes\Sorter;

// Print out all user notes for this manual page
function manual_notes($notes) {
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 {
// Get needed values
list($filename) = $GLOBALS['PGI']['this'];

Expand Down Expand Up @@ -68,68 +74,63 @@ END_USERNOTE_HEADER;
// If we have notes, print them out
echo '<div id="allnotes">';
foreach ($notes as $note) {
manual_note_display(
$note['xwhen'], $note['user'], $note['note'], $note['id'], $note['votes']
);
manual_note_display($note);
}
echo "</div>\n";
echo "<div class=\"foot\">$addnotesnippet</div>\n";
}
echo "</section>";
}
// Get user notes from the appropriate text dump
function manual_notes_load($id)

/**
* Get user notes from the appropriate text dump
*
* @return array<string, UserNote>
*/
function manual_notes_load(string $id): array
{
// Initialize values
$notes = [];
$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;
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] = [
"id" => $id,
"sect" => $sect,
"rate" => $rate,
"xwhen" => $ts,
"user" => $user,
"note" => base64_decode($note, true),
"votes" => ["up" => (int)$up, "down" => (int)$down]
];
$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($date, $name, $text, $id, $votes = ['up' => 0, 'down' => 0], $voteOption = true)
function manual_note_display(UserNote $note, $voteOption = true)
{
if ($name) {
$name = "\n <strong class=\"user\"><em>" . htmlspecialchars($name) . "</em></strong>";
if ($note->user) {
$name = "\n <strong class=\"user\"><em>" . htmlspecialchars($note->user) . "</em></strong>";
} else {
$name = "<strong class=\"user\"><em>Anonymous</em></strong>";
}
$name = ($id ? "\n <a href=\"#$id\" class=\"name\">$name</a><a class=\"genanchor\" href=\"#$id\"> &para;</a>" : "\n $name");
$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
$datestr = relTime(new DateTime("@{$date}"));
$fdatestr = date("Y-m-d h:i", $date);
$text = clean_note($text);
$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 = $votes['up'] - $votes['down'];
$p = floor(($votes['up'] / (($votes['up'] + $votes['down']) ?: 1)) * 100);
$rate = !$p && !($votes['up'] + $votes['down']) ? "no votes..." : "$p% like this...";
$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) {
Expand All @@ -140,13 +141,13 @@ function manual_note_display($date, $name, $text, $id, $votes = ['up' => 0, 'dow
$rredir_filename = urlencode($redir_filename);
$votediv = <<<VOTEDIV
<div class="votes">
<div id="Vu{$id}">
<a href="/manual/vote-note.php?id={$id}&amp;page={$rredir_filename}&amp;vote=up" title="Vote up!" class="usernotes-voteu">up</a>
<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{$id}">
<a href="/manual/vote-note.php?id={$id}&amp;page={$rredir_filename}&amp;vote=down" title="Vote down!" class="usernotes-voted">down</a>
<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{$id}" title="{$rate}">
<div class="tally" id="V{$note->id}" title="{$rate}">
{$vote}
</div>
</div>
Expand All @@ -156,26 +157,26 @@ VOTEDIV;
}

// If the viewer is logged in, show admin options
if (isset($_COOKIE['IS_DEV']) && $id) {
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+' . $id,
'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+' . $id,
'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+' . $id,
'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'
Expand All @@ -187,8 +188,8 @@ VOTEDIV;

echo <<<USER_NOTE_TEXT

<div class="note" id="$id">{$votediv}{$name}{$admin}<div class="date" title="$fdatestr"><strong>{$datestr}</strong></div>
<div class="text" id="Hcom{$id}">
<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>
Expand Down Expand Up @@ -295,7 +296,7 @@ function manual_setup($setup) {
$USERNOTES = manual_notes_load($filename);
if ($USERNOTES) {
$note = current($USERNOTES);
$timestamps[] = $note["xwhen"];
$timestamps[] = $note->ts;
}

$lastmod = max($timestamps);
Expand Down
4 changes: 3 additions & 1 deletion manual/add-note.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
include_once __DIR__ . '/../include/shared-manual.inc';
include __DIR__ . '/spam_challenge.php';

use phpweb\UserNotes\UserNote;
kamil-tekiela marked this conversation as resolved.
Show resolved Hide resolved

site_header("Add Manual Note", ['css' => 'add-note.css']);

// Copy over "sect" and "redirect" from GET to POST
Expand Down Expand Up @@ -140,7 +142,7 @@
// Print out preview of note
echo '<p>This is what your entry will look like, roughly:</p>';
echo '<div id="usernotes">';
manual_note_display(time(), $user, $note, false);
manual_note_display(new UserNote('', '', '', time(), $user, $note));
echo '</div><br><br>';
}

Expand Down
22 changes: 6 additions & 16 deletions manual/vote-note.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,8 @@
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')) {
$response = [];
$update = $N[$_REQUEST['id']]['votes']['up'] - $N[$_REQUEST['id']]['votes']['down'];
$hash = substr(md5($_REQUEST['page']), 0, 16);
$notes_file = $_SERVER['DOCUMENT_ROOT'] . "/backend/notes/" .
substr($hash, 0, 2) . "/$hash";
$notes_file = $_SERVER['DOCUMENT_ROOT'] . "/backend/notes/" . substr($hash, 0, 2) . "/$hash";
if (!file_exists($notes_file)) {
$response["success"] = false;
$response["msg"] = "Invalid request.";
Expand Down Expand Up @@ -60,15 +58,13 @@
}
else {
if ($_REQUEST['vote'] === 'up') {
$N[$_REQUEST['id']]['votes']['up']++;
$N[$_REQUEST['id']]->upvotes++;
}
elseif ($_REQUEST['vote'] === 'down') {
$N[$_REQUEST['id']]['votes']['down']++;
$N[$_REQUEST['id']]->downvotes++;
}
$update = $N[$_REQUEST['id']]['votes']['up'] - $N[$_REQUEST['id']]['votes']['down'];
$hash = substr(md5($_REQUEST['page']), 0, 16);
$notes_file = $_SERVER['DOCUMENT_ROOT'] . "/backend/notes/" .
substr($hash, 0, 2) . "/$hash";
$notes_file = $_SERVER['DOCUMENT_ROOT'] . "/backend/notes/" . substr($hash, 0, 2) . "/$hash";
if (file_exists($notes_file)) {
$data = [
"noteid" => $_REQUEST['id'],
Expand Down Expand Up @@ -128,10 +124,7 @@
<?php
$backID = htmlspecialchars($_REQUEST['id']);
$backPAGE = htmlspecialchars($_REQUEST['page']);
manual_note_display(
$N[$_REQUEST['id']]['xwhen'], $N[$_REQUEST['id']]['user'], $N[$_REQUEST['id']]['note'], $N[$_REQUEST['id']]['id'],
$N[$_REQUEST['id']]['votes'], false
);
manual_note_display($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>
Expand Down Expand Up @@ -184,10 +177,7 @@
<?php
$backID = htmlspecialchars($_REQUEST['id']);
$backPAGE = htmlspecialchars($_REQUEST['page']);
manual_note_display(
$N[$_REQUEST['id']]['xwhen'], $N[$_REQUEST['id']]['user'], $N[$_REQUEST['id']]['note'], $N[$_REQUEST['id']]['id'],
$N[$_REQUEST['id']]['votes'], false
);
manual_note_display($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>
Expand Down
97 changes: 39 additions & 58 deletions src/UserNotes/Sorter.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ class Sorter {

private $ageWeight = 2;

public function sort(array &$notes) {
/**
* @param array<string, UserNote> $notes
*/
public function sort(array &$notes):void {
// First we make a pass over the data to get the min and max values
// for data normalization.
$this->findMinMaxValues($notes);
Expand All @@ -36,80 +39,58 @@ public function sort(array &$notes) {
$this->ageFactor *= $this->ageWeight;

// Second we loop through to calculate sort priority using the above numbers
$this->calcSortPriority($notes);
$prio = $this->calcSortPriority($notes);

// Third we sort the data.
uasort($notes, [$this, 'factorSort']);
uasort($notes, function ($a, $b) use ($prio) {
return $prio[$b->id] <=> $prio[$a->id];
});
}

private function calcVotePriority(array $note) {
return ($note['score'] - $this->minVote) * $this->voteFactor + .3;
private function calcVotePriority(UserNote $note):float {
return ($note->upvotes - $note->downvotes - $this->minVote) * $this->voteFactor + .3;
}

private function calcRatingPriority(array $note) {
if ($note['total'] <= 2) {
return 0.5;
}

return $note['rating'];
private function calcRatingPriority(UserNote $note):float {
return $note->upvotes + $note->downvotes <= 2 ? 0.5 : $this->calcRating($note);
}

private function calcSortPriority(array &$notes) {
foreach ($notes as &$note) {
$prio = [
'vote' => $this->calcVotePriority($note) * $this->voteWeight,
'rating' => $this->calcRatingPriority($note) * $this->ratingWeight,
'age' => ($note['xwhen'] - $this->minAge) * $this->ageFactor
];
$note['sort'] = $prio['value'] = array_sum($prio);
}
private function calcRating(UserNote $note):float {
$totalVotes = $note->upvotes + $note->downvotes;
return $totalVotes > 0 ? $note->upvotes / $totalVotes : .5;
}

/*
* Not sure why, but using `$b['sort'] - $a['sort']` does not seem to
* work properly.
/**
* @param array<string, UserNote> $notes
*/
private function factorSort($a, $b) {
if ($a['sort'] < $b['sort']) {
return 1;
}

if ($a['sort'] == $b['sort']) {
return 0;
private function calcSortPriority(array $notes): array {
$prio = [];
foreach ($notes as $note) {
$prio[$note->id] = ($this->calcVotePriority($note) * $this->voteWeight)
+ ($this->calcRatingPriority($note) * $this->ratingWeight)
+ (($note->ts - $this->minAge) * $this->ageFactor);
}

return -1;
return $prio;
}

private function findMinMaxValues(array &$notes) {
$count = count($notes);
if ($count <= 0) {
/**
* @param array<string, UserNote> $notes
*/
private function findMinMaxValues(array $notes):void {
if ($notes === []) {
return;
}
$note = array_shift($notes);
$note['score'] = $net = ($note['votes']['up'] - $note['votes']['down']);
$note['total'] = $totalVotes = ($note['votes']['up'] + $note['votes']['down']);
$note['rating'] = $totalVotes > 0
? $note['votes']['up'] / $totalVotes
: .5;

$this->minVote = $this->maxVote = $net;
$this->minAge = $this->maxAge = $age = $note['xwhen'];

$first = $note;

foreach ($notes as &$note) {
$note['score'] = $net = ($note['votes']['up'] - $note['votes']['down']);
$note['total'] = $totalVotes = ($note['votes']['up'] + $note['votes']['down']);
$note['rating'] = $totalVotes > 0
? $note['votes']['up'] / $totalVotes
: .5;
$age = $note['xwhen'];
$this->maxVote = max($this->maxVote, $net);
$this->minVote = min($this->minVote, $net);
$this->maxAge = max($this->maxAge, $age);
$this->minAge = min($this->minAge, $age);
$first = array_shift($notes);

$this->minVote = $this->maxVote = ($first->upvotes - $first->downvotes);
$this->minAge = $this->maxAge = $first->ts;

foreach ($notes as $note) {
$this->maxVote = max($this->maxVote, ($note->upvotes - $note->downvotes));
$this->minVote = min($this->minVote, ($note->upvotes - $note->downvotes));
$this->maxAge = max($this->maxAge, $note->ts);
$this->minAge = min($this->minAge, $note->ts);
}
array_unshift($notes, $first);
}
}
Loading