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

This time we will take a look at the current state of the gallery section.


I’ve opted to use a Flex and Javascript combination for the image uploading which might make this section of the project interesting for other people than ZF/Smarty geeks. Due to that fact most of the markup and actionscript for this piece is a separate article called: Multiple uploads with jQuery and Flex or Flash.

The gallery is currently implemented with a simple file structure that looks like this:

site_url/gallery/user_id
site_url/gallery/user_id/thumbs

We will store the big pictures in the main gallery folder and the thumbnails in the thumbs folder. The database is not involved at all. However I did opt for storing configuration data in a table that looks like this at the moment:

CREATE TABLE `com_gallery` (
  `max_w` bigint(12) NOT NULL,
  `max_h` bigint(12) NOT NULL,
  `max_s` bigint(12) NOT NULL,
  `thumb_w` int(5) NOT NULL,
  `thumb_h` int(5) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO `com_gallery` (`max_w`, `max_h`, `max_s`, `thumb_w`, `thumb_h`) VALUES (500, 500, 1048576, 100, 100);

It’s pretty self explanatory, max_s will store the maximum allowed size of an image in bytes. The reason I chose a database solution instead of a config file is that I’ve decided that only the core functionality belongs in the config file and the gallery is not a core component. Yes it’s Joomla style yet again.

Most of the logic is in our new GalleryController which currently is responsible for two main actions. It will generate a page used for uploading images to the gallery and a page to display the user’s gallery. Let’s take a look at the upload logic first:

class GalleryController extends ExtController{
	
	function init(){
		parent::init();
		$this->name = "gallery";
		$this->info = Common::loadModel('gallery')->fetchAll()->current();
		$this->setDirs($this->gs->usr_info['id']);
		parent::finishInit();
	}
	
	function setDirs($id){
		$this->dir = "gallery/$id/";
		$this->thumb_dir = $this->dir."thumbs/";
	}
	
	function upload($params){
		$this->chkSessRdr();
		$this->smarty->assign('max_size', $this->info->max_s);
		$conf = $this->getZendConfSection('paths');
		$base_path = "http://{$conf->site_url}{$this->base_url}/gallery";
		$this->smarty->assign('upload_script', "$base_path/save/PHPSESSID/{$_REQUEST['PHPSESSID']}");
		$this->smarty->assign('get_thumb_url', "$base_path/newThumbs/");
		$this->commonThumbs();
		return $this->fetch('gallery_upload.tpl');
	}
	.
	.
	.
function commonThumbs(){
  $thumbs = $this->getThumbs(); 
  $this->smarty->assign_by_ref("thumbs", $thumbs);
  $this->smarty->assign("thumb_dir", $this->thumb_dir);
  
}

function getThumbs(){
  if(is_dir($this->thumb_dir)){
    return Common::listDir($this->thumb_dir);;
  }else{
    return array();
  }
}

function newThumbsAction(){
  $this->commonThumbs();
  echo $this->fetch('gallery_thumbs.tpl');
  exit;
}

We start by loading the above mentioned configuration information in init(). Then we setup our current directories with the id of the user owning the session in this case. Since the last part I have created a convenience function to reduce code in the form of getZendConfSection():

function getZendConfSection($section){
  $registry 	= Zend_Registry::getInstance();
  $config_file = $registry['config_file'];
  $conf = new Zend_Config_Ini($config_file, $section);
  return $conf;
}

Anyway, the above upload function is responsible for generating the markup that is covered by the Flex/Flash article mentioned in the beginning of this tutorial. As you can see newThumbsAction() is the Ajax function called in that markup.

Let’s move on to the function that the Shockwave is calling in order to upload each image:

function saveAction(){
  
  if(!is_dir($this->dir)){
    mkdir($this->dir);
    mkdir($this->thumb_dir);
    chmod($this->dir, 0777);
    chmod($this->thumb_dir, 0777);
  }
  
  $filename_only = uniqid().".jpg";
  $filename = $this->dir.$filename_only;
  $thumbname = $this->thumb_dir.$filename_only;
  
  $move_result = move_uploaded_file($_FILES["Filedata"]["tmp_name"], $filename);
  $result = Common::reScaleImage($filename, $this->info->max_w, $this->info->max_h);
  
  copy($filename, $thumbname);
  $result2 = Common::reScaleImage($thumbname, $this->info->thumb_w, $this->info->thumb_h);
  
  if(!$result){
    unlink($filename);
    unlink($thumbname);
  }else{
    chmod($filename, 0777);
    chmod($thumbname, 0777);
  }
  
  exit;
}

The chmodding at the top should be unnecessary since mkdir can be called like this: mkdir($this->dir, 0777). However, that would not work with our current Samba configuration we are using to access the Ubuntu server here. Don’t ask me why. After setting up this function I thought I was home free but oh so wrong I was. Note that I make use of session data in the form of the current user’s unique id when using $this->dir above.

No problem you might think since I append the session id to the url used to call saveAction() with. I did too, but I was wrong. It seems the Zend Framework won’t pick up on this without a little help. Why can nothing ever be simple? I was fortunate and quickly found Rob’s Flash upload in Symfony article before I got too panicked. Empowered by the basic aha feeling I got from that piece I was able to quickly hack ZF in a similar way:

class ExtSession extends Zend_Session{
	public static function start($options = false){
    if(strpos($_SERVER['REQUEST_URI'],'PHPSESSID') !== false){
      $tok = strtok($_SERVER['REQUEST_URI'], "/");
      while($tok !== false){
        if($tok == 'PHPSESSID'){
          session_id( strtok("/") );
          break;
        }
        $tok = strtok("/");
      }
    }
    parent::start($options);
  }
}

We simply check our URL for PHPSESSID, and if we find it we start the session with that id instead. Calling my extended session instead of the original Zend Session in the bootstrap file solved the problem.

Let’s move on to the viewing logic:

function view($params){
  $this->chkSessRdr();
  if(!empty($params['id']))
    $this->setDirs($params['id']);
  $this->smarty->assign("gallery_dir", $this->dir);
  $this->commonThumbs();
  return $this->fetch('gallery_view.tpl');
}

If we explicitly pass an id to this function we will use that id instead of currently logged in user’s.

The markup:

{config_load file="$config_file"}
<script src="{$baseUrl}/js/jquery.js" language="JavaScript"></script>
<script src="{$baseUrl}/js/scrollTo.js" language="JavaScript"></script>

<script type="text/javascript">
	  // <![CDATA[
	  var galleryDir 	= "{$baseUrl}"+"/{$gallery_dir}";
	  {literal}
	  function showMainPic(pic){
	  	var inner_html = '<img src="'+galleryDir+pic+'"/>';
                $("#mainPic").html(inner_html);
                $.scrollTo("#mainPic", {speed:500});
	  }
	  {/literal}
	  // ]]>
</script>

<div id="thumbs">
	{if !empty($thumbs)}
		{foreach from=$thumbs item=thumb}
			<a href="javascript:showMainPic('{$thumb}')">
				<img class="gallery_thumb" src="{$baseUrl}/{$thumb_dir}{$thumb}"/>
			</a>
		{/foreach}
	{else}
		{#no_pics#}
	{/if}
</div>
<div id="mainPic"></div>
.gallery_thumb{}

.gallery_thumb:hover{
  filter:alpha(opacity=70);
-moz-opacity: 0.4;
}

I use the jQuery scrollTo plugin yet again. The reason being that if a user loads a vertically big image we will scroll down automatically so that the image is shown completely. Being able to automatically scroll the main window this easily is awesome because it’s one of the most helpful usability features one can implement. No one likes to scroll so when we have content that the user obviously want to access and there might be some scrolling involved it makes no sense to not implement automatic scrolling. And when we have ease in and out like we have with the scrollTo plugin we also avoid the jarring and confusing feeling that it is possible to get with instantaneous jumps.

That was it for this time, next time we will take a look at the blogging component which is the second major requirement we have for the community part of this project.

Related Posts

Tags: , , , , , ,