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: , , ,