bizley/yii2-podium

View on GitHub
src/models/Thread.php

Summary

Maintainability
F
5 days
Test Coverage
File `Thread.php` has 491 lines of code (exceeds 250 allowed). Consider refactoring.
<?php
 
namespace bizley\podium\models;
 
use bizley\podium\log\Log;
use bizley\podium\models\db\ThreadActiveRecord;
use bizley\podium\Podium;
use bizley\podium\PodiumCache;
use bizley\podium\rbac\Rbac;
use cebe\markdown\GithubMarkdown;
use Exception;
use Yii;
use yii\data\ActiveDataProvider;
use yii\db\Expression;
 
/**
* Thread model
*
* @author Paweł Bizley Brzozowski <pawel@positive.codes>
* @since 0.1
*
* @property string $parsedPost
*/
The class Thread has an overall complexity of 104 which is very high. The configured complexity threshold is 50.
The class Thread has 11 public methods. Consider refactoring Thread to keep number of public methods under 10.
The class Thread has a coupling between objects value of 17. Consider to reduce the number of dependencies under 13.
class Thread extends ThreadActiveRecord
{
/**
* Color classes.
*/
const CLASS_DEFAULT = 'default';
const CLASS_EDITED = 'warning';
const CLASS_NEW = 'success';
 
/**
* Icon classes.
*/
const ICON_HOT = 'fire';
const ICON_LOCKED = 'lock';
const ICON_NEW = 'leaf';
const ICON_NO_NEW = 'comment';
const ICON_PINNED = 'pushpin';
 
/**
* Returns first post to see.
* @return Post
*/
public function firstToSee()
{
if ($this->firstNewNotSeen) {
return $this->firstNewNotSeen;
}
if ($this->firstEditedNotSeen) {
return $this->firstEditedNotSeen;
}
return $this->latest;
}
 
/**
* Searches for thread.
* @param int $forumId
* @return ActiveDataProvider
*/
Method `search` has 44 lines of code (exceeds 25 allowed). Consider refactoring.
Function `search` has a Cognitive Complexity of 14 (exceeds 5 allowed). Consider refactoring.
The method search() has an NPath complexity of 866. The configured NPath complexity threshold is 200.
The method search() has a Cyclomatic Complexity of 15. The configured cyclomatic complexity threshold is 10.
public function search($forumId = null, $filters = null)
{
$query = static::find();
if ($forumId) {
$query->where(['forum_id' => (int)$forumId]);
}
if (!empty($filters)) {
$loggedId = User::loggedId();
if (!empty($filters['pin']) && $filters['pin'] == 1) {
$query->andWhere(['pinned' => 1]);
}
if (!empty($filters['lock']) && $filters['lock'] == 1) {
$query->andWhere(['locked' => 1]);
}
if (!empty($filters['hot']) && $filters['hot'] == 1) {
$query->andWhere(['>=', 'posts', Podium::getInstance()->podiumConfig->get('hot_minimum')]);
}
Similar blocks of code found in 2 locations. Consider refactoring.
if (!empty($filters['new']) && $filters['new'] == 1 && !Podium::getInstance()->user->isGuest) {
$query->joinWith(['threadView tvn' => function ($q) use ($loggedId) {
$q->onCondition(['tvn.user_id' => $loggedId]);
$q->andWhere(['or',
new Expression('tvn.new_last_seen < new_post_at'),
['tvn.new_last_seen' => null]
]);
}], false);
}
Similar blocks of code found in 2 locations. Consider refactoring.
if (!empty($filters['edit']) && $filters['edit'] == 1 && !Podium::getInstance()->user->isGuest) {
$query->joinWith(['threadView tve' => function ($q) use ($loggedId) {
$q->onCondition(['tve.user_id' => $loggedId]);
$q->andWhere(['or',
new Expression('tve.edited_last_seen < edited_post_at'),
['tve.edited_last_seen' => null]
]);
}], false);
}
}
 
$dataProvider = new ActiveDataProvider([
'query' => $query,
'pagination' => false,
]);
$dataProvider->sort->defaultOrder = [
'pinned' => SORT_DESC,
'updated_at' => SORT_DESC,
'id' => SORT_ASC
];
 
return $dataProvider;
}
 
/**
* Searches for threads created by user of given ID.
* @param int $userId
* @return ActiveDataProvider
*/
public function searchByUser($userId)
{
$query = static::find();
$query->where(['author_id' => $userId]);
if (Podium::getInstance()->user->isGuest) {
$query->joinWith(['forum' => function ($q) {
$q->where([Forum::tableName() . '.visible' => 1]);
}]);
}
 
$dataProvider = new ActiveDataProvider([
'query' => $query,
'pagination' => false,
]);
$dataProvider->sort->defaultOrder = [
'updated_at' => SORT_DESC,
'id' => SORT_ASC
];
 
return $dataProvider;
}
 
/**
* Returns proper icon for thread.
* @return string
*/
Function `getIcon` has a Cognitive Complexity of 16 (exceeds 5 allowed). Consider refactoring.
Method `getIcon` has 28 lines of code (exceeds 25 allowed). Consider refactoring.
The method getIcon() has a Cyclomatic Complexity of 10. The configured cyclomatic complexity threshold is 10.
public function getIcon()
{
$icon = self::ICON_NO_NEW;
$append = false;
 
if ($this->locked) {
$icon = self::ICON_LOCKED;
$append = true;
} elseif ($this->pinned) {
$icon = self::ICON_PINNED;
$append = true;
} elseif ($this->posts >= Podium::getInstance()->podiumConfig->get('hot_minimum')) {
$icon = self::ICON_HOT;
$append = true;
}
if ($this->userView) {
if ($this->new_post_at > $this->userView->new_last_seen) {
if (!$append) {
$icon = self::ICON_NEW;
}
} elseif ($this->edited_post_at > $this->userView->edited_last_seen) {
if (!$append) {
$icon = self::ICON_NEW;
}
}
} else {
if (!$append) {
$icon = self::ICON_NEW;
}
}
return $icon;
}
 
/**
* Returns proper description for thread.
* @return string
*/
Function `getDescription` has a Cognitive Complexity of 19 (exceeds 5 allowed). Consider refactoring.
Method `getDescription` has 34 lines of code (exceeds 25 allowed). Consider refactoring.
The method getDescription() has a Cyclomatic Complexity of 10. The configured cyclomatic complexity threshold is 10.
public function getDescription()
{
$description = Yii::t('podium/view', 'No New Posts');
$append = false;
 
if ($this->locked) {
$description = Yii::t('podium/view', 'Locked Thread');
$append = true;
} elseif ($this->pinned) {
$description = Yii::t('podium/view', 'Pinned Thread');
$append = true;
} elseif ($this->posts >= Podium::getInstance()->podiumConfig->get('hot_minimum')) {
$description = Yii::t('podium/view', 'Hot Thread');
$append = true;
}
if ($this->userView) {
if ($this->new_post_at > $this->userView->new_last_seen) {
if (!$append) {
$description = Yii::t('podium/view', 'New Posts');
} else {
$description .= ' (' . Yii::t('podium/view', 'New Posts') . ')';
}
} elseif ($this->edited_post_at > $this->userView->edited_last_seen) {
if (!$append) {
$description = Yii::t('podium/view', 'Edited Posts');
} else {
$description = ' (' . Yii::t('podium/view', 'Edited Posts') . ')';
}
}
} else {
if (!$append) {
$description = Yii::t('podium/view', 'New Posts');
} else {
$description .= ' (' . Yii::t('podium/view', 'New Posts') . ')';
}
}
return $description;
}
 
/**
* Returns proper CSS class for thread.
* @return string
*/
public function getCssClass()
{
$class = self::CLASS_DEFAULT;
 
if ($this->userView) {
if ($this->new_post_at > $this->userView->new_last_seen) {
$class = self::CLASS_NEW;
} elseif ($this->edited_post_at > $this->userView->edited_last_seen) {
$class = self::CLASS_EDITED;
}
} else {
$class = self::CLASS_NEW;
}
return $class;
}
 
/**
* Checks if user is this thread moderator.
* @param int $userId
* @return bool
*/
public function isMod($userId = null)
{
if (User::can(Rbac::ROLE_ADMIN)) {
return true;
}
if (in_array($userId, $this->forum->getMods())) {
return true;
}
return false;
}
 
/**
* Performs thread delete with parent forum counters update.
* @return bool
* @since 0.2
*/
public function podiumDelete()
{
$transaction = Thread::getDb()->beginTransaction();
try {
if (!$this->delete()) {
throw new Exception('Thread deleting error!');
}
$this->forum->updateCounters([
'threads' => -1,
'posts' => -$this->postsCount
]);
$transaction->commit();
PodiumCache::clearAfter('threadDelete');
Log::info('Thread deleted', $this->id, __METHOD__);
return true;
} catch (Exception $e) {
$transaction->rollBack();
Log::error($e->getMessage(), null, __METHOD__);
}
return false;
}
 
/**
* Performs thread posts delete with parent forum counters update.
* @param array $posts posts IDs
* @return bool
* @throws Exception
* @since 0.2
*/
Method `podiumDeletePosts` has 41 lines of code (exceeds 25 allowed). Consider refactoring.
Function `podiumDeletePosts` has a Cognitive Complexity of 13 (exceeds 5 allowed). Consider refactoring.
public function podiumDeletePosts($posts)
{
$transaction = static::getDb()->beginTransaction();
try {
foreach ($posts as $post) {
if (!is_numeric($post) || $post < 1) {
throw new Exception('Incorrect post ID');
}
$nPost = Post::find()
->where([
'id' => $post,
'thread_id' => $this->id,
'forum_id' => $this->forum->id
])
->limit(1)
->one();
if (!$nPost) {
throw new Exception('No post of given ID found');
}
if (!$nPost->delete()) {
throw new Exception('Post deleting error!');
}
}
Avoid unused local variables such as '$wholeThread'.
$wholeThread = false;
Similar blocks of code found in 2 locations. Consider refactoring.
if ($this->postsCount) {
$this->updateCounters(['posts' => -count($posts)]);
$this->forum->updateCounters(['posts' => -count($posts)]);
} else {
$wholeThread = true;
if (!$this->delete()) {
throw new Exception('Thread deleting error!');
}
$this->forum->updateCounters(['posts' => -count($posts), 'threads' => -1]);
}
$transaction->commit();
PodiumCache::clearAfter('postDelete');
Log::info('Posts deleted', null, __METHOD__);
return true;
} catch (Exception $e) {
$transaction->rollBack();
Log::error($e->getMessage(), null, __METHOD__);
}
return false;
}
 
/**
* Performs thread lock / unlock.
* @return bool
* @since 0.2
*/
public function podiumLock()
{
$this->locked = !$this->locked;
if ($this->save()) {
Log::info($this->locked ? 'Thread locked' : 'Thread unlocked', $this->id, __METHOD__);
return true;
}
return false;
}
 
/**
* Performs thread move with counters update.
* @param int $target new parent forum's ID
* @return bool
* @since 0.2
*/
public function podiumMoveTo($target = null)
{
$newParent = Forum::find()->where(['id' => $target])->limit(1)->one();
if (empty($newParent)) {
Log::error('No parent forum of given ID found', $this->id, __METHOD__);
return false;
}
 
$postsCount = $this->postsCount;
$transaction = Forum::getDb()->beginTransaction();
try {
$this->forum->updateCounters(['threads' => -1, 'posts' => -$postsCount]);
$newParent->updateCounters(['threads' => 1, 'posts' => $postsCount]);
$this->forum_id = $newParent->id;
$this->category_id = $newParent->category_id;
if (!$this->save()) {
throw new Exception('Thread saving error!');
}
Post::updateAll(['forum_id' => $newParent->id], ['thread_id' => $this->id]);
$transaction->commit();
PodiumCache::clearAfter('threadMove');
Log::info('Thread moved', $this->id, __METHOD__);
return true;
} catch (Exception $e) {
$transaction->rollBack();
Log::error($e->getMessage(), null, __METHOD__);
}
return false;
}
 
/**
* Performs thread posts move with counters update.
* @param int $target new parent thread ID
* @param array $posts IDs of posts to move
* @param string $name new thread name if $target = 0
* @param type $forum new thread parent forum if $target = 0
* @return bool
* @throws Exception
* @since 0.2
*/
Function `podiumMovePostsTo` has a Cognitive Complexity of 22 (exceeds 5 allowed). Consider refactoring.
Method `podiumMovePostsTo` has 69 lines of code (exceeds 25 allowed). Consider refactoring.
The method podiumMovePostsTo() has an NPath complexity of 469. The configured NPath complexity threshold is 200.
The method podiumMovePostsTo() has a Cyclomatic Complexity of 14. The configured cyclomatic complexity threshold is 10.
public function podiumMovePostsTo($target = null, $posts = [], $name = null, $forum = null)
{
$transaction = static::getDb()->beginTransaction();
try {
if ($target == 0) {
$parent = Forum::find()->where(['id' => $forum])->limit(1)->one();
if (empty($parent)) {
throw new Exception('No parent forum of given ID found');
}
$newThread = new Thread();
$newThread->name = $name;
$newThread->posts = 0;
$newThread->views = 0;
$newThread->category_id = $parent->category_id;
$newThread->forum_id = $parent->id;
$newThread->author_id = User::loggedId();
if (!$newThread->save()) {
throw new Exception('Thread saving error!');
}
} else {
$newThread = Thread::find()->where(['id' => $target])->limit(1)->one();
if (empty($newThread)) {
throw new Exception('No thread of given ID found');
}
}
if (empty($newThread)) {
throw new Exception('No target thread selected!');
}
foreach ($posts as $post) {
if (!is_numeric($post) || $post < 1) {
throw new Exception('Incorrect post ID');
}
$newPost = Post::find()
->where([
'id' => $post,
'thread_id' => $this->id,
'forum_id' => $this->forum->id
])
->limit(1)
->one();
if (empty($newPost)) {
throw new Exception('No post of given ID found');
}
$newPost->thread_id = $newThread->id;
$newPost->forum_id = $newThread->forum_id;
if (!$newPost->save()) {
throw new Exception('Post saving error!');
}
}
Avoid unused local variables such as '$wholeThread'.
$wholeThread = false;
Similar blocks of code found in 2 locations. Consider refactoring.
if ($this->postCount) {
$this->updateCounters(['posts' => -count($posts)]);
$this->forum->updateCounters(['posts' => -count($posts)]);
} else {
$wholeThread = true;
if (!$this->delete()) {
throw new Exception('Thread deleting error!');
}
$this->forum->updateCounters(['posts' => -count($posts), 'threads' => -1]);
}
$newThread->updateCounters(['posts' => count($posts)]);
$newThread->forum->updateCounters(['posts' => count($posts)]);
$transaction->commit();
PodiumCache::clearAfter('postMove');
Log::info('Posts moved', null, __METHOD__);
return true;
} catch (Exception $e) {
$transaction->rollBack();
Log::error($e->getMessage(), null, __METHOD__);
}
return false;
}
 
/**
* Performs new thread with first post creation and subscription.
* Saves thread poll.
* @return bool
* @since 0.2
*/
Method `podiumNew` has 61 lines of code (exceeds 25 allowed). Consider refactoring.
Function `podiumNew` has a Cognitive Complexity of 15 (exceeds 5 allowed). Consider refactoring.
The method podiumNew() has a Cyclomatic Complexity of 12. The configured cyclomatic complexity threshold is 10.
public function podiumNew()
{
$transaction = static::getDb()->beginTransaction();
try {
if (!$this->save()) {
throw new Exception('Thread saving error!');
}
 
$loggedIn = User::loggedId();
 
if ($this->pollAdded && Podium::getInstance()->podiumConfig->get('allow_polls')) {
$poll = new Poll();
$poll->thread_id = $this->id;
$poll->question = $this->pollQuestion;
$poll->votes = $this->pollVotes;
$poll->hidden = $this->pollHidden;
$poll->end_at = !empty($this->pollEnd) ? Podium::getInstance()->formatter->asTimestamp($this->pollEnd . ' 23:59:59') : null;
$poll->author_id = $loggedIn;
if (!$poll->save()) {
throw new Exception('Poll saving error!');
}
 
foreach ($this->pollAnswers as $answer) {
$pollAnswer = new PollAnswer();
$pollAnswer->poll_id = $poll->id;
$pollAnswer->answer = $answer;
if (!$pollAnswer->save()) {
throw new Exception('Poll Answer saving error!');
}
}
Log::info('Poll added', $poll->id, __METHOD__);
}
 
$this->forum->updateCounters(['threads' => 1]);
 
$post = new Post();
$post->content = $this->post;
$post->thread_id = $this->id;
$post->forum_id = $this->forum_id;
$post->author_id = $loggedIn;
$post->likes = 0;
$post->dislikes = 0;
if (!$post->save()) {
throw new Exception('Post saving error!');
}
 
$post->markSeen();
$this->forum->updateCounters(['posts' => 1]);
$this->updateCounters(['posts' => 1]);
 
$this->touch('new_post_at');
$this->touch('edited_post_at');
 
if ($this->subscribe) {
$subscription = new Subscription();
$subscription->user_id = $loggedIn;
$subscription->thread_id = $this->id;
$subscription->post_seen = Subscription::POST_SEEN;
if (!$subscription->save()) {
throw new Exception('Subscription saving error!');
}
}
 
$transaction->commit();
PodiumCache::clearAfter('newThread');
Log::info('Thread added', $this->id, __METHOD__);
return true;
} catch (Exception $e) {
$transaction->rollBack();
Log::error($e->getMessage(), null, __METHOD__);
}
return false;
}
 
/**
* Performs thread pin / unpin.
* @return bool
* @since 0.2
*/
public function podiumPin()
{
$this->pinned = !$this->pinned;
if ($this->save()) {
Log::info($this->pinned ? 'Thread pinned' : 'Thread unpinned', $this->id, __METHOD__);
return true;
}
return false;
}
 
/**
* Performs marking all unread threads as seen for user.
* @return bool
* @throws Exception
* @since 0.2
*/
Method `podiumMarkAllSeen` has 41 lines of code (exceeds 25 allowed). Consider refactoring.
Function `podiumMarkAllSeen` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.
public static function podiumMarkAllSeen()
{
try {
$loggedId = User::loggedId();
if (empty($loggedId)) {
throw new Exception('User ID missing');
}
$updateBatch = [];
$threadsPrevMarked = Thread::find()->joinWith(['threadView' => function ($q) use ($loggedId) {
$q->onCondition(['user_id' => $loggedId]);
$q->andWhere(['or',
new Expression('new_last_seen < new_post_at'),
new Expression('edited_last_seen < edited_post_at'),
]);
}], false);
$time = time();
foreach ($threadsPrevMarked->each() as $thread) {
$updateBatch[] = $thread->id;
}
if (!empty($updateBatch)) {
Podium::getInstance()->db->createCommand()->update(ThreadView::tableName(), [
'new_last_seen' => $time,
'edited_last_seen' => $time
], ['thread_id' => $updateBatch, 'user_id' => $loggedId])->execute();
}
 
$insertBatch = [];
$threadsNew = Thread::find()->joinWith(['threadView' => function ($q) use ($loggedId) {
$q->onCondition(['user_id' => $loggedId]);
$q->andWhere(['new_last_seen' => null]);
}], false);
foreach ($threadsNew->each() as $thread) {
$insertBatch[] = [$loggedId, $thread->id, $time, $time];
}
if (!empty($insertBatch)) {
Podium::getInstance()->db->createCommand()->batchInsert(ThreadView::tableName(), [
'user_id', 'thread_id', 'new_last_seen', 'edited_last_seen'
], $insertBatch)->execute();
}
return true;
} catch (Exception $e) {
Log::error($e->getMessage(), null, __METHOD__);
}
return false;
}
 
/**
* Returns post Markdown-parsed if WYSIWYG editor is switched off.
* @return string
* @since 0.6
*/
public function getParsedPost()
{
if (Podium::getInstance()->podiumConfig->get('use_wysiwyg') == '0') {
$parser = new GithubMarkdown();
$parser->html5 = true;
return $parser->parse($this->post);
}
return $this->post;
}
}