bizley/yii2-podium

View on GitHub
src/controllers/ForumController.php

Summary

Maintainability
F
4 days
Test Coverage
<?php

namespace bizley\podium\controllers;

use bizley\podium\db\Query;
use bizley\podium\filters\AccessControl;
use bizley\podium\models\Category;
use bizley\podium\models\forms\SearchForm;
use bizley\podium\models\Forum;
use bizley\podium\models\Poll;
use bizley\podium\models\Post;
use bizley\podium\models\Thread;
use bizley\podium\models\User;
use bizley\podium\models\Vocabulary;
use bizley\podium\rbac\Rbac;
use bizley\podium\services\ThreadVerifier;
use Exception;
use Yii;
use yii\helpers\Html;
use yii\helpers\Json;
use yii\web\Response;

/**
 * Podium Forum controller
 * All actions concerning viewing and moderating forums and posts.
 *
 * @author Paweł Bizley Brzozowski <pawel@positive.codes>
 * @since 0.2
 */
class ForumController extends ForumPostController
{
    /**
     * @inheritdoc
     */
    public function behaviors()
    {
        return [
            'access' => [
                'class' => AccessControl::className(),
                'ruleConfig' => ['class' => 'bizley\podium\filters\LoginRequiredRule'],
                'rules' => [
                    ['class' => 'bizley\podium\filters\InstallRule'],
                    [
                        'actions' => ['unread-posts'],
                        'type' => 'info',
                        'message' => Yii::t('podium/flash', 'This page is available for registered users only.')
                    ],
                    [
                        'actions' => ['deletepoll'],
                        'message' => Yii::t('podium/flash', 'Please sign in to delete the poll.')
                    ],
                    [
                        'actions' => ['editpoll'],
                        'message' => Yii::t('podium/flash', 'Please sign in to edit the poll.')
                    ],
                    [
                        'actions' => ['deletepost'],
                        'message' => Yii::t('podium/flash', 'Please sign in to delete the post.')
                    ],
                    [
                        'actions' => ['deleteposts', 'moveposts', 'post', 'lock', 'move', 'pin'],
                        'message' => Yii::t('podium/flash', 'Please sign in to update the thread.')
                    ],
                    [
                        'actions' => ['edit'],
                        'message' => Yii::t('podium/flash', 'Please sign in to edit the post.')
                    ],
                    [
                        'actions' => ['report'],
                        'message' => Yii::t('podium/flash', 'Please sign in to report the post.')
                    ],
                    [
                        'actions' => ['mark-seen'],
                        'type' => 'info',
                        'message' => Yii::t('podium/flash', 'This action is available for registered users only.')
                    ],
                    [
                        'actions' => ['delete'],
                        'message' => Yii::t('podium/flash', 'Please sign in to delete the thread.')
                    ],
                    [
                        'actions' => ['new-thread'],
                        'message' => Yii::t('podium/flash', 'Please sign in to create a new thread.')
                    ],
                    [
                        'class' => 'bizley\podium\filters\PodiumRoleRule',
                        'allow' => true
                    ],
                ],
            ],
        ];
    }

    /**
     * Showing ban info.
     * @return string
     */
    public function actionBan()
    {
        $this->layout = 'maintenance';
        return $this->render('ban');
    }

    /**
     * Displaying the category of given ID and slug.
     * @param int $id
     * @param string $slug
     * @return string|Response
     */
    public function actionCategory($id = null, $slug = null)
    {
        if (!is_numeric($id) || $id < 1 || empty($slug)) {
            $this->error(Yii::t('podium/flash', 'Sorry! We can not find the category you are looking for.'));
            return $this->redirect(['forum/index']);
        }
        $conditions = ['id' => $id, 'slug' => $slug];
        if ($this->module->user->isGuest) {
            $conditions['visible'] = 1;
        }
        $model = Category::find()->where($conditions)->limit(1)->one();
        if (empty($model)) {
            $this->error(Yii::t('podium/flash', 'Sorry! We can not find the category you are looking for.'));
            return $this->redirect(['forum/index']);
        }
        $this->setMetaTags($model->keywords, $model->description);
        return $this->render('category', ['model' => $model]);
    }

    /**
     * Displaying the forum of given category ID, own ID and slug.
     * @param int $cid category ID
     * @param int $id forum ID
     * @param string $slug forum slug
     * @return string|Response
     */
    public function actionForum($cid = null, $id = null, $slug = null, $toggle = null)
    {
        $filters = Yii::$app->session->get('forum-filters');
        if (in_array(strtolower($toggle), ['new', 'edit', 'hot', 'pin', 'lock', 'all'])) {
            if (strtolower($toggle) == 'all') {
                $filters = null;
            } else {
                $filters[strtolower($toggle)] = empty($filters[strtolower($toggle)]) || $filters[strtolower($toggle)] == 0 ? 1 : 0;
            }
            Yii::$app->session->set('forum-filters', $filters);
            return $this->redirect([
                'forum/forum',
                'cid' => $cid,
                'id' => $id,
                'slug' => $slug
            ]);
        }

        $forum = Forum::verify($cid, $id, $slug, $this->module->user->isGuest);
        if (empty($forum)) {
            $this->error(Yii::t('podium/flash', 'Sorry! We can not find the forum you are looking for.'));
            return $this->redirect(['forum/index']);
        }

        $this->setMetaTags(
            $forum->keywords ?: $forum->category->keywords,
            $forum->description ?: $forum->category->description
        );

        return $this->render('forum', [
            'model' => $forum,
            'filters' => $filters
        ]);
    }

    /**
     * Displaying the list of categories.
     * @return string
     */
    public function actionIndex()
    {
        $this->setMetaTags();
        return $this->render('index', ['dataProvider' => (new Category())->search()]);
    }

    /**
     * Direct link for the last post in thread of given ID.
     * @param int $id
     * @return Response
     */
    public function actionLast($id = null)
    {
        if (!is_numeric($id) || $id < 1) {
            $this->error(Yii::t('podium/flash', 'Sorry! We can not find the thread you are looking for.'));
            return $this->redirect(['forum/index']);
        }

        $thread = Thread::find()->where(['id' => $id])->limit(1)->one();
        if (empty($thread)) {
            $this->error(Yii::t('podium/flash', 'Sorry! We can not find the thread you are looking for.'));
            return $this->redirect(['forum/index']);
        }

        $url = [
            'forum/thread',
            'cid' => $thread->category_id,
            'fid' => $thread->forum_id,
            'id' => $thread->id,
            'slug' => $thread->slug
        ];

        $count = $thread->postsCount;
        $page = floor($count / 10) + 1;
        if ($page > 1) {
            $url['page'] = $page;
        }
        return $this->redirect($url);
    }

    /**
     * Showing maintenance info.
     */
    public function actionMaintenance()
    {
        $this->layout = 'maintenance';
        return $this->render('maintenance');
    }

    /**
     * Direct link for the post of given ID.
     * @param int $id
     * @return Response
     */
    public function actionShow($id = null)
    {
        if (!is_numeric($id) || $id < 1) {
            $this->error(Yii::t('podium/flash', 'Sorry! We can not find the post you are looking for.'));
            return $this->redirect(['forum/index']);
        }

        $post = Post::find()->where(['id' => $id])->limit(1)->one();
        if (empty($post)) {
            $this->error(Yii::t('podium/flash', 'Sorry! We can not find the post you are looking for.'));
            return $this->redirect(['forum/index']);
        }

        $url = [
            'forum/thread',
            'cid' => $post->thread->category_id,
            'fid' => $post->forum_id,
            'id' => $post->thread_id,
            'slug' => $post->thread->slug
        ];

        try {
            $count = (new Query)->from(Post::tableName())->where([
                    'and',
                    ['thread_id' => $post->thread_id],
                    ['<', 'id', $post->id]
                ])->count();
            $page = floor($count / 10) + 1;
            if ($page > 1) {
                $url['page'] = $page;
            }
            $url['#'] = 'post' . $post->id;
            return $this->redirect($url);
        } catch (Exception $e) {
            $this->error(Yii::t('podium/flash', 'Sorry! We can not find the post you are looking for.'));
            return $this->redirect(['forum/index']);
        }
    }

    /**
     * Displaying the thread of given category ID, forum ID, own ID and slug.
     * @param int $cid category ID
     * @param int $fid forum ID
     * @param int $id thread ID
     * @param string $slug thread slug
     * @return string|Response
     */
    public function actionThread($cid = null, $fid = null, $id = null, $slug = null)
    {
        $thread = (new ThreadVerifier([
            'categoryId' => $cid,
            'forumId' => $fid,
            'threadId' => $id,
            'threadSlug' => $slug
        ]))->verify();
        if (empty($thread)) {
            $this->error(Yii::t('podium/flash', 'Sorry! We can not find the thread you are looking for.'));
            return $this->redirect(['forum/index']);
        }

        $this->setMetaTags(
            $thread->forum->keywords ?: $thread->forum->category->keywords,
            $thread->forum->description ?: $thread->forum->category->description
        );

        $model = new Post;
        $model->subscribe = 1;
        $dataProvider = $model->search($thread->forum->id, $thread->id);

        return $this->render('thread', [
            'model' => $model,
            'dataProvider' => $dataProvider,
            'thread' => $thread,
        ]);
    }

    /**
     * Setting meta tags.
     * @param string $keywords
     * @param string $description
     */
    public function setMetaTags($keywords = null, $description = null)
    {
        if (empty($keywords)) {
            $keywords = $this->module->podiumConfig->get('meta_keywords');
        }
        if ($keywords) {
            $this->getView()->registerMetaTag([
                'name' => 'keywords',
                'content' => $keywords
            ]);
        }
        if (empty($description)) {
            $description = $this->module->podiumConfig->get('meta_description');
        }
        if ($description) {
            $this->getView()->registerMetaTag([
                'name' => 'description',
                'content' => $description
            ]);
        }
    }

    /**
     * Listing all unread posts.
     * @return string|Response
     */
    public function actionUnreadPosts()
    {
        return $this->render('unread-posts');
    }

    /**
     * Deleting the poll of given category ID, forum ID, thread ID and ID.
     * @param int $cid category ID
     * @param int $fid forum ID
     * @param int $tid thread ID
     * @param int $pid poll ID
     * @return string|Response
     * @since 0.5
     */
    public function actionDeletepoll($cid = null, $fid = null, $tid = null, $pid = null)
    {
        $poll = Poll::find()->joinWith('thread')->where([
            Poll::tableName() . '.id' => $pid,
            Poll::tableName() . '.thread_id' => $tid,
            Thread::tableName() . '.category_id' => $cid,
            Thread::tableName() . '.forum_id' => $fid,
        ])->limit(1)->one();
        if (empty($poll)) {
            $this->error(Yii::t('podium/flash', 'Sorry! We can not find the poll you are looking for.'));
            return $this->redirect(['forum/index']);
        }

        if ($poll->thread->locked == 1 && !User::can(Rbac::PERM_UPDATE_THREAD, ['item' => $poll->thread])) {
            $this->info(Yii::t('podium/flash', 'This thread is locked.'));
            return $this->redirect([
                'forum/thread',
                'cid' => $poll->thread->category_id,
                'fid' => $poll->thread->forum_id,
                'id' => $poll->thread->id,
                'slug' => $poll->thread->slug
            ]);
        }

        if ($poll->author_id != User::loggedId() && !User::can(Rbac::PERM_UPDATE_THREAD, ['item' => $poll->thread])) {
            $this->error(Yii::t('podium/flash', 'Sorry! You do not have the required permission to perform this action.'));
            return $this->redirect(['forum/index']);
        }

        $postData = Yii::$app->request->post('poll');
        if ($postData) {
            if ($postData != $poll->id) {
                $this->error(Yii::t('podium/flash', 'Sorry! There was an error while deleting the poll.'));
            } else {
                if ($poll->podiumDelete()) {
                    $this->success(Yii::t('podium/flash', 'Poll has been deleted.'));
                    return $this->redirect([
                        'forum/thread',
                        'cid' => $poll->thread->category_id,
                        'fid' => $poll->thread->forum_id,
                        'id' => $poll->thread->id,
                        'slug' => $poll->thread->slug
                    ]);
                }
                $this->error(Yii::t('podium/flash', 'Sorry! There was an error while deleting the poll.'));
            }
        }

        return $this->render('deletepoll', ['model' => $poll]);
    }

    /**
     * Searching through the forum.
     * @return string
     */
    public function actionSearch()
    {
        $searchModel = new Vocabulary;

        if (!$searchModel->load(Yii::$app->request->get(), '')) {
            return $this->redirect(['forum/advanced-search']);
        }

        return $this->render('search', [
            'dataProvider' => $searchModel->search(),
            'query' => $searchModel->query,
        ]);
    }

    /**
     * Advanced searching through the forum.
     * @return string
     * @since 0.8
     */
    public function actionAdvancedSearch()
    {
        $dataProvider = null;
        $list = [];

        $model = new SearchForm;
        $model->setParams();

        if ($model->nextPage) {
            $dataProvider = $model->searchAdvanced();
        } else {
            $categories = Category::find()->orderBy(['name' => SORT_ASC]);
            $forums = Forum::find()->orderBy(['name' => SORT_ASC]);

            foreach ($categories->each() as $cat) {
                $catlist = [];
                foreach ($forums->each() as $for) {
                    if ($for->category_id == $cat->id) {
                        $catlist[$for->id] = '|-- ' . Html::encode($for->name);
                    }
                }
                $list[Html::encode($cat->name)] = $catlist;
            }

            if ($model->load(Yii::$app->request->post()) && $model->validate()) {
                if (empty($model->query) && empty($model->author)) {
                    $this->error(Yii::t('podium/flash', "You have to enter words or author's name first."));
                } else {
                    $stop = false;
                    if (!empty($model->query)) {
                        $words = explode(' ', preg_replace('/\s+/', ' ', $model->query));
                        $checkedWords = [];
                        foreach ($words as $word) {
                            if (mb_strlen($word, 'UTF-8') > 2) {
                                $checkedWords[] = $word;
                            }
                        }
                        $model->query = implode(' ', $checkedWords);
                        if (mb_strlen($model->query, 'UTF-8') < 3) {
                            $this->error(Yii::t('podium/flash', 'You have to enter word at least 3 characters long.'));
                            $stop = true;
                        }
                    }
                    if (!$stop) {
                        $dataProvider = $model->searchAdvanced();
                    }
                }
            }
        }

        return $this->render('search', [
            'model' => $model,
            'list' => $list,
            'dataProvider' => $dataProvider,
            'query' => $model->query,
            'author' => $model->author,
        ]);
    }

    /**
     * Voting in poll.
     * @return array
     * @since 0.5
     */
    public function actionPoll()
    {
        if (!Yii::$app->request->isAjax) {
            return $this->redirect(['forum/index']);
        }

        $data = [
            'error' => 1,
            'msg' => Html::tag('span',
                Html::tag('span', '', ['class' => 'glyphicon glyphicon-warning-sign'])
                . ' ' . Yii::t('podium/view', 'Error while voting in this poll!'),
                ['class' => 'text-danger']
            ),
        ];

        if ($this->module->user->isGuest) {
            $data['msg'] = Html::tag('span',
                Html::tag('span', '', ['class' => 'glyphicon glyphicon-warning-sign'])
                . ' ' . Yii::t('podium/view', 'Please sign in to vote in this poll'),
                ['class' => 'text-info']
            );
            return Json::encode($data);
        }

        $pollId = Yii::$app->request->post('poll_id');
        $votes = Yii::$app->request->post('poll_vote');

        if (is_numeric($pollId) && $pollId > 0 && !empty($votes)) {
            /* @var $poll Poll */
            $poll = Poll::find()->where([
                'and',
                ['id' => $pollId],
                [
                    'or',
                    ['>', 'end_at', time()],
                    ['end_at' => null]
                ]
            ])->limit(1)->one();
            if (empty($poll)) {
                $data['msg'] = Html::tag('span',
                    Html::tag('span', '', ['class' => 'glyphicon glyphicon-warning-sign'])
                    . ' ' . Yii::t('podium/view', 'This poll is not active.'),
                    ['class' => 'text-danger']
                );
                return Json::encode($data);
            }

            $loggedId = User::loggedId();

            if ($poll->getUserVoted($loggedId)) {
                $data['msg'] = Html::tag('span',
                    Html::tag('span', '', ['class' => 'glyphicon glyphicon-info-sign'])
                    . ' ' . Yii::t('podium/view', 'You have already voted in this poll.'),
                    ['class' => 'text-info']
                );
                return Json::encode($data);
            }

            $checkedAnswers = [];
            foreach ($votes as $vote) {
                if (!$poll->hasAnswer((int)$vote)) {
                    $data['msg'] = Html::tag('span',
                        Html::tag('span', '', ['class' => 'glyphicon glyphicon-warning-sign'])
                        . ' ' . Yii::t('podium/view', 'Invalid poll answer given.'),
                        ['class' => 'text-danger']
                    );
                    return Json::encode($data);
                }
                $checkedAnswers[] = (int)$vote;
            }
            if (empty($checkedAnswers)) {
                $data['msg'] = Html::tag('span',
                    Html::tag('span', '', ['class' => 'glyphicon glyphicon-warning-sign'])
                    . ' ' . Yii::t('podium/view', 'You need to select at least one answer.'),
                    ['class' => 'text-warning']
                );
                return Json::encode($data);
            }
            if (count($checkedAnswers) > $poll->votes) {
                $data['msg'] = Html::tag('span',
                    Html::tag('span', '', ['class' => 'glyphicon glyphicon-warning-sign'])
                    . ' ' . Yii::t('podium/view', 'This poll allows maximum {n, plural, =1{# answer} other{# answers}}.', ['n' => $poll->votes]),
                    ['class' => 'text-danger']
                );
                return Json::encode($data);
            }
            if ($poll->vote($loggedId, $checkedAnswers)) {
                $data = [
                    'error' => 0,
                    'votes' => $poll->currentVotes,
                    'count' => $poll->votesCount,
                    'msg' => Html::tag('span',
                        Html::tag('span', '', ['class' => 'glyphicon glyphicon-ok-circle'])
                        . ' ' . Yii::t('podium/view', 'Your vote has been saved!'),
                        ['class' => 'text-success']
                    ),
                ];
            }
        }
        return Json::encode($data);
    }

    /**
     * Editing the poll of given category ID, forum ID, thread ID and own ID.
     * @param int $cid category ID
     * @param int $fid forum ID
     * @param int $tid thread ID
     * @param int $pid poll ID
     * @return string|Response
     * @since 0.5
     */
    public function actionEditpoll($cid = null, $fid = null, $tid = null, $pid = null)
    {
        $poll = Poll::find()->joinWith('thread')->where([
            Poll::tableName() . '.id' => $pid,
            Poll::tableName() . '.thread_id' => $tid,
            Thread::tableName() . '.category_id' => $cid,
            Thread::tableName() . '.forum_id' => $fid,
        ])->limit(1)->one();
        if (empty($poll)) {
            $this->error(Yii::t('podium/flash', 'Sorry! We can not find the poll you are looking for.'));
            return $this->redirect(['forum/index']);
        }

        if ($poll->thread->locked == 1 && !User::can(Rbac::PERM_UPDATE_THREAD, ['item' => $poll->thread])) {
            $this->info(Yii::t('podium/flash', 'This thread is locked.'));
            return $this->redirect(['forum/thread',
                'cid' => $poll->thread->forum->category->id,
                'fid' => $poll->thread->forum->id,
                'id' => $poll->thread->id,
                'slug' => $poll->thread->slug
            ]);
        }
        if ($poll->author_id != User::loggedId() && !User::can(Rbac::PERM_UPDATE_THREAD, ['item' => $poll->thread])) {
            $this->error(Yii::t('podium/flash', 'Sorry! You do not have the required permission to perform this action.'));
            return $this->redirect(['forum/thread',
                'cid' => $poll->thread->forum->category->id,
                'fid' => $poll->thread->forum->id,
                'id' => $poll->thread->id,
                'slug' => $poll->thread->slug
            ]);
        }
        if ($poll->votesCount) {
            $this->error(Yii::t('podium/flash', 'Sorry! Someone has already voted and this poll can no longer be edited.'));
            return $this->redirect(['forum/thread',
                'cid' => $poll->thread->forum->category->id,
                'fid' => $poll->thread->forum->id,
                'id' => $poll->thread->id,
                'slug' => $poll->thread->slug
            ]);
        }

        foreach ($poll->answers as $answer) {
            $poll->editAnswers[] = $answer->answer;
        }

        $postData = Yii::$app->request->post();
        if ($poll->load($postData)) {
            if ($poll->validate()) {
                if ($poll->podiumEdit()) {
                    $this->success(Yii::t('podium/flash', 'Poll has been updated.'));
                    return $this->redirect(['forum/thread',
                        'cid' => $poll->thread->forum->category->id,
                        'fid' => $poll->thread->forum->id,
                        'id' => $poll->thread->id,
                        'slug' => $poll->thread->slug
                    ]);
                }
                $this->error(Yii::t('podium/flash', 'Sorry! There was an error while updating the post. Contact administrator about this problem.'));
            }
        }
        return $this->render('editpoll', ['model' => $poll]);
    }
}