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


In this part we will take a look at what happens after a user logs on. We will also revisit quite a lot of code that has been changed.

First of all, part 4 has been updated with new information in a reply from me to a request. You might want to take a look at that.

Let’s begin by looking at the login function in UserController again:

function login($params = false){
	if($params['do'] == 'login'){
		$usr_info = $this->obj->fetchRowToArr(false, false, false, $params);
		if($usr_info != false){
			$this->globalsess->usr_row = $usr_info;
			$this->globalsess->usr_info = $usr_info->toArray();
			$result = $this->extraLoginCheck();
			if($result !== true)
				return $result;
			else{
				$this->setAclLevel($usr_info);
				$this->rdrParams["f"] = "desktop";
				$this->refresh($this->rdrParams);
			}
		}else
			$this->smarty->assign('val_err', 'yes');
	}
	
	return $this->getLoginForm();
}

So, upon successful login we set some member variable called $this->rdrParams[”f”] to “desktop” and then we call a new function called refresh. As you might suspect, the point of the refresh function is to reload the page, but why? Well, since we call all logic with the help of Smarty insert functions and return the results we have no way of affecting the rendering of other objects. In case of a successful login for instance, the menus above the main content area are already rendered, they can not magically change as a result of what happens in the login function. For instance, adding a Logout item to one of the menus is impossible if we do not refresh the whole page.

If we revisit the init function of the UserController:

function init(){
	parent::init();
	$this->name			= 'user';
	$this->obj 			= Common::loadModel($this->name);
	$this->rdrParams	= array("index" => "d", "c" => $this->name);
	if(!empty($this->globalsess->usr_row))
		$this->globalsess->usr_row->setTable($this->obj);
	parent::finishInit();
}

There you have the rest of the rdrParams variable, apparently we are storing a default path to use in case we need to redraw the whole page from within the UserController.

function refresh($params = false){
	$location = "Location: {$this->base_url}";
	if(is_array($params)){
		foreach($params as $key => $value)
			$location .= "/$key/$value";
	}
	header($location);
	exit;
}

So with the information above we see that the URL we use to redraw the page with, will end with index/d/c/user/f/desktop:

function desktop($params = false){
	$this->chkSessRdr($params);
	return $this->fetch('desktop.tpl');
}

function chkSessRdr($params = false){
	if(!$this->chkSess())
		$this->refresh($params);
}

function chkSess($function = '', $params = ''){
	if(empty($this->globalsess->usr_info)){
		if(!empty($function))
			return $this->$function( $params );
		return false;
	}
	return true;
}

So when we call the desktop function after the login we will also call chkSessRdr() which will use chkSess() with the default values. In this case globalsess->usr_info will not be empty because it was set during the login. ChkSess() will therefore return true and no redirection will take place, we will simply return the contents of desktop.tpl:

<table>
	<tr>
		<td>
			{insert name="item" controller="Menu" id="usermenu"}
		</td>
		<td>
			{insert name="item" controller="Article" id="sitenews"}
		</td>
	</tr>
</table>

The menu above will now change based on settings set in the future admin module. At the moment we make do with phpMyAdmin. When the user is logged in we will now display Logout and Desktop links. If the user is logged out we display Register and Login links. From the MenuController:

function filterItems(&$menu_items, $have_session){
	$rarr = array();
	foreach($menu_items as &$item){
		if( ($have_session && $item['sess_show']) || (!$have_session && $item['nosess_show']) )
			$rarr[] = $item;
	}
	return $rarr;
}

function render($params){
	$menu = $this->obj->fetchRowToArr($params['id'], false, 'menu_slug');

	$have_session = empty($this->globalsess->usr_info) ? false : true;
	$menu_items = $menu->findDependentRowset('menuitem', 'db_menu')->toArray();
	$menu_items = $this->filterItems($menu_items, $have_session);
	Common::sort2DAsc($menu_items, 'position');
	
	$this->smarty->assign_by_ref('menu', $menu->toArray());
	$this->smarty->assign_by_ref('menuitems', $menu_items);
	return $this->fetch();
}

The desktop will have a usermenu and display some article with the slug “sitenews”. In my case the usermenu will only have two alternatives so far; edit profile, and view profile. Let’s take a look at what happens if our user chooses to edit her profile:

function update(){
	$this->smarty->assign_by_ref('prepop', $this->globalsess->usr_info);
	return $this->smarty->fetch('user_update_form.tpl');
}

We simply prepopulate a form with the current values and display it:

{config_load file="$config_file"}
{assign var="input_class" value=""}
{assign var="label_class" value=""}
{insert name="val" ctrl="user" field="password" 	val="atleast-6"}
{insert name="val" ctrl="user" field="password2" 	val="pass_again-password"}
{insert name="val" ctrl="user" field="avatar" 		max_width="100" max_height="100" folder="upload"}
<form action="{$baseUrl}/index/d/c/user/f/saveUpdate" method="post" enctype="multipart/form-data">
<table cellpadding="0" cellspacing="0" border="0">
<tr>
	<td class="{$label_class}">
		{#description#}:
	</td>
	<td colspan="2" class="{$input_class}">
		<textarea name="description">{$prepop.description}</textarea>
	</td>
</tr>
<tr>
	<td class="{$label_class}">
		{#avatar#}:
	</td>
	<td class="{$input_class}">
		<input type="file" name="avatar" class="{$input_class}"/>
	</td>
	<td>
		{$val_error.avatar}
	</td>
</tr>
.
.
.

Currently the form has four fields, the about me description, small avatar upload and password one and two.

New stuff:
{insert name=”val” ctrl=”user” field=”avatar” max_width=”100″ max_height=”100″ folder=”upload”}

Let’s revisit insertVal() in UserController:

function insertVal($params){
	if(!empty($params['val'])){
		$validation = $this->validationToArr($params['val']);
		$this->session->fields[$params['field']]['validation'] = $validation;
	}
	
	if(!empty($params['folder'])){
		
		if(empty($params['max_width']) || empty($params['max_height'])){
			echo "Error: An image needs 'max_width' and 'max_height' in pixels.";
			exit;
		}else if(empty($params['folder'])){
			echo "Error: An image needs a 'folder' to upload to.";
			exit;
		}
			
		$this->session->images[$params['field']]['max_width'] 	= $params['max_width'];
		$this->session->images[$params['field']]['max_height'] 	= $params['max_height'];
		$this->session->images[$params['field']]['folder'] 		= $params['folder'];
	}
}

So the new information gets saved in a session variable called “images”. Note the pluralism here, I’m already preparing for the future gallery extension with multiple image uploads. Onwards to saveUpdate() and Co:

function saveUpdate($params = false){
	$post = $this->_request->getParams();
	$result = $this->validatePost($post, $this->session->fields, "insert");
	if($result === true){
		$images = $this->upload($this->session->images);
		if(!empty($images['avatar'])){
			if(!empty($this->globalsess->usr_row->avatar))
				unlink($this->globalsess->usr_row->avatar);
			$this->globalsess->usr_row->avatar = $images['avatar'];
		}
		$this->globalsess->usr_row->setFromArray( $this->intersectPost($post) );
		$this->globalsess->usr_info = $this->globalsess->usr_row->toArray();
		$this->globalsess->usr_row->save();
		return $this->view();
	}else{
		return $this->validationError('update', $post, $result);
	}
}

function intersectPost(&$post){
	return array_intersect_key($post, $this->globalsess->usr_info);
}

function viewProfile(){
	$this->smarty->assign_by_ref('user', $this->globalsess->usr_info);
	return $this->fetch('user_profile.tpl');
}

To get the above logic to work We’ve had to add the following In UserController init() and In the index.php boostrap file:

if(!empty($this->globalsess->usr_row))
	$this->globalsess->usr_row->setTable($this->obj);

Zend_Loader::loadClass('Zend_Db_Table_Row');
Zend_Loader::loadClass('Zend_Db_Table_Row_Abstract');
Zend_Loader::loadClass('Zend_Db_Table_Rowset');
Zend_Loader::loadClass('Zend_Db_Table_Rowset_Abstract');

The above lets us load the user information into a row object with the help of session data, no database fetching in order to re-activate the object is required. This functionality is covered thoroughly in the ZF manual. The reason we keep the user information in a row object in addition to an array is convenience, the row object has a few nifty functions in addition to the save function that could come in handy. Having the information duplicated in an array will let us have easy access to it in other modules where we probably just need to read values. Actually activating the user row object there would be overkill. But damit, saveUpdate() could really benefit from an alias for $this->globalsess->usr_row…

Let’s upload that avatar:

function upload($image_fields){
	$insert_arr = array();
	foreach($image_fields as $key => $value){
		if($_FILES[$key]['name'] != ''){
			$insert_arr[$key] = $value['folder']."/".uniqid().".jpg";
			$move_result = move_uploaded_file($_FILES[$key]['tmp_name'], $insert_arr[$key]);
			if($move_result){
				$result = Common::reScaleImage($insert_arr[$key], $value['max_width'], $value['max_height']);
				if(!$result){
					unlink($insert_arr[$key]);
					$insert_arr[$key] = '';
				}else
					chmod($insert_arr[$key], 0777);
			}else
				$insert_arr[$key] = '';
		}
	}
	return $insert_arr;
}

Update: The image manipulation logic now has its own page, check it to get more info on reScaleImage et al.

We try and move each image in the array and if it works we rescale them according to what was assigned above the update form in the template, by some designer or project manager probably. 100×100 if I remember correctly.

SaveUpdate() shares some logic with save() nowadays:

function validationError($handler, &$post, &$result){
	$this->smarty->assign('prepop', $post);
	$this->smarty->assign('val_error', $result);
	return $this->$handler();
}

That’s the end of this update. In the next part we take a look at the gallery component, there is some really exiting stuff there, multiple uploads with a Shockwave generated with the Flex SDK for instance. Some neat jQuery stuff too.


Related Posts

Tags: , , ,

Posts linking to this article:

links for 2007-12-09
oscmax 2.0 - oscommerce maximized. oscmax v2.0 is a powerful e-commerce/shopping cart web application. there are many advantages to using oscmax as your e-commerce/shopping cart for your web site. it has all the features needed to run a ...

[web] 連結分享
php. the new way to write ajax applications with php. 繼承phpajax 這個類別後,你就可以很方便地產生以ajax 操作的html 元素。 the completely unofficial xdebug.ini. 最完整的xdebug 可在php.ini 設定的項目及說明。 ...

Ecrire un CMS avec Smarty et le Zend Framework
ProDevTips publie un dossier en 5 volets pour mettre en place un système de gestion de contenu à l'aide de Smarty et du Zend Engine. Le tutoriel, destiné à ceux qui connaissent déjà le zend framework et Smarty, réalise la gestion du ...

ProDevTips.com: Writing a CMS/Community with Smarty and the Zend ...
The ProDevTips.com website has posted part five of their look at creating a cms/community website by combining Smarty and the Zend Framework. In this part we will take a look at what happens after a user logs on. ...

Subscribe with Google Reader