misuzu/src/Changelog/Changelog.php
flash 383e2ed0e0 Rewrote the user information class.
This one took multiple days and it pretty invasive into the core of Misuzu so issue might (will) arise, there's also some features that have gone temporarily missing in the mean time and some inefficiencies introduced that will be fixed again at a later time.
The old class isn't gone entirely because I still have to figure out what I'm gonna do about validation, but for the most part this knocks out one of the "layers of backwards compatibility", as I've been referring to it, and is moving us closer to a future where Flashii actually gets real updates.
If you run into anything that's broken and you're inhibited from reporting it through the forum, do it through chat or mail me at flashii-issues@flash.moe.
2023-08-02 22:12:47 +00:00

428 lines
15 KiB
PHP

<?php
namespace Misuzu\Changelog;
use InvalidArgumentException;
use RuntimeException;
use Index\DateTime;
use Index\Data\DbStatementCache;
use Index\Data\DbTools;
use Index\Data\IDbConnection;
use Index\Data\IDbResult;
use Misuzu\Pagination;
use Misuzu\Users\UserInfo;
class Changelog {
// not a strict list but useful to have
public const ACTIONS = ['add', 'remove', 'update', 'fix', 'import', 'revert'];
private IDbConnection $dbConn;
private DbStatementCache $cache;
private array $tags = [];
public function __construct(IDbConnection $dbConn) {
$this->dbConn = $dbConn;
$this->cache = new DbStatementCache($dbConn);
}
public static function convertFromActionId(int $actionId): string {
return match($actionId) {
1 => 'add',
2 => 'remove',
3 => 'update',
4 => 'fix',
5 => 'import',
6 => 'revert',
default => 'unknown',
};
}
public static function convertToActionId(string $action): int {
return match($action) {
'add' => 1,
'remove' => 2,
'update' => 3,
'fix' => 4,
'import' => 5,
'revert' => 6,
default => 0,
};
}
public static function actionText(string|int $action): string {
if(is_int($action))
$action = self::convertFromActionId($action);
return match($action) {
'add' => 'Added',
'remove' => 'Removed',
'update' => 'Updated',
'fix' => 'Fixed',
'import' => 'Imported',
'revert' => 'Reverted',
default => 'Changed',
};
}
private function readChanges(IDbResult $result, bool $withTags): array {
$changes = [];
if($withTags) {
while($result->next())
$changes[] = new ChangeInfo(
$result,
$this->getTagsByChange((string)$result->getInteger(0))
);
} else {
while($result->next())
$changes[] = new ChangeInfo($result);
}
return $changes;
}
private function readTags(IDbResult $result): array {
$tags = [];
while($result->next()) {
$tagId = (string)$result->getInteger(0);
if(array_key_exists($tagId, $this->tags))
$tags[] = $this->tags[$tagId];
else
$tags[] = $this->tags[$tagId] = new ChangeTagInfo($result);
}
return $tags;
}
public function countAllChanges(
UserInfo|string|null $userInfo = null,
DateTime|int|null $dateTime = null,
?array $tags = null
): int {
if($userInfo instanceof UserInfo)
$userInfo = $userInfo->getId();
if($dateTime instanceof DateTime)
$dateTime = $dateTime->getUnixTimeSeconds();
$args = 0;
$hasUserInfo = $userInfo !== null;
$hasDateTime = $dateTime !== null;
$hasTags = !empty($tags);
$query = 'SELECT COUNT(*) FROM msz_changelog_changes';
if($hasUserInfo) {
++$args;
$query .= ' WHERE user_id = ?';
}
if($hasDateTime) {
$query .= (++$args > 1 ? ' AND' : ' WHERE');
$query .= ' DATE(change_created) = DATE(FROM_UNIXTIME(?))';
}
if($hasTags) {
$query .= (++$args > 1 ? ' AND' : ' WHERE');
$query .= sprintf(
' change_id IN (SELECT change_id FROM msz_changelog_change_tags WHERE tag_id IN (%s))',
DbTools::prepareListString($tags)
);
}
$stmt = $this->cache->get($query);
$args = 0;
if($hasUserInfo)
$stmt->addParameter(++$args, $userInfo);
if($hasDateTime)
$stmt->addParameter(++$args, $dateTime);
if($hasTags)
foreach($tags as $tag)
$stmt->addParameter(++$args, (string)$tag);
$stmt->execute();
$result = $stmt->getResult();
$count = 0;
if($result->next())
$count = $result->getInteger(0);
return $count;
}
public function getAllChanges(
bool $withTags = false,
UserInfo|string|null $userInfo = null,
DateTime|int|null $dateTime = null,
?array $tags = null,
?Pagination $pagination = null
): array {
if($userInfo instanceof UserInfo)
$userInfo = $userInfo->getId();
if($dateTime instanceof DateTime)
$dateTime = $dateTime->getUnixTimeSeconds();
$args = 0;
$hasUserInfo = $userInfo !== null;
$hasDateTime = $dateTime !== null;
$hasTags = !empty($tags);
$hasPagination = $pagination !== null;
$query = 'SELECT change_id, user_id, change_action, UNIX_TIMESTAMP(change_created), change_log, change_text FROM msz_changelog_changes';
if($hasUserInfo) {
++$args;
$query .= ' WHERE user_id = ?';
}
if($hasDateTime) {
$query .= (++$args > 1 ? ' AND' : ' WHERE');
$query .= ' DATE(change_created) = DATE(FROM_UNIXTIME(?))';
}
if($hasTags) {
$query .= (++$args > 1 ? ' AND' : ' WHERE');
$query .= sprintf(
' change_id IN (SELECT change_id FROM msz_changelog_change_tags WHERE tag_id IN (%s))',
DbTools::prepareListString($tags)
);
}
$query .= ' GROUP BY change_created, change_id ORDER BY change_created DESC, change_id DESC';
if($hasPagination)
$query .= ' LIMIT ? OFFSET ?';
$stmt = $this->cache->get($query);
$args = 0;
if($hasUserInfo)
$stmt->addParameter(++$args, $userInfo);
if($hasDateTime)
$stmt->addParameter(++$args, $dateTime);
if($hasTags)
foreach($tags as $tag)
$stmt->addParameter(++$args, (string)$tag);
if($hasPagination) {
$stmt->addParameter(++$args, $pagination->getRange());
$stmt->addParameter(++$args, $pagination->getOffset());
}
$stmt->execute();
return self::readChanges($stmt->getResult(), $withTags);
}
public function getChangeById(string $changeId, bool $withTags = false): ChangeInfo {
$stmt = $this->cache->get('SELECT change_id, user_id, change_action, UNIX_TIMESTAMP(change_created), change_log, change_text FROM msz_changelog_changes WHERE change_id = ?');
$stmt->addParameter(1, $changeId);
$stmt->execute();
$result = $stmt->getResult();
if(!$result->next())
throw new RuntimeException('No tag with that ID exists.');
$tags = [];
if($withTags)
$tags = $this->getTagsByChange((string)$result->getInteger(0));
return new ChangeInfo($result, $tags);
}
public function createChange(
string|int $action,
string $summary,
string $body = '',
UserInfo|string|null $userInfo = null,
DateTime|int|null $createdAt = null
): ChangeInfo {
if(is_string($action))
$action = self::convertToActionId($action);
if($userInfo instanceof UserInfo)
$userInfo = $userInfo->getId();
if($createdAt instanceof DateTime)
$createdAt = $createdAt->getUnixTimeSeconds();
$summary = trim($summary);
if(empty($summary))
throw new InvalidArgumentException('$summary may not be empty');
$body = trim($body);
if(empty($body))
$body = null;
$stmt = $this->cache->get('INSERT INTO msz_changelog_changes (user_id, change_action, change_created, change_log, change_text) VALUES (?, ?, FROM_UNIXTIME(?), ?, ?)');
$stmt->addParameter(1, $userInfo);
$stmt->addParameter(2, $action);
$stmt->addParameter(3, $createdAt);
$stmt->addParameter(4, $summary);
$stmt->addParameter(5, $body);
$stmt->execute();
return $this->getChangeById((string)$this->dbConn->getLastInsertId());
}
public function deleteChange(ChangeInfo|string $infoOrId): void {
if($infoOrId instanceof ChangeInfo)
$infoOrId = $infoOrId->getId();
$stmt = $this->cache->get('DELETE FROM msz_changelog_changes WHERE change_id = ?');
$stmt->addParameter(1, $infoOrId);
$stmt->execute();
}
public function updateChange(
ChangeInfo|string $infoOrId,
string|int|null $action = null,
?string $summary = null,
?string $body = null,
bool $updateUserInfo = false,
UserInfo|string|null $userInfo = null,
DateTime|int|null $createdAt = null
): void {
if($infoOrId instanceof ChangeInfo)
$infoOrId = $infoOrId->getId();
if(is_string($action))
$action = self::convertToActionId($action);
if($userInfo instanceof UserInfo)
$userInfo = $userInfo->getId();
if($createdAt instanceof DateTime)
$createdAt = $createdAt->getUnixTimeSeconds();
if($summary !== null) {
$summary = trim($summary);
if(empty($summary))
throw new InvalidArgumentException('$summary may not be empty');
}
$hasBody = $body !== null;
if($hasBody) {
$body = trim($body);
if(empty($body))
$body = null;
}
$stmt = $this->cache->get('UPDATE msz_changelog_changes SET change_action = COALESCE(?, change_action), change_log = COALESCE(?, change_log), change_text = IF(?, ?, change_text), user_id = IF(?, ?, user_id), change_created = COALESCE(FROM_UNIXTIME(?), change_created) WHERE change_id = ?');
$stmt->addParameter(1, $action);
$stmt->addParameter(2, $summary);
$stmt->addParameter(3, $hasBody ? 1 : 0);
$stmt->addParameter(4, $body);
$stmt->addParameter(5, $updateUserInfo ? 1 : 0);
$stmt->addParameter(6, $userInfo);
$stmt->addParameter(7, $createdAt);
$stmt->addParameter(8, $infoOrId);
$stmt->execute();
}
public function getAllTags(): array {
// only putting the changes count in here for now, it is only used in manage
return $this->readTags(
$this->dbConn->query('SELECT tag_id, tag_name, tag_description, UNIX_TIMESTAMP(tag_created), UNIX_TIMESTAMP(tag_archived), (SELECT COUNT(*) FROM msz_changelog_change_tags AS ct WHERE ct.tag_id = t.tag_id) AS `tag_changes` FROM msz_changelog_tags AS t')
);
}
public function getTagsByChange(ChangeInfo|string $infoOrId): array {
if($infoOrId instanceof ChangeInfo)
$infoOrId = $infoOrId->getId();
$stmt = $this->cache->get('SELECT tag_id, tag_name, tag_description, UNIX_TIMESTAMP(tag_created), UNIX_TIMESTAMP(tag_archived), 0 AS `tag_changes` FROM msz_changelog_tags WHERE tag_id IN (SELECT tag_id FROM msz_changelog_change_tags WHERE change_id = ?)');
$stmt->addParameter(1, $infoOrId);
$stmt->execute();
return $this->readTags($stmt->getResult());
}
public function getTagById(string $tagId): ChangeTagInfo {
$stmt = $this->cache->get('SELECT tag_id, tag_name, tag_description, UNIX_TIMESTAMP(tag_created), UNIX_TIMESTAMP(tag_archived), 0 AS `tag_changes` FROM msz_changelog_tags WHERE tag_id = ?');
$stmt->addParameter(1, $tagId);
$stmt->execute();
$result = $stmt->getResult();
if(!$result->next())
throw new RuntimeException('No tag with that ID exists.');
return new ChangeTagInfo($result);
}
public function createTag(
string $name,
string $description,
bool $archived
): ChangeTagInfo {
$name = trim($name);
if(empty($name))
throw new InvalidArgumentException('$name may not be empty');
$description = trim($description);
if(empty($description))
$description = null;
$stmt = $this->cache->get('INSERT INTO msz_changelog_tags (tag_name, tag_description, tag_archived) VALUES (?, ?, IF(?, NOW(), NULL))');
$stmt->addParameter(1, $name);
$stmt->addParameter(2, $description);
$stmt->addParameter(3, $archived ? 1 : 0);
$stmt->execute();
return $this->getTagById((string)$this->dbConn->getLastInsertId());
}
public function deleteTag(ChangeTagInfo|string $infoOrId): void {
if($infoOrId instanceof ChangeTagInfo)
$infoOrId = $infoOrId->getId();
$stmt = $this->cache->get('DELETE FROM msz_changelog_tags WHERE tag_id = ?');
$stmt->addParameter(1, $infoOrId);
$stmt->execute();
}
public function updateTag(
ChangeTagInfo|string $infoOrId,
?string $name = null,
?string $description = null,
?bool $archived = null
): void {
if($infoOrId instanceof ChangeTagInfo)
$infoOrId = $infoOrId->getId();
if($name !== null) {
$name = trim($name);
if(empty($name))
throw new InvalidArgumentException('$name may not be empty');
}
$hasDescription = $description !== null;
if($hasDescription) {
$description = trim($description);
if(empty($description))
$description = null;
}
$hasArchived = $archived !== null;
$stmt = $this->cache->get('UPDATE msz_changelog_tags SET tag_name = COALESCE(?, tag_name), tag_description = IF(?, ?, tag_description), tag_archived = IF(?, IF(?, NOW(), NULL), tag_archived) WHERE tag_id = ?');
$stmt->addParameter(1, $name);
$stmt->addParameter(2, $hasDescription ? 1 : 0);
$stmt->addParameter(3, $description);
$stmt->addParameter(4, $hasArchived ? 1 : 0);
$stmt->addParameter(5, $archived ? 1 : 0);
$stmt->addParameter(6, $infoOrId);
$stmt->execute();
}
public function addTagToChange(ChangeInfo|string $change, ChangeTagInfo|string $tag): void {
if($change instanceof ChangeInfo)
$change = $change->getId();
if($tag instanceof ChangeTagInfo)
$tag = $tag->getId();
$stmt = $this->cache->get('INSERT INTO msz_changelog_change_tags (change_id, tag_id) VALUES (?, ?)');
$stmt->addParameter(1, $change);
$stmt->addParameter(2, $tag);
$stmt->execute();
}
public function removeTagFromChange(ChangeInfo|string $change, ChangeTagInfo|string $tag): void {
if($change instanceof ChangeInfo)
$change = $change->getId();
if($tag instanceof ChangeTagInfo)
$tag = $tag->getId();
$stmt = $this->cache->get('DELETE FROM msz_changelog_change_tags WHERE change_id = ? AND tag_id = ?');
$stmt->addParameter(1, $change);
$stmt->addParameter(2, $tag);
$stmt->execute();
}
}