src/models/Post.php
File `Post.php` has 462 lines of code (exceeds 250 allowed). Consider refactoring.<?php namespace bizley\podium\models; use bizley\podium\db\Query;use bizley\podium\log\Log;use bizley\podium\models\db\PostActiveRecord;use bizley\podium\Podium;use bizley\podium\PodiumCache;use cebe\markdown\GithubMarkdown;use Exception;use yii\data\ActiveDataProvider;use yii\helpers\HtmlPurifier; /** * Post model * * @author Paweł Bizley Brzozowski <pawel@positive.codes> * @since 0.1 * * @property string $parsedContent */The class Post has an overall complexity of 116 which is very high. The configured complexity threshold is 50.
The class Post has a coupling between objects value of 15. Consider to reduce the number of dependencies under 13.class Post extends PostActiveRecord{ /** * Returns latest posts for registered users. * @param int $limit Number of latest posts. * @return Post[] */ public static function getLatestPostsForMembers($limit = 5) { return static::find()->orderBy(['created_at' => SORT_DESC])->limit($limit)->all(); } /** * Returns latest visible posts for guests. * @param int $limit Number of latest posts. * @return Post[] */ public static function getLatestPostsForGuests($limit = 5) { return static::find()->joinWith(['forum' => function ($query) { $query->andWhere([Forum::tableName() . '.visible' => 1])->joinWith(['category' => function ($query) { $query->andWhere([Category::tableName() . '.visible' => 1]); }]); }])->orderBy(['created_at' => SORT_DESC])->limit($limit)->all(); } /** * Updates post tag words. * @param bool $insert * @param array $changedAttributes * @throws Exception */ public function afterSave($insert, $changedAttributes) { try { if ($insert) { $this->insertWords(); } else { $this->updateWords(); } } catch (Exception $e) { throw $e; } parent::afterSave($insert, $changedAttributes); } /** * Prepares tag words. * @return string[] */ protected function prepareWords() { $cleanHtml = HtmlPurifier::process(trim($this->content)); $purged = preg_replace('/<[^>]+>/', ' ', $cleanHtml); $wordsRaw = array_unique(preg_split('/[\s,\.\n]+/', $purged)); $allWords = []; foreach ($wordsRaw as $word) { if (mb_strlen($word, 'UTF-8') > 2 && mb_strlen($word, 'UTF-8') <= 255) { $allWords[] = $word; } } return $allWords; } /** * Adds new tag words. * @param string[] $allWords All words extracted from post * @throws Exception */Function `addNewWords` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.
Avoid assigning values to variables in if clauses and the like (line '98', column '22'). protected function addNewWords($allWords) { try { $newWords = $allWords; $query = (new Query())->from(Vocabulary::tableName())->where(['word' => $allWords]); foreach ($query->each() as $vocabularyFound) { if (($key = array_search($vocabularyFound['word'], $allWords)) !== false) { unset($newWords[$key]); } } $formatWords = []; foreach ($newWords as $word) { $formatWords[] = [$word]; } if (!empty($formatWords)) { if (!Podium::getInstance()->db->createCommand()->batchInsert( Vocabulary::tableName(), ['word'], $formatWords )->execute()) { throw new Exception('Words saving error!'); } } } catch (Exception $e) { Log::error($e->getMessage(), null, __METHOD__); throw $e; } } /** * Inserts tag words. * @throws Exception */ protected function insertWords() { try { $vocabulary = []; $allWords = $this->prepareWords(); $this->addNewWords($allWords); $query = (new Query())->from(Vocabulary::tableName())->where(['word' => $allWords]); foreach ($query->each() as $vocabularyNew) { $vocabulary[] = [$vocabularyNew['id'], $this->id]; } if (!empty($vocabulary)) { if (!Podium::getInstance()->db->createCommand()->batchInsert( '{{%podium_vocabulary_junction}}', ['word_id', 'post_id'], $vocabulary )->execute()) { throw new Exception('Words connections saving error!'); } } } catch (Exception $e) { Log::error($e->getMessage(), null, __METHOD__); throw $e; } } /** * Updates tag words. * @throws Exception */Function `updateWords` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring.
Method `updateWords` has 29 lines of code (exceeds 25 allowed). Consider refactoring. protected function updateWords() { try { $vocabulary = []; $allWords = $this->prepareWords(); $this->addNewWords($allWords); $queryVocabulary = (new Query())->from(Vocabulary::tableName())->where(['word' => $allWords]); foreach ($queryVocabulary->each() as $vocabularyNew) { $vocabulary[$vocabularyNew['id']] = [$vocabularyNew['id'], $this->id]; } if (!empty($vocabulary)) { if (!Podium::getInstance()->db->createCommand()->batchInsert( '{{%podium_vocabulary_junction}}', ['word_id', 'post_id'], array_values($vocabulary) )->execute()) { throw new Exception('Words connections saving error!'); } } $queryJunction = (new Query())->from('{{%podium_vocabulary_junction}}')->where(['post_id' => $this->id]); foreach ($queryJunction->each() as $junk) { if (!array_key_exists($junk['word_id'], $vocabulary)) { if (!Podium::getInstance()->db->createCommand()->delete( '{{%podium_vocabulary_junction}}', ['id' => $junk['id']] )->execute()) { throw new Exception('Words connections deleting error!'); } } } } catch (Exception $e) { Log::error($e->getMessage(), null, __METHOD__); throw $e; } } /** * Verifies if user is this forum's moderator. * @param int|null $userId user ID or null for current signed in. * @return bool */ public function isMod($userId = null) { return $this->forum->isMod($userId); } /** * Searches for posts. * @param int $forumId * @param int $threadId * @return ActiveDataProvider */ public function search($forumId, $threadId) { $dataProvider = new ActiveDataProvider([ 'query' => static::find()->where(['forum_id' => $forumId, 'thread_id' => $threadId]), 'pagination' => [ 'defaultPageSize' => 10, 'pageSizeLimit' => false, 'forcePageParam' => false ], ]); $dataProvider->sort->defaultOrder = ['id' => SORT_ASC]; return $dataProvider; } /** * Searches for posts added by given user. * @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' => [ 'defaultPageSize' => 10, 'pageSizeLimit' => false, 'forcePageParam' => false ], ]); $dataProvider->sort->defaultOrder = ['id' => SORT_ASC]; return $dataProvider; } /** * Marks post as seen by current user. * @param bool $updateCounters Whether to update view counter */Function `markSeen` has a Cognitive Complexity of 77 (exceeds 5 allowed). Consider refactoring.
Method `markSeen` has 71 lines of code (exceeds 25 allowed). Consider refactoring.
The method markSeen() has a Cyclomatic Complexity of 22. The configured cyclomatic complexity threshold is 10.
The method markSeen has a boolean flag argument $updateCounters, which is a certain sign of a Single Responsibility Principle violation. public function markSeen($updateCounters = true) { if (!Podium::getInstance()->user->isGuest) { $transaction = static::getDb()->beginTransaction(); try { $loggedId = User::loggedId(); $threadView = ThreadView::find()->where([ 'user_id' => $loggedId, 'thread_id' => $this->thread_id ])->limit(1)->one(); if (empty($threadView)) { $threadView = new ThreadView(); $threadView->user_id = $loggedId; $threadView->thread_id = $this->thread_id; $threadView->new_last_seen = $this->created_at; $threadView->edited_last_seen = !empty($this->edited_at) ? $this->edited_at : $this->created_at; if (!$threadView->save()) { throw new Exception('Thread View saving error!'); } if ($updateCounters) { if (!$this->thread->updateCounters(['views' => 1])) { throw new Exception('Thread views adding error!'); } } } else { if ($this->edited) { if ($threadView->edited_last_seen < $this->edited_at) { $threadView->edited_last_seen = $this->edited_at; if (!$threadView->save()) { throw new Exception('Thread View saving error!'); } if ($updateCounters) { if (!$this->thread->updateCounters(['views' => 1])) { throw new Exception('Thread views adding error!'); } } } } else { $save = false; if ($threadView->new_last_seen < $this->created_at) { $threadView->new_last_seen = $this->created_at; $save = true; } if ($threadView->edited_last_seen < max($this->created_at, $this->edited_at)) { $threadView->edited_last_seen = max($this->created_at, $this->edited_at); $save = true; } if ($save) { if (!$threadView->save()) { throw new Exception('Thread View saving error!'); } if ($updateCounters) { if (!$this->thread->updateCounters(['views' => 1])) { throw new Exception('Thread views adding error!'); } } } } } if ($this->thread->subscription) { if ($this->thread->subscription->post_seen == Subscription::POST_NEW) { $this->thread->subscription->post_seen = Subscription::POST_SEEN; if (!$this->thread->subscription->save()) { throw new Exception('Thread Subscription saving error!'); } } } $transaction->commit(); } catch (Exception $e) { $transaction->rollBack(); Log::error($e->getMessage(), null, __METHOD__); } } } /** * Returns latest post. * @param int $limit * @return array */ public static function getLatest($limit = 5) { $cacheKey = Podium::getInstance()->user->isGuest ? 'guest' : 'member'; $method = Podium::getInstance()->user->isGuest ? 'getLatestPostsForGuests' : 'getLatestPostsForMembers'; $latest = Podium::getInstance()->podiumCache->getElement('forum.latestposts', $cacheKey); if ($latest === false) { $posts = static::$method($limit); foreach ($posts as $post) { $latest[] = [ 'id' => $post->id, 'title' => $post->thread->name, 'created' => $post->created_at, 'author' => $post->author->podiumTag ]; } Podium::getInstance()->podiumCache->setElement('forum.latestposts', $cacheKey, $latest); } return $latest; } /** * Returns the verified post. * @param int $categoryId post category ID * @param int $forumId post forum ID * @param int $threadId post thread ID * @param int $id post ID * @return Post * @since 0.2 */ public static function verify($categoryId = null, $forumId = null, $threadId = null, $id = null) {Consider simplifying this complex logical expression. if (!is_numeric($categoryId) || $categoryId < 1 || !is_numeric($forumId) || $forumId < 1 || !is_numeric($threadId) || $threadId < 1 || !is_numeric($id) || $id < 1) { return null; } return static::find()->joinWith(['thread', 'forum' => function ($query) use ($categoryId) { $query->joinWith(['category'])->andWhere([Category::tableName() . '.id' => $categoryId]); }])->where([ static::tableName() . '.id' => $id, static::tableName() . '.thread_id' => $threadId, static::tableName() . '.forum_id' => $forumId, ])->limit(1)->one(); } /** * Performs post delete with parent forum and thread counters update. * @return bool * @since 0.2 */Function `podiumDelete` has a Cognitive Complexity of 12 (exceeds 5 allowed). Consider refactoring.
Method `podiumDelete` has 31 lines of code (exceeds 25 allowed). Consider refactoring. public function podiumDelete() { $transaction = static::getDb()->beginTransaction(); try { if (!$this->delete()) { throw new Exception('Post deleting error!'); }Avoid unused local variables such as '$wholeThread'. $wholeThread = false; if ($this->thread->postsCount) { if (!$this->thread->updateCounters(['posts' => -1])) { throw new Exception('Thread Post counter subtracting error!'); } if (!$this->forum->updateCounters(['posts' => -1])) { throw new Exception('Forum Post counter subtracting error!'); } } else { $wholeThread = true; if (!$this->thread->delete()) { throw new Exception('Thread deleting error!'); } if (!$this->forum->updateCounters(['posts' => -1, 'threads' => -1])) { throw new Exception('Forum Post and Thread counter subtracting error!'); } } $transaction->commit(); PodiumCache::clearAfter('postDelete'); Log::info('Post deleted', !empty($this->id) ? $this->id : '', __METHOD__); return true; } catch (Exception $e) { $transaction->rollBack(); Log::error($e->getMessage(), null, __METHOD__); } return false; } /** * Performs post update with parent thread topic update in case of first post in thread. * @param bool $isFirstPost whether post is first in thread * @return bool * @since 0.2 */The method podiumEdit has a boolean flag argument $isFirstPost, which is a certain sign of a Single Responsibility Principle violation. public function podiumEdit($isFirstPost = false) { $transaction = static::getDb()->beginTransaction(); try { $this->edited = 1; $this->touch('edited_at'); if (!$this->save()) { throw new Exception('Post saving error!'); } if ($isFirstPost) { $this->thread->name = $this->topic; if (!$this->thread->save()) { throw new Exception('Thread saving error!'); } } $this->markSeen(); $this->thread->touch('edited_post_at'); $transaction->commit(); Log::info('Post updated', $this->id, __METHOD__); return true; } catch (Exception $e) { $transaction->rollBack(); Log::error($e->getMessage(), null, __METHOD__); } return false; } /** * Performs new post creation and subscription. * Depending on the settings previous post can be merged. * @param Post $previous previous post * @return bool * @since 0.2 */Function `podiumNew` has a Cognitive Complexity of 18 (exceeds 5 allowed). Consider refactoring.
Method `podiumNew` has 58 lines of code (exceeds 25 allowed). Consider refactoring.
The method podiumNew() has a Cyclomatic Complexity of 14. The configured cyclomatic complexity threshold is 10. public function podiumNew($previous = null) { $transaction = static::getDb()->beginTransaction(); try { $id = null; $loggedId = User::loggedId(); $sameAuthor = !empty($previous->author_id) && $previous->author_id == $loggedId; if ($sameAuthor && Podium::getInstance()->podiumConfig->get('merge_posts')) { $separator = '<hr>'; if (Podium::getInstance()->podiumConfig->get('use_wysiwyg') == '0') { $separator = "\n\n---\n"; } $previous->content .= $separator . $this->content; $previous->edited = 1; $previous->touch('edited_at'); if (!$previous->save()) { throw new Exception('Previous Post saving error!'); } $previous->markSeen(false); $previous->thread->touch('edited_post_at'); $id = $previous->id; $thread = $previous->thread; } else { if (!$this->save()) { throw new Exception('Post saving error!'); } $this->markSeen(!$sameAuthor); if (!$this->forum->updateCounters(['posts' => 1])) { throw new Exception('Forum Post counter adding error!'); } if (!$this->thread->updateCounters(['posts' => 1])) { throw new Exception('Thread Post counter adding error!'); } $this->thread->touch('new_post_at'); $this->thread->touch('edited_post_at'); $id = $this->id; $thread = $this->thread; } if (empty($id)) { throw new Exception('Saved Post ID missing'); } Subscription::notify($thread->id); if ($this->subscribe && !$thread->subscription) { $subscription = new Subscription(); $subscription->user_id = $loggedId; $subscription->thread_id = $thread->id; $subscription->post_seen = Subscription::POST_SEEN; if (!$subscription->save()) { throw new Exception('Subscription saving error!'); } } $transaction->commit(); PodiumCache::clearAfter('newPost'); Log::info('Post added', $id, __METHOD__); return true; } catch (Exception $e) { $transaction->rollBack(); Log::error($e->getMessage(), null, __METHOD__); } return false; } /** * Performs vote processing. * @param bool $up whether this is upvote * @param int $count number of user cached votes * @return bool * @since 0.2 */Function `podiumThumb` has a Cognitive Complexity of 32 (exceeds 5 allowed). Consider refactoring.
Method `podiumThumb` has 51 lines of code (exceeds 25 allowed). Consider refactoring.
The method podiumThumb() has a Cyclomatic Complexity of 17. The configured cyclomatic complexity threshold is 10.
The method podiumThumb has a boolean flag argument $up, which is a certain sign of a Single Responsibility Principle violation. public function podiumThumb($up = true, $count = 0) { $transaction = static::getDb()->beginTransaction(); try { $loggedId = User::loggedId(); if ($this->thumb) { if ($this->thumb->thumb == 1 && !$up) { $this->thumb->thumb = -1; if (!$this->thumb->save()) { throw new Exception('Thumb saving error!'); } if (!$this->updateCounters(['likes' => -1, 'dislikes' => 1])) { throw new Exception('Thumb counters saving error!'); } } elseif ($this->thumb->thumb == -1 && $up) { $this->thumb->thumb = 1; if (!$this->thumb->save()) { throw new Exception('Thumb saving error!'); } if (!$this->updateCounters(['likes' => 1, 'dislikes' => -1])) { throw new Exception('Thumb counters saving error!'); } } } else { $postThumb = new PostThumb(); $postThumb->post_id = $this->id; $postThumb->user_id = $loggedId; $postThumb->thumb = $up ? 1 : -1; if (!$postThumb->save()) { throw new Exception('PostThumb saving error!'); } if ($postThumb->thumb) { if (!$this->updateCounters(['likes' => 1])) { throw new Exception('Thumb counters saving error!'); } } else { if (!$this->updateCounters(['dislikes' => 1])) { throw new Exception('Thumb counters saving error!'); } } } if ($count == 0) { Podium::getInstance()->podiumCache->set('user.votes.' . $loggedId, ['count' => 1, 'expire' => time() + 3600]); } else { Podium::getInstance()->podiumCache->setElement('user.votes.' . $loggedId, 'count', $count + 1); } $transaction->commit(); return true; } catch (Exception $e) { $transaction->rollBack(); Log::error($e->getMessage(), null, __METHOD__); } return false; } /** * Returns content Markdown-parsed if WYSIWYG editor is switched off. * @return string * @since 0.6 */ public function getParsedContent() { if (Podium::getInstance()->podiumConfig->get('use_wysiwyg') == '0') { $parser = new GithubMarkdown(); $parser->html5 = true; return $parser->parse($this->content); } return $this->content; }}