Build a forum with CakePHP (part 4)

In this tutorial, we will add functions for viewing topics of the forum, adding topic, posting replies. At the end of this tutorial, you will also be able to download the full source code of this forum.

Show/View/Add topics

Now we have completed the section for forum index page. However the whole forum does not really work yet.

The first thing we are going to build in this tutorial is the function for showing/viewing/adding topics. These functions are handled by the same controller (TopicsController).

Copy following to "app/Controller/TopicsController.php":

<?php
App::uses('AppController', 'Controller');
 
class TopicsController extends AppController {
 
    public $components = array('Paginator');
     
    public function beforeFilter() {
        $this->Auth->allow('index','view');
    }
     
    public function index($forumId=null) {
        if (!$this->Topic->Forum->exists($forumId)) {
            throw new NotFoundException(__('Invalid forum'));
        }
         
        $forum = $this->Topic->Forum->read(null,$forumId);
        $this->set('forum',$forum);
         
        $this->Paginator->settings['contain'] = array('User','Post'=>array('User'));
        $this->set('topics', $this->Paginator->paginate());
    }
     
    public function add() {
        $forums = $this->Topic->Forum->find('list');
         
        if ($this->request->is('post')) {
            $this->request->data['Topic']['user_id'] = $this->Auth->user('id');
            if ($this->Topic->save($this->request->data)) {
                $this->Session->setFlash(__('Topic has been created'));
                $this->redirect('/');
            }
        }
         
        $this->set('forums',$forums);
    }
 
    public function view($id) {
        if (!$this->Topic->exists($id)) {
            throw new NotFoundException(__('Invalid topic'));
        }
         
        $topic = $this->Topic->read(null,$id);
        $forum = $this->Topic->Forum->read(null,$topic['Topic']['forum_id']);
        $this->Paginator->settings['Post']['conditions'] = array('Post.topic_id'=>$topic['Topic']['id']);
        $this->Paginator->settings['Post']['contain'] = array('User');
        $this->Paginator->settings['Post']['order'] = array('Post.id'=>'DESC');
         
        $this->set('topics', $this->Paginator->paginate('Post'));
         
        $this->set('topic', $topic);
        $this->set('forum', $forum);
        $this->set('posts', $this->Paginator->paginate('Post'));
    }
 
}

Let us take a look at each function:

  1. public function beforeFilter(): In this function, we tell Auth component to restrict access to "add" page only. Since only "add" topic page requires user to log in.
  2. public function index(): This function is for the page of showing topics of one particular forum.
  3. ppublic function view(): This function is for the page of viewing the particular topic's content.
  4. public function add(): This function is for the page of adding topics to the forum.

Now let us create view files according to the TopicsController's functions.

"app/View/Topics/index.ctp":

<div class="row">
  <div class="col-lg-12">
        <ol class="breadcrumb">
          <li>
            <?php echo $this->Html->link(__('Forum'),'/')?>
          </li>
          <li class="active">
           <?php echo $forum['Forum']['name']?>
          </li>
        </ol>
  </div>
</div>
 
<div class="row">    
  <div class="col-lg-4">
      <p style="font-weight:bold;">
        <?php
        echo $this->Paginator->counter(
                'Showing {:start} - {:end} of {:count}'
        );
        ?>
      </p>
  </div>
   
  <div class="col-lg-8">
    <p class="text-right">
        <?php echo $this->Html->link(__('Create Topic'),array('action'=>'add'),array('class'=>'btn btn-primary'))?>
    </p>
  </div>
</div> 
 
 
<div class="row">
  <div class="col-lg-12 ">
        <table class="table table-bordered">
            <thead>
                <tr>
                    <th colspan=2>Topic</th>
                    <th>Author</th>
                    <th>Created</th>
                    <th>Posts</th>
                    <th>Activity</th>
                </tr>
            </thead>
             
            <tbody>
                <?php foreach ($topics as $topic): ?>
                <tr>
                    <td>&nbsp;</td>
                    <td>
                        <?php
                        echo $this->Html->link($topic['Topic']['name'],
                                                array('controller'=>'topics','action'=>'view',$topic['Topic']['id']));
                        ?>
                    </td>
                    <td><?php
                        echo $this->Html->link($topic['User']['username'],
                                                array('controller'=>'users','action'=>'profile',$topic['User']['id']));
                        ?>
                    </td>
                    <td><?php
                            echo $this->Time->timeAgoInWords($topic['Topic']['created']);
                        ?>
                    </td>
                    <td>
                       <?php
                        echo count($topic['Post']);
                       ?>
                    </td>
                    <td>
                     <?php
                       if(count($topic['Post'])>0) {
                        $post = $topic['Post'][0];
                        echo $this->Time->timeAgoInWords($post['created']);
                        echo '&nbsp;<small>by</small>&nbsp;';
                        echo $this->Html->link($post['User']['username'],array('controller'=>'users',
                                                                                        'action'=>'profile',
                                                                                        $post['User']['id']));
                       }
                       ?>
                    </td>
                </tr>
                <?php endforeach;?>
            </tbody>
        </table>
        <div class="pull-right">
            <?php
                echo $this->element('paginator');
            ?>
         </div>
</div>
</div>

Now if you click on the topic's title from forum index page, you should be able to see page as below: CapForum forum index

"app/View/Topics/view.ctp":

<div class="row">
    <div class="col-lg-12">
        <ol class="breadcrumb">
            <li>
                <?php echo $this->Html->link(__('Forum'),'/')?>
            </li>
            <li>
                <?php echo $this->Html->link($forum['Forum']['name'],array('controller'=>'topics','action'=>'index',$forum['Forum']['id']))?>
            </li>
            <li class="active">
                <?php echo $topic['Topic']['name'];?>
            </li>
        </ol>
    </div>
</div>
<div class="row">
    <div class="col-lg-8">
        <p class="lead">
        <?php echo $topic['Topic']['content'];?>
        </p>
    </div>
 
    <div class="col-lg-4">
        <p class="text-right">
            <?php echo $this->Html->link(__('Create Topic'),array('action'=>'add'),
                                                            array('class'=>'btn btn-primary'))?>
 
            <?php echo $this->Html->link(__('Post Reply'),array('controller'=>'posts','action'=>'add',$topic['Topic']['id']),
                                                            array('class'=>'btn btn-primary'))?>
        </p>
    </div>
</div>
<div class="row">
    <div class="col-lg-4">
        <p style="font-weight: bold;">
        <?php
                echo $this->Paginator->counter(
                        'Showing {:start} - {:end} of {:count}'
                );
                ?>
        </p>
    </div>
</div>
 
<div class="row">
    <div class="col-lg-12">
        <table class="table table-bordered  ">
 
 
            <tbody>
                <?php
                foreach ($posts as $post) :
                ?>
                    <tr>
                        <td><small>
                            <?php
                                echo $this->Time->timeAgoInWords($post['Post']['created']);
                            ?></small>
                        </td>
                        <td>&nbsp;</td>
                    </tr>
                    </tr>
                    <td width="150px">
                        <p>
                            <?php
                                echo $this->Html->link($post['User']['username'],
                                                        array('controller'=>'users','action'=>'profile',$post['User']['id']));
                            ?>
                        </p>
                        <?php $hash = md5($post['User']['email']);?>
                        <img src="http://www.gravatar.com/avatar/<?php echo $hash;?>?s=100" >
                    </td>
                    <td>
                        <p>
                            <?php echo $post['Post']['content'];?>
                        </p>
                    </td>
                    </tr>
                <?php endforeach;?>
            </tbody>
 
        </table>
        <div class="pull-right">
            <?php
                echo $this->element('paginator');
            ?>
        </div>
        <div class="clearfix"></div>
        <div class="well">
            <h4><?php echo __('Quick Reply');?></h4>
            <?php echo $this->Form->create('Post',array('url'=>array('controller'=>'posts','action'=>'add'),
                                                         'inputDefaults'=>array('label'=>false)));?>
                <div class="form-group">
                    <?php echo $this->Form->textarea('content',array('class'=>'form-control','rows'=>5));?>
                </div>
                <?php echo $this->Form->hidden('topic_id',array('value'=>$topic['Topic']['id']));?>
                <?php echo $this->Form->hidden('forum_id',array('value'=>$forum['Forum']['id']));?>
                <?php echo $this->Form->submit(__('Post Reply'),array('class'=>'btn btn-primary'))?>
            <?php echo $this->Form->end();?>
        </div>
    </div>
</div>

Now clicking on the topic's title from page above, you should be able to see page as below: CapForum

<div class="row">
  <div class="col-lg-12">
        <ol class="breadcrumb">
          <li>
            <?php echo $this->Html->link(__('Forum'),'/')?>
          </li>
          <li class="active">
            <?php echo __('Create Topic');?>
          </li>
        </ol>
  </div>
</div>
 
<div class="row"> 
  <div class="col-lg-12">
    <p class="lead"><?php echo __('Create Topic');?></p>
     
    <div class="well">
        <?php echo $this->Form->create('Topic',array('class'=>'form-horizontal','inputDefaults'=>array('label'=>false)));?>
          <div class="form-group">
            <label for="inputEmail3" class="col-sm-2 control-label">Title</label>
            <div class="col-sm-10">
               <?php echo $this->Form->input('name',array('class'=>'form-control'));?>
            </div>
          </div>
          <div class="form-group">
            <label for="inputPassword3" class="col-sm-2 control-label">Forum</label>
            <div class="col-sm-10">
                      <?php echo $this->Form->input('forum_id',array('options'=>$forums, 'class'=>'form-control'));?>
            </div>
          </div>
         <div class="form-group">
            <label for="inputPassword3" class="col-sm-2 control-label">Content</label>
            <div class="col-sm-10">
                <?php echo $this->Form->textarea('content',array('class'=>'form-control','rows'=>5));?>
            </div>
          </div>
           
          <div class="form-group">
            <div class="col-sm-offset-2 col-sm-10">
              <?php echo $this->Form->submit(__('Create'),array('class'=>'btn btn-primary'))?>
            </div>
          </div>
        </form>
    </div>
         
</div>
</div>

Now clicking on "Create Topic", you should be able to see page as below. The form should also let you create new topic: CapForum

Post reply

Last piece of function we need is to allow users to post reply to the topic. This is handled by PostsController and its view files.

"app/Controller/PostsController.php":

?

<?php
App::uses('AppController', 'Controller');
 
class PostsController extends AppController {
 
    public function add($topicId=null) {
        if ($this->request->is('post')) {
            $this->request->data['Post']['user_id'] = $this->Auth->user('id');
             
            if ($this->Post->save($this->request->data)) {
                $this->Session->setFlash(__('Post has been created'));
                $this->redirect(array('controller'=>'topics','action'=>'view',$this->request->data['Post']['topic_id']));
            }
             
        } else {
            if (!$this->Post->Topic->exists($topicId)) {
                throw new NotFoundException(__('Invalid topic'));
            }
             
            $this->Post->Topic->recursive = -1;
            $topic = $this->Post->Topic->read(null,$topicId);
             
            $this->Post->Forum->recursive = -1;
            $forum = $this->Post->Forum->read(null,$topic['Topic']['forum_id']);
             
            $this->set('topic',$topic);
            $this->set('forum',$forum);
        }
    }
     
}

"app/View/Posts/add.ctp":

<div class="row">
          <div class="col-lg-12">
            <ol class="breadcrumb">
                <li>
                    <?php echo $this->Html->link(__('Forum'),'/')?>
                </li>
                <li>
                    <?php echo $this->Html->link($forum['Forum']['name'],array('controller'=>'topics','action'=>'index',$forum['Forum']['id']))?>
                </li>
                <li>
                    <?php echo $this->Html->link($topic['Topic']['name'],array('controller'=>'topics','action'=>'view',$topic['Topic']['id']))?>
                </li>
                <li class="active">
                    <?php echo __('Post Reply');?>
                </li>
            </ol>
          </div>
</div>
 
<div class="row"> 
          <div class="col-lg-12">
            <p class="lead"><?php echo __('Post Reply');?></p>
             
            <div class="well">
                <?php echo $this->Form->create('Post',array('class'=>'form-horizontal','inputDefaults'=>array('label'=>false)));?>
                  <div class="form-group">
                    <label for="inputPassword3" class="col-sm-2 control-label">Content</label>
                    <div class="col-sm-10">
                        <?php echo $this->Form->textarea('content',array('class'=>'form-control','rows'=>8));?>
                    </div>
                  </div>
                   
                  <div class="form-group">
                    <div class="col-sm-offset-2 col-sm-10">
                       <?php echo $this->Form->submit(__('Post Reply'),array('class'=>'btn btn-primary'))?>
                    </div>
                  </div>
                  <?php echo $this->Form->hidden('topic_id',array('value'=>$topic['Topic']['id']))?>
                  <?php echo $this->Form->hidden('forum_id',array('value'=>$forum['Forum']['id']))?>
                <?php echo $this->Form->end();?>
            </div>
                 
          </div>
 </div>

With the completion above, now CapForum should be a fully working application. By clicking "Post Reply" you should be able to see page as below. CapForum

Download

You can download CapForum's full source code from the link below:

Download

The end

We have completed the series of build a forum with CakePHP. As mentioned in first tutorial, this forum is not product ready. There are plenty of features you can add in. Such as adding a backend module, so admin is able to manage all aspects of the system. You are free to extend this application to your own use.

Hopefully this simple tutorial helped you with your development. If you like our post, please follow us on Twitter and help spread the word. We need your support to continue. If you have questions or find our mistakes in above tutorial, do leave a comment below to let us know.