2023-07-15 02:05:49 +00:00
< ? 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 ) {
2023-07-18 22:24:23 +00:00
++ $args ;
$query .= ' WHERE user_id = ?' ;
2023-07-15 02:05:49 +00:00
}
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 ) {
2023-07-18 22:24:23 +00:00
++ $args ;
$query .= ' WHERE user_id = ?' ;
2023-07-15 02:05:49 +00:00
}
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 ();
}
}