Category Archives: CakePHP

CakePHP Image Helper

I’ve rewritten my image helper to extend off of CakePHP’s built in HTML helper, rather than reinvent the wheel. The major benefit of using this helper is that it determines the image dimensions, if not specified, and applies them to the image tag, which is one of Google’s rules to optimize browser rendering.

<?php
/**
 * This class builds an image tag. The main purpose of this is to get the image dimensions and
 * include the appropriate attributes if not specified. This will improve front end performance.
 *
 * @author Seth Cardoza <[email protected]>
 * @category image
 * @package helper
 */
class HtmlImageHelper extends AppHelper
{
    var $helpers = array('Html');

    /**
     * Builds html img tag determining width and height if not specified in the
     * attributes parameter.
     *
     * @param string $src relative path to image including the 'img' directory
     * @param array $attributes array of html attributes to apply to the image
     *
     * @access public
     *
     * @return no return value, outputs the img tag
     */
    public function image($src, $attributes = array()) {
        //get width/height via exif data
        //build image html
        if(file_exists(WWW_ROOT . $src)) {
            $image_size = getimagesize(WWW_ROOT . $src);
            if(!array_key_exists('width', $attributes) && array_key_exists('height', $attributes)) {
                $attributes['width'] = ($image_size[0] * $attributes['height']) / $image_size[1];
            } elseif(array_key_exists('width', $attributes) && !array_key_exists('height', $attributes)) {
                $attributes['height'] = ($image_size[1] * $attributes['width']) / $image_size[0];
            } else {
                $attributes['width'] = $image_size[0];
                $attributes['height'] = $image_size[1];
            }
        }

      
        return $this->Html->image($src, $attributes);
    }
}

Download CakePHP HTML Image Helper

Posted in CakePHP | Comments closed

CakePHP HasAndBelongsToMany (HABTM) Checkboxes Instead of Multiple Select

Changing CakePHP’s default multiple select for HasAndBelongsToMany (HABTM) relationships to use multiple checkboxes used to be an arduous task. It is now a simple option as follows.

If you have a Post model that has a HABTM relationship with a Tag model, you would use the following line to display multiple checkboxes instead of the default multiple select:

<?php echo $form->input('Tag', array('multiple' => 'checkbox')); ?>

The magic is the array(‘multiple’ => ‘checkbox’).

As you probably already know, and the reason you were searching for this solution, the multiple checkboxes are much more user friendly than the multiple select.

Posted in CakePHP | Comments closed

Akismet API Component for CakePHP 1.2

This is a component for CakePHP 1.2 and PHP 5+ that utilizes the Akismet API to fight comment SPAM. You will need an API key, which can be retrieved from wordpress.com.

<?php
/**
 * This is a component for CakePHP that utilizes the Akismet API
 *
 * See http://akismet.com/development/api/ for more information.
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 *
 * @author Seth Cardoza <www.sethcardoza.com>
 * @category akismet
 * @package component
 **/

class AkismetComponent extends Object {

    /**
     * @var string
     */
    private $http;

    const API_KEY = '';
    const BASE_URL = 'rest.akismet.com';
    const API_VERSION = '1.1';
    const VERIFY_KEY_ACTION = 'verify-key';
    const COMMENT_CHECK_ACTION = 'comment-check';
    const SUBMIT_SPAM_ACTION = 'submit-spam';
    const SUBMIT_HAM_ACTION = 'submit-ham';

    const APP_USER_AGENT = 'CakePHP/1.2 | Akismet Model 1.0';

    public function __construct() {
        App::Import('Core', 'HttpSocket');
        $this->http =& new HttpSocket();
    }

    public function verifyKey($data) {
        $data = array();
       
        if (!isset($data['blog'])) {
            $data['blog'] = FULL_BASE_URL;
        }
       
        if (!isset($data['key'])) {
            $data['key'] = self::API_KEY;
        }
       
        $uri = 'http://' . self::BASE_URL . '/' . self::API_VERSION . '/' . self::VERIFY_KEY_ACTION;
       
        $request = array('header' => array('User-Agent: ' . self::APP_USER_AGENT));
       
        return $this->http->post($uri, $data, $request);
    }
   
    /**
     * This is just a wrapper function for Akismet::commentCheck(). the return result makes more sense calling this function.
     * The two functions can be used interchangeably
     * @param array $comment
     * @return string
     */
    public function isSpam($comment) {
        return $this->commentCheck($comment);
    }
   
    /**
     * returns true if comment is spam, false otherwise
     *
     * From API Documentation: If you are having trouble triggering you can send "viagra-test-123" as the author and it will trigger a true response, always.
     *
     * @param array $comment
     * @return string
     */
    public function commentCheck($comment) {
        return $this->__makeRequest($comment, self::COMMENT_CHECK_ACTION);
    }
   
    /**
     * @param array $comment
     * @return string
     */
    public function submitSpam($comment) {
        return $this->__makeRequest($comment, self::SUBMIT_SPAM_ACTION);
    }
   
    /**
     * @param array $comment
     * @return string
     */   
    public function submitHam($comment) {
        return $this->__makeRequest($comment, self::SUBMIT_HAM_ACTION);
    }
   
    /**
     * this is where the magic happens. this makes the call to get the default info if not set, and
     * makes the request, passing the necessary data
     */
    private function __makeRequest($comment, $action) {
        $comment = $this->__getDefaultData($comment);
       
        $request = array('header' => array('User-Agent: ' . self::APP_USER_AGENT));
       
        $uri = 'http://' . self::API_KEY . '.' . self::BASE_URL . '/' . self::API_VERSION . '/' . $action;
       
        $return = $this->http->post($uri, $comment, $request);
       
        return $return;
    }
   
    private function __getDefaultData($comment) {
        App::import('Component', 'RequestHandler');
        if (!isset($comment['blog'])) {
            $comment['blog'] = FULL_BASE_URL;
        }

        if (!isset($comment['user_ip'])) {
            $comment['user_ip'] = RequestHandlerComponent::getClientIP();
        }
       
        if (!isset($comment['referrer'])) {
            $comment['referrer'] = RequestHandlerComponent::getReferrer();
        }

        if (!isset($comment['user_agent'])) {
            $vars['user_agent'] = env('HTTP_USER_AGENT');
        }
       
        return $comment;
    }
}
?>

Download Akismet Component for CakePHP 1.2

Posted in CakePHP | Comments closed

CakePHP Reverse Routing

CakePHP provides a very strong and flexible routing engine, for both routing, and reverse routing. Creating a route for the home page is as simple as adding the following line to your routes.php configuration file.

Router::connect('/about_us', array('controller' => 'pages', 'action' => 'display', 'about_us'));

Now, anyone visiting http://example.com/about_us page, will see the view defined in your Pages controller, and the about_us view. This is a very simple example, but leads us to the topic of reverse routing.

We can easily create a link to the about_us page in our markup very easily with the following:

<a href="/about_us">About Us</a>

However, CakePHP provides us with a way to do this through reverse routing and the HTML helper.

<?php $html->link('About Us', array('controller' => 'pages', 'action' => 'display', 'about_us'); ?>

Will generate the following output.

<a href="/about_us">About Us</a>

And, if we later update the route defined in the beginning to a more detailed url, for SEO purposes:

Router::connect('/about-seth-cardoza', array('controller' => 'pages', 'action' => 'display', 'about_us'));

We will not have to update the $html->link(); because the reverse routing takes care of that for us.

This is just a brief introduction to the power and capabilities of CakePHP’s routing and reverse routing. I recommend you read more about CakePHP’s routing in their online manual.

Posted in CakePHP | Comments closed

Disabling Layouts and Views in CakePHP

It is easy to disable both the layout and view in CakePHP by putting the following line in your controller action:

$this->autoRender = false;

If you want to disable just the layout, use the following line in your controller action:

$this->layout = false;

And if you only want to disable the view for this action, use the following line in your controller:

$this->render(false);

Note that using $this->layout = false; and $this->render(false); together in your controller action will give you the same results as $this->autoRender = false;

Posted in CakePHP | Comments closed

CakePHP Image Helper for Front End Optimization

Going along with one of the many ways to optimize front end performance, I created an image helper for CakePHP that will get the image dimensions and include them as html attributes.

<?php
/**
 * This class builds an image tag. The main purpose of this is to get the image dimensions and
 * include the appropriate attributes if not specified. This will improve front end performance.
 * 
 * @author Seth Cardoza <[email protected]>
 * @category image
 * @package helper
 */
class ImageHelper extends Helper
{
    /**
     * Builds html img tag determining width and height if not specified in the
     * attributes parameter.
     *
     * @param string $src relative path to image including the 'img' directory
     * @param array $attributes array of html attributes to apply to the image
     *
     * @access public
     *
     * @return no return value, outputs the img tag
     */
    public function displayImage($src, $attributes = array()) {
        //get width/height via exif data
        //build image html
        if(file_exists(WWW_ROOT . $src)) {
            $image_size = getimagesize(WWW_ROOT . $src);
            if(!array_key_exists('width', $attributes) && array_key_exists('height', $attributes)) {
                $attributes['width'] = ($image_size[0] * $attributes['height']) / $image_size[1];
            } elseif(array_key_exists('width', $attributes) && !array_key_exists('height', $attributes)) {
                $attributes['height'] = ($image_size[1] * $attributes['width']) / $image_size[0];
            } else {
                $attributes['width'] = $image_size[0];
                $attributes['height'] = $image_size[1];
            }
        }

       
        $html = '<img src="' . $src . '"';
       
        foreach($attributes as $key => $value) {
            $html .= ' ' . $key . '="' . htmlentities($value, ENT_COMPAT, 'ISO-8859-1', false) . '"';
        }
       
        $html .= ' />';
        echo $html;
    }
}
?>

Download CakePHP Image Helper

Posted in CakePHP | Comments closed

Image Resizing Class

This is a simple script to resize images with PHP. It uses the GD Library functions, and is currently not compatible with ImageMagick. It will automatically determine the image type and use the appropriate functions to resize.

<?php
/**
 * This class handles image resizing. It will automatically determine the image
 * type and use the appropriate php resize functions. It uses GD libs and is not
 * currently compatibly with ImageMagick.
 * 
 * @author Seth Cardoza <[email protected]>
 * @category image
 * @package component
 */
class Image {
    public $name = 'Image';
    private $__errors = array();

    /**
     * Determines image type, calculates scaled image size, and returns resized image. If no width or height is
     * specified for the new image, the dimensions of the original image will be used, resulting in a copy
     * of the original image.
     *
     * @param string $original absolute path to original image file
     * @param string $new_filename absolute path to new image file to be created
     * @param integer $new_width (optional) width to scale new image (default 0)
     * @param integer $new_height (optional) height to scale image (default 0)
     * @param integer $quality quality of new image (default 100, resizePng will recalculate this value)
     *
     * @access public
     *
     * @return returns new image on success, false on failure. use ImageComponent::getErrors() to get an array
     * of errors on failure
     */
    public function resize($original, $new_filename, $new_width = 0, $new_height = 0, $quality = 100) {
        if(!($image_params = getimagesize($original))) {
            $this->__errors[] = 'Original file is not a valid image: ' . $orignal;
            return false;
        }
       
        $width = $image_params[0];
        $height = $image_params[1];
       
        if(0 != $new_width && 0 == $new_height) {
            $scaled_width = $new_width;
            $scaled_height = floor($new_width * $height / $width);
        } elseif(0 != $new_height && 0 == $new_width) {
            $scaled_height = $new_height;
            $scaled_width = floor($new_height * $width / $height);
        } elseif(0 == $new_width && 0 == $new_height) { //assume we want to create a new image the same exact size
            $scaled_width = $width;
            $scaled_height = $height;
        } else { //assume we want to create an image with these exact dimensions, most likely resulting in distortion
            $scaled_width = $new_width;
            $scaled_height = $new_height;
        }

        //create image       
        $ext = $image_params[2];
        switch($ext) {
            case IMAGETYPE_GIF:
                $return = $this->__resizeGif($original, $new_filename, $scaled_width, $scaled_height, $width, $height, $quality);
                break;
            case IMAGETYPE_JPEG:
                $return = $this->__resizeJpeg($original, $new_filename, $scaled_width, $scaled_height, $width, $height, $quality);
                break;
            case IMAGETYPE_PNG:
                $return = $this->__resizePng($original, $new_filename, $scaled_width, $scaled_height, $width, $height, $quality);
                break;   
            default:
                $return = $this->__resizeJpeg($original, $new_filename, $scaled_width, $scaled_height, $width, $height, $quality);
                break;
        }
       
        return $return;
    }
   
    public function getErrors() {
        return $this->__errors;
    }
   
    private function __resizeGif($original, $new_filename, $scaled_width, $scaled_height, $width, $height) {
        $error = false;
       
        if(!($src = imagecreatefromgif($original))) {
            $this->__errors[] = 'There was an error creating your resized image (gif).';
            $error = true;
        }
       
        if(!($tmp = imagecreatetruecolor($scaled_width, $scaled_height))) {
            $this->__errors[] = 'There was an error creating your true color image (gif).';
            $error = true;
        }
       
        if(!imagecopyresampled($tmp, $src, 0, 0, 0, 0, $scaled_width, $scaled_height, $width, $height)) {
            $this->__errors[] = 'There was an error creating your true color image (gif).';
            $error = true;
        }

        if(!($new_image = imagegif($tmp, $new_filename))) {
            $this->__errors[] = 'There was an error writing your image to file (gif).';
            $error = true;
        }
       
        imagedestroy($tmp);

        if(false == $error) {
            return $new_image;
        }
       
        return false;
    }
   
    private function __resizeJpeg($original, $new_filename, $scaled_width, $scaled_height, $width, $height, $quality) {
        $error = false;
       
        if(!($src = imagecreatefromjpeg($original))) {
            $this->__errors[] = 'There was an error creating your resized image (jpg).';
            $error = true;
        }

        if(!($tmp = imagecreatetruecolor($scaled_width, $scaled_height))) {
            $this->__errors[] = 'There was an error creating your true color image (jpg).';
            $error = true;
        }
       
        if(!imagecopyresampled($tmp, $src, 0, 0, 0, 0, $scaled_width, $scaled_height, $width, $height)) {
            $this->__errors[] = 'There was an error creating your true color image (jpg).';
            $error = true;
        }

        if(!($new_image = imagejpeg($tmp, $new_filename, $quality))) {
            $this->__errors[] = 'There was an error writing your image to file (jpg).';
            $error = true;
        }
       
        imagedestroy($tmp);
       
        if(false == $error) {
            return $new_image;
        }
       
        return false;
    }
   
    private function __resizePng($original, $new_filename, $scaled_width, $scaled_height, $width, $height, $quality) {
        $error = false;
        /**
         * we need to recalculate the quality for imagepng()
         * the quality parameter in imagepng() is actually the compression level,
         * so the higher the value (0-9), the lower the quality. this is pretty much
         * the opposite of how imagejpeg() works.
         */
        $quality = ceil($quality / 10); // 0 - 100 value
        if(0 == $quality) {
            $quality = 9;
        } else {
            $quality = ($quality - 1) % 9;
        }

       
        if(!($src = imagecreatefrompng($original))) {
            $this->__errors[] = 'There was an error creating your resized image (png).';
            $error = true;
        }
       
        if(!($tmp = imagecreatetruecolor($scaled_width, $scaled_height))) {
            $this->__errors[] = 'There was an error creating your true color image (png).';
            $error = true;
        }
       
        imagealphablending($tmp, false);
       
        if(!imagecopyresampled($tmp, $src, 0, 0, 0, 0, $scaled_width, $scaled_height, $width, $height)) {
            $this->__errors[] = 'There was an error creating your true color image (png).';
            $error = true;
        }
       
        imagesavealpha($tmp, true);
       
        if(!($new_image = imagepng($tmp, $new_filename, $quality))) {
            $this->__errors[] = 'There was an error writing your image to file (png).';
            $error = true;
        }
       
        imagedestroy($tmp);
       
        if(false == $error) {
            return $new_image;
        }
       
        return false;
    }
}
?>

I originally wrote this as a CakePHP component and added it to the Bakery. The script is general enough that I was able to remove the few bits of code that were CakePHP specific. If/when the article is published, I will link it here.

Download Image Resizing Class

Posted in CakePHP | Comments closed

CakePHP Upgrade from 1.1 to 1.2

CakePHP 1.2 has finally released, but how do you upgrade site your site running CakePHP 1.1? It is a fairly simple process with brief formal instructions on cakephp.org. Most will only have to make a few changes as CakePHP 1.2 is not backwards compatible. All of the html helper form element methods have been moved to a form helper. This means that this:

    <?php echo $html->input('username'); ?>

becomes this:

    <?php echo $form->input('username'); ?>

One other big change was the removal of the generateList() method used to create an array for later use in select elements. The change in code is as follows:

    <?php $this->ModelName->generateList($conditions, $order, $limit, $keyPath, $valuePath); ?>

becomes this:

    <?php $this->ModelName->find('list', $params); ?>

The upgrade instructions provide a possible migration approach suggested in the documentation. It was a relatively simple process, only taking a few hours, including a good deal of testing.

Posted in CakePHP | Comments closed