totalMatches;
}
public function result(): array {
return $this->result;
}
public function page(int $perPage, int $offset, string $searchTerm, bool $bypassSphinx = false): array {
if ($searchTerm === '' || $bypassSphinx) {
// either no search term or realtime query
$args = [$offset, $perPage];
if ($searchTerm === '') {
$where = '';
} else {
$where = " WHERE Message LIKE concat('%', ?, '%')";
array_unshift($args, $searchTerm);
}
self::$db->prepared_query("
SELECT ID AS id,
Message AS message,
Time AS created
FROM log$where
ORDER BY ID DESC
LIMIT ?, ?
", ...$args
);
$this->totalMatches = (int)self::$db->record_count();
if ($this->totalMatches < $perPage) {
$this->totalMatches += $offset;
} else {
$result = (new \SphinxqlQuery())->select('id')->from('log, log_delta')->limit(0, 1, 1)->sphinxquery();
$this->totalMatches = $result
? min(SPHINX_MAX_MATCHES, (int)$result->get_meta('total_found'))
: 0;
}
return $this->decorate(self::$db->to_array(false, MYSQLI_NUM, false));
}
$sq = new \SphinxqlQuery();
$sq->select('id')
->from('log, log_delta')
->order_by('id', 'DESC')
->limit($offset, $perPage, $offset + $perPage);
foreach (explode(' ', $searchTerm) as $s) {
$sq->where_match($s, 'message');
}
$result = $sq->sphinxquery();
if (!$result || $result->Errno) {
return [];
}
$this->totalMatches = min(SPHINX_MAX_MATCHES, $result->get_meta('total_found'));
$logIds = $result->collect('id') ?: [0];
self::$db->prepared_query("
SELECT ID AS id,
Message AS message,
Time AS created
FROM log
WHERE ID IN (" . placeholders($logIds) . ")
ORDER BY ID DESC
", ...$logIds
);
return $this->decorate(self::$db->to_array(false, MYSQLI_NUM, false));
}
public function tgroupLogList($tgroupId): array {
self::$db->prepared_query("
SELECT gl.TorrentID AS torrent_id,
gl.UserID AS user_id,
gl.Info AS info,
gl.Time AS created,
t.Media AS media,
t.Format AS format,
t.Encoding AS encoding,
if(t.ID IS NULL, 1, 0) AS deleted
FROM group_log gl
LEFT JOIN torrents t ON (t.ID = gl.TorrentID)
WHERE gl.GroupID = ?
ORDER BY gl.ID DESC
", $tgroupId
);
return self::$db->to_array(false, MYSQLI_ASSOC, false);
}
/**
* Parse the log messages and decorate where applicable with links and class
*/
public function decorate(array $in): array {
$out = [];
foreach ($in as [$id, $message, $created]) {
[$class, $message] = $this->colorize($message);
$out[] = [
'id' => $id,
'class' => $class,
'message' => $message,
'created' => $created,
];
}
return $out;
}
/**
* Parse an individual message and enrich it.
* Returns a flag indicated whether color was applied, and the enriched message
*/
public function colorize(string $message): array {
$parts = explode(' ', $message);
$message = '';
$colon = false;
$class = false;
// need a C for loop because sometime we need to look at the next element of the message
for ($i = 0, $n = count($parts); $i < $n; $i++) {
if (str_starts_with($parts[$i], SITE_URL)) {
$offset = strlen(SITE_URL) + 1; // trailing slash
$parts[$i] = '' . substr($parts[$i], $offset) . '';
}
switch (strtolower($parts[$i])) {
case 'artist':
$id = $parts[$i + 1];
if ((int)$id) {
$message .= ' ' . $parts[$i++] . " $id";
} else {
$message .= ' ' . $parts[$i];
}
break;
case 'collage':
$id = $parts[$i + 1];
if ((int)$id) {
$message .= ' ' . $parts[$i] . " $id";
$i++;
} else {
$message .= " {$parts[$i]}";
}
break;
case 'group':
$id = $parts[$i + 1];
if ((int)$id) {
$message .= ' ' . $parts[$i] . " $id";
} else {
$message .= ' ' . $parts[$i];
}
$i++;
break;
case 'request':
$id = $parts[$i + 1];
if ((int)$id) {
$message .= ' ' . $parts[$i++] . " $id";
} else {
$message .= ' ' . $parts[$i];
}
break;
case 'torrent':
$id = $parts[$i + 1];
if ((int)$id) {
$message .= ' ' . $parts[$i++] . " $id";
} else {
$message .= ' ' . $parts[$i];
}
break;
case 'by':
$userId = 0;
$URL = '';
if ($parts[$i + 1] == 'user') {
$i++;
if ((int)($parts[$i + 1])) {
$userId = $parts[++$i];
}
$URL = "user $userId (" . substr($parts[++$i], 1, -1) . ')';
} elseif (in_array($parts[$i - 1], ['deleted', 'uploaded', 'edited', 'created', 'recovered'])) {
$username = $parts[++$i];
if (str_ends_with($username, ':')) {
$username = substr($username, 0, -1);
$colon = true;
}
$userId = $this->usernameLookup($username);
$URL = $userId ? "$username" . ($colon ? ':' : '') : $username;
}
$message .= " by $URL";
break;
case 'uploaded':
if ($class === false) {
$class = 'log-uploaded';
}
$message .= " {$parts[$i]}";
break;
case 'deleted':
if ($class === false || $class === 'log-uploaded') {
$class = 'log-removed';
}
$message .= " {$parts[$i]}";
break;
case 'edited':
if ($class === false) {
$class = 'log-edited';
}
$message .= " {$parts[$i]}";
break;
case 'un-filled':
if ($class === false) {
$class = '';
}
$message .= " {$parts[$i]}";
break;
case 'marked':
if ($i == 1) {
$username = $parts[$i - 1];
$userId = $this->usernameLookup($username);
$URL = $userId ? "$username" : $username;
$message = "$URL {$parts[$i]}";
} else {
$message .= " {$parts[$i]}";
}
break;
default:
$message .= " {$parts[$i]}";
}
}
return [$class, trim($message)]; // strip off the leading space
}
protected function usernameLookup(string $username): int|false {
if (!isset($this->usernames[$username])) {
$user = $this->userMan->findByUsername($username);
$this->usernames[$username] = $user?->id() ?? false;
}
return $this->usernames[$username];
}
}