405 lines
14 KiB
PHP
405 lines
14 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',
|
|
};
|
|
}
|
|
|
|
public function countChanges(
|
|
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 getChanges(
|
|
UserInfo|string|null $userInfo = null,
|
|
DateTime|int|null $dateTime = null,
|
|
?array $tags = null,
|
|
?Pagination $pagination = null
|
|
): iterable {
|
|
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 $stmt->getResult()->getIterator(ChangeInfo::fromResult(...));
|
|
}
|
|
|
|
public function getChange(string $changeId): 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.');
|
|
|
|
return ChangeInfo::fromResult($result);
|
|
}
|
|
|
|
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->getChange((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 getTags(
|
|
ChangeInfo|string|null $changeInfo = null
|
|
): array {
|
|
if($changeInfo instanceof ChangeInfo)
|
|
$changeInfo = $changeInfo->getId();
|
|
|
|
$hasChangeInfo = $changeInfo !== null;
|
|
|
|
$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';
|
|
if($hasChangeInfo)
|
|
$query .= ' WHERE tag_id IN (SELECT tag_id FROM msz_changelog_change_tags WHERE change_id = ?)';
|
|
|
|
$stmt = $this->cache->get($query);
|
|
if($hasChangeInfo)
|
|
$stmt->addParameter(1, $changeInfo);
|
|
$stmt->execute();
|
|
|
|
$result = $stmt->getResult();
|
|
$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 getTag(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->getTag((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();
|
|
}
|
|
}
|