517 lines
12 KiB
PHP
517 lines
12 KiB
PHP
<?php
|
|
|
|
namespace Aternos\Mclogs;
|
|
|
|
use Aternos\Codex\Analysis\Analysis;
|
|
use Aternos\Codex\Log\AnalysableLogInterface;
|
|
use Aternos\Codex\Log\File\StringLogFile;
|
|
use Aternos\Codex\Log\Level;
|
|
use Aternos\Codex\Log\LogInterface;
|
|
use Aternos\Mclogs\Config\ConfigKey;
|
|
use Aternos\Mclogs\Data\Deobfuscator;
|
|
use Aternos\Mclogs\Data\MetadataEntry;
|
|
use Aternos\Mclogs\Data\Token;
|
|
use Aternos\Mclogs\Filter\Filter;
|
|
use Aternos\Mclogs\Frontend\Cookie\TokenCookie;
|
|
use Aternos\Mclogs\Printer\Printer;
|
|
use Aternos\Mclogs\Storage\MongoDBClient;
|
|
use Aternos\Mclogs\Util\URL;
|
|
use MongoDB\BSON\UTCDateTime;
|
|
use Uri\Rfc3986\Uri;
|
|
|
|
class Log
|
|
{
|
|
protected const int SOURCE_MAX_LENGTH = 64;
|
|
|
|
protected ?string $source = null;
|
|
protected ?UTCDateTime $expires = null;
|
|
protected ?UTCDateTime $created = null;
|
|
protected ?Token $token = null;
|
|
|
|
/**
|
|
* @var MetadataEntry[]
|
|
*/
|
|
protected array $metadata = [];
|
|
|
|
protected ?LogInterface $log = null;
|
|
protected ?Printer $printer = null;
|
|
|
|
/**
|
|
* Find a log by its id
|
|
*
|
|
* @param Id $id
|
|
* @param bool $includeContent
|
|
* @return static|null
|
|
*/
|
|
public static function find(Id $id, bool $includeContent = true): ?static
|
|
{
|
|
$data = MongoDBClient::getInstance()->findLog($id, $includeContent);
|
|
if ($data === null) {
|
|
return null;
|
|
}
|
|
|
|
return static::fromObject($id, $data);
|
|
}
|
|
|
|
/**
|
|
* @param (string|Id)[] $ids
|
|
* @param bool $includeContent
|
|
* @return array<string, Log>
|
|
*/
|
|
public static function findAll(array $ids, bool $includeContent = true): array
|
|
{
|
|
$ids = array_map(fn($id) => (string)$id, $ids);
|
|
$objects = MongoDBClient::getInstance()->findLogs($ids, $includeContent);
|
|
$logs = [];
|
|
foreach ($objects as $data) {
|
|
$id = new Id($data->_id);
|
|
$logs[$id->get()] = static::fromObject($id, $data);
|
|
}
|
|
return $logs;
|
|
}
|
|
|
|
/**
|
|
* @param Id $id
|
|
* @param object $data
|
|
* @return static
|
|
*/
|
|
protected static function fromObject(Id $id, object $data): static
|
|
{
|
|
return new static($id)
|
|
->setContent($data->data ?? "")
|
|
->setToken(isset($data->token) ? new Token($data->token) : null)
|
|
->setMetadata(MetadataEntry::allFromArray($data->metadata ?? []))
|
|
->setSource($data->source ?? null)
|
|
->setCreated($data->created ?? null)
|
|
->setExpires($data->expires ?? null);
|
|
}
|
|
|
|
/**
|
|
* Create and save a new log
|
|
*
|
|
* @param string $content
|
|
* @param MetadataEntry[] $metadata
|
|
* @param string|null $source
|
|
* @return static
|
|
*/
|
|
public static function create(string $content, array $metadata = [], ?string $source = null): static
|
|
{
|
|
return new static()
|
|
->setMetadata($metadata)
|
|
->setSource($source)
|
|
->setToken(new Token())
|
|
->save($content);
|
|
}
|
|
|
|
/**
|
|
* @param Id|null $id
|
|
*/
|
|
public function __construct(protected ?Id $id = null)
|
|
{
|
|
}
|
|
|
|
/**
|
|
* @param Token|null $token
|
|
* @return $this
|
|
*/
|
|
public function setToken(?Token $token): static
|
|
{
|
|
$this->token = $token;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @param MetadataEntry[] $metadata
|
|
* @return $this
|
|
*/
|
|
public function setMetadata(array $metadata): static
|
|
{
|
|
$this->metadata = $metadata;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @param MetadataEntry $metadataEntry
|
|
* @return $this
|
|
*/
|
|
public function addMetadata(MetadataEntry $metadataEntry): static
|
|
{
|
|
$this->metadata[] = $metadataEntry;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @param string|null $source
|
|
* @return $this
|
|
*/
|
|
public function setSource(?string $source): static
|
|
{
|
|
if (is_string($source) && strlen($source) > static::SOURCE_MAX_LENGTH) {
|
|
$source = substr($source, 0, static::SOURCE_MAX_LENGTH);
|
|
}
|
|
$this->source = $source;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @return string|null
|
|
*/
|
|
public function getSource(): ?string
|
|
{
|
|
return $this->source;
|
|
}
|
|
|
|
/**
|
|
* @param UTCDateTime|null $created
|
|
* @return $this
|
|
*/
|
|
public function setCreated(?UTCDateTime $created): static
|
|
{
|
|
$this->created = $created;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @param UTCDateTime|null $expires
|
|
* @return $this
|
|
*/
|
|
public function setExpires(?UTCDateTime $expires): static
|
|
{
|
|
$this->expires = $expires;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @return UTCDateTime|null
|
|
*/
|
|
public function getCreated(): ?UTCDateTime
|
|
{
|
|
return $this->created;
|
|
}
|
|
|
|
/**
|
|
* @return UTCDateTime|null
|
|
*/
|
|
public function getExpires(): ?UTCDateTime
|
|
{
|
|
return $this->expires;
|
|
}
|
|
|
|
/**
|
|
* @param string $content
|
|
* @return $this
|
|
*/
|
|
public function setContent(string $content): static
|
|
{
|
|
$this->processAndDeobfuscate($content);
|
|
return $this;
|
|
}
|
|
|
|
public function getContent(): string
|
|
{
|
|
return $this->log->getLogFile()->getContent();
|
|
}
|
|
|
|
protected function processAndDeobfuscate(string $data): void
|
|
{
|
|
$this->process($data);
|
|
$deobfuscator = new Deobfuscator($this->getCodexLog());
|
|
if ($deobfuscatedData = $deobfuscator->deobfuscate()) {
|
|
$this->process($deobfuscatedData);
|
|
}
|
|
}
|
|
|
|
protected function process($data): void
|
|
{
|
|
$this->log = new Detective()->setLogFile(new StringLogFile($data))->detect();
|
|
$this->log->parse();
|
|
if ($this->log instanceof AnalysableLogInterface) {
|
|
$this->log->analyse();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the codex log object
|
|
*
|
|
* @return LogInterface
|
|
*/
|
|
public function getCodexLog(): LogInterface
|
|
{
|
|
return $this->log;
|
|
}
|
|
|
|
/**
|
|
* Get the log analysis
|
|
*
|
|
* @return Analysis|null
|
|
*/
|
|
public function getAnalysis(): ?Analysis
|
|
{
|
|
$log = $this->getCodexLog();
|
|
if ($log instanceof AnalysableLogInterface) {
|
|
return $log->analyse();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* @return Printer
|
|
*/
|
|
public function getPrinter(): Printer
|
|
{
|
|
if ($this->printer === null) {
|
|
$this->printer = new Printer()->setLog($this->log)->setId($this->id);
|
|
}
|
|
return $this->printer;
|
|
}
|
|
|
|
/**
|
|
* Get the amount of lines in this log
|
|
*
|
|
* @return int
|
|
*/
|
|
public function getLinesCount(): int
|
|
{
|
|
$codexLog = $this->getCodexLog();
|
|
$lines = 0;
|
|
foreach ($codexLog as $entry) {
|
|
$lines += count($entry);
|
|
}
|
|
return $lines;
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getLinesString(): string
|
|
{
|
|
$lineCount = $this->getLinesCount();
|
|
return $lineCount . ($lineCount === 1 ? " line" : " lines");
|
|
}
|
|
|
|
/**
|
|
* @return int
|
|
*/
|
|
public function getSize(): int
|
|
{
|
|
return strlen($this->getContent());
|
|
}
|
|
|
|
/**
|
|
* Get the amount of error entries in the log
|
|
*
|
|
* @return int
|
|
*/
|
|
public function getErrorsCount(): int
|
|
{
|
|
$errorCount = 0;
|
|
|
|
foreach ($this->log as $entry) {
|
|
if ($entry->getLevel()->asInt() <= Level::ERROR->asInt()) {
|
|
$errorCount++;
|
|
}
|
|
}
|
|
|
|
return $errorCount;
|
|
}
|
|
|
|
/**
|
|
* @return bool
|
|
*/
|
|
public function hasErrors(): bool
|
|
{
|
|
return $this->getErrorsCount() > 0;
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getErrorsString(): string
|
|
{
|
|
$errorCount = $this->getErrorsCount();
|
|
return $errorCount . ($errorCount === 1 ? " error" : " errors");
|
|
}
|
|
|
|
protected function generateId(): Id
|
|
{
|
|
do {
|
|
$this->id = new Id();
|
|
} while (MongoDBClient::getInstance()->hasLog($this->id));
|
|
return $this->id;
|
|
}
|
|
|
|
/**
|
|
* Save the log to the database
|
|
*
|
|
* @return $this
|
|
*/
|
|
public function save(string $content): static
|
|
{
|
|
if ($this->id === null) {
|
|
$this->generateId();
|
|
}
|
|
|
|
$content = Filter::filterAll($content);
|
|
|
|
MongoDBClient::getInstance()->getLogsCollection()->insertOne([
|
|
"_id" => $this->id->get(),
|
|
"data" => $content,
|
|
"token" => $this->token?->get(),
|
|
"source" => $this->source,
|
|
"metadata" => $this->metadata,
|
|
"expires" => $this->expires = $this->getExpiryTimestamp(),
|
|
"created" => $this->created = new UTCDateTime()
|
|
]);
|
|
|
|
return $this->setContent($content);
|
|
}
|
|
|
|
/**
|
|
* @return UTCDateTime
|
|
*/
|
|
protected function getExpiryTimestamp(): UTCDateTime
|
|
{
|
|
$ttl = \Aternos\Mclogs\Config\Config::getInstance()->get(ConfigKey::STORAGE_TTL);
|
|
$expires = time() + $ttl;
|
|
return new UTCDateTime($expires * 1000);
|
|
}
|
|
|
|
/**
|
|
* Renew the expiry timestamp to expand the ttl
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function renew(): bool
|
|
{
|
|
$expires = $this->getExpiryTimestamp();
|
|
$result = MongoDBClient::getInstance()->setLogExpires($this->id, $expires);
|
|
if ($result) {
|
|
$this->expires = $expires;
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @return Uri
|
|
*/
|
|
public function getURL(): Uri
|
|
{
|
|
return URL::getBase()->withPath("/" . $this->id->get());
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getDisplayURL(): string
|
|
{
|
|
$url = $this->getURL();
|
|
return $url->getHost() . $url->getPath();
|
|
}
|
|
|
|
/**
|
|
* @return Uri
|
|
*/
|
|
public function getRawURL(): Uri
|
|
{
|
|
return URL::getApi()->withPath("/1/raw/" . $this->id->get());
|
|
}
|
|
|
|
/**
|
|
* @return Id|null
|
|
*/
|
|
public function getId(): ?Id
|
|
{
|
|
return $this->id;
|
|
}
|
|
|
|
/**
|
|
* @return Token|null
|
|
*/
|
|
public function getToken(): ?Token
|
|
{
|
|
return $this->token;
|
|
}
|
|
|
|
/**
|
|
* @return bool
|
|
*/
|
|
public function delete(): bool
|
|
{
|
|
return MongoDBClient::getInstance()->deleteLog($this->id->get());
|
|
}
|
|
|
|
/**
|
|
* @return MetadataEntry[]
|
|
*/
|
|
public function getMetadata(): array
|
|
{
|
|
return $this->metadata;
|
|
}
|
|
|
|
/**
|
|
* @return MetadataEntry[]
|
|
*/
|
|
public function getVisibleMetadata(): array
|
|
{
|
|
return array_filter($this->metadata, function (MetadataEntry $entry) {
|
|
return $entry->isVisible();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @return bool
|
|
*/
|
|
public function setTokenCookie(): bool
|
|
{
|
|
if (!$this->getToken()) {
|
|
return false;
|
|
}
|
|
return new TokenCookie($this)->set($this->getToken()->get());
|
|
}
|
|
|
|
/**
|
|
* @return bool
|
|
*/
|
|
public function hasValidTokenCookie(): bool
|
|
{
|
|
$tokenCookie = new TokenCookie();
|
|
$cookieValue = $tokenCookie->getValue();
|
|
if ($cookieValue === null || !$this->getToken()) {
|
|
return false;
|
|
}
|
|
return $this->getToken()->matches($cookieValue);
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getPageTitle(): string
|
|
{
|
|
return $this->getCodexLog()?->getTitle() . " [#" . $this->getId()?->get() . "]";
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getPageDescription(): string
|
|
{
|
|
$description = $this->getLinesString();
|
|
if ($this->hasErrors()) {
|
|
$description .= " | " . $this->getErrorsString();
|
|
}
|
|
|
|
$problems = $this->getAnalysis()->getProblems();
|
|
|
|
if (count($problems) > 0) {
|
|
$problemString = "problems";
|
|
if (count($problems) === 1) {
|
|
$problemString = "problem";
|
|
}
|
|
$description .= " | " . count($problems) . " " . $problemString . " automatically detected";
|
|
}
|
|
|
|
return $description;
|
|
}
|
|
}
|