Programming With Yii2: Building Community With Voting, Comments, and Sharing

In this Programming With Yii2 series, I’m guiding readers in use of the Yii2 Framework for PHP. You may also be interested in my Introduction to the Yii Framework, which reviews the benefits of Yii and includes an overview of what’s new in Yii 2.x.

Programming With Yii2: Building Community With Voting, Comments, and Sharing

Programming With Yii2: Building Community With Voting, Comments, and Sharing

Introduction

In today’s tutorial, I’m going to show you how to extend Yii to easily mimic a site like Reddit with voting, comments, and sharing.

Recently, I’ve been working on creating my own personal extension of the great Yii advanced template. The template provides built-in user registration and authentication and multiple sites for front-end and administrative websites.

I built some my latest Twitter API episodes on the early version of this platform, following friends on behalf of users and analyzing our followers. The site I described in those, Twixxr, forms the foundation of my Yii customization work.

So adding core functionality like voting, comments and sharing makes so much sense. As you expand your Yii codebase with these kinds of features, building new sites becomes faster, easier and increasingly powerful.

Getting Started

I’m going to walk you through using three Yii2 plugins:

They make it relatively fast and easy to build a powerful social community on Yii2.

I’ve created a model called Item which represents an object that you want users to vote on, comment on, and share.

Frankly, after building the item pages with these features in my platform, I felt more impressed than ever with Yii… more impressed than I’ve been to date even building my startup series. You can do so much with this framework.

Let’s dig in.

Installing the Extensions

First, let’s add all three extensions to composer.json at once:

{
    "name": "yiisoft/yii2-app-advanced",
    "description": "Yii 2 Advanced Project Template",
    "keywords": ["yii2", "framework", "advanced", "project template"],
    "homepage": "http://www.yiiframework.com/",
    "type": "project",
    "license": "BSD-3-Clause",
    "support": {
        "issues": "https://github.com/yiisoft/yii2/issues?state=open",
        "forum": "http://www.yiiframework.com/forum/",
        "wiki": "http://www.yiiframework.com/wiki/",
        "irc": "irc://irc.freenode.net/yii",
        "source": "https://github.com/yiisoft/yii2"
    },
    "minimum-stability": "stable",
    "require": {
        "php": ">=5.4.0",
        "yiisoft/yii2": ">=2.0.10",
        "yiisoft/yii2-bootstrap": "*",
        "yiisoft/yii2-swiftmailer": "*",
        "yiisoft/yii2-authclient": "~2.1.0",
        "google/apiclient": "1.0.*@beta",
        "machour/yii2-google-apiclient":"@dev",
        "machour/yii2-google-gmail": "@dev",
        "ruskid/yii2-stripe": "dev-master",
        "2amigos/yii2-disqus-widget":"~1.0",
        "abraham/twitteroauth":"*",
        "codeception/codeception":"*",
        "notamedia/yii2-sentry": "^1.1",
        "chiliec/yii2-vote": "^4.0",
        "yiidoc/yii2-redactor": "*",
        "kartik-v/yii2-social": "@dev"

Then run composer update.

Adding Voting

Vladimir Babin is Chiliec, and I very much like the way he and others have collaborated to create this plugin. All the basic features that you want are included, and you can easily customize it, specifically by overriding the view. They have great documentation and keep it well updated too.

Here’s a helpful animated gif of the plugin’s default features which they host on GitHub. I’ve posted a static image below (ThemeKeeper Tuts+ doesn’t support gifs in our tutorials).

Programming With Yii2: Building Community With Voting, Comments, and Sharing

Of course, I decided to customize the view and eliminate down votes, and it was fairly easy.

Configuration

Next, we add the voting plugin to /active/config/main.php so that it’s loaded everywhere in bootstrap and configured for our application:

return [
    'id' => 'app-active',
    'basePath' => dirname(__DIR__),
    'bootstrap' => ['chiliecvotecomponentsVoteBootstrap',
        'log','commoncomponentsSiteHelper'],
    'modules' => [
    ...
    'vote' => [
      'class' => 'chiliecvoteModule',
      // show messages in popover
      'popOverEnabled' => true,
      // global values for all models
      // 'allowGuests' => true,
      // 'allowChangeVote' => true,
      'models' => [
          1 => [
              'modelName' => activemodelsItem::className(),
              'allowGuests' => false,
          ],
          // example declaration of models
          // commonmodelsPost::className(),
          // 'backendmodelsPost',
          // 2 => 'frontendmodelsStory',
          // 3 => [
          //     'modelName' => backendmodelsMail::className(),
          //     you can rewrite global values for specific model
          //     'allowGuests' => false,
          //     'allowChangeVote' => false,
          // ],
      ],
      ],        

You can see that I’ve turned off guest voting so that people are required to sign up to vote on items.

Database Integration

Next, you have to run the database migration to create tables that track the votes.

$ php yii migrate/up [email protected]/chiliec/yii2-vote/migrations

It’s important to remember to run this migration when installing your product server! It’s quite easy to forget.

Displaying the Voting Widget

My item model is part of a collection model called Topic, so you can find the partial view for my voting widget in /views/topic/_item.php:

<?php
use yiihelpersHtml;
use yiihelpersHtmlPurifier;
use dosamigosdisqusCommentsCount;
use kartiksocialTwitterPlugin;
use kartiksocialFacebookPlugin;
use yiihelpersStringHelper;

?>
<div class="item_row row">
  <div class="col-xs-1 col-md-1 col-lg-1">
<?= chiliecvotewidgetsVote::widget([
    'model' => $model,
    // optional fields
     'showAggregateRating' => false,
]);?>
  </div>

Topic index calls displays a grid which displays _item.php as a row. I didn’t want to display a rating, just the positive vote totals, so I set that to false.

To override the view, I created /views/vote/vote.php:

<div class="vote-row text-center" id="vote-<?=$modelId?>-<?=$targetId?>" data-placement="top" data-container="body" data-toggle="popover">
    <span  class="glyphicon glyphicon-chevron-up" onclick="vote(<?=$modelId?>, <?=$targetId?>, 'like'); return false;" style="cursor: pointer;"></span><br /><span id="vote-up-<?=$modelId?>-<?=$targetId?>"><?=$likes?></span>    
    <div id="vote-response-<?=$modelId?>-<?=$targetId?>">
        <?php if ($showAggregateRating) { ?>
            <?=Yii::t('vote', 'Aggregate rating')?>: <?=$rating?>
        <?php } ?>
    </div>
</div>
<div itemprop="aggregateRating" itemscope itemtype="http://schema.org/AggregateRating">
    <meta itemprop="interactionCount" content="UserLikes:<?=$likes?>"/>
    <meta itemprop="interactionCount" content="UserDislikes:<?=$dislikes?>"/>
    <meta itemprop="ratingValue" content="<?=$rating?>"/>
    <meta itemprop="ratingCount" content="<?=$likes+$dislikes?>"/>
    <meta itemprop="bestRating" content="10"/>
    <meta itemprop="worstRating" content="0"/>
</div>

Not a lot of plugins make overriding so easy.

I removed the vote down icon and changed the vote up icon to a chevron. Here’s what it looks like now:

Programming With Yii2: Building Community With Voting, Comments, and Sharing

I know this seems like a lot of layers, but it actually didn’t take too long to make it work.

Adding Disqus Comments

Next, I created a Disqus site for the upcoming site, ActiveTogether.org, which will be available for you to see these features in action by the time you read this. Thus, the Disqus site shortname is ‘active-together’.

I began using 2Amigos’ widget before I integrated Kartik’s social extension (discussed below), which also offers Disqus comments.

Creating a Unique Identifier for Each Comment Board

Whenever a new Item is created, the Item::beforeSave() action creates a unique identifier for Disqus to link comments too. You can also rely on the URL of a page, but this is more predictable generally.

In other words, Disqus collates all comments for each item separately, and that helps makes up each item’s comment board.

public function beforeSave($insert)
      {
          if (parent::beforeSave($insert)) {
            if ($insert) {
              $this->identifier = Yii::$app->security->generateRandomString(8);
              $this->site_id = Yii::$app->params['site']['id'];
            }
          }
          return true;
      }

Displaying the Comments

Then, the comments board is easily displayed at the bottom of the Item view in /active/views/Item.php:

<?php

use yiihelpersHtml;
use yiihelpersHtmlPurifier;
use yiihelpersUrl;
use dosamigosdisqusComments;
...
<?= Comments::widget([
    'shortname' => 'active-together',
    'identifier'=>$model->identifier,
    ]); ?>

Notice how the widget needs the shortname and the identifier to provide Disqus for the comments.

Here’s an example of what the comments board looks like:

Programming With Yii2: Building Community With Voting, Comments, and Sharing

The Index View With Comment Counts

2Amigos also leverages the Disqus JavaScript libraries for displaying comment counts. But there are a few pieces to put together to make this happen.

First, I created a jQuery script to request an item’s comment counts. When there are lots of items on a page, you need to request it with reset: true;:

$(document).ready(function(){
  DISQUSWIDGETS.getCount({reset: true});  
});

Then I created a TopicAsset.php file to load the .js file:

<?php
namespace activeassets;
use yiiwebAssetBundle;

class TopicAsset extends AssetBundle
{
    public $basePath = '@webroot';
    public $baseUrl = '@active';
    public $css = [

    ];
    public $js = [
      'js/topic.js',
    ];
    public $depends = [
        'yiiwebYiiAsset',
        'yiibootstrapBootstrapAsset',
    ];

Then, the /active/views/Topic.php file registers the TopicAsset bundle:

<?php

use yiihelpersHtml;
use yiigridGridView;
use yiiwidgetsBreadcrumbs;
use commonwidgetsAlert;
use activeassetsTopicAsset;
TopicAsset::register($this);

Next, each _item.php partial includes a comment count:

<p><?= $this->render('_social', ['model' => $model,
    'includeCommentCount'=>true]);?></p>

And the _social partial displays it like this using each Item->identifier:

<li class="share_adjust_vert"><?= Html::a(Yii::t('active','Comments')
    ,['/item/'.$model->slug.'#disqus_thread'],
    ['data-disqus-identifier'=>$model->identifier]) ?>
    <?= CommentsCount::widget([
    'shortname' => 'active-together',
    'identifier' => $model->identifier,
  ]);
  ?>

In order for Disqus to find where to update elements with comment counts, each link must end with #disqus_thread.

Here’s what that page looks like. Each item has a distinct comment count loaded by referencing its identifier:

Programming With Yii2: Building Community With Voting, Comments, and Sharing

Let’s move on to those social sharing buttons you’ve been seeing.

Adding Social Sharing

Kartik’s done a great job with his social widget building a basic configuration for connection to a number of social companies like Twitter, Disqus, and Facebook. For now, I’m only using the Facebook share button. Twitter’s share button doesn’t have very good aesthetics, so I replaced it with an HTML web intents link.

Here’s my code for the pair of buttons beside the comments count in /active/views/topic/_social.php:

</li>
  <li class="share_adjust_vert"><a class="twitter-share"
    href="https://twitter.com/intent/tweet?
        text=<?= urlencode($model->title); ?>
        &url=<?= urlencode(Url::canonical());?>
        &via=<?= Yii::$app->params['site']['twitter_account']?>
        "><img src="<?= Url::to(Url::home(true).'/images/social/twitter_icon.png');
        ?>"> Tweet</a></li>
<li><?= FacebookPlugin::widget
        (['type'=>FacebookPlugin::SHARE,
        'settings' => ['dataSize'=>'small',
        'class'=>"fb_iframe_widget"]]); ?></li>
</ul>

Seems simple, except that vertically aligning Facebook’s widget requires some CSS adjustments. In /active/views/topic/_grid.php, I placed this adjustment:

<style media="screen" type="text/css">
.fb_iframe_widget span
{
    vertical-align: baseline !important;
}
</style>

It has to come after the other CSS files load.

And, in the site.css file, I placed this to get the precise look I wanted:

.share_adjust_vert {
  margin-top:-1px;
  font-size:90%;
  vertical-align: top;
}

Wrapping Up

Honestly, I’m so excited at how easy it was to use Yii and essentially create a mini social clone. These are great plugins for a great framework, and generally Yii’s developers and its community of plugin developers are responsive on GitHub with questions and issues.

I hope you are eager to check out ActiveTogether and try out this framework for yourself.

If you have any questions or suggestions, please post them in the comments. If you’d like to keep up on my future ThemeKeeper Tuts+ tutorials and other series, please visit my instructor page or follow @lookahead_io. Definitely check out my startup series and Meeting Planner.

Related Links