Zend Framework Smarty Integration

I’ve been working with zend framework for some time and have been using the Smarty template engine for the views. Initially we were working well with the smarty integration but have just come across a problem. I’ve been using partials for Zend Navigation elements so I can create custom layouts.

The problem I was experiencing was that all my assigned variables were being cleared after the navigation partial has been called. The cause of this problem is that when partials are rendered the view object gets cloned to make sure that the partial is rendered in it’s own address space. With my previous integration the view object was not being cloned correctly and the current view object was being returned and then all the variables in the object was being cleared.

To fix this I found a new smarty integration on the internet based on the currently used view integration and adopted this into my object… I’ve included it below!

/**
* Smarty template engine integration into Zend Framework
* Some ideas borrowed from http://devzone.zend.com/article/120
*/
class ZendExt_View_Smarty extends Zend_View_Abstract
{
	/**
	 * Instance of Smarty
	 * @var Smarty
	 */
	protected $_smarty = null;
 
	/**
	 * Template explicitly set to render in this view
	 * @var string
	 */
	protected $_customTemplate = '';
 
	/**
	 * Smarty config
	 * @var array
	 */
	private $_config = null;
 
	/**
	 * Class definition and constructor
	 *
	 * Let's start with the class definition and the constructor part. My class Travello_View_Smarty is extending the Zend_View_Abstract class. In the constructor the parent constructor from Zend_View_Abstract is called first. After that a Smarty object is instantiated, configured and stored in a private attribute.
	 * Please note that I use a configuration object from the object store to get the configuration data for Smarty.
	 *
	 * @param array $smartyConfig
	 * @param array $config
	 */
	public function __construct($smartyConfig, $config = array())
	{
		$this->_config = $smartyConfig;
		parent::__construct($config);
		$this->_loadSmarty();
	}
 
	/**
	 * Return the template engine object
	 *
	 * @return Smarty
	 */
	public function getEngine()
	{
		return $this->_smarty;
	}
 
	/**
	 * Implement _run() method
	 *
	 * The method _run() is the only method that needs to be implemented in any subclass of Zend_View_Abstract. It is called automatically within the render() method. My implementation just uses the display() method from Smarty to generate and output the template.
	 *
	 * @param string $template
	 */
	protected function _run()
	{
		$file = func_num_args() > 0 && file_exists(func_get_arg(0)) ? func_get_arg(0) : '';
		if ($this->_customTemplate || $file) {
			$template = $this->_customTemplate;
			if (!$template) {
				$template = $file;
			}
 
			$this->_smarty->display($template);
		} else {
			throw new Zend_View_Exception('Cannot render view without any template being assigned or file does not exist');
		}
	}
 
	/**
	 * Overwrite assign() method
	 *
	 * The next part is an overwrite of the assign() method from Zend_View_Abstract, which works in a similar way. The big difference is that the values are assigned to the Smarty object and not to the $this->_vars variables array of Zend_View_Abstract.
	 *
	 * @param string|array $var
	 * @return Ext_View_Smarty
	 */
	public function assign($var, $value = null)
	{
		if (is_string($var)) {
			$this->_smarty->assign($var, $value);
		} elseif (is_array($var)) {
			foreach ($var as $key => $value) {
				$this->assign($key, $value);
			}
		} else {
			throw new Zend_View_Exception('assign() expects a string or array, got '.gettype($var));
		}
	}
 
	/**
	 * Overwrite escape() method
	 *
	 * The next part is an overwrite of the escape() method from Zend_View_Abstract. It works both for string and array values and also uses the escape() method from the Zend_View_Abstract. The advantage of this is that I don't have to care about each value of an array to get properly escaped.
	 *
	 * @param mixed $var
	 * @return mixed
	 */
	public function escape($var)
	{
		if (is_string($var)) {
			return parent::escape($var);
		} elseif (is_array($var)) {
			foreach ($var as $key => $val) {
				$var[$key] = $this->escape($val);
			}
		}
		return $var;
	}
 
	/**
	 * Print the output
	 *
	 * The next method output() is a wrapper on the render() method from Zend_View_Abstract. It just sets some headers before printing the output.
	 *
	 * @param <type> $name
	 */
	public function output($name)
	{
		header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
		header("Cache-Control: no-cache");
		header("Pragma: no-cache");
		header("Cache-Control: post-check=0, pre-check=0", false);
 
		print parent::render($name);
	}
 
	/**
	 * Use Smarty caching
	 *
	 * The last two methods were created to simply integrate the Smarty caching mechanism in the View class. With the first one you can check for cached template and with the second one you can set the caching on or of.
	 *
	 * @param string $template
	 * @return bool
	 */
	public function isCached($template)
	{
		return $this->_smarty->is_cached($template);
	}
 
	/**
	 * Enable/disable caching
	 *
	 * @param bool $caching
	 * @return Ext_View_Smarty
	 */
	public function setCaching($caching)
	{
		$this->_smarty->caching = $caching;
		return $this;
	}
 
	/**
	 * Template getter (return file path)
	 * @return string
	 */
	public function getTemplate()
	{
		return $this->_customTemplate;
	}
 
	/**
	 * Template filename setter
	 * @param string
	 * @return Ext_View_Smarty
	 */
	public function setTemplate($tpl)
	{
		$this->_customTemplate = $tpl;
		return $this;
	}
 
	/**
	 * Magic setter for Zend_View compatibility. Performs assign()
	 *
	 * @param string $key
	 * @param mixed $val
	 */
	public function __set($key, $val)
	{
		$this->assign($key, $val);
	}
 
 
	/**
	 * Magic getter for Zend_View compatibility. Retrieves template var
	 *
	 * @param string $key
	 * @return mixed
	 */
	public function __get($key)
	{
		return $this->_smarty->getTemplateVars($key);
	}
 
	/**
	 * Magic getter for Zend_View compatibility. Removes template var
	 *
	 * @see View/Zend_View_Abstract::__unset()
	 * @param string $key
	 */
	public function __unset($key)
	{
		$this->_smarty->clearAssign($key);
	}
 
	/**
	 * Allows testing with empty() and isset() to work
	 * Zend_View compatibility. Checks template var for existance
	 *
	 * @param string $key
	 * @return boolean
	 */
	public function __isset($key)
	{
		return (null !== $this->_smarty->getTemplateVars($key));
	}
 
	/**
	 * Zend_View compatibility. Retrieves all template vars
	 *
	 * @see Zend_View_Abstract::getVars()
	 * @return array
	 */
	public function getVars()
	{
		return $this->_smarty->getTemplateVars();
	}
 
	/**
	 * Updates Smarty's template_dir field with new value
	 *
	 * @param string $dir
	 * @return Ext_View_Smarty
	 */
	public function setTemplateDir($dir)
	{
		$this->_smarty->setTemplateDir($dir);
		return $this;
	}
 
	/**
	 * Adds another Smarty template_dir to scan for templates
	 *
	 * @param string $dir
	 * @return Ext_View_Smarty
	 */
	public function addTemplateDir($dir)
	{
		$this->_smarty->addTemplateDir($dir);
		return $this;
	}
 
	/**
	 * Adds another Smarty plugin directory to scan for plugins
	 *
	 * @param string $dir
	 * @return Ext_View_Smarty
	 */
	public function addPluginDir($dir)
	{
		$this->_smarty->addPluginsDir($dir);
		return $this;
	}
 
	/**
	 * Zend_View compatibility. Removes all template vars
	 *
	 * @see View/Zend_View_Abstract::clearVars()
	 * @return Ext_View_Smarty
	 */
	public function clearVars()
	{
		$this->_smarty->clearAllAssign();
		$this->assign('this', $this);
		return $this;
	}
 
	/**
	 * Zend_View compatibility. Add the templates dir
	 *
	 * @see View/Zend_View_Abstract::addBasePath()
	 * @return Ext_View_Smarty
	 */	public function addBasePath($path, $classPrefix = 'Zend_View')
	{
		parent::addBasePath($path, $classPrefix);
		$this->addScriptPath($path . '/templates');
		$this->addTemplateDir($path . '/templates/static');
		return $this;
	}
 
	/**
	 * Zend_View compatibility. Set the templates dir instead of scripts
	 *
	 * @see View/Zend_View_Abstract::setBasePath()
	 * @return Ext_View_Smarty
	 */
	public function setBasePath($path, $classPrefix = 'Zend_View')
	{
		parent::setBasePath($path, $classPrefix);
		$this->setScriptPath($path . '/templates');
		$this->addTemplateDir($path . '/templates/static');
		return $this;
	}
 
	/**
	 * Magic clone method, on clone create diferent smarty object
	 */
	public function __clone() {
		$this->_loadSmarty();
	}
 
	/**
	 * Initializes the smarty and populates config params
	 *
	 * @throws Zend_View_Exception
	 * @return void
	 */
	private function _loadSmarty()
	{
		if (!class_exists('Smarty', true)) {
			require_once 'Smarty/Smarty.class.php';
		}
 
		$this->_smarty = new Smarty();
 
		if ($this->_config === null) {
			throw new Zend_View_Exception("Could not locate Smarty config - node 'smarty' not found");
		}
 
		$this->_smarty->caching = $this->_config['smarty']['caching'];
		$this->_smarty->cache_lifetime = $this->_config['smarty']['cache_lifetime'];
		$this->_smarty->template_dir = $this->_config['smarty']['template_dir'];
		$this->_smarty->compile_dir = $this->_config['smarty']['compile_dir'];
		$this->_smarty->config_dir = $this->_config['smarty']['config_dir'];
		$this->_smarty->cache_dir = $this->_config['smarty']['cache_dir'];
		$this->_smarty->left_delimiter = $this->_config['smarty']['left_delimiter'];
		$this->_smarty->right_delimiter = $this->_config['smarty']['right_delimiter'];
 
		if(isset($this->_config['smarty']['plugins_dir']))
		{
			foreach($this->_config['smarty']['plugins_dir'] as $pluginDir)
			{
				$this->addPluginDir($pluginDir);
			}
		}
 
		$this->assign('this', $this);
	}
 
 
 
	public function addScriptPath($path)
	{
		if(!isset($this->_smarty->template_dir))
		{
			$this->_smarty->template_dir = $path;
		}
		else
		{
			if(!$this->checkScriptPathExists($path))
			{
				if(!is_array($this->_smarty->template_dir))
				{
					$this->_smarty->template_dir = array($this->_smarty->template_dir);
				}
 
				array_unshift($this->_smarty->template_dir, $path);
			}
		}
 
		parent::addScriptPath($path);
	}
 
	private function checkScriptPathExists($path)
	{
		$dirs = $this->_smarty->template_dir;
 
		if(is_array($dirs))
		{
			foreach($dirs as $dir)
			{
				if($dir == $path)
				{
					return true;
				}
			}
		}
		else
		{
			if($dirs == $path)
			{
				return true;
			}
		}
		return false;
	}
}

Zend_Auth_Adapter with multiple identity columns (username and email)

I’ve just started working with the Zend Framework and I must say it’s awesome. So powerful and so many helpers in place to make things easy to do. I’ve just been working with an authentication adapter. I wanted my users to be able to login using either their username or email address and a password. Zend_Auth_Adapter_DbTable does a brilliant job of using one field (email or username) but does not allow you to use both.

To do this I’ve written an auth adapter which has allowed me to use both. It’s a very simple class that adds an additional where clause option with the additional field. See below for the code…

 

class ZendExt_Auth_Adapter_MultiColumnDbTable 
              extends Zend_Auth_Adapter_DbTable
{
  protected $_alternativeIdentityColumn = null;
 
  protected function _authenticateCreateSelect()
  {
    $select = parent::_authenticateCreateSelect();
 
    if(isset($this->_alternativeIdentityColumn))
    {
      $select->orWhere($this->_zendDb->quoteIdentifier(
                      $this->_alternativeIdentityColumn, true) . ' = ?', 
                      $this->_identity);
    }
 
    return $select;
  }
 
  public function setAlternativeIdentityColumn($alternativeIdentityColumn)
  {
    $this->_alternativeIdentityColumn = $alternativeIdentityColumn;
    return $this;
  }
}

Pretty simple really. The important line is the “orWhere” bit which adds the additional column you want to use.
To use it you just have to do the following…

$dbAdapter = Zend_Db_Table::getDefaultAdapter();
$authAdapter = new ZendExt_Auth_Adapter_MultiColumnDbTable($dbAdapter);
$authAdapter->setTableName('user_user')
  ->setIdentityColumn('username')
  ->setAlternativeIdentityColumn('email')
  ->setCredentialColumn('password')
  ->setCredentialTreatment('MD5(?)');

PHP xdiff (libxdiff) on Windows

Not this is a tricky little bugger to get working. Xdiff is a fantastic set of difference libraries for obtaining differences between files or strings. The installation on Linux was covered in a previous post but Windows was a completely different ball game. I’ve finally managed to get it working however… so here we go.

Process Overview

To install this you need to compile a php_xdiff.dll file. To do this you will need to obtain Visual Studio 2008 (a.k.a VC9). This opens up several problems however. To run the compiled xdiff dll file you will need to have a VC9 version of php installed. If you install a VC9 version of PHP and use apache you will need a VC9 version of Apache. See where the problems start?!!!

PHP

There is no getting away from the fact that you are going to require a VC9 version of PHP if you compile a php_xdiff.dll file. VC6 is not available any more and it makes sense to compile this with the most updated version available. This is reasonably easily fixed as PHP offer a VC9 compiled version of PHP (http://windows.php.net/download/)

Apache

This is reasonably easily fixed as well. The guys at ApacheLounge compile VC9 versions of Apache and make them readily available. See http://www.apachelounge.com/download/.

Compiling php_xdiff.dll

This is the more troublesome stretch of the process. There is a good guide available at http://wiki.php.net/internals/windows/stepbystepbuild for getting started. If you follow this up to the point of adding extensions you should be on good ground. One word of advice would be to download all the extensions headers and libraries available. Just make sure you have everything available. If you’ve managed to compile a version of PHP with no extensions added you’re a good way there!

When you’ve got the PHP build available it’s now time to start working on xdiff. First of all you need to obtain and compile libxdiff. The source can be obtained from http://www.xmailserver.org/xdiff-lib.html. Once you have this you need to compile it using the visual studio tools. If you’ve still got your compiler window open from compiling PHP, change to the directly where you have libxdiff extracted to and run “nmake”. This should compile some headers and library files. Once compiled you need to copy all the headers and libraries into the appropriate PHP library directories.

From here you need to follow the steps at the bottom of the PHP internals page regarding installing additional PECL extensions. This should then compile. As a note. When you give the additional “configure” command use “–with-xdiff=shared”. This will cause the library to be built as a dll file rather than compiled into php5ts.dll file.

Putting it all together

Finally you need to add the dll to the php install you insatlled earlier. Copy the dll file to the “ext” directory under the php install (with the other extensions). Edit php.ini and add the line “extension=php_xdiff.dll” into the extensions section.

Finally restart apache and you should now have xdiff functionality within PHP. If you’ve managed this… give your self a pat on the back!

UPDATE:

I’ve uploaded the xdiff file that I compiled. If you want to use this simply install VC9 versions of PHP and Apache and place this file into the ext directory, edit the php.ini file and you’re away. It’s available here: php_xdiff.

PHP xdiff installation in Linux

Ok… I’ve been trying to get this working for several days and have finally made it work. XDiff is a brilliant diffing utility for PHP but requires you to install it as an extension. Before you are able to do the main part of this you need to install the libxdiff libraries. This is the part that took me some time to get working.

Ok here is the part that has taken me the time… libxdiff. To install this you need to do the following:
cd /usr/src
wget http://www.xmailserver.org/libxdiff-0.22.tar.gz
tar -xzf libxdiff-0.22.tar.gz
cd libxdiff-0.22
./configure
make
make install

This will get you libxdiff installed. The next part is then simple. Install the php extension using the following:
pecl install xdiff

When you have this installed you should then be able to use xdiff from within PHP. See the php manual for more instructions: http://www.php.net/manual/en/ref.xdiff.php

SugarCRM and SVN

Ever tried to keep SugarCRM under version control? It’s a nightmare! Upgrading and keeping things in sync is just a headache. If you wanted to upgrade SugarCRM and keep those little core hacks that you have had to make to ensure Sugar works the way you need it to… it’s hellish!

Until I stumbled over this post. It’s a new way of working with SugarCRM and SVN that really works well. I’m in the middle of upgrading our sugar instance from version 5.2.0c to 6.0.1 and so far no problems! I’ll comment when I’m done and let you know any problems I came across!

SugarCRM SOAP set_relationship

I’ve been working a lot with SugarCRM and it’s SOAP interface in the last few weeks. I’m writing a script to transfer booking that have been made on our company website to a SugarCRM instance. I’ve been reasonably impressed with the SOAP interface but it’s by no means perfect. In fact in the last two days I’ve come across a problem that makes it almost dangerous!!

We have a custom module that has two many to many relationships with the Contacts module. There is a problem however… when I call set_relationship to join the two modules together the SOAP interface is only programmed work when there is one relationships between the two. If there are two relationships… it simply selects the first one. By the first one I mean it does a SQL query on the relationships database table (which in my case returns two rows) and then uses the first row returned from the query.

The first row from a SQL query is going to be the first record that was entered into the database. If/when I do a repair and rebuild in SugarCRM (which is required when installing new modules or making certain changes) the records could get entered in a different order and as such change the relationship that the SOAP interface would use.

Personally I see this a problem. I have had to do yet another Core hack on Sugar to work around this. My next steps will be to attempt to upgrade our SugarCRM instance to a newer version and see if that makes any difference to this problem… I’m suspecting not!

Photo gallery for your site

Have you got your own website and want to find a good piece of gallery software to host your photos on it? I have always used gallery2 for this very purpose. Recently however they have updated the software and at last a release candidate has been released for gallery3. It’s basically a streamlined and greatly superior version of gallery2. I’m still playing with it at the moment but so far it’s been much more stable than gallery2.

Take a look and let me know what you think: http://gallery.menalto.com/

iFrame resizing across different domains

Don’t you just hate all those scroll bars that appear when you want to add an iframe to a page. You end up with scroll bars inside scroll bars. The usual problem is that you need to resize the page to fit all of the iframe’s content in but you can’t access any properties of the inner content because it comes from a different source (different domain).

I foundd this article that does the job fantastically. It is  relatively simple but the one caviat of this is that you need control over both domains. In this case I do (TCC (http://www.tcc-net.com) and Quizical (http://www.quizical.co.uk)

Give it a try and let me know what you think.

Chrome Development Tools

Google Chrome (beta version) now has a brilliant set of plug-ins available to use. The best of the additional features in Chrome is the developer tool-bar. It rivals Firebug and I’ve found myself using Chrome as my development browser rather than fireFox. The main reason for this is that it’s just so much faster to start and do anything in. I’ll obviously need to test in all browsers but at the moment I am finding development work in Chrome a lot faster than FireFox… may have just ousted FireFox from everyday use… sorry!

Apache running webs on a network drive…

Ok… here’s a tricky one I’ve just had to work through.

I have apache, Mysql, PHP running on my Windows machine. So that my development files are stored in a backed up location I wanted to move my files to a mapped network drive. This causes some problems which I’ve just worked through.
When you map a network drive this drive gets mapped under your user account. The apache service runs under the computer system account so has no visibility to the mapped drive and as such using w:\…. in your DocumentRoot directive will not work. Only local drives can be accessed in this manner. As a mapped drive this should have a UNC path associated with it. E.g. \\domain.local\Development\MyWebs. This is the style of path required for Apache.
When entering this path into the httpd.conf file you need to ensure that you use “/” instead of “\”. So the above would be “//domain.local/Development/MyWebs”.
You will then need to change the user that the Apache service runs as. I’ve set it to run under my domain account. This account I know has NTFS permissions to the mapped drive/UNC path.
One final step is to give the new account that the service runs as access to the logs directory under the server root (or move your logs to a location that has permission). After all that you should be able to have a local Apache server running webs stored on a network drive.