Writing a CMS/Community with Smarty and the Zend Framework: Part 7


Since XOOPS didn’t do it for me when it comes to this project I have started with the blog component. As it turns out, a blog is more complicated than you would think. Therefore this part will not cover the whole component, only as far as I’ve gotten at the moment. Covering the whole process in one piece would make it a very long piece indeed.

I’ve also separated the template stuff into it’s own piece as there might be people interested in some of the stuff there but not in the PHP back end. Check it out:

Validation and AJAX with jQuery

You could have that article in a new tab and switch tabs to get the whole picture.

Let’s move on, first the SQL:

CREATE TABLE `com_blog` (
  `id` bigint(12) NOT NULL auto_increment,
  `article` text NOT NULL,
  `post_date` bigint(15) NOT NULL,
  `headline` varchar(250) NOT NULL,
  `user_id` bigint(12) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

CREATE TABLE `com_blog_categories` (
  `id` bigint(12) NOT NULL auto_increment,
  `user_id` bigint(12) NOT NULL,
  `headline` varchar(100) NOT NULL,
  `description` varchar(250) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

CREATE TABLE `com_blog_connect` (
  `id` bigint(15) NOT NULL auto_increment,
  `article_id` bigint(12) NOT NULL,
  `category_id` bigint(12) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

INSERT INTO `db_menuitems` (`id`, `db_menu_id`, `item_class`, `item_link`,
 `item_image`, `image_alt`, `link_text`, `position`, `item_imageover`, `nosess_show`,
  `sess_show`) VALUES (24, 4, '', 'index/d/c/blog/f/writeArticle', '', '', 'Write
   Article', 4, '', 0, 1);

INSERT INTO `db_menuitems` (`id`, `db_menu_id`, `item_class`, `item_link`,
 `item_image`, `image_alt`, `link_text`, `position`, `item_imageover`, `nosess_show`,
  `sess_show`) VALUES (25, 4, '', 'index/d/c/blog/f/view', '', '', 'View Blog', 5,
   '', 0, 1);

The first table is the main table that will hold all the posts, the category table will hold all the categories of course. The connection table will link a certain amount of categories to a single article. If we hadn’t had the user id in the categories table I believe we would have had to call it tags instead.

The inserts at the end are two new menu items that provide the current blog functionality. Let’s examine the links, first we have a controller called blog, and a function called writeArticle, let’s go there:

class BlogController extends ExtController{
	
	function init(){
		parent::init();
		$this->name = "blog";
		$this->obj 	= $this->loadModel($this->name);
		$this->usrid = $this->gs->usr_info['id'];
		parent::finishInit();
	}
	
	function writeArticle($params){
		$this->assignCategories();
		return $this->fetch('blog_write.tpl');
	}
		
	function assignCategories(){
		$categories = $this->loadModel('blogcategories')->fetchToArr($this->usrid, 'user_id');
		$this->smarty->assign('categories', $categories);
	}
.
.
.

We simply fetch this user’s categories, assign them to Smarty and return the result. The result will contain the markup where the user can write a new article. After an article is written it has to be saved:

function saveArticle(){
  
  $this->chkSessRdr();
  $post 		    = $this->_request->getParams();
  $post['post_date'] 	= time();
  $post['user_id']	  = $this->usrid;
  $article_id 		    = $this->obj->insert($post);
  $con_obj 	           = $this->loadModel('blogcatcon');
  $cat_ids 	            = explode(" ", trim($post['category_selections']));
  
  foreach($cat_ids as $cat_id){
    if(!empty($cat_id)){
      $insert_arr = array("article_id" => $article_id, "category_id" => $cat_id);
      $con_obj->insert($insert_arr);
    }
  }
  
  return $this->view();
}

As you can see I don’t filter the $post before I insert it, there is a little bit of convention over configuration magic going on here. I make sure I name input elements after their database field names, that way I can use an insert function that looks like this in ExtModel:

function insert(&$insert_arr){
  $clean_arr = array_intersect_key($insert_arr, array_combine($this->_cols, $this->_cols));
  return parent::insert($clean_arr);
}

It will simply filter out all the stuff that is not a field in the database table. I can’t remember if I’ve discussed this function before, if that is the case then well, a little bit of repetition never hurt anyone.

Let’s return to saveArticle(), notice how category ids is a space delimited string. We have some Javascript in the markup that updates a hidden field so we can get more than just one category for each article. We proceed with looping through them and inserting them one by one. Since we are trimming the string before we explode it I don’t think we really need to test for empty but a little bit of redundant safety never hurt anyone either, or maybe it did?

There is also an AJAX call that can be made from the interface to create new categories on the fly, it will call saveCategoryAction():

function saveCategoryAction(){
  $headline = $this->_request->getParam("headline");
  if(!empty($headline)){
    $insert_arr = array("headline" => $headline, "user_id" => $this->usrid);
    $new_id = $this->loadModel('blogcategories')->insert($insert_arr);
    $new_cat = array("headline" => $headline, "id" => $new_id);
    $this->smarty->assign('category', $new_cat);
    echo $this->fetch('blog_category.tpl');
  }
  exit;
}

What we have in the markup is a text field and a button, when the button is pressed whatever is entered in the text field will end up in our headline parameter. If we have something that is not empty we will create a new category with it and use the returned id to display this new category.

Back to saving the article, we finish off by returning view():

function view($params = array(), $articles = array()){
  
  $id = $this->getUsrId($params);
  
  if(empty($articles))
    $articles = $this->obj->fetchAll("user_id = $id", "post_date DESC", 10, 0)->toArray();
    
  $categories = $this->loadModel('blogcategories')->fetchAll("user_id = $id")->toArray();
  $this->smarty->assign(array("articles" => $articles, "categories" => $categories, "id" => $id));
  return $this->fetch('blog_view.tpl');
  
}

function getUsrId($params){
  $id = empty($params['user_id']) ? $this->usrid : $params['user_id'];
  return $id;
}

First we make sure we have some kind of id to use when fetching articles, someone else’s or the currently logged in user’s. Anyway, we fetch the ten newest articles plus all the categories created by the user in question and return blog_view.tpl:

{config_load file="$config_file"}
<table>
	<tr>
		<td width="300px">
			{foreach from=$articles item=article}
				<div>
					{$article.headline}<br/>
					{$article.post_date|date_format}
				</div>
				<br/>
				<div>
					{$article.article}
				</div>
			{/foreach}
		</td>
		<td>
			<ul>
			{foreach from=$categories item=category}
				<li>
					<a href="{$baseUrl}/index/d/c/blog/f/getArticlesByCat/cat_id/{$category.id}">{$category.headline}</a>
				</li>
			{/foreach}
			</ul>
		</td>
	</tr>
</table>

Not much to explain here, we use the default Smarty date format. In the future this has to be set in some configuration table that can be changed in a future admin back end. When a user clicks a category link all articles in that category will be displayed:

function getArticlesByCat($params){
  $cur_cat = $this->loadModel('blogcategories')->fetchAll("id = {$params['cat_id']}")->current();
  $articles = $cur_cat->findManyToManyRowset('blog', 'blogcatcon')->toArray();
  return $this->view($params, $articles);
}

Here we use findManyToManyRowset() which brings us to the models:

blog.php:

class Blog extends ExtModel{
	protected $_name 			= 'com_blog';
	protected $_primary 	= 'id';
}

blogcategories.php:

class Blogcategories extends ExtModel{
	protected $_name 			= 'com_blog_categories';
	protected $_primary 	= 'id';
}

blogcatcon.php:

class Blogcatcon extends ExtModel{
	protected $_name 			= 'com_blog_connect';
	protected $_primary 	= 'id';
	
	protected $_referenceMap    = array(
           'category' => array(
                 'columns'           => 'category_id',
                 'refTableClass'     => 'Blogcategories',
                 'refColumns'        => 'id'
           ),
	   'article' => array(
		'columns'           => 'article_id',
		'refTableClass'     => 'Blog',
		'refColumns'        => 'id'
           )
    );
}

The reference map is the configuration that makes findManyToManyRowset() work. This is fairly well documented in the ZF manual so I will not elaborate. That’s it for now, the next time we will continue with implementing viewing other people’s blogs, updating articles and more.

Related Posts

Tags: , ,