Rewrote the Changelog code.
This commit is contained in:
parent
6d0d49171e
commit
76c9cc50f4
28 changed files with 963 additions and 707 deletions
|
@ -107,9 +107,7 @@ if(!empty($repoInfo['master']))
|
|||
if($data->ref !== $repoMaster)
|
||||
die('only the master branch is tracked');
|
||||
|
||||
// the actual changelog api sucks ass
|
||||
$changeCreate = DB::prepare('INSERT INTO `msz_changelog_changes` (`change_log`, `change_text`, `change_action`, `user_id`, `change_created`) VALUES (:log, :text, :action, :user, FROM_UNIXTIME(:created))');
|
||||
$changeTag = DB::prepare('REPLACE INTO `msz_changelog_change_tags` VALUES (:change_id, :tag_id)');
|
||||
$changelog = $msz->getChangelog();
|
||||
|
||||
$tags = $repoInfo['tags'] ?? [];
|
||||
$addresses = $config['addresses'] ?? [];
|
||||
|
@ -126,21 +124,14 @@ foreach($data->commits as $commit) {
|
|||
$line = $index === false ? $message : mb_substr($message, 0, $index);
|
||||
$body = trim($index === false ? '' : mb_substr($message, $index + 1));
|
||||
|
||||
$changeCreate->bind('user', $addresses[$commit->author->email] ?? null);
|
||||
$changeCreate->bind('action', ghcb_changelog_action($line));
|
||||
$changeCreate->bind('log', $line);
|
||||
$changeCreate->bind('text', empty($body) ? null : $body);
|
||||
$changeCreate->bind('created', max(1, strtotime($commit->timestamp)));
|
||||
$changeId = $changeCreate->executeGetId();
|
||||
$changeInfo = $changelog->createChange(
|
||||
ghcb_changelog_action($line),
|
||||
$line, $body,
|
||||
$addresses[$commit->author->email] ?? null,
|
||||
max(1, strtotime($commit->timestamp))
|
||||
);
|
||||
|
||||
if(!empty($tags) && !empty($changeId)) {
|
||||
$changeTag->bind('change_id', $changeId);
|
||||
|
||||
foreach($tags as $tag) {
|
||||
$changeTag->bind('tag_id', $tag);
|
||||
$changeTag->execute();
|
||||
}
|
||||
}
|
||||
|
||||
unset($changeId, $tag);
|
||||
if(!empty($tags))
|
||||
foreach($tags as $tag)
|
||||
$changelog->addTagToChange($changeInfo, $tag);
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
use DateTimeInterface;
|
||||
use RuntimeException;
|
||||
use Index\DateTime;
|
||||
use Misuzu\AuditLog;
|
||||
use Misuzu\Changelog\ChangelogChange;
|
||||
use Misuzu\Changelog\ChangelogChangeNotFoundException;
|
||||
use Misuzu\Changelog\ChangelogTag;
|
||||
use Misuzu\Changelog\ChangelogTagNotFoundException;
|
||||
use Misuzu\Changelog\Changelog;
|
||||
use Misuzu\Users\User;
|
||||
use Misuzu\Users\UserNotFoundException;
|
||||
|
||||
|
@ -16,78 +16,105 @@ if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_CHANGELOG, User::getCurren
|
|||
return;
|
||||
}
|
||||
|
||||
define('MANAGE_ACTIONS', [
|
||||
['action_id' => ChangelogChange::ACTION_ADD, 'action_name' => 'Added'],
|
||||
['action_id' => ChangelogChange::ACTION_REMOVE, 'action_name' => 'Removed'],
|
||||
['action_id' => ChangelogChange::ACTION_UPDATE, 'action_name' => 'Updated'],
|
||||
['action_id' => ChangelogChange::ACTION_FIX, 'action_name' => 'Fixed'],
|
||||
['action_id' => ChangelogChange::ACTION_IMPORT, 'action_name' => 'Imported'],
|
||||
['action_id' => ChangelogChange::ACTION_REVERT, 'action_name' => 'Reverted'],
|
||||
]);
|
||||
$changeActions = [];
|
||||
foreach(Changelog::ACTIONS as $action)
|
||||
$changeActions[$action] = Changelog::actionText($action);
|
||||
|
||||
$changeId = (int)filter_input(INPUT_GET, 'c', FILTER_SANITIZE_NUMBER_INT);
|
||||
$tags = ChangelogTag::all();
|
||||
$changelog = $msz->getChangelog();
|
||||
$changeId = (string)filter_input(INPUT_GET, 'c', FILTER_SANITIZE_NUMBER_INT);
|
||||
$loadChangeInfo = fn() => $changelog->getChangeById($changeId, withTags: true);
|
||||
$changeTags = $changelog->getAllTags();
|
||||
|
||||
if($changeId > 0)
|
||||
if(empty($changeId))
|
||||
$isNew = true;
|
||||
else
|
||||
try {
|
||||
$change = ChangelogChange::byId($changeId);
|
||||
} catch(ChangelogChangeNotFoundException $ex) {
|
||||
$isNew = false;
|
||||
$changeInfo = $loadChangeInfo();
|
||||
} catch(RuntimeException $ex) {
|
||||
echo render_error(404);
|
||||
return;
|
||||
}
|
||||
|
||||
if($_SERVER['REQUEST_METHOD'] === 'GET' && !empty($_GET['delete'])) {
|
||||
if(CSRF::validateRequest()) {
|
||||
$changelog->deleteChange($changeInfo);
|
||||
AuditLog::create(AuditLog::CHANGELOG_ENTRY_DELETE, [$changeInfo->getId()]);
|
||||
url_redirect('manage-changelog-changes');
|
||||
return;
|
||||
} else render_error(403);
|
||||
return;
|
||||
}
|
||||
|
||||
// make errors not echos lol
|
||||
while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
||||
$action = trim((string)filter_input(INPUT_POST, 'cl_action'));
|
||||
$summary = trim((string)filter_input(INPUT_POST, 'cl_summary'));
|
||||
$body = trim((string)filter_input(INPUT_POST, 'cl_body'));
|
||||
$userId = (int)filter_input(INPUT_POST, 'cl_user', FILTER_SANITIZE_NUMBER_INT);
|
||||
$createdAt = trim((string)filter_input(INPUT_POST, 'cl_created'));
|
||||
$tags = filter_input(INPUT_POST, 'cl_tags', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY);
|
||||
|
||||
if($userId < 1) $userId = null;
|
||||
else $userId = (string)$userId;
|
||||
|
||||
if(empty($createdAt))
|
||||
$createdAt = null;
|
||||
else {
|
||||
$createdAt = DateTime::createFromFormat(DateTimeInterface::ATOM, $createdAt . ':00Z');
|
||||
if($createdAt->getUnixTimeSeconds() < 0)
|
||||
$createdAt = null;
|
||||
}
|
||||
|
||||
if($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
||||
if(!empty($_POST['change']) && is_array($_POST['change'])) {
|
||||
if(!isset($change)) {
|
||||
$change = new ChangelogChange;
|
||||
$isNew = true;
|
||||
if($isNew) {
|
||||
$changeInfo = $changelog->createChange($action, $summary, $body, $userId, $createdAt);
|
||||
} else {
|
||||
if($action === $changeInfo->getAction())
|
||||
$action = null;
|
||||
if($summary === $changeInfo->getSummary())
|
||||
$summary = null;
|
||||
if($body === $changeInfo->getBody())
|
||||
$body = null;
|
||||
if($createdAt !== null && $createdAt->equals($changeInfo->getCreatedAt()))
|
||||
$createdAt = null;
|
||||
$updateUserInfo = $userId !== $changeInfo->getUserId();
|
||||
|
||||
if($action !== null || $summary !== null || $body !== null || $createdAt !== null || $updateUserInfo)
|
||||
$changelog->updateChange($changeInfo, $action, $summary, $body, $updateUserInfo, $userId, $createdAt);
|
||||
}
|
||||
|
||||
$tCurrent = $changeInfo->getTagIds();
|
||||
$tApply = $tags;
|
||||
$tRemove = [];
|
||||
|
||||
foreach($tCurrent as $tag)
|
||||
if(!in_array($tag, $tApply)) {
|
||||
$tRemove[] = $tag;
|
||||
$changelog->removeTagFromChange($changeInfo, $tag);
|
||||
}
|
||||
|
||||
$changeUserId = filter_var($_POST['change']['user'], FILTER_SANITIZE_NUMBER_INT);
|
||||
if($changeUserId === 0)
|
||||
$changeUser = null;
|
||||
else
|
||||
try {
|
||||
$changeUser = User::byId($changeUserId);
|
||||
} catch(UserNotFoundException $ex) {
|
||||
$changeUser = User::getCurrent();
|
||||
}
|
||||
$tCurrent = array_diff($tCurrent, $tRemove);
|
||||
|
||||
$change->setHeader($_POST['change']['log'])
|
||||
->setBody($_POST['change']['text'])
|
||||
->setAction($_POST['change']['action'])
|
||||
->setUser($changeUser)
|
||||
->save();
|
||||
|
||||
AuditLog::create(
|
||||
empty($isNew)
|
||||
? AuditLog::CHANGELOG_ENTRY_EDIT
|
||||
: AuditLog::CHANGELOG_ENTRY_CREATE,
|
||||
[$change->getId()]
|
||||
);
|
||||
}
|
||||
|
||||
if(isset($change) && !empty($_POST['tags']) && is_array($_POST['tags'])) {
|
||||
$applyTags = [];
|
||||
foreach($_POST['tags'] as $tagId) {
|
||||
if(!ctype_digit($tagId))
|
||||
die('Invalid item encountered in roles list.');
|
||||
|
||||
try {
|
||||
$applyTags[] = ChangelogTag::byId((int)filter_var($tagId, FILTER_SANITIZE_NUMBER_INT));
|
||||
} catch(ChangelogTagNotFoundException $ex) {}
|
||||
foreach($tApply as $tag)
|
||||
if(!in_array($tag, $tCurrent)) {
|
||||
$changelog->addTagToChange($changeInfo, $tag);
|
||||
$tCurrent[] = $tag;
|
||||
}
|
||||
$change->setTags($applyTags);
|
||||
}
|
||||
|
||||
if(!empty($isNew)) {
|
||||
url_redirect('manage-changelog-change', ['change' => $change->getId()]);
|
||||
AuditLog::create(
|
||||
$isNew ? AuditLog::CHANGELOG_ENTRY_CREATE : AuditLog::CHANGELOG_ENTRY_EDIT,
|
||||
[$changeInfo->getId()]
|
||||
);
|
||||
|
||||
if($isNew) {
|
||||
url_redirect('manage-changelog-change', ['change' => $changeInfo->getId()]);
|
||||
return;
|
||||
}
|
||||
} else $changeInfo = $loadChangeInfo();
|
||||
break;
|
||||
}
|
||||
|
||||
Template::render('manage.changelog.change', [
|
||||
'change' => $change ?? null,
|
||||
'change_tags' => $tags,
|
||||
'change_actions' => MANAGE_ACTIONS,
|
||||
'change_new' => $isNew,
|
||||
'change_info' => $changeInfo ?? null,
|
||||
'change_tags' => $changeTags,
|
||||
'change_actions' => $changeActions,
|
||||
]);
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
use Misuzu\Changelog\ChangelogChange;
|
||||
use Misuzu\Users\User;
|
||||
|
||||
require_once '../../../misuzu.php';
|
||||
|
@ -11,14 +10,38 @@ if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_CHANGELOG, User::getCurren
|
|||
return;
|
||||
}
|
||||
|
||||
$changelogPagination = new Pagination(ChangelogChange::countAll(), 30);
|
||||
$changelog = $msz->getChangelog();
|
||||
$changelogPagination = new Pagination($changelog->countAllChanges(), 30);
|
||||
|
||||
if(!$changelogPagination->hasValidOffset()) {
|
||||
echo render_error(404);
|
||||
return;
|
||||
}
|
||||
|
||||
$changes = ChangelogChange::all($changelogPagination);
|
||||
$changeInfos = $changelog->getAllChanges(withTags: true, pagination: $changelogPagination);
|
||||
$changes = [];
|
||||
$userInfos = [];
|
||||
|
||||
foreach($changeInfos as $changeInfo) {
|
||||
$userId = $changeInfo->getUserId();
|
||||
|
||||
if(array_key_exists($userId, $userInfos)) {
|
||||
$userInfo = $userInfos[$userId];
|
||||
} else {
|
||||
try {
|
||||
$userInfo = User::byId($userId);
|
||||
} catch(UserNotFoundException $ex) {
|
||||
$userInfo = null;
|
||||
}
|
||||
|
||||
$userInfos[$userId] = $userInfo;
|
||||
}
|
||||
|
||||
$changes[] = [
|
||||
'change' => $changeInfo,
|
||||
'user' => $userInfo,
|
||||
];
|
||||
}
|
||||
|
||||
Template::render('manage.changelog.changes', [
|
||||
'changelog_changes' => $changes,
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
use RuntimeException;
|
||||
use Misuzu\AuditLog;
|
||||
use Misuzu\Changelog\ChangelogTag;
|
||||
use Misuzu\Changelog\ChangelogTagNotFoundException;
|
||||
use Misuzu\Users\User;
|
||||
|
||||
require_once '../../../misuzu.php';
|
||||
|
@ -13,40 +12,62 @@ if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_CHANGELOG, User::getCurren
|
|||
return;
|
||||
}
|
||||
|
||||
$tagId = (int)filter_input(INPUT_GET, 't', FILTER_SANITIZE_NUMBER_INT);
|
||||
$changelog = $msz->getChangelog();
|
||||
$tagId = (string)filter_input(INPUT_GET, 't', FILTER_SANITIZE_NUMBER_INT);
|
||||
$loadTagInfo = fn() => $changelog->getTagById($tagId);
|
||||
|
||||
if($tagId > 0)
|
||||
if(empty($tagId))
|
||||
$isNew = true;
|
||||
else
|
||||
try {
|
||||
$tagInfo = ChangelogTag::byId($tagId);
|
||||
} catch(ChangelogTagNotFoundException $ex) {
|
||||
url_redirect('manage-changelog-tags');
|
||||
$isNew = false;
|
||||
$tagInfo = $loadTagInfo();
|
||||
} catch(RuntimeException $ex) {
|
||||
echo render_error(404);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!empty($_POST['tag']) && is_array($_POST['tag']) && CSRF::validateRequest()) {
|
||||
if(!isset($tagInfo)) {
|
||||
$tagInfo = new ChangelogTag;
|
||||
$isNew = true;
|
||||
if($_SERVER['REQUEST_METHOD'] === 'GET' && !empty($_GET['delete'])) {
|
||||
if(CSRF::validateRequest()) {
|
||||
$changelog->deleteTag($tagInfo);
|
||||
AuditLog::create(AuditLog::CHANGELOG_TAG_DELETE, [$tagInfo->getId()]);
|
||||
url_redirect('manage-changelog-tags');
|
||||
} else render_error(403);
|
||||
return;
|
||||
}
|
||||
|
||||
while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
||||
$name = trim((string)filter_input(INPUT_POST, 'ct_name'));
|
||||
$description = trim((string)filter_input(INPUT_POST, 'ct_desc'));
|
||||
$archive = !empty($_POST['ct_archive']);
|
||||
|
||||
if($isNew) {
|
||||
$tagInfo = $changelog->createTag($name, $description, $archive);
|
||||
} else {
|
||||
if($name === $tagInfo->getName())
|
||||
$name = null;
|
||||
if($description === $tagInfo->getDescription())
|
||||
$description = null;
|
||||
if($archive === $tagInfo->isArchived())
|
||||
$archive = null;
|
||||
|
||||
if($name !== null || $description !== null || $archive !== null)
|
||||
$changelog->updateTag($tagInfo, $name, $description, $archive);
|
||||
}
|
||||
|
||||
$tagInfo->setName($_POST['tag']['name'])
|
||||
->setDescription($_POST['tag']['description'])
|
||||
->setArchived(!empty($_POST['tag']['archived']))
|
||||
->save();
|
||||
|
||||
AuditLog::create(
|
||||
empty($isNew)
|
||||
? AuditLog::CHANGELOG_TAG_EDIT
|
||||
: AuditLog::CHANGELOG_TAG_CREATE,
|
||||
$isNew ? AuditLog::CHANGELOG_TAG_CREATE : AuditLog::CHANGELOG_TAG_EDIT,
|
||||
[$tagInfo->getId()]
|
||||
);
|
||||
|
||||
if(!empty($isNew)) {
|
||||
if($isNew) {
|
||||
url_redirect('manage-changelog-tag', ['tag' => $tagInfo->getId()]);
|
||||
return;
|
||||
}
|
||||
} else $tagInfo = $loadTagInfo();
|
||||
break;
|
||||
}
|
||||
|
||||
Template::render('manage.changelog.tag', [
|
||||
'edit_tag' => $tagInfo ?? null,
|
||||
'tag_new' => $isNew,
|
||||
'tag_info' => $tagInfo ?? null,
|
||||
]);
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
use Misuzu\Changelog\ChangelogTag;
|
||||
use Misuzu\Users\User;
|
||||
|
||||
require_once '../../../misuzu.php';
|
||||
|
@ -12,5 +11,5 @@ if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_CHANGELOG, User::getCurren
|
|||
}
|
||||
|
||||
Template::render('manage.changelog.tags', [
|
||||
'changelog_tags' => ChangelogTag::all(),
|
||||
'changelog_tags' => $msz->getChangelog()->getAllTags(),
|
||||
]);
|
||||
|
|
|
@ -49,7 +49,6 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
|||
if($order == 0)
|
||||
$order = null;
|
||||
|
||||
$reload = false;
|
||||
if($isNew) {
|
||||
$emoteInfo = $emotes->createEmote($url, $minRank, $order);
|
||||
} else {
|
||||
|
@ -60,10 +59,8 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
|||
if($url === $emoteInfo->getUrl())
|
||||
$url = null;
|
||||
|
||||
if($order !== null || $minRank !== null || $url !== null) {
|
||||
$reload = true;
|
||||
if($order !== null || $minRank !== null || $url !== null)
|
||||
$emotes->updateEmote($emoteInfo, $order, $minRank, $url);
|
||||
}
|
||||
}
|
||||
|
||||
$sCurrent = $emoteInfo->getStringsRaw();
|
||||
|
@ -78,9 +75,6 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
|||
|
||||
$sCurrent = array_diff($sCurrent, $sRemove);
|
||||
|
||||
if(!$reload)
|
||||
$reload = !empty($sRemove) || !empty(array_diff($sApply, $sCurrent));
|
||||
|
||||
foreach($sApply as $string)
|
||||
if(!in_array($string, $sCurrent)) {
|
||||
$checkString = $emotes->checkEmoteString($string);
|
||||
|
@ -101,12 +95,15 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
|||
$sCurrent[] = $string;
|
||||
}
|
||||
|
||||
if($reload) {
|
||||
if($isNew)
|
||||
url_redirect('manage-general-emoticon', ['emote' => $emoteInfo->getId()]);
|
||||
else
|
||||
$emoteInfo = $loadEmoteInfo();
|
||||
}
|
||||
AuditLog::create(
|
||||
$isNew ? AuditLog::EMOTICON_CREATE : AuditLog::EMOTICON_EDIT,
|
||||
[$emoteInfo->getId()]
|
||||
);
|
||||
|
||||
if($isNew) {
|
||||
url_redirect('manage-general-emoticon', ['emote' => $emoteInfo->getId()]);
|
||||
return;
|
||||
} else $emoteInfo = $loadEmoteInfo();
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -25,17 +25,21 @@ if(CSRF::validateRequest() && !empty($_GET['emote'])) {
|
|||
|
||||
if(!empty($_GET['delete'])) {
|
||||
$emotes->deleteEmote($emoteInfo);
|
||||
AuditLog::create(AuditLog::EMOTICON_DELETE, [$emoteInfo->getId()]);
|
||||
} else {
|
||||
if(isset($_GET['order'])) {
|
||||
$order = filter_input(INPUT_GET, 'order');
|
||||
$offset = $order === 'i' ? 1 : ($order === 'd' ? -1 : 0);
|
||||
$emotes->updateEmoteOrderOffset($emoteInfo, $offset);
|
||||
AuditLog::create(AuditLog::EMOTICON_ORDER, [$emoteInfo->getId()]);
|
||||
}
|
||||
|
||||
if(isset($_GET['alias'])) {
|
||||
$alias = (string)filter_input(INPUT_GET, 'alias');
|
||||
if($emotes->checkEmoteString($alias) === '')
|
||||
if($emotes->checkEmoteString($alias) === '') {
|
||||
$emotes->addEmoteString($emoteInfo, $alias);
|
||||
AuditLog::create(AuditLog::EMOTICON_ALIAS, [$emoteInfo->getId(), $alias]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ class AuditLog {
|
|||
public const CHANGELOG_TAG_REMOVE = 'CHANGELOG_TAG_REMOVE';
|
||||
public const CHANGELOG_TAG_CREATE = 'CHANGELOG_TAG_CREATE';
|
||||
public const CHANGELOG_TAG_EDIT = 'CHANGELOG_TAG_EDIT';
|
||||
public const CHANGELOG_TAG_DELETE = 'CHANGELOG_TAG_DELETE';
|
||||
public const CHANGELOG_ACTION_CREATE = 'CHANGELOG_ACTION_CREATE';
|
||||
public const CHANGELOG_ACTION_EDIT = 'CHANGELOG_ACTION_EDIT';
|
||||
|
||||
|
@ -51,6 +52,12 @@ class AuditLog {
|
|||
public const CONFIG_UPDATE = 'CONFIG_UPDATE';
|
||||
public const CONFIG_DELETE = 'CONFIG_DELETE';
|
||||
|
||||
public const EMOTICON_CREATE = 'EMOTICON_CREATE';
|
||||
public const EMOTICON_EDIT = 'EMOTICON_EDIT';
|
||||
public const EMOTICON_DELETE = 'EMOTICON_DELETE';
|
||||
public const EMOTICON_ORDER = 'EMOTICON_ORDER';
|
||||
public const EMOTICON_ALIAS = 'EMOTICON_ALIAS';
|
||||
|
||||
public const FORMATS = [
|
||||
self::PERSONAL_EMAIL_CHANGE => 'Changed e-mail address to %s.',
|
||||
self::PERSONAL_PASSWORD_CHANGE => 'Changed account password.',
|
||||
|
@ -66,6 +73,7 @@ class AuditLog {
|
|||
self::CHANGELOG_TAG_REMOVE => 'Removed tag #%2$d from changelog entry #%1$d.',
|
||||
self::CHANGELOG_TAG_CREATE => 'Created new changelog tag #%d.',
|
||||
self::CHANGELOG_TAG_EDIT => 'Edited changelog tag #%d.',
|
||||
self::CHANGELOG_TAG_DELETE => 'Deleted changelog tag #%d.',
|
||||
self::CHANGELOG_ACTION_CREATE => 'Created new changelog action #%d.',
|
||||
self::CHANGELOG_ACTION_EDIT => 'Edited changelog action #%d.',
|
||||
|
||||
|
@ -95,6 +103,12 @@ class AuditLog {
|
|||
self::CONFIG_CREATE => 'Created config value with name "%s".',
|
||||
self::CONFIG_UPDATE => 'Updated config value with name "%s".',
|
||||
self::CONFIG_DELETE => 'Deleted config value with name "%s".',
|
||||
|
||||
self::EMOTICON_CREATE => 'Created emoticon #%s.',
|
||||
self::EMOTICON_EDIT => 'Edited emoticon #%s.',
|
||||
self::EMOTICON_DELETE => 'Deleted emoticon #%s.',
|
||||
self::EMOTICON_ORDER => 'Changed order of emoticon #%s.',
|
||||
self::EMOTICON_ALIAS => 'Added alias "%2$s" to emoticon #%1$s.',
|
||||
];
|
||||
|
||||
// Database fields
|
||||
|
|
88
src/Changelog/ChangeInfo.php
Normal file
88
src/Changelog/ChangeInfo.php
Normal file
|
@ -0,0 +1,88 @@
|
|||
<?php
|
||||
namespace Misuzu\Changelog;
|
||||
|
||||
use Index\DateTime;
|
||||
use Index\Data\IDbResult;
|
||||
|
||||
class ChangeInfo {
|
||||
private string $id;
|
||||
private ?string $userId;
|
||||
private int $action;
|
||||
private int $created;
|
||||
private string $summary;
|
||||
private string $body;
|
||||
private array $tags;
|
||||
|
||||
public function __construct(IDbResult $result, array $tags = []) {
|
||||
$this->id = (string)$result->getInteger(0);
|
||||
$this->userId = $result->isNull(1) ? null : (string)$result->getInteger(1);
|
||||
$this->action = $result->getInteger(2);
|
||||
$this->created = $result->getInteger(3);
|
||||
$this->summary = $result->getString(4);
|
||||
$this->body = $result->getString(5);
|
||||
$this->tags = $tags;
|
||||
}
|
||||
|
||||
public function getId(): string {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getUserId(): ?string {
|
||||
return $this->userId;
|
||||
}
|
||||
|
||||
public function getActionId(): int {
|
||||
return $this->action;
|
||||
}
|
||||
|
||||
public function getAction(): string {
|
||||
return Changelog::convertFromActionId($this->action);
|
||||
}
|
||||
|
||||
public function getActionText(): string {
|
||||
return Changelog::actionText($this->action);
|
||||
}
|
||||
|
||||
public function getCreatedTime(): int {
|
||||
return $this->created;
|
||||
}
|
||||
|
||||
public function getCreatedAt(): DateTime {
|
||||
return DateTime::fromUnixTimeSeconds($this->created);
|
||||
}
|
||||
|
||||
public function getDate(): string {
|
||||
return gmdate('Y-m-d', $this->created);
|
||||
}
|
||||
|
||||
public function getSummary(): string {
|
||||
return $this->summary;
|
||||
}
|
||||
|
||||
public function hasBody(): bool {
|
||||
return !empty($this->body);
|
||||
}
|
||||
|
||||
public function getBody(): string {
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
public function getCommentsCategoryName(): string {
|
||||
return sprintf('changelog-date-%s', $this->getDate());
|
||||
}
|
||||
|
||||
public function getTags(): array {
|
||||
return $this->tags;
|
||||
}
|
||||
|
||||
public function getTagIds(): array {
|
||||
$ids = [];
|
||||
foreach($this->tags as $tagInfo)
|
||||
$ids[] = $tagInfo->getId();
|
||||
return $ids;
|
||||
}
|
||||
|
||||
public function hasTag(ChangeTagInfo|string $infoOrId): bool {
|
||||
return in_array($infoOrId, $this->tags);
|
||||
}
|
||||
}
|
64
src/Changelog/ChangeTagInfo.php
Normal file
64
src/Changelog/ChangeTagInfo.php
Normal file
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
namespace Misuzu\Changelog;
|
||||
|
||||
use Stringable;
|
||||
use Index\DateTime;
|
||||
use Index\Data\IDbResult;
|
||||
|
||||
class ChangeTagInfo implements Stringable {
|
||||
private string $id;
|
||||
private string $name;
|
||||
private string $description;
|
||||
private int $created;
|
||||
private int $archived;
|
||||
private int $changes;
|
||||
|
||||
public function __construct(IDbResult $result) {
|
||||
$this->id = (string)$result->getInteger(0);
|
||||
$this->name = $result->getString(1);
|
||||
$this->description = $result->getString(2);
|
||||
$this->created = $result->getInteger(3);
|
||||
$this->archived = $result->getInteger(4);
|
||||
$this->changes = $result->getInteger(5);
|
||||
}
|
||||
|
||||
public function getId(): string {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getDescription(): string {
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
public function getCreatedTime(): int {
|
||||
return $this->created;
|
||||
}
|
||||
|
||||
public function getCreatedAt(): DateTime {
|
||||
return DateTime::fromUnixTimeSeconds($this->created);
|
||||
}
|
||||
|
||||
public function getArchivedTime(): int {
|
||||
return $this->archived;
|
||||
}
|
||||
|
||||
public function getArchivedAt(): DateTime {
|
||||
return DateTime::fromUnixTimeSeconds($this->created);
|
||||
}
|
||||
|
||||
public function isArchived(): bool {
|
||||
return $this->archived > 0;
|
||||
}
|
||||
|
||||
public function getChangesCount(): int {
|
||||
return $this->changes;
|
||||
}
|
||||
|
||||
public function __toString(): string {
|
||||
return $this->id;
|
||||
}
|
||||
}
|
426
src/Changelog/Changelog.php
Normal file
426
src/Changelog/Changelog.php
Normal file
|
@ -0,0 +1,426 @@
|
|||
<?php
|
||||
namespace Misuzu\Changelog;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use Index\DateTime;
|
||||
use Index\Data\IDbConnection;
|
||||
use Index\Data\IDbResult;
|
||||
use Misuzu\DbStatementCache;
|
||||
use Misuzu\Pagination;
|
||||
use Misuzu\Users\User;
|
||||
|
||||
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(
|
||||
User|string|null $userInfo = null,
|
||||
DateTime|int|null $dateTime = null,
|
||||
?array $tags = null
|
||||
): int {
|
||||
if($userInfo instanceof User)
|
||||
$userInfo = (string)$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) {
|
||||
$query .= (++$args > 1 ? ' AND' : ' WHERE');
|
||||
$query .= ' 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))',
|
||||
implode(', ', array_fill(0, count($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,
|
||||
User|string|null $userInfo = null,
|
||||
DateTime|int|null $dateTime = null,
|
||||
?array $tags = null,
|
||||
?Pagination $pagination = null
|
||||
): array {
|
||||
if($userInfo instanceof User)
|
||||
$userInfo = (string)$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) {
|
||||
$query .= (++$args > 1 ? ' AND' : ' WHERE');
|
||||
$query .= ' 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))',
|
||||
implode(', ', array_fill(0, count($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 = '',
|
||||
User|string|null $userInfo = null,
|
||||
DateTime|int|null $createdAt = null
|
||||
): ChangeInfo {
|
||||
if(is_string($action))
|
||||
$action = self::convertToActionId($action);
|
||||
if($userInfo instanceof User)
|
||||
$userInfo = (string)$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,
|
||||
User|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 User)
|
||||
$userInfo = (string)$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();
|
||||
}
|
||||
}
|
|
@ -1,19 +1,6 @@
|
|||
<?php
|
||||
namespace Misuzu\Changelog;
|
||||
|
||||
use UnexpectedValueException;
|
||||
use Misuzu\DB;
|
||||
use Misuzu\Memoizer;
|
||||
use Misuzu\Pagination;
|
||||
use Misuzu\Comments\CommentsCategory;
|
||||
use Misuzu\Comments\CommentsCategoryNotFoundException;
|
||||
use Misuzu\Parsers\Parser;
|
||||
use Misuzu\Users\User;
|
||||
use Misuzu\Users\UserNotFoundException;
|
||||
|
||||
class ChangelogChangeException extends ChangelogException {}
|
||||
class ChangelogChangeNotFoundException extends ChangelogChangeException {}
|
||||
|
||||
class ChangelogChange {
|
||||
public const ACTION_UNKNOWN = 0;
|
||||
public const ACTION_ADD = 1;
|
||||
|
@ -22,247 +9,4 @@ class ChangelogChange {
|
|||
public const ACTION_FIX = 4;
|
||||
public const ACTION_IMPORT = 5;
|
||||
public const ACTION_REVERT = 6;
|
||||
|
||||
private const ACTION_STRINGS = [
|
||||
self::ACTION_UNKNOWN => ['unknown', 'Changed'],
|
||||
self::ACTION_ADD => ['add', 'Added'],
|
||||
self::ACTION_REMOVE => ['remove', 'Removed'],
|
||||
self::ACTION_UPDATE => ['update', 'Updated'],
|
||||
self::ACTION_FIX => ['fix', 'Fixed'],
|
||||
self::ACTION_IMPORT => ['import', 'Imported'],
|
||||
self::ACTION_REVERT => ['revert', 'Reverted'],
|
||||
];
|
||||
|
||||
public const DEFAULT_DATE = '0000-00-00';
|
||||
|
||||
// Database fields
|
||||
private $change_id = -1;
|
||||
private $user_id = null;
|
||||
private $change_action = null; // defaults null apparently, probably a previous oversight
|
||||
private $change_created = null;
|
||||
private $change_log = '';
|
||||
private $change_text = '';
|
||||
|
||||
private $user = null;
|
||||
private $userLookedUp = false;
|
||||
private $comments = null;
|
||||
private $tags = null;
|
||||
private $tagRelations = null;
|
||||
|
||||
public const TABLE = 'changelog_changes';
|
||||
private const QUERY_SELECT = 'SELECT %1$s FROM `' . DB::PREFIX . self::TABLE . '` AS '. self::TABLE;
|
||||
private const SELECT = '%1$s.`change_id`, %1$s.`user_id`, %1$s.`change_action`, %1$s.`change_log`, %1$s.`change_text`'
|
||||
. ', UNIX_TIMESTAMP(%1$s.`change_created`) AS `change_created`';
|
||||
|
||||
public function getId(): int {
|
||||
return $this->change_id < 1 ? -1 : $this->change_id;
|
||||
}
|
||||
|
||||
public function getUserId(): int {
|
||||
return $this->user_id < 1 ? -1 : $this->user_id;
|
||||
}
|
||||
public function setUserId(?int $userId): self {
|
||||
$this->user_id = $userId;
|
||||
$this->userLookedUp = false;
|
||||
$this->user = null;
|
||||
return $this;
|
||||
}
|
||||
public function getUser(): ?User {
|
||||
if(!$this->userLookedUp && ($userId = $this->getUserId()) > 0) {
|
||||
$this->userLookedUp = true;
|
||||
try {
|
||||
$this->user = User::byId($userId);
|
||||
} catch(UserNotFoundException $ex) {}
|
||||
}
|
||||
return $this->user;
|
||||
}
|
||||
public function setUser(?User $user): self {
|
||||
$this->user_id = $user === null ? null : $user->getId();
|
||||
$this->userLookedUp = true;
|
||||
$this->user = $user;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAction(): int {
|
||||
return $this->change_action ?? self::ACTION_UNKNOWN;
|
||||
}
|
||||
public function setAction(int $actionId): self {
|
||||
$this->change_action = $actionId;
|
||||
return $this;
|
||||
}
|
||||
private function getActionInfo(): array {
|
||||
return self::ACTION_STRINGS[$this->getAction()] ?? self::ACTION_STRINGS[self::ACTION_UNKNOWN];
|
||||
}
|
||||
public function getActionClass(): string {
|
||||
return $this->getActionInfo()[0];
|
||||
}
|
||||
public function getActionString(): string {
|
||||
return $this->getActionInfo()[1];
|
||||
}
|
||||
|
||||
public function getCreatedTime(): int {
|
||||
return $this->change_created ?? -1;
|
||||
}
|
||||
public function getDate(): string {
|
||||
return ($time = $this->getCreatedTime()) < 0 ? self::DEFAULT_DATE : gmdate('Y-m-d', $time);
|
||||
}
|
||||
|
||||
public function getHeader(): string {
|
||||
return $this->change_log;
|
||||
}
|
||||
public function setHeader(string $header): self {
|
||||
$this->change_log = $header;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getBody(): string {
|
||||
return $this->change_text ?? '';
|
||||
}
|
||||
public function setBody(string $body): self {
|
||||
$this->change_text = $body;
|
||||
return $this;
|
||||
}
|
||||
public function hasBody(): bool {
|
||||
return !empty($this->change_text);
|
||||
}
|
||||
public function getParsedBody(): string {
|
||||
return Parser::instance(Parser::MARKDOWN)->parseText($this->getBody());
|
||||
}
|
||||
|
||||
public function getCommentsCategoryName(): ?string {
|
||||
return ($date = $this->getDate()) === self::DEFAULT_DATE ? null : sprintf('changelog-date-%s', $this->getDate());
|
||||
}
|
||||
public function hasCommentsCategory(): bool {
|
||||
return $this->getCreatedTime() >= 0;
|
||||
}
|
||||
public function getCommentsCategory(): CommentsCategory {
|
||||
if($this->comments === null) {
|
||||
$categoryName = $this->getCommentsCategoryName();
|
||||
|
||||
if(empty($categoryName))
|
||||
throw new UnexpectedValueException('Change comments category name is empty.');
|
||||
|
||||
try {
|
||||
$this->comments = CommentsCategory::byName($categoryName);
|
||||
} catch(CommentsCategoryNotFoundException $ex) {
|
||||
$this->comments = new CommentsCategory($categoryName);
|
||||
$this->comments->save();
|
||||
}
|
||||
}
|
||||
return $this->comments;
|
||||
}
|
||||
|
||||
public function getTags(): array {
|
||||
if($this->tags === null)
|
||||
$this->tags = ChangelogTag::byChange($this);
|
||||
return $this->tags;
|
||||
}
|
||||
public function getTagRelations(): array {
|
||||
if($this->tagRelations === null)
|
||||
$this->tagRelations = ChangelogChangeTag::byChange($this);
|
||||
return $this->tagRelations;
|
||||
}
|
||||
public function setTags(array $tags): self {
|
||||
ChangelogChangeTag::purgeChange($this);
|
||||
foreach($tags as $tag)
|
||||
if($tag instanceof ChangelogTag)
|
||||
ChangelogChangeTag::create($this, $tag);
|
||||
$this->tags = $tags;
|
||||
$this->tagRelations = null;
|
||||
return $this;
|
||||
}
|
||||
public function hasTag(ChangelogTag $other): bool {
|
||||
foreach($this->getTags() as $tag)
|
||||
if($tag->compare($other))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public function save(): void {
|
||||
$isInsert = $this->getId() < 1;
|
||||
if($isInsert) {
|
||||
$query = 'INSERT INTO `%1$s%2$s` (`user_id`, `change_action`, `change_log`, `change_text`)'
|
||||
. ' VALUES (:user, :action, :header, :body)';
|
||||
} else {
|
||||
$query = 'UPDATE `%1$s%2$s` SET `user_id` = :user, `change_action` = :action, `change_log` = :header, `change_text` = :body'
|
||||
. ' WHERE `change_id` = :change';
|
||||
}
|
||||
|
||||
$saveChange = DB::prepare(sprintf($query, DB::PREFIX, self::TABLE))
|
||||
->bind('user', $this->user_id)
|
||||
->bind('action', $this->change_action)
|
||||
->bind('header', $this->change_log)
|
||||
->bind('body', $this->change_text);
|
||||
|
||||
if($isInsert) {
|
||||
$this->change_id = $saveChange->executeGetId();
|
||||
$this->change_created = time();
|
||||
} else {
|
||||
$saveChange->bind('change', $this->getId())
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
|
||||
private static function countQueryBase(): string {
|
||||
return sprintf(self::QUERY_SELECT, sprintf('COUNT(%s.`change_id`)', self::TABLE));
|
||||
}
|
||||
public static function countAll(?int $date = null, ?User $user = null): int {
|
||||
$countChanges = DB::prepare(
|
||||
self::countQueryBase()
|
||||
. ' WHERE 1' // this is still disgusting
|
||||
. ($date === null ? '' : ' AND DATE(`change_created`) = :date')
|
||||
. ($user === null ? '' : ' AND `user_id` = :user')
|
||||
);
|
||||
if($date !== null)
|
||||
$countChanges->bind('date', gmdate('Y-m-d', $date));
|
||||
if($user !== null)
|
||||
$countChanges->bind('user', $user->getId());
|
||||
return (int)$countChanges->fetchColumn();
|
||||
}
|
||||
|
||||
private static function memoizer(): Memoizer {
|
||||
static $memoizer = null;
|
||||
if($memoizer === null)
|
||||
$memoizer = new Memoizer;
|
||||
return $memoizer;
|
||||
}
|
||||
|
||||
private static function byQueryBase(): string {
|
||||
return sprintf(self::QUERY_SELECT, sprintf(self::SELECT, self::TABLE));
|
||||
}
|
||||
public static function byId(int $changeId): self {
|
||||
return self::memoizer()->find($changeId, function() use ($changeId) {
|
||||
$change = DB::prepare(self::byQueryBase() . ' WHERE `change_id` = :change')
|
||||
->bind('change', $changeId)
|
||||
->fetchObject(self::class);
|
||||
if(!$change)
|
||||
throw new ChangelogChangeNotFoundException;
|
||||
return $change;
|
||||
});
|
||||
}
|
||||
public static function all(?Pagination $pagination = null, ?int $date = null, ?User $user = null): array {
|
||||
$changeQuery = self::byQueryBase()
|
||||
. ' WHERE 1' // this is still disgusting
|
||||
. ($date === null ? '' : ' AND DATE(`change_created`) = :date')
|
||||
. ($user === null ? '' : ' AND `user_id` = :user')
|
||||
. ' GROUP BY `change_created`, `change_id`'
|
||||
. ' ORDER BY `change_created` DESC, `change_id` DESC';
|
||||
|
||||
if($pagination !== null)
|
||||
$changeQuery .= ' LIMIT :range OFFSET :offset';
|
||||
|
||||
$getChanges = DB::prepare($changeQuery);
|
||||
|
||||
if($date !== null)
|
||||
$getChanges->bind('date', gmdate('Y-m-d', $date));
|
||||
|
||||
if($user !== null)
|
||||
$getChanges->bind('user', $user->getId());
|
||||
|
||||
if($pagination !== null)
|
||||
$getChanges->bind('range', $pagination->getRange())
|
||||
->bind('offset', $pagination->getOffset());
|
||||
|
||||
return $getChanges->fetchObjects(self::class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,88 +0,0 @@
|
|||
<?php
|
||||
namespace Misuzu\Changelog;
|
||||
|
||||
use Misuzu\DB;
|
||||
|
||||
class ChangelogChangeTagException extends ChangelogException {}
|
||||
class ChangelogChangeTagNotFoundException extends ChangelogChangeTagException {}
|
||||
class ChangelogChangeCreationFailedException extends ChangelogChangeTagException {}
|
||||
|
||||
class ChangelogChangeTag {
|
||||
// Database fields
|
||||
private $change_id = -1;
|
||||
private $tag_id = -1;
|
||||
|
||||
private $change = null;
|
||||
private $tag = null;
|
||||
|
||||
public const TABLE = 'changelog_change_tags';
|
||||
private const QUERY_SELECT = 'SELECT %1$s FROM `' . DB::PREFIX . self::TABLE . '` AS '. self::TABLE;
|
||||
private const SELECT = '%1$s.`change_id`, %1$s.`tag_id`';
|
||||
|
||||
public function getChangeId(): int {
|
||||
return $this->change_id < 1 ? -1 : $this->change_id;
|
||||
}
|
||||
public function getChange(): ChangelogChange {
|
||||
if($this->change === null)
|
||||
$this->change = ChangelogChange::byId($this->getChangeId());
|
||||
return $this->change;
|
||||
}
|
||||
|
||||
public function getTagId(): int {
|
||||
return $this->tag_id < 1 ? -1 : $this->tag_id;
|
||||
}
|
||||
public function getTag(): ChangelogTag {
|
||||
if($this->tag === null)
|
||||
$this->tag = ChangelogTag::byId($this->getTagId());
|
||||
return $this->tag;
|
||||
}
|
||||
|
||||
public static function create(ChangelogChange $change, ChangelogTag $tag, bool $return = false): ?self {
|
||||
$createRelation = DB::prepare(
|
||||
'REPLACE INTO `' . DB::PREFIX . self::TABLE . '` (`change_id`, `tag_id`)'
|
||||
. ' VALUES (:change, :tag)'
|
||||
)->bind('change', $change->getId())->bind('tag', $tag->getId());
|
||||
|
||||
if(!$createRelation->execute())
|
||||
throw new ChangelogChangeCreationFailedException;
|
||||
if(!$return)
|
||||
return null;
|
||||
|
||||
return self::byExact($change, $tag);
|
||||
}
|
||||
|
||||
public static function purgeChange(ChangelogChange $change): void {
|
||||
DB::prepare(
|
||||
'DELETE FROM `' . DB::PREFIX . self::TABLE . '` WHERE `change_id` = :change'
|
||||
)->bind('change', $change->getId())->execute();
|
||||
}
|
||||
|
||||
private static function countQueryBase(string $column): string {
|
||||
return sprintf(self::QUERY_SELECT, sprintf('COUNT(%s.`%s`)', self::TABLE, $column));
|
||||
}
|
||||
public static function countByTag(ChangelogTag $tag): int {
|
||||
return (int)DB::prepare(
|
||||
self::countQueryBase('change_id')
|
||||
. ' WHERE `tag_id` = :tag'
|
||||
)->bind('tag', $tag->getId())->fetchColumn();
|
||||
}
|
||||
|
||||
private static function byQueryBase(): string {
|
||||
return sprintf(self::QUERY_SELECT, sprintf(self::SELECT, self::TABLE));
|
||||
}
|
||||
public static function byExact(ChangelogChange $change, ChangelogTag $tag): self {
|
||||
$tag = DB::prepare(self::byQueryBase() . ' WHERE `tag_id` = :tag')
|
||||
->bind('change', $change->getId())
|
||||
->bind('tag', $tag->getId())
|
||||
->fetchObject(self::class);
|
||||
if(!$tag)
|
||||
throw new ChangelogChangeTagNotFoundException;
|
||||
return $tag;
|
||||
}
|
||||
public static function byChange(ChangelogChange $change): array {
|
||||
return DB::prepare(
|
||||
self::byQueryBase()
|
||||
. ' WHERE `change_id` = :change'
|
||||
)->bind('change', $change->getId())->fetchObjects(self::class);
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
<?php
|
||||
namespace Misuzu\Changelog;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class ChangelogException extends RuntimeException {}
|
|
@ -1,129 +0,0 @@
|
|||
<?php
|
||||
namespace Misuzu\Changelog;
|
||||
|
||||
use Misuzu\DB;
|
||||
use Misuzu\Memoizer;
|
||||
|
||||
class ChangelogTagException extends ChangelogException {}
|
||||
class ChangelogTagNotFoundException extends ChangelogTagException {}
|
||||
|
||||
class ChangelogTag {
|
||||
// Database fields
|
||||
private $tag_id = -1;
|
||||
private $tag_name = '';
|
||||
private $tag_description = '';
|
||||
private $tag_created = null;
|
||||
private $tag_archived = null;
|
||||
|
||||
private $changeCount = -1;
|
||||
|
||||
public const TABLE = 'changelog_tags';
|
||||
private const QUERY_SELECT = 'SELECT %1$s FROM `' . DB::PREFIX . self::TABLE . '` AS '. self::TABLE;
|
||||
private const SELECT = '%1$s.`tag_id`, %1$s.`tag_name`, %1$s.`tag_description`'
|
||||
. ', UNIX_TIMESTAMP(%1$s.`tag_created`) AS `tag_created`'
|
||||
. ', UNIX_TIMESTAMP(%1$s.`tag_archived`) AS `tag_archived`';
|
||||
|
||||
public function getId(): int {
|
||||
return $this->tag_id < 1 ? -1 : $this->tag_id;
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return $this->tag_name;
|
||||
}
|
||||
public function setName(string $name): self {
|
||||
$this->tag_name = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDescription(): string {
|
||||
return $this->tag_description;
|
||||
}
|
||||
public function hasDescription(): bool {
|
||||
return !empty($this->tag_description);
|
||||
}
|
||||
public function setDescription(string $description): self {
|
||||
$this->tag_description = $description;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCreatedTime(): int {
|
||||
return $this->tag_created ?? -1;
|
||||
}
|
||||
|
||||
public function getArchivedTime(): int {
|
||||
return $this->tag_archived ?? -1;
|
||||
}
|
||||
public function isArchived(): bool {
|
||||
return $this->getArchivedTime() >= 0;
|
||||
}
|
||||
public function setArchived(bool $archived): self {
|
||||
if($this->isArchived() !== $archived)
|
||||
$this->tag_archived = $archived ? time() : null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getChangeCount(): int {
|
||||
if($this->changeCount < 0)
|
||||
$this->changeCount = ChangelogChangeTag::countByTag($this);
|
||||
return $this->changeCount;
|
||||
}
|
||||
|
||||
public function compare(ChangelogTag $other): bool {
|
||||
return $other === $this || $other->getId() === $this->getId();
|
||||
}
|
||||
|
||||
public function save(): void {
|
||||
$isInsert = $this->getId() < 1;
|
||||
if($isInsert) {
|
||||
$query = 'INSERT INTO `%1$s%2$s` (`tag_name`, `tag_description`, `tag_archived`)'
|
||||
. ' VALUES (:name, :description, FROM_UNIXTIME(:archived))';
|
||||
} else {
|
||||
$query = 'UPDATE `%1$s%2$s` SET `tag_name` = :name, `tag_description` = :description, `tag_archived` = FROM_UNIXTIME(:archived)'
|
||||
. ' WHERE `tag_id` = :tag';
|
||||
}
|
||||
|
||||
$saveTag = DB::prepare(sprintf($query, DB::PREFIX, self::TABLE))
|
||||
->bind('name', $this->tag_name)
|
||||
->bind('description', $this->tag_description)
|
||||
->bind('archived', $this->tag_archived);
|
||||
|
||||
if($isInsert) {
|
||||
$this->tag_id = $saveTag->executeGetId();
|
||||
$this->tag_created = time();
|
||||
} else {
|
||||
$saveTag->bind('tag', $this->getId())
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
|
||||
private static function memoizer(): Memoizer {
|
||||
static $memoizer = null;
|
||||
if($memoizer === null)
|
||||
$memoizer = new Memoizer;
|
||||
return $memoizer;
|
||||
}
|
||||
|
||||
private static function byQueryBase(): string {
|
||||
return sprintf(self::QUERY_SELECT, sprintf(self::SELECT, self::TABLE));
|
||||
}
|
||||
public static function byId(int $tagId): self {
|
||||
return self::memoizer()->find($tagId, function() use ($tagId) {
|
||||
$tag = DB::prepare(self::byQueryBase() . ' WHERE `tag_id` = :tag')
|
||||
->bind('tag', $tagId)
|
||||
->fetchObject(self::class);
|
||||
if(!$tag)
|
||||
throw new ChangelogTagNotFoundException;
|
||||
return $tag;
|
||||
});
|
||||
}
|
||||
public static function byChange(ChangelogChange $change): array {
|
||||
return DB::prepare(
|
||||
self::byQueryBase()
|
||||
. ' WHERE `tag_id` IN (SELECT `tag_id` FROM `' . DB::PREFIX . ChangelogChangeTag::TABLE . '` WHERE `change_id` = :change)'
|
||||
)->bind('change', $change->getId())->fetchObjects(self::class);
|
||||
}
|
||||
public static function all(): array {
|
||||
return DB::prepare(self::byQueryBase())
|
||||
->fetchObjects(self::class);
|
||||
}
|
||||
}
|
|
@ -28,7 +28,7 @@ class Emotes {
|
|||
|
||||
$result = $stmt->getResult();
|
||||
if(!$result->next())
|
||||
throw new RuntimeException('No emoticon with ID exists.');
|
||||
throw new RuntimeException('No emoticon with that ID exists.');
|
||||
|
||||
$strings = $withStrings ? $this->getEmoteStrings($emoteId) : [];
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
namespace Misuzu\Http\Handlers;
|
||||
|
||||
use Misuzu\GitInfo;
|
||||
use Misuzu\MisuzuContext;
|
||||
use Misuzu\Users\User;
|
||||
use Misuzu\Users\UserNotFoundException;
|
||||
use Misuzu\Users\Assets\StaticUserImageAsset;
|
||||
|
@ -20,9 +21,9 @@ final class AssetsHandler extends Handler {
|
|||
],
|
||||
];
|
||||
|
||||
public function __construct() {
|
||||
public function __construct(MisuzuContext $context) {
|
||||
$GLOBALS['misuzuBypassLockdown'] = true;
|
||||
parent::__construct();
|
||||
parent::__construct($context);
|
||||
}
|
||||
|
||||
private static function recurse(string $dir): string {
|
||||
|
|
|
@ -2,14 +2,13 @@
|
|||
namespace Misuzu\Http\Handlers;
|
||||
|
||||
use ErrorException;
|
||||
use RuntimeException;
|
||||
use Misuzu\Config;
|
||||
use Misuzu\Config\IConfig;
|
||||
use Misuzu\Pagination;
|
||||
use Misuzu\Template;
|
||||
use Misuzu\Changelog\ChangelogChange;
|
||||
use Misuzu\Changelog\ChangelogChangeNotFoundException;
|
||||
use Misuzu\Changelog\ChangelogTag;
|
||||
use Misuzu\Changelog\ChangelogTagNotFoundException;
|
||||
use Misuzu\Comments\CommentsCategory;
|
||||
use Misuzu\Comments\CommentsCategoryNotFoundException;
|
||||
use Misuzu\Feeds\Feed;
|
||||
use Misuzu\Feeds\FeedItem;
|
||||
use Misuzu\Feeds\AtomFeedSerializer;
|
||||
|
@ -19,11 +18,13 @@ use Misuzu\Users\UserNotFoundException;
|
|||
|
||||
class ChangelogHandler extends Handler {
|
||||
public function index($response, $request) {
|
||||
$filterDate = $request->getParam('date');
|
||||
$filterUser = $request->getParam('user', FILTER_SANITIZE_NUMBER_INT);
|
||||
//$filterTags = $request->getParam('tags');
|
||||
$filterDate = (string)$request->getParam('date');
|
||||
$filterUser = (int)$request->getParam('user', FILTER_SANITIZE_NUMBER_INT);
|
||||
$filterTags = (string)$request->getParam('tags');
|
||||
|
||||
if($filterDate !== null)
|
||||
if(empty($filterDate))
|
||||
$filterDate = null;
|
||||
else
|
||||
try {
|
||||
$dateParts = explode('-', $filterDate, 3);
|
||||
$filterDate = gmmktime(12, 0, 0, (int)$dateParts[1], (int)$dateParts[2], (int)$dateParts[0]);
|
||||
|
@ -31,57 +32,102 @@ class ChangelogHandler extends Handler {
|
|||
return 404;
|
||||
}
|
||||
|
||||
if($filterUser !== null)
|
||||
if($filterUser > 0)
|
||||
try {
|
||||
$filterUser = User::byId($filterUser);
|
||||
} catch(UserNotFoundException $ex) {
|
||||
return 404;
|
||||
}
|
||||
else
|
||||
$filterUser = null;
|
||||
|
||||
/*if($filterTags !== null) {
|
||||
$splitTags = explode(',', $filterTags);
|
||||
$filterTags = [];
|
||||
for($i = 0; $i < min(10, count($splitTags)); ++$i)
|
||||
try {
|
||||
$filterTags[] = ChangelogTag::byId($splitTags[$i]);
|
||||
} catch(ChangelogTagNotFoundException $ex) {
|
||||
return 404;
|
||||
}
|
||||
}*/
|
||||
if(empty($filterTags))
|
||||
$filterTags = null;
|
||||
else {
|
||||
$filterTags = explode(',', $filterTags);
|
||||
foreach($filterTags as &$tag)
|
||||
$tag = trim($tag);
|
||||
}
|
||||
|
||||
$count = $filterDate !== null ? -1 : ChangelogChange::countAll($filterDate, $filterUser);
|
||||
$changelog = $this->context->getChangelog();
|
||||
$count = $changelog->countAllChanges($filterUser, $filterDate, $filterTags);
|
||||
$pagination = new Pagination($count, 30);
|
||||
if(!$pagination->hasValidOffset())
|
||||
return 404;
|
||||
|
||||
$changes = ChangelogChange::all($pagination, $filterDate, $filterUser);
|
||||
if(empty($changes))
|
||||
$changeInfos = $changelog->getAllChanges(userInfo: $filterUser, dateTime: $filterDate, tags: $filterTags, pagination: $pagination);
|
||||
if(empty($changeInfos))
|
||||
return 404;
|
||||
|
||||
$changes = [];
|
||||
$userInfos = [];
|
||||
|
||||
foreach($changeInfos as $changeInfo) {
|
||||
$userId = $changeInfo->getUserId();
|
||||
|
||||
if(array_key_exists($userId, $userInfos)) {
|
||||
$userInfo = $userInfos[$userId];
|
||||
} else {
|
||||
try {
|
||||
$userInfo = User::byId($userId);
|
||||
} catch(UserNotFoundException $ex) {
|
||||
$userInfo = null;
|
||||
}
|
||||
|
||||
$userInfos[$userId] = $userInfo;
|
||||
}
|
||||
|
||||
$changes[] = [
|
||||
'change' => $changeInfo,
|
||||
'user' => $userInfo,
|
||||
];
|
||||
}
|
||||
|
||||
$response->setContent(Template::renderRaw('changelog.index', [
|
||||
'changelog_infos' => $changes,
|
||||
'changelog_date' => $filterDate,
|
||||
'changelog_user' => $filterUser,
|
||||
'changelog_tags' => $filterTags,
|
||||
'changelog_pagination' => $pagination,
|
||||
'comments_user' => User::getCurrent(),
|
||||
'comments_category' => empty($filterDate) ? null : self::getCommentsCategory($changeInfos[0]->getCommentsCategoryName()),
|
||||
]));
|
||||
}
|
||||
|
||||
public function change($response, $request, int $changeId) {
|
||||
private static function getCommentsCategory(string $categoryName): CommentsCategory {
|
||||
try {
|
||||
$changeInfo = ChangelogChange::byId($changeId);
|
||||
} catch(ChangelogChangeNotFoundException $ex) {
|
||||
$category = CommentsCategory::byName($categoryName);
|
||||
} catch(CommentsCategoryNotFoundException $ex) {
|
||||
$category = new CommentsCategory($categoryName);
|
||||
$category->save();
|
||||
}
|
||||
|
||||
return $category;
|
||||
}
|
||||
|
||||
public function change($response, $request, string $changeId) {
|
||||
try {
|
||||
$changeInfo = $this->context->getChangelog()->getChangeById($changeId, withTags: true);
|
||||
} catch(RuntimeException $ex) {
|
||||
return 404;
|
||||
}
|
||||
|
||||
try {
|
||||
$userInfo = User::byId($changeInfo->getUserId());
|
||||
} catch(UserNotFoundException $ex) {
|
||||
$userInfo = null;
|
||||
}
|
||||
|
||||
$response->setContent(Template::renderRaw('changelog.change', [
|
||||
'change_info' => $changeInfo,
|
||||
'change_user_info' => $userInfo,
|
||||
'comments_user' => User::getCurrent(),
|
||||
'comments_category' => self::getCommentsCategory($changeInfo->getCommentsCategoryName()),
|
||||
]));
|
||||
}
|
||||
|
||||
private function createFeed(string $feedMode): Feed {
|
||||
$changes = ChangelogChange::all(new Pagination(10));
|
||||
$changes = $this->context->getChangelog()->getAllChanges(pagination: new Pagination(10));
|
||||
|
||||
$feed = (new Feed)
|
||||
->setTitle(Config::get('site.name', IConfig::T_STR, 'Misuzu') . ' » Changelog')
|
||||
|
@ -94,7 +140,7 @@ class ChangelogHandler extends Handler {
|
|||
$commentsUrl = url_prefix(false) . url('changelog-change-comments', ['change' => $change->getId()]);
|
||||
|
||||
$feedItem = (new FeedItem)
|
||||
->setTitle($change->getActionString() . ': ' . $change->getHeader())
|
||||
->setTitle($change->getActionText() . ': ' . $change->getSummary())
|
||||
->setCreationDate($change->getCreatedTime())
|
||||
->setUniqueId($changeUrl)
|
||||
->setContentUrl($changeUrl)
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
<?php
|
||||
namespace Misuzu\Http\Handlers;
|
||||
|
||||
use Misuzu\MisuzuContext;
|
||||
|
||||
abstract class Handler {
|
||||
public function __construct() {
|
||||
protected MisuzuContext $context;
|
||||
|
||||
public function __construct(MisuzuContext $context) {
|
||||
\Misuzu\mszLockdown();
|
||||
$this->context = $context;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ use Misuzu\Config\IConfig;
|
|||
use Misuzu\DB;
|
||||
use Misuzu\Pagination;
|
||||
use Misuzu\Template;
|
||||
use Misuzu\Changelog\ChangelogChange;
|
||||
use Misuzu\News\NewsPost;
|
||||
use Misuzu\Users\User;
|
||||
use Misuzu\Users\UserSession;
|
||||
|
@ -115,7 +114,7 @@ final class HomeHandler extends Handler {
|
|||
. ' (SELECT COUNT(`post_id`) FROM `msz_forum_posts` WHERE `post_deleted` IS NULL) AS `count_forum_posts`'
|
||||
)->fetch();
|
||||
|
||||
$changelog = ChangelogChange::all(new Pagination(10));
|
||||
$changelog = $this->context->getChangelog()->getAllChanges(pagination: new Pagination(10));
|
||||
|
||||
$birthdays = User::byBirthdate();
|
||||
$latestUser = !empty($birthdays) ? null : User::byLatest();
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
namespace Misuzu;
|
||||
|
||||
use Misuzu\Template;
|
||||
use Misuzu\Changelog\Changelog;
|
||||
use Misuzu\Config\IConfig;
|
||||
use Misuzu\Emoticons\Emotes;
|
||||
use Misuzu\SharpChat\SharpChatRoutes;
|
||||
|
@ -23,12 +24,14 @@ class MisuzuContext {
|
|||
private Users $users;
|
||||
private HttpFx $router;
|
||||
private Emotes $emotes;
|
||||
private Changelog $changelog;
|
||||
|
||||
public function __construct(IDbConnection $dbConn, IConfig $config) {
|
||||
$this->dbConn = $dbConn;
|
||||
$this->config = $config;
|
||||
$this->users = new Users($this->dbConn);
|
||||
$this->emotes = new Emotes($this->dbConn);
|
||||
$this->changelog = new Changelog($this->dbConn);
|
||||
}
|
||||
|
||||
public function getDbConn(): IDbConnection {
|
||||
|
@ -64,6 +67,10 @@ class MisuzuContext {
|
|||
return $this->emotes;
|
||||
}
|
||||
|
||||
public function getChangelog(): Changelog {
|
||||
return $this->changelog;
|
||||
}
|
||||
|
||||
public function setUpHttp(bool $legacy = false): void {
|
||||
$this->router = new HttpFx;
|
||||
$this->router->use('/', function($response) {
|
||||
|
@ -101,36 +108,31 @@ class MisuzuContext {
|
|||
}
|
||||
|
||||
private function registerHttpRoutes(): void {
|
||||
function msz_compat_handler(string $className, string $method) {
|
||||
return function(...$args) use ($className, $method) {
|
||||
$className = "\\Misuzu\\Http\\Handlers\\{$className}Handler";
|
||||
return (new $className)->{$method}(...$args);
|
||||
};
|
||||
}
|
||||
$mszCompatHandler = fn($className, $method) => fn(...$args) => (new ("\\Misuzu\\Http\\Handlers\\{$className}Handler")($this))->{$method}(...$args);
|
||||
|
||||
$this->router->get('/', msz_compat_handler('Home', 'index'));
|
||||
$this->router->get('/', $mszCompatHandler('Home', 'index'));
|
||||
|
||||
$this->router->get('/assets/:filename', msz_compat_handler('Assets', 'serveComponent'));
|
||||
$this->router->get('/assets/avatar/:filename', msz_compat_handler('Assets', 'serveAvatar'));
|
||||
$this->router->get('/assets/profile-background/:filename', msz_compat_handler('Assets', 'serveProfileBackground'));
|
||||
$this->router->get('/assets/:filename', $mszCompatHandler('Assets', 'serveComponent'));
|
||||
$this->router->get('/assets/avatar/:filename', $mszCompatHandler('Assets', 'serveAvatar'));
|
||||
$this->router->get('/assets/profile-background/:filename', $mszCompatHandler('Assets', 'serveProfileBackground'));
|
||||
|
||||
$this->router->get('/info', msz_compat_handler('Info', 'index'));
|
||||
$this->router->get('/info/:name', msz_compat_handler('Info', 'page'));
|
||||
$this->router->get('/info/:project/:name', msz_compat_handler('Info', 'page'));
|
||||
$this->router->get('/info', $mszCompatHandler('Info', 'index'));
|
||||
$this->router->get('/info/:name', $mszCompatHandler('Info', 'page'));
|
||||
$this->router->get('/info/:project/:name', $mszCompatHandler('Info', 'page'));
|
||||
|
||||
$this->router->get('/changelog', msz_compat_handler('Changelog', 'index'));
|
||||
$this->router->get('/changelog.rss', msz_compat_handler('Changelog', 'feedRss'));
|
||||
$this->router->get('/changelog.atom', msz_compat_handler('Changelog', 'feedAtom'));
|
||||
$this->router->get('/changelog/change/:id', msz_compat_handler('Changelog', 'change'));
|
||||
$this->router->get('/changelog', $mszCompatHandler('Changelog', 'index'));
|
||||
$this->router->get('/changelog.rss', $mszCompatHandler('Changelog', 'feedRss'));
|
||||
$this->router->get('/changelog.atom', $mszCompatHandler('Changelog', 'feedAtom'));
|
||||
$this->router->get('/changelog/change/:id', $mszCompatHandler('Changelog', 'change'));
|
||||
|
||||
$this->router->get('/news', msz_compat_handler('News', 'index'));
|
||||
$this->router->get('/news.rss', msz_compat_handler('News', 'feedIndexRss'));
|
||||
$this->router->get('/news.atom', msz_compat_handler('News', 'feedIndexAtom'));
|
||||
$this->router->get('/news/:category', msz_compat_handler('News', 'viewCategory'));
|
||||
$this->router->get('/news/post/:id', msz_compat_handler('News', 'viewPost'));
|
||||
$this->router->get('/news', $mszCompatHandler('News', 'index'));
|
||||
$this->router->get('/news.rss', $mszCompatHandler('News', 'feedIndexRss'));
|
||||
$this->router->get('/news.atom', $mszCompatHandler('News', 'feedIndexAtom'));
|
||||
$this->router->get('/news/:category', $mszCompatHandler('News', 'viewCategory'));
|
||||
$this->router->get('/news/post/:id', $mszCompatHandler('News', 'viewPost'));
|
||||
|
||||
$this->router->get('/forum/mark-as-read', msz_compat_handler('Forum', 'markAsReadGET'));
|
||||
$this->router->post('/forum/mark-as-read', msz_compat_handler('Forum', 'markAsReadPOST'));
|
||||
$this->router->get('/forum/mark-as-read', $mszCompatHandler('Forum', 'markAsReadGET'));
|
||||
$this->router->post('/forum/mark-as-read', $mszCompatHandler('Forum', 'markAsReadPOST'));
|
||||
|
||||
new SharpChatRoutes($this->router, $this->config->scopeTo('sockChat'), $this->emotes);
|
||||
}
|
||||
|
|
10
src/url.php
10
src/url.php
|
@ -110,8 +110,10 @@ define('MSZ_URLS', [
|
|||
|
||||
'manage-changelog-changes' => ['/manage/changelog'],
|
||||
'manage-changelog-change' => ['/manage/changelog/change.php', ['c' => '<change>']],
|
||||
'manage-changelog-change-delete' => ['/manage/changelog/change.php', ['c' => '<change>', 'delete' => '1', 'csrf' => '{token}']],
|
||||
'manage-changelog-tags' => ['/manage/changelog/tags.php'],
|
||||
'manage-changelog-tag' => ['/manage/changelog/tag.php', ['t' => '<tag>']],
|
||||
'manage-changelog-tag-delete' => ['/manage/changelog/tag.php', ['t' => '<tag>', 'delete' => '1', 'csrf' => '{token}']],
|
||||
|
||||
'manage-news-categories' => ['/manage/news/categories.php'],
|
||||
'manage-news-category' => ['/manage/news/category.php', ['c' => '<category>']],
|
||||
|
@ -173,8 +175,12 @@ function url_redirect(string $name, array $variables = []): void {
|
|||
}
|
||||
|
||||
function url_variable(string $value, array $variables): string {
|
||||
if(str_starts_with($value, '<') && str_ends_with($value, '>'))
|
||||
return $variables[trim($value, '<>')] ?? '';
|
||||
if(str_starts_with($value, '<') && str_ends_with($value, '>')) {
|
||||
$value = $variables[trim($value, '<>')] ?? '';
|
||||
if(is_array($value))
|
||||
$value = implode(',', $value);
|
||||
return (string)$value;
|
||||
}
|
||||
|
||||
if(str_starts_with($value, '[') && str_ends_with($value, ']'))
|
||||
return '';
|
||||
|
|
|
@ -5,32 +5,32 @@
|
|||
{% set title = 'Changelog » Change #' ~ change_info.id %}
|
||||
{% set canonical_url = url('changelog-change', {'change': change_info.id}) %}
|
||||
{% set manage_link = url('manage-changelog-change', {'change': change_info.id}) %}
|
||||
{% set description = change_info.header %}
|
||||
{% set description = change_info.summary %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container changelog__log changelog__action--{{ change_info.actionClass }}">
|
||||
<div class="container changelog__log changelog__action--{{ change_info.action }}">
|
||||
<div class="changelog__log__action">
|
||||
{{ change_info.actionString }}
|
||||
{{ change_info.actionText }}
|
||||
</div>
|
||||
|
||||
<div class="changelog__log__text">
|
||||
{{ change_info.header }}
|
||||
{{ change_info.summary }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container changelog__change"{% if change_info.user is not null %} style="--accent-colour: {{ change_info.user.colour }}"{% endif %}>
|
||||
<div class="container changelog__change"{% if change_user_info is not null %} style="--accent-colour: {{ change_user_info.colour }}"{% endif %}>
|
||||
<div class="changelog__change__info">
|
||||
<div class="changelog__change__info__background"></div>
|
||||
<div class="changelog__change__info__content">
|
||||
{% if change_info.user.id|default(null) is not null %}
|
||||
{% if change_user_info.id|default(null) is not null %}
|
||||
<div class="changelog__change__user">
|
||||
<a class="changelog__change__avatar" href="{{ url('user-profile', {'user': change_info.user.id}) }}">
|
||||
{{ avatar(change_info.user.id, 60, change_info.user.username) }}
|
||||
<a class="changelog__change__avatar" href="{{ url('user-profile', {'user': change_user_info.id}) }}">
|
||||
{{ avatar(change_user_info.id, 60, change_user_info.username) }}
|
||||
</a>
|
||||
|
||||
<div class="changelog__change__user__details">
|
||||
<a class="changelog__change__username" href="{{ url('user-profile', {'user': change_info.user.id}) }}">{{ change_info.user.username }}</a>
|
||||
<a class="changelog__change__userrole" href="{{ url('user-list', {'role': change_info.user.displayRoleId}) }}">{{ change_info.user.title }}</a>
|
||||
<a class="changelog__change__username" href="{{ url('user-profile', {'user': change_user_info.id}) }}">{{ change_user_info.username }}</a>
|
||||
<a class="changelog__change__userrole" href="{{ url('user-list', {'role': change_user_info.displayRoleId}) }}">{{ change_user_info.title }}</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
@ -67,10 +67,8 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{% if change_info.hasCommentsCategory %}
|
||||
<div class="container">
|
||||
{{ container_title('<i class="fas fa-comments fa-fw"></i> Comments for ' ~ change_info.date) }}
|
||||
{{ comments_section(change_info.commentsCategory, comments_user) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="container">
|
||||
{{ container_title('<i class="fas fa-comments fa-fw"></i> Comments for ' ~ change_info.date) }}
|
||||
{{ comments_section(comments_category, comments_user) }}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -11,11 +11,22 @@
|
|||
{% set canonical_url = url('changelog-index', {
|
||||
'date': changelog_date_fmt,
|
||||
'user': changelog_user.id|default(0),
|
||||
'tags': changelog_tags,
|
||||
'page': changelog_pagination.page < 2 ? 0 : changelog_pagination.page,
|
||||
}) %}
|
||||
|
||||
{% if is_date or is_user %}
|
||||
{% set title = title ~ ' »' ~ (is_date ? ' ' ~ changelog_infos[0].date : '') ~ (is_user ? ' by ' ~ changelog_infos[0].user.username : '') %}
|
||||
{% set title = title ~ ' »' %}
|
||||
{% set first_change_info = changelog_infos[0] %}
|
||||
|
||||
{% if is_date %}
|
||||
{% set first_change_date = first_change_info.date is defined ? first_change_info.date : first_change_info.change.date %}
|
||||
{% set title = title ~ ' ' ~ first_change_date %}
|
||||
{% endif %}
|
||||
|
||||
{% if is_user %}
|
||||
{% set title = title ~ ' by ' ~ first_change_info.user.username %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% set feeds = [
|
||||
{
|
||||
|
@ -47,7 +58,7 @@
|
|||
{% if is_date %}
|
||||
<div class="container">
|
||||
{{ container_title('<i class="fas fa-comments fa-fw"></i> Comments') }}
|
||||
{{ comments_section(changelog_infos[0].commentsCategory, comments_user) }}
|
||||
{{ comments_section(comments_category, comments_user) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -4,8 +4,10 @@
|
|||
<div class="changelog__listing">
|
||||
{% if changes|length > 0 %}
|
||||
{% for change in changes %}
|
||||
{% if not hide_dates and (last_date is not defined or last_date != change.date) %}
|
||||
{% set last_date = change.date %}
|
||||
{% set change_date = change.change is defined ? change.change.date : change.date %}
|
||||
|
||||
{% if not hide_dates and (last_date is not defined or last_date != change_date) %}
|
||||
{% set last_date = change_date %}
|
||||
|
||||
<a href="{{ is_manage ? '#cd' ~ last_date : url('changelog-index', {'date': last_date}) }}" class="changelog__listing__date" id="cd{{ last_date }}">
|
||||
{{ last_date }}
|
||||
|
@ -23,6 +25,11 @@
|
|||
{% endmacro %}
|
||||
|
||||
{% macro changelog_entry(change, is_small, is_manage) %}
|
||||
{% set user = change.user %}
|
||||
{% if change.change is defined %}
|
||||
{% set change = change.change %}
|
||||
{% endif %}
|
||||
|
||||
{% set change_url = url(is_manage ? 'manage-changelog-change' : 'changelog-change', {'change': change.id}) %}
|
||||
|
||||
<div class="changelog__entry" id="cl{{ change.id }}">
|
||||
|
@ -37,22 +44,22 @@
|
|||
</a>
|
||||
{% endif %}
|
||||
|
||||
<a class="changelog__entry__action changelog__action--{{ change.actionClass }}"
|
||||
<a class="changelog__entry__action changelog__action--{{ change.action }}"
|
||||
href="{{ change_url }}"
|
||||
{% if is_small %}title="{{ change.actionString }}"{% endif %}>
|
||||
{% if is_small %}title="{{ change.actionText }}"{% endif %}>
|
||||
{% if not is_small %}
|
||||
<div class="changelog__entry__action__text">
|
||||
{{ change.actionString }}
|
||||
{{ change.actionText }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</a>
|
||||
|
||||
{% if not is_small %}
|
||||
<a class="changelog__entry__user"
|
||||
href="{{ url(is_manage ? 'manage-user' : 'user-profile', {'user': change.user.id|default(0)}) }}"
|
||||
style="--user-colour: {{ change.user.colour|default('inherit') }}">
|
||||
href="{{ url(is_manage ? 'manage-user' : 'user-profile', {'user': user.id|default(0)}) }}"
|
||||
style="--user-colour: {{ user.colour|default('inherit') }}">
|
||||
<div class="changelog__entry__user__text">
|
||||
{{ change.user.username|default('Anonymous') }}
|
||||
{{ user.username|default('Anonymous') }}
|
||||
</div>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
@ -61,13 +68,13 @@
|
|||
<div class="changelog__entry__text">
|
||||
<a class="changelog__entry__log{% if change.hasBody %} changelog__entry__log--link{% endif %}"
|
||||
{% if change.hasBody %}href="{{ change_url }}"{% endif %}>
|
||||
{{ change.header }}
|
||||
{{ change.summary }}
|
||||
</a>
|
||||
|
||||
{% if is_manage %}
|
||||
<div class="changelog__entry__tags">
|
||||
{% for tag in change.tags %}
|
||||
<a href="{{ url(is_manage ? 'manage-changelog-tag' : 'changelog-tag', {'tag': tag.id}) }}" class="changelog__entry__tag">
|
||||
<a href="{{ is_manage ? url('manage-changelog-tag', {'tag': tag.id}) : url('changelog-index', {'tags': tag.id}) }}" class="changelog__entry__tag">
|
||||
{{ tag.name }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
|
|
|
@ -2,40 +2,40 @@
|
|||
{% from 'macros.twig' import container_title %}
|
||||
{% from '_layout/input.twig' import input_csrf, input_text, input_select, input_checkbox %}
|
||||
|
||||
{% if change is not null %}
|
||||
{% set site_link = url('changelog-change', {'change': change.id}) %}
|
||||
{% if not change_new %}
|
||||
{% set site_link = url('changelog-change', {'change': change_info.id}) %}
|
||||
{% endif %}
|
||||
|
||||
{% block manage_content %}
|
||||
<div class="container">
|
||||
<form action="{{ url('manage-changelog-change', {'change': change.id|default(0)}) }}" method="post">
|
||||
<form action="{{ url('manage-changelog-change', {'change': change_info.id|default(0)}) }}" method="post">
|
||||
{{ input_csrf() }}
|
||||
|
||||
{{ container_title(change is not null ? 'Editing #' ~ change.id : 'Adding a new change') }}
|
||||
{{ container_title(change_new ? 'Adding a new change' : 'Editing #' ~ change_info.id) }}
|
||||
|
||||
<div style="display: flex; margin: 2px 5px;">
|
||||
{{ input_select('change[action]', change_actions, change.action|default(0), 'action_name', 'action_id') }}
|
||||
{{ input_text('change[log]', '', change.header|default(''), 'text', '', true, {'maxlength':255,'style':'flex-grow:1'}) }}
|
||||
{{ input_select('cl_action', change_actions, change_info.action|default('add')) }}
|
||||
{{ input_text('cl_summary', '', change_info.summary|default(''), 'text', '', true, {'maxlength': 255, 'style': 'flex-grow: 1'}) }}
|
||||
</div>
|
||||
|
||||
<label class="form__label">
|
||||
<div class="form__label__text">Text</div>
|
||||
<div class="form__label__input">
|
||||
<textarea class="input__textarea" name="change[text]" maxlength="65535">{{ change.body|default('') }}</textarea>
|
||||
<textarea class="input__textarea" name="cl_body" maxlength="65535">{{ change_info.body|default('') }}</textarea>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="form__label">
|
||||
<div class="form__label__text">Contributor Id</div>
|
||||
<div class="form__label__input">
|
||||
{{ input_text('change[user]', '', change.userId|default(current_user.id), 'number', '', false, {'min':1}) }}
|
||||
{{ input_text('cl_user', '', change_info.userId|default(current_user.id), 'number', '', false, {'min':1}) }}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="form__label">
|
||||
<div class="form__label__text">Created</div>
|
||||
<div class="form__label__input">
|
||||
{{ input_text('change[created]', '', change.createdTime|default(null)|date('Y-m-d H:i:s'), 'text', '', true) }}
|
||||
{{ input_text('cl_created', '', change_info.createdTime|default(-1)|date('Y-m-d\\TH:i'), 'datetime-local', '', true) }}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
|
@ -44,7 +44,7 @@
|
|||
<label class="manage__tag">
|
||||
<div class="manage__tag__background"></div>
|
||||
<div class="manage__tag__content">
|
||||
{{ input_checkbox('tags[]', '', change.hasTag(tag)|default(false), 'manage__tag__checkbox', tag.id) }}
|
||||
{{ input_checkbox('cl_tags[]', '', change_info.hasTag(tag)|default(0), 'manage__tag__checkbox', tag.id) }}
|
||||
<div class="manage__tag__title">
|
||||
{{ tag.name }}
|
||||
</div>
|
||||
|
@ -55,6 +55,9 @@
|
|||
|
||||
<div>
|
||||
<button class="input__button">Save</button>
|
||||
{% if not change_new %}
|
||||
<a href="{{ url('manage-changelog-change-delete', {'change': change_info.id}) }}" class="input__button input__button--destroy" onclick="return confirm('Are you sure?');">Delete</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -4,43 +4,46 @@
|
|||
|
||||
{% block manage_content %}
|
||||
<div class="container">
|
||||
<form action="{{ url('manage-changelog-tag', {'tag': edit_tag.id|default(0)}) }}" method="post">
|
||||
<form action="{{ url('manage-changelog-tag', {'tag': tag_info.id|default(0)}) }}" method="post">
|
||||
{{ input_csrf() }}
|
||||
|
||||
{{ container_title(edit_tag.id is defined ? 'Editing ' ~ edit_tag.name ~ ' (' ~ edit_tag.id ~ ')' : 'Adding a new tag') }}
|
||||
{{ container_title(tag_new ? 'Adding a new tag' : 'Editing ' ~ tag_info.name ~ ' (' ~ tag_info.id ~ ')') }}
|
||||
|
||||
<label class="form__label" style="width:100%">
|
||||
<div class="form__label__text">Name</div>
|
||||
<div class="form__label__input">
|
||||
{{ input_text('tag[name]', '', edit_tag.id is defined ? edit_tag.name : '', 'text', '', true, {'maxlength':255}) }}
|
||||
{{ input_text('ct_name', '', tag_info.id is defined ? tag_info.name : '', 'text', '', true, {'maxlength': 255}) }}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="form__label" style="width:100%">
|
||||
<div class="form__label__text">Description</div>
|
||||
<div class="form__label__input">
|
||||
<textarea class="input__textarea" name="tag[description]" maxlength="65535">{{ edit_tag.description|default('') }}</textarea>
|
||||
<textarea class="input__textarea" name="ct_desc" maxlength="65535">{{ tag_info.description|default('') }}</textarea>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="form__label">
|
||||
<div class="form__label__text">Archived</div>
|
||||
<div class="form__label__input">
|
||||
{{ input_checkbox('tag[archived]', '', edit_tag.archived|default(false)) }}
|
||||
{{ input_checkbox('ct_archive', '', tag_info.archived|default(false)) }}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
{% if edit_tag.id is defined %}
|
||||
{% if not tag_new %}
|
||||
<label class="form__label">
|
||||
<div class="form__label__text">Created</div>
|
||||
<div class="form__label__input">
|
||||
{{ input_text('', '', edit_tag.createdTime|date('r')) }}
|
||||
{{ input_text('', '', tag_info.createdTime|date('r')) }}
|
||||
</div>
|
||||
</label>
|
||||
{% endif %}
|
||||
|
||||
<div>
|
||||
<button class="input__button">Save</button>
|
||||
{% if not tag_new %}
|
||||
<a href="{{ url('manage-changelog-tag-delete', {'tag': tag_info.id}) }}" class="input__button input__button--destroy" onclick="return confirm('Are you sure?');">Delete</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<a href="{{ url('manage-changelog-tag', {'tag': tag.id}) }}" class="changelog-actions-tags__entry">
|
||||
<div class="listing__entry__content changelog-tags__content">
|
||||
<div class="changelog-tags__text">
|
||||
{{ tag.name }} ({{ tag.changeCount }})
|
||||
{{ tag.name }} ({{ tag.changesCount }}){% if tag.archived %} <strong>[ARCHIVED]</strong>{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="changelog-tags__description">
|
||||
|
|
Loading…
Reference in a new issue