2023-09-08 13:22:46 +00:00
< ? php
namespace Misuzu\Forum ;
use InvalidArgumentException ;
use RuntimeException ;
use stdClass ;
use Index\Data\DbStatementCache ;
use Index\Data\DbTools ;
use Index\Data\IDbConnection ;
use Misuzu\Pagination ;
use Misuzu\Users\UserInfo ;
class ForumTopics {
private IDbConnection $dbConn ;
private DbStatementCache $cache ;
public function __construct ( IDbConnection $dbConn ) {
$this -> dbConn = $dbConn ;
$this -> cache = new DbStatementCache ( $dbConn );
}
public function countTopics (
ForumCategoryInfo | string | array | null $categoryInfo = null ,
UserInfo | string | null $userInfo = null ,
? bool $global = null ,
? bool $deleted = null
) : int {
if ( $categoryInfo instanceof ForumCategoryInfo )
$categoryInfo = $categoryInfo -> getId ();
if ( $userInfo instanceof UserInfo )
$userInfo = $userInfo -> getId ();
$hasCategoryInfo = $categoryInfo !== null ;
$hasUserInfo = $userInfo !== null ;
$hasGlobal = $global !== null ;
$hasDeleted = $deleted !== null ;
$args = 0 ;
$query = 'SELECT COUNT(*) FROM msz_forum_topics' ;
if ( $hasCategoryInfo || $hasGlobal ) {
++ $args ;
// wow this sucks
$hasGlobalAndCategory = $hasCategoryInfo && $hasGlobal ;
$query .= ' WHERE ' ;
if ( $hasGlobalAndCategory )
$query .= '(' ;
if ( $hasCategoryInfo ) {
if ( is_array ( $categoryInfo ))
$query .= sprintf ( 'forum_id IN (%s)' , DbTools :: prepareListString ( $categoryInfo ));
else
$query .= 'forum_id = ?' ;
}
if ( $hasGlobalAndCategory )
$query .= ' OR ' ;
if ( $hasGlobal ) // not sure why you would ever set this to false, but consistency!
$query .= sprintf ( 'topic_type %s %d' , $global ? '=' : '<>' , ForumTopicInfo :: TYPE_GLOBAL );
if ( $hasGlobalAndCategory )
$query .= ')' ;
}
if ( $hasUserInfo )
$query .= sprintf ( ' %s user_id = ?' , ++ $args > 1 ? 'AND' : 'WHERE' );
if ( $hasDeleted )
$query .= sprintf ( ' %s topic_deleted %s NULL' , ++ $args > 1 ? 'AND' : 'WHERE' , $deleted ? 'IS NOT' : 'IS' );
$args = 0 ;
$stmt = $this -> cache -> get ( $query );
if ( $hasCategoryInfo ) {
if ( is_array ( $categoryInfo )) {
foreach ( $categoryInfo as $categoryInfoEntry )
$stmt -> addParameter ( ++ $args , $categoryInfoEntry instanceof ForumCategoryInfo ? $categoryInfoEntry -> getId () : ( string ) $categoryInfoEntry );
} else
$stmt -> addParameter ( ++ $args , $categoryInfo );
}
if ( $hasUserInfo )
$stmt -> addParameter ( ++ $args , $userInfo );
$stmt -> execute ();
$result = $stmt -> getResult ();
return $result -> next () ? $result -> getInteger ( 0 ) : 0 ;
}
public function getTopics (
ForumCategoryInfo | string | array | null $categoryInfo = null ,
UserInfo | string | null $userInfo = null ,
? array $searchQuery = null ,
? bool $global = null ,
? bool $deleted = null ,
? Pagination $pagination = null
2024-02-07 00:04:45 +00:00
) : iterable {
2023-09-08 13:22:46 +00:00
// remove this hack when search server
$hasSearchQuery = $searchQuery !== null ;
$hasAfterTopicId = false ;
$afterTopicId = null ;
$doSearchOrder = false ;
if ( $hasSearchQuery ) {
if ( ! empty ( $searchQuery [ 'type' ])
&& $searchQuery [ 'type' ] !== 'forum'
&& $searchQuery [ 'type' ] !== 'forum:topic' )
return [];
$deleted = false ;
$pagination = null ;
$doSearchOrder = true ;
if ( ! empty ( $searchQuery [ 'author' ]))
$userInfo = $searchQuery [ 'author' ];
if ( ! empty ( $searchQuery [ 'after' ])) {
$hasAfterTopicId = true ;
$afterTopicId = $searchQuery [ 'after' ];
}
$searchQuery = $searchQuery [ 'query_string' ];
$hasSearchQuery = ! empty ( $searchQuery );
}
if ( $categoryInfo instanceof ForumCategoryInfo )
$categoryInfo = $categoryInfo -> getId ();
if ( $userInfo instanceof UserInfo )
$userInfo = $userInfo -> getId ();
$hasCategoryInfo = $categoryInfo !== null ;
$hasUserInfo = $userInfo !== null ;
$hasGlobal = $global !== null ;
$hasDeleted = $deleted !== null ;
$hasPagination = $pagination !== null ;
$args = 0 ;
$query = 'SELECT topic_id, forum_id, user_id, topic_type, topic_title, topic_count_views, UNIX_TIMESTAMP(topic_created), UNIX_TIMESTAMP(topic_bumped), UNIX_TIMESTAMP(topic_deleted), UNIX_TIMESTAMP(topic_locked), (SELECT COUNT(*) FROM msz_forum_posts WHERE topic_id = ft.topic_id AND post_deleted IS NULL) AS topic_count_posts, (SELECT COUNT(*) FROM msz_forum_posts WHERE topic_id = ft.topic_id AND post_deleted IS NOT NULL) AS topic_count_posts_deleted FROM msz_forum_topics AS ft' ;
if ( $hasCategoryInfo || $hasGlobal ) {
++ $args ;
// wow this sucks
$hasGlobalAndCategory = $hasCategoryInfo && $hasGlobal ;
$query .= ' WHERE ' ;
if ( $hasGlobalAndCategory )
$query .= '(' ;
if ( $hasCategoryInfo ) {
if ( is_array ( $categoryInfo ))
$query .= sprintf ( 'forum_id IN (%s)' , DbTools :: prepareListString ( $categoryInfo ));
else
$query .= 'forum_id = ?' ;
}
if ( $hasGlobalAndCategory )
$query .= ' OR ' ;
if ( $hasGlobal ) // not sure why you would ever set this to false, but consistency!
$query .= sprintf ( 'topic_type %s %d' , $global ? '=' : '<>' , ForumTopicInfo :: TYPE_GLOBAL );
if ( $hasGlobalAndCategory )
$query .= ')' ;
}
if ( $hasUserInfo )
$query .= sprintf ( ' %s user_id = ?' , ++ $args > 1 ? 'AND' : 'WHERE' );
if ( $hasAfterTopicId )
$query .= sprintf ( ' %s topic_id > ?' , ++ $args > 1 ? 'AND' : 'WHERE' );
if ( $hasSearchQuery )
$query .= sprintf ( ' %s MATCH(topic_title) AGAINST (? IN NATURAL LANGUAGE MODE)' , ++ $args > 1 ? 'AND' : 'WHERE' );
if ( $hasDeleted )
$query .= sprintf ( ' %s topic_deleted %s NULL' , ++ $args > 1 ? 'AND' : 'WHERE' , $deleted ? 'IS NOT' : 'IS' );
if ( $doSearchOrder ) {
$query .= ' ORDER BY topic_id ASC LIMIT 20' ;
} else {
$query .= ' ORDER BY topic_type DESC, topic_bumped DESC' ;
if ( $hasPagination )
$query .= ' LIMIT ? OFFSET ?' ;
}
$args = 0 ;
$stmt = $this -> cache -> get ( $query );
if ( $hasCategoryInfo ) {
if ( is_array ( $categoryInfo )) {
foreach ( $categoryInfo as $categoryInfoEntry )
$stmt -> addParameter ( ++ $args , $categoryInfoEntry instanceof ForumCategoryInfo ? $categoryInfoEntry -> getId () : ( string ) $categoryInfoEntry );
} else
$stmt -> addParameter ( ++ $args , $categoryInfo );
}
if ( $hasUserInfo )
$stmt -> addParameter ( ++ $args , $userInfo );
if ( $hasAfterTopicId )
$stmt -> addParameter ( ++ $args , $afterTopicId );
if ( $hasSearchQuery )
$stmt -> addParameter ( ++ $args , $searchQuery );
if ( $hasPagination ) {
$stmt -> addParameter ( ++ $args , $pagination -> getRange ());
$stmt -> addParameter ( ++ $args , $pagination -> getOffset ());
}
$stmt -> execute ();
2024-02-07 00:04:45 +00:00
return $stmt -> getResult () -> getIterator ( ForumTopicInfo :: fromResult ( ... ));
2023-09-08 13:22:46 +00:00
}
public function getTopic (
? string $topicId = null ,
ForumPostInfo | string | null $postInfo = null
) : ForumTopicInfo {
$hasTopicId = $topicId !== null ;
$hasPostInfo = $postInfo !== null ;
if ( ! $hasTopicId && ! $hasPostInfo )
throw new InvalidArgumentException ( 'At least one argument must be specified.' );
if ( $hasTopicId && $hasPostInfo )
throw new InvalidArgumentException ( 'Only one argument may be specified.' );
$value = null ;
$query = 'SELECT topic_id, forum_id, user_id, topic_type, topic_title, topic_count_views, UNIX_TIMESTAMP(topic_created), UNIX_TIMESTAMP(topic_bumped), UNIX_TIMESTAMP(topic_deleted), UNIX_TIMESTAMP(topic_locked), (SELECT COUNT(*) FROM msz_forum_posts WHERE topic_id = ft.topic_id AND post_deleted IS NULL) AS topic_count_posts, (SELECT COUNT(*) FROM msz_forum_posts WHERE topic_id = ft.topic_id AND post_deleted IS NOT NULL) AS topic_count_posts_deleted FROM msz_forum_topics AS ft' ;
if ( $hasTopicId ) {
$query .= ' WHERE topic_id = ?' ;
$value = $topicId ;
}
if ( $hasPostInfo ) {
if ( $postInfo instanceof ForumPostInfo ) {
$query .= ' WHERE topic_id = ?' ;
$value = $postInfo -> getTopicId ();
} else {
$query .= ' WHERE topic_id = (SELECT topic_id FROM msz_forum_posts WHERE post_id = ?)' ;
$value = $postInfo ;
}
}
$stmt = $this -> cache -> get ( $query );
$stmt -> addParameter ( 1 , $value );
$stmt -> execute ();
$result = $stmt -> getResult ();
if ( ! $result -> next ())
throw new RuntimeException ( 'Forum topic not found.' );
2024-02-07 00:04:45 +00:00
return ForumTopicInfo :: fromResult ( $result );
2023-09-08 13:22:46 +00:00
}
public function createTopic (
ForumCategoryInfo | string $categoryInfo ,
UserInfo | string | null $userInfo ,
string $title ,
string | int $type = ForumTopicInfo :: TYPE_DISCUSSION
) : ForumTopicInfo {
if ( is_string ( $type )) {
if ( ! array_key_exists ( $type , ForumTopicInfo :: TYPE_ALIASES ))
throw new InvalidArgumentException ( '$type is not a valid alias.' );
$type = ForumTopicInfo :: TYPE_ALIASES [ $type ];
}
if ( $categoryInfo instanceof ForumCategoryInfo )
$categoryInfo = $categoryInfo -> getId ();
if ( $userInfo instanceof UserInfo )
$userInfo = $userInfo -> getId ();
$stmt = $this -> cache -> get ( 'INSERT INTO msz_forum_topics (forum_id, user_id, topic_type, topic_title) VALUES (?, ?, ?, ?)' );
$stmt -> addParameter ( 1 , $categoryInfo );
$stmt -> addParameter ( 2 , $userInfo );
$stmt -> addParameter ( 3 , $type );
$stmt -> addParameter ( 4 , $title );
$stmt -> execute ();
return $this -> getTopic ( topicId : ( string ) $this -> dbConn -> getLastInsertId ());
}
public function updateTopic (
ForumTopicInfo | string $topicInfo ,
? string $title = null ,
string | int | null $type = null
) : void {
if ( $topicInfo instanceof ForumTopicInfo )
$topicInfo = $topicInfo -> getId ();
$fields = [];
$values = [];
if ( $title !== null ) {
$fields [] = 'topic_title = ?' ;
$values [] = $title ;
}
if ( $type !== null ) {
if ( is_string ( $type )) {
if ( ! array_key_exists ( $type , ForumTopicInfo :: TYPE_ALIASES ))
throw new InvalidArgumentException ( '$type is not a valid type alias.' );
$type = ForumTopicInfo :: TYPE_ALIASES [ $type ];
}
$fields [] = 'topic_type = ?' ;
$values [] = $type ;
}
if ( empty ( $fields ))
return ;
$args = 0 ;
$stmt = $this -> cache -> get ( sprintf ( 'UPDATE msz_forum_topics SET %s WHERE topic_id = ?' , implode ( ', ' , $fields )));
foreach ( $values as $value )
$stmt -> addParameter ( ++ $args , $value );
$stmt -> addParameter ( ++ $args , $topicInfo );
$stmt -> execute ();
}
public function incrementTopicViews ( ForumTopicInfo | string $topicInfo ) : void {
if ( $topicInfo instanceof ForumTopicInfo )
$topicInfo = $topicInfo -> getId ();
$stmt = $this -> cache -> get ( 'UPDATE msz_forum_topics SET topic_count_views = topic_count_views + 1 WHERE topic_id = ?' );
$stmt -> addParameter ( 1 , $topicInfo );
$stmt -> execute ();
}
public function bumpTopic ( ForumTopicInfo | string $topicInfo ) : void {
if ( $topicInfo instanceof ForumTopicInfo )
$topicInfo = $topicInfo -> getId ();
$stmt = $this -> cache -> get ( 'UPDATE msz_forum_topics SET topic_bumped = NOW() WHERE topic_id = ?' );
$stmt -> addParameter ( 1 , $topicInfo );
$stmt -> execute ();
}
public function lockTopic ( ForumTopicInfo | string $topicInfo ) : void {
if ( $topicInfo instanceof ForumTopicInfo )
$topicInfo = $topicInfo -> getId ();
$stmt = $this -> cache -> get ( 'UPDATE msz_forum_topics SET topic_locked = NOW() WHERE topic_id = ?' );
$stmt -> addParameter ( 1 , $topicInfo );
$stmt -> execute ();
}
public function unlockTopic ( ForumTopicInfo | string $topicInfo ) : void {
if ( $topicInfo instanceof ForumTopicInfo )
$topicInfo = $topicInfo -> getId ();
$stmt = $this -> cache -> get ( 'UPDATE msz_forum_topics SET topic_locked = NULL WHERE topic_id = ?' );
$stmt -> addParameter ( 1 , $topicInfo );
$stmt -> execute ();
}
public function deleteTopic ( ForumTopicInfo | string $topicInfo ) : void {
if ( $topicInfo instanceof ForumTopicInfo )
$topicInfo = $topicInfo -> getId ();
$stmt = $this -> cache -> get ( 'UPDATE msz_forum_topics SET topic_deleted = COALESCE(topic_deleted, NOW()) WHERE topic_id = ? AND topic_deleted IS NULL' );
$stmt -> addParameter ( 1 , $topicInfo );
$stmt -> execute ();
$stmt = $this -> cache -> get ( 'UPDATE msz_forum_posts AS fp SET post_deleted = (SELECT topic_deleted FROM msz_forum_topics WHERE topic_id = fp.topic_id) WHERE topic_id = ? AND post_deleted = NULL' );
$stmt -> addParameter ( 1 , $topicInfo );
$stmt -> execute ();
}
public function restoreTopic ( ForumTopicInfo | string $topicInfo ) : void {
if ( $topicInfo instanceof ForumTopicInfo )
$topicInfo = $topicInfo -> getId ();
$stmt = $this -> cache -> get ( 'UPDATE msz_forum_posts AS fp SET post_deleted = NULL WHERE topic_id = ? AND post_deleted = (SELECT topic_deleted FROM msz_forum_topics WHERE topic_id = fp.topic_id)' );
$stmt -> addParameter ( 1 , $topicInfo );
$stmt -> execute ();
$stmt = $this -> cache -> get ( 'UPDATE msz_forum_topics SET topic_deleted = NULL WHERE topic_id = ?' );
$stmt -> addParameter ( 1 , $topicInfo );
$stmt -> execute ();
}
public function nukeTopic ( ForumTopicInfo | string $topicInfo ) : void {
if ( $topicInfo instanceof ForumTopicInfo )
$topicInfo = $topicInfo -> getId ();
$stmt = $this -> cache -> get ( 'DELETE FROM msz_forum_topics WHERE topic_id = ?' );
$stmt -> addParameter ( 1 , $topicInfo );
$stmt -> execute ();
}
public function checkTopicParticipated (
ForumTopicInfo | string $topicInfo ,
UserInfo | string | null $userInfo
) : bool {
if ( $userInfo === null )
return false ;
if ( $topicInfo instanceof ForumTopicInfo )
$topicInfo = $topicInfo -> getId ();
if ( $userInfo instanceof UserInfo )
$userInfo = $userInfo -> getId ();
$stmt = $this -> cache -> get ( 'SELECT COUNT(*) FROM msz_forum_posts WHERE topic_id = ? AND user_id = ?' );
$stmt -> addParameter ( 1 , $topicInfo );
$stmt -> addParameter ( 2 , $userInfo );
$stmt -> execute ();
$result = $stmt -> getResult ();
return $result -> next () && $result -> getInteger ( 0 ) > 0 ;
}
public function checkTopicUnread (
ForumTopicInfo | string $topicInfo ,
UserInfo | string | null $userInfo
) : bool {
if ( $userInfo === null )
return false ;
$topicInfoIsInstance = $topicInfo instanceof ForumTopicInfo ;
if ( $topicInfoIsInstance && ! $topicInfo -> isActive ())
return false ;
$query = 'SELECT UNIX_TIMESTAMP(track_last_read) FROM msz_forum_topics_track AS ftt WHERE user_id = ? AND topic_id = ?' ;
if ( ! $topicInfoIsInstance )
$query .= ' AND track_last_read = (SELECT topic_bumped FROM msz_forum_topics WHERE topic_id = ftt.topic_id AND topic_bumped >= NOW() - INTERVAL 1 MONTH)' ;
$stmt = $this -> cache -> get ( $query );
$stmt -> addParameter ( 1 , $userInfo instanceof UserInfo ? $userInfo -> getId () : $userInfo );
$stmt -> addParameter ( 2 , $topicInfoIsInstance ? $topicInfo -> getId () : $topicInfo );
$stmt -> execute ();
$result = $stmt -> getResult ();
// user has never read this topic, return unread
if ( ! $result -> next ())
return true ;
return $result -> getInteger ( 0 ) < $topicInfo -> getBumpedTime ();
}
public function getMostActiveTopicInfo (
UserInfo | string $userInfo ,
array $exceptCategoryInfos = [],
array $exceptTopicInfos = [],
? bool $deleted = null
) : object {
if ( $userInfo instanceof UserInfo )
$userInfo = $userInfo -> getId ();
$hasExceptCategoryInfos = ! empty ( $exceptCategoryInfos );
$hasExceptTopicInfos = ! empty ( $exceptTopicInfos );
$hasDeleted = $deleted !== null ;
$query = 'SELECT topic_id, forum_id, COUNT(*) AS post_count FROM msz_forum_posts WHERE user_id = ?' ;
if ( $hasDeleted )
$query .= sprintf ( ' AND post_deleted %s NULL' , $deleted ? 'IS NOT' : 'IS' );
if ( $hasExceptCategoryInfos )
$query .= sprintf ( ' AND forum_id NOT IN (%s)' , DbTools :: prepareListString ( $exceptCategoryInfos ));
if ( $hasExceptTopicInfos )
$query .= sprintf ( ' AND topic_id NOT IN (%s)' , DbTools :: prepareListString ( $exceptTopicInfos ));
$query .= ' GROUP BY topic_id ORDER BY post_count DESC LIMIT 1' ;
$args = 0 ;
$stmt = $this -> cache -> get ( $query );
$stmt -> addParameter ( ++ $args , $userInfo );
foreach ( $exceptCategoryInfos as $categoryInfo ) {
if ( $categoryInfo instanceof ForumCategoryInfo )
$stmt -> addParameter ( ++ $args , $categoryInfo -> getId ());
elseif ( is_string ( $categoryInfo ) || is_int ( $categoryInfo ))
$stmt -> addParameter ( ++ $args , ( string ) $categoryInfo );
else
throw new InvalidArgumentException ( '$exceptCategoryInfos may only contain string ids or instances of ForumCategoryInfo.' );
}
foreach ( $exceptTopicInfos as $topicInfo ) {
if ( $topicInfo instanceof ForumTopicInfo )
$stmt -> addParameter ( ++ $args , $topicInfo -> getId ());
elseif ( is_string ( $topicInfo ) || is_int ( $topicInfo ))
$stmt -> addParameter ( ++ $args , ( string ) $topicInfo );
else
throw new InvalidArgumentException ( '$exceptTopicInfos may only contain string ids or instances of ForumTopicInfo.' );
}
$stmt -> execute ();
$result = $stmt -> getResult ();
$info = new stdClass ;
$info -> success = $result -> next ();
if ( $info -> success ) {
$info -> topicId = $result -> getString ( 0 );
$info -> categoryId = $result -> getString ( 1 );
$info -> postCount = $result -> getInteger ( 2 );
}
return $info ;
}
public function checkUserHasReadTopic (
UserInfo | string | null $userInfo ,
ForumTopicInfo | string $topicInfo
) : bool {
// this method is primarily used to check if we should increment the view count
// guests shouldn't increment it so we just
if ( $userInfo === null )
return true ;
$stmt = $this -> cache -> get ( 'SELECT COUNT(*) FROM msz_forum_topics_track WHERE topic_id = ? AND user_id = ?' );
$stmt -> addParameter ( 1 , $topicInfo instanceof ForumTopicInfo ? $topicInfo -> getId () : $topicInfo );
$stmt -> addParameter ( 2 , $userInfo instanceof UserInfo ? $userInfo -> getId () : $userInfo );
$stmt -> execute ();
$result = $stmt -> getResult ();
return $result -> next () && $result -> getInteger ( 0 ) > 0 ;
}
public function updateUserReadTopic (
UserInfo | string | null $userInfo ,
ForumTopicInfo | string $topicInfo ,
ForumCategoryInfo | string | null $categoryInfo = null
) : void {
if ( $userInfo === null )
return ;
if ( $userInfo instanceof UserInfo )
$userInfo = $userInfo -> getId ();
if ( $topicInfo instanceof ForumTopicInfo ) {
$categoryInfo = $topicInfo -> getCategoryId ();
$topicInfo = $topicInfo -> getId ();
} else {
if ( $categoryInfo === null )
throw new InvalidArgumentException ( '$categoryInfo must be specified if $topicInfo is not an instance of ForumTopicInfo.' );
if ( $categoryInfo instanceof ForumCategoryInfo )
$categoryInfo = $categoryInfo -> getId ();
}
$stmt = $this -> cache -> get ( 'REPLACE INTO msz_forum_topics_track (user_id, topic_id, forum_id, track_last_read) VALUES (?, ?, ?, NOW())' );
$stmt -> addParameter ( 1 , $userInfo );
$stmt -> addParameter ( 2 , $topicInfo );
$stmt -> addParameter ( 3 , $categoryInfo );
$stmt -> execute ();
}
}