jump to navigation

Book on CakePHP December 16, 2008

Posted by rossoft in CakePHP.
comments closed

I recommend all of you to take a look at this book: CakePHP Application Development. It is a step-by-step introduction to rapid web development using the open-source MVC CakePHP framework.

You can download the forth chapter “Controllers: Programming Application Logic” for free HERE

AJAX Star Rating helper September 7, 2006

Posted by rossoft in CakePHP.
67 comments

An AJAX Star Rating Helper based on link1 and link2.

Installation instructions.
1) Download + install CJS Templates version 2.1 from cakeforge snippets
2) Copy star CSS file to app/webroot/css/star_rating/star_rating.css
  and add the line “overflow: hidden;” to .star-rating
3) Copy star Image file to app/webroot/css/star_rating/alt_star.gif
4) Copy the helper to app/views/helpers/star_rating.php

Usage example: include the helper and in your view file:

echo $starRating->display(‘Post/rating’,”/posts/rate/$id”)

It will create a star rater for the field Post/rating. An AJAX call will be made to /posts/rate/$id/$rate_value ( /$rate_value is appended to the CakeUrl that you pass to display function )

<?php
/**
 * Star Rating Helper
 *
 * @author RosSoft
 * @version 0.1
 * @license MIT
 *
 * @link http://www.naffis.com/blog/articles/2006/08/31/rails-ajax-star-rating-system
 *
 * Copy star CSS file to app/webroot/css/star_rating/star_rating.css
 * and add the line “overflow: hidden;” to .star-rating
 *
 * @link http://komodomedia.com/blog/samples/star_rating/example2.htm
 *
 *
 *
 * Copy star Image file to app/webroot/css/star_rating/alt_star.gif
 * http://komodomedia.com//blog/samples/star_rating/alt_star.gif
 *
 * @package helpers
 *
 * Usage example: include the helper and in your view file:
 * echo $starRating->display(‘Post/rating’,”/posts/rate/$id”)
 * It will create a star rater for the field Post/rating. An AJAX call
 * will be made to /posts/rate/$id/$rate_value
 * ( /$rate_value is appended to the CakeUrl that you pass to display function )
 */

class StarRatingHelper extends Helper
{
    var $helpers=array(‘Head’,’Util’,’Page’);

    /**
     * Pixels of star width
     */
    var $_star_width=25;

    var $ratings=array(
        array(    ‘title’=>’1 estrella de 5’,
                ‘class’=>’one-star’),

        array(    ‘title’=>’2 estrellas de 5’,
                ‘class’=>’two-stars’),

        array(    ‘title’=>’3 estrellas de 5’,
                ‘class’=>’three-stars’),

        array(    ‘title’=>’4 estrellas de 5’,
                ‘class’=>’four-stars’),

        array(    ‘title’=>’5 estrellas de 5’,
                ‘class’=>’five-stars’)
        );

    function _width($score)
    {
        if ($score >=0 && $score <=5)
        {
            $width = $score * $this->_star_width;
        }
        else
        {
            $width=0;
        }
        return $width;
    }

    /**
     * Creates a star rater.
     * @param mixed $value Model/field of the actual rate or the rate in float [0..5]
     *     Examples:
     *   3.5
     *   Post/rate
     * @url Url to be called through AJAX
     *  Example: if you need to rate the post id 3
     *     $url=’/post/rate/3′
     *  Then when clicked the 5 stars, the url called will be /post/rate/3/5
     * @return string The html code for the star rater
     */
    function display($value,$url)
    {
        static $index=0;
        $index++;

        $this->Head->css(‘star_rating/star_rating’);
        if (!is_numeric($value))
        {
            $score=$this->Util->retrieve_value($value);
            $id=$this->Util->fieldname_to_formid($value);
        }
        else
        {
            $score=$value;
            $id=”star_$index”;
        }
        $width=$this->_width($score);
        ob_start();
        ?>
        <ul class=’star-rating’ id='<?php echo $id?>’>
            <li class=’current-rating’ style=’width:<?php echo $width?>px;’></li>

            <?php for ($i=0;$i<5;$i++):?>
                <li><a href=’#’ onclick=”return false;” title='<?php echo $this->ratings[$i][‘title’]?>’ class=’star <?php echo $this->ratings[$i][‘class’]?>’><?php echo($i+1)?></a></li>
                <?php echo $this->Page->event(“#$id .{$this->ratings[$i][‘class’]}”,’click’,$this->Page->remote_url($url . ‘/’ . ($i+1),array(),false))?>
            <?php endfor;?>
        </ul>
        <?php
        return ob_get_clean();
    }

    /**
     * Change the number of stars of a displayed star rater
     * @param string $dom_id The DOM ID of the star rater to change
     * @param float $score The score from 0 to 5
     *
     * @return string HTML-Javascript Code
     */
    function change($dom_id,$score)
    {
        $width=$this->_width($score);
        $css=”#{$dom_id} .current-rating”;
        $js=”_elem.style.width='{$width}px’;”;
        return $this->Page->for_each($css,$js);
    }

    /**
     * Creates an effect of highlight in a displayed star rater
     * @param string $dom_id The DOM ID of the star rater to change
     * @param float $duration Duration of the effect in seconds
     *
     * @return string HTML-Javascript Code
     */
    function highlight($dom_id,$duration=0.3)
    {
        return $this->Page->effect(
            array(    “#$dom_id .current-rating”,
                    “#$dom_id”)
            ,’Highlight’,array(‘duration’=>$duration));
    }

}
?>

Optimizing your queries August 31, 2006

Posted by rossoft in CakePHP.
8 comments

I’m working on a project with CakePHP & MySQL 5, but it needs to be
DBMS independant. I don’t like putting raw queries with Model->query.
Some complex queries require some mysql custom functions. Most of that functions are available in all the common DBMS, but have a different syntax.
For that purposes I use Mysql 5 Views. CakePHP hasn’t support for them yet, but I’ve reported a ticket for fixing it.
Mysql Views with CakePHP are very powerful.
You can create a view that links several tables with complex ‘not exists’ where clausules, then rename all the tables ‘id’ fields to model_id. Now you can use standard CakePHP belongsTo relations to retrieve the other fields that you need.
With this way you can create very complex queries with minimum overhead.
For maximum performance, you must create indexes. For seeing where you need them, use the query ‘EXPLAIN SELECT ….’.
It will return several rows, with a lot of info for each operation that the DBMS must do for retrieve your query. In the column ‘type’ it says how addresses the data. If it shows the ‘all’ type, then that’s the worst system. If you see lots of them, create indexes in the fields that you use in your where clausules and execute again the ‘explain’ statement for seeing if there’re improvement.
With some indexes and good views, you can have the most complex queries with good performance and DBMS independence.

MySecurity Component August 23, 2006

Posted by rossoft in CakePHP.
8 comments

Some tricks for extra security in cake.

If you call http://example.com/users/render/delete (you’ve an UsersController) you will get rendered the delete.thtml view.
Try it at home with any controller.
The render() method from Controller gets called. I’m not sure if something dangerous can be done, but some other functions can be called like redirect. For forbidding all the methods from Controller base class, use this component.

<?php
/**
 * MySecurity.php
 * Some security things for Cake
 *
 * Features:
 * – The public functions from controller & object now can’t be called from url
 * –
 *
 * @author RosSoft
 * @version 0.1
 * @license MIT
 *
 * @package components
 */

class MySecurityComponent extends Object
{
    var $components=array(‘Security’);

    /**
     * Extra forbidden actions
     *
     * @var array $forbidden_actions
     */
    var $forbidden_actions=array();

    function startup(&$controller)
    {
        $this->forbidden_actions=am($this->forbidden_actions, get_class_methods(‘Controller’));

        $this->Security->startup($controller);
        if (in_array($controller->action,$this->forbidden_actions))
        {
            $this->Security->blackHoleCallback=null;
            $this->Security->blackHole($this);
        }
    }
}
?>

Log messages to SQL debug window August 23, 2006

Posted by rossoft in CakePHP.
2 comments

My models have custom methods that do a lot of findAll(), save() etc. because I want to encapsulate that login on the models. Then the controller calls some of them in one action.

Sometimes, the SQL debug window has a lot of queries, and I don’t know what queries belongs to some method.

Now, I can do
$this->MyModel->_log_info(‘test begin’);
$this->MyModel->my_custom_method_that_does_a_lot_of_things();
$this->MyModel->_log_info(‘end begin’);

And I see the text ‘test begin’ and ‘end begin’ inside the SQL Debug Window, and between there’s the queries that belongs to ‘my_custom_method’

     /**
     * Logs the message to the sql debug window
     *
     * @param string $msg Message to show
     */
    function _log_info($msg)
    {
        if (DEBUG)
        {
            $db =& ConnectionManager::getDataSource($this->useDbConfig);
            $db->error=”;
             $db->affected=”;
            $db->numRows=”;
            $db->took=”;
            $db->logQuery(“<span class=\”query_log\”>[{$this->name}] $msg</span>”);
        }
    }

Working with HABTM associations August 23, 2006

Posted by rossoft in CakePHP.
24 comments

I’ve done some functions for working with HABTM associations.
For example:
You’ve the model Post and the model Tag. Post HABTM Tag.
You want to add the Tag with id 12 to the Post with id 3. Then you can do it easy with
$this->Post->addAssoc(3,’Tag’,12);

Now you want to delete the association between the Post 3 and the Tag 15? $this->Post->deleteAssoc(3,’Tag’,15);

You can even add more than one tag at once:
$this->Post->addAssoc(3,’Tag’,array(11,12));

Put these functions in your AppModel:
    /**
     * Adds to a HABTM association some instances
     *
     * @param integer $id The id of the record in this model
     * @param mixed $assoc_name The name of the HABTM association
     * @param mixed $assoc_id The associated id or an array of id’s to be added
     * @return boolean Success
     */
    function addAssoc($id,$assoc_name,$assoc_id)
    {
        $data=$this->_auxAssoc($id,$assoc_name);
        if (!is_array($assoc_id)) $assoc_id=array($assoc_id);
        $data[$assoc_name][$assoc_name]=am($data[$assoc_name][$assoc_name],$assoc_id);
        return $this->save($data);
    }

    /**
     * Deletes from a HABTM association some instances
     *
     * @param integer $id The id of the record in this model
     * @param mixed $assoc_name The name of the HABTM association
     * @param mixed $assoc_id The associated id or an array of id’s to be removed
     * @return boolean Success
     */
    function deleteAssoc($id,$assoc_name,$assoc_id)
    {
        $data=$this->_auxAssoc($id,$assoc_name);
        if (!is_array($assoc_id)) $assoc_id=array($assoc_id);
        $result=array();
        foreach ($data[$assoc_name][$assoc_name] as $id)
        {
            if (!in_array($id, $assoc_id)) $result[]=$id;
        }
        $data[$assoc_name][$assoc_name]=$result;
        return $this->save($data);
    }

    /**
     * Returns the data associated with a HABTM in an array
     * suitable for save without deleting the current relationships
     *
     * @param integer $id The id of the record in this model
     * @param mixed $assoc_name The name of the HABTM association
     * @return array Data array with current HABTM association intact
     */
    function _auxAssoc($id,$assoc_name)
    {
        //disable query cache
        $back_cache=$this->cacheQueries;
        $this->cacheQueries=false;

        $this->recursive=1;
        $this->unbindAll(array(‘hasAndBelongsToMany’=>array($assoc_name)));
        $data=$this->findById($id);
        $assoc_data=array();
        foreach ($data[$assoc_name] as $assoc)
        {
            $assoc_data[]=$assoc[‘id’];
        }
        unset($data[$assoc_name]);
        $data[$assoc_name][$assoc_name]=$assoc_data;

        //restore previous setting of query cache
        $this->cacheQueries=$back_cache;

        return $data;
    }

Updated packages in cakeforge August 23, 2006

Posted by rossoft in CakePHP.
5 comments

Hi! I’ve been out because my internet connection at home was down đŸ˜¦ Now I return with some improvements in the HeadHelper and CJS Templates. They’re in cakeforge (CJS Templates version 2.1)

The syntax has changed a bit in HeadHelper (read the version changes) and don’t forget to read the installation instructions for CJS Templates.

Mail Templates July 1, 2006

Posted by rossoft in CakePHP.
5 comments

New template view for sending mail through SMTP

Example:
– file app/views/foo/mail_view.mail
        <m:from name=”Miguel Ros” mail=”fake@teleline.es” />
        <m:to>
            fake1@gmail.com
        </m:to>
       
        <m:to>
            fake2@teleline.es
        </m:to>
       
        <m:subject> Hi! That is the subject
        </m:subject>
       
        <m:body_text>
        This is the plain body text
        </m:body_text>
       
        <m:body_html>
        This is the HTML <b>Body</b>
        </m:body_html>
       
        <m:attach path=”/tmp/test1.txt” name=”test_file_1.txt” />
        <m:attach path=”/tmp/test2.txt” />

– File views/foo/mail_view.thtml:
        The mail has been sent

 
– Action in FooController:
        function mail_view()
        {
            $this->view=’Mail’;
             $this->render(); //render the mail_view.mail
        
             $this->view=’View’;
             $this->render(); //now the mail_view.thtml renders
         }                       

Needs the WidgetHelper and PHPMailer class
Copy to app/views/mail.php

<?php
/**
 * Mail Templates
 * Sends email through a view
 *
 * The templates rendered must have extension .mail
 *
 * Uses:
 * @link http://phpmailer.sourceforge.net/
 * @see MailWidgetHelper
 *
 * @author RosSoft
 * @version 0.1
 * @license MIT
 *
 *
 * Usage example:
 * First, edit this file and change the define(‘CONFIG_SMTP…
 * with your ISP config.
 *  
 * file views/foo/mail_view.mail
        <m:from name=”Miguel Ros” mail=”rosmiguel@teleline.es” />
        <m:to>
            rossoft@gmail.com
        </m:to>
       
        <m:to>
            rosmiguel@teleline.es
        </m:to>
       
        <m:subject> Hi! That is the subject
        </m:subject>
       
        <m:body_text>
        This is the plain body text
        </m:body_text>
       
        <m:body_html>
        This is the HTML <b>Body</b>
        </m:body_html>
       
        <m:attach path=”/tmp/test1.txt” name=”test_file_1.txt” />
        <m:attach path=”/tmp/test2.txt” />
    File views/foo/mail_view.thtml:
        The mail has been set
    Action in FooController:
        function mail_view()
        {
            $this->view=’Mail’;
             $this->render(); //render the mail_view.mail
        
             $this->view=’View’;
             $this->render(); //now the mail_view.thtml renders
         }                       
 
 */
 
define(‘CONFIG_SMTP_HOST’,’xxxx’);
define(‘CONFIG_SMTP_USER’,’yyyy’);
define(‘CONFIG_SMTP_PASS’,’zzzz’);

//Directory within VENDORS/
define(‘PHPMAILER_SUBDIR’,’phpmailer’ . DS);     

class MailView extends View
{
    var $mail=null; //instance of MailAux
       
    function render($action = null, $layout = null, $file = null)
    {       
        $this->mail=& new MailAux;
        $this->controller->mail=& $this->mail;
           
        $file=null;      
       
        $this->load_helper(‘MailWidget’);
       
        if ($action==NULL)
        {
            $action=$this->action;
        }
        $file=$this->get_filename($action,’.mail’);
        ob_start();       
        parent::render($action,$layout,$file);
        ob_get_clean();
        return $this->mail->send();       
    }   

    /**
     * Adds a helper to the helpers array for loading it
     * @param string $helper Name of the helper like ‘Javascript’
     */   
    function load_helper($helper)
    {
        if (!in_array($helper,$this->helpers))
        {
            $this->helpers[]=$helper;
        }
    }
   
    /**
     * Returns the filename associated with the action
     * with the extension “.$ext”
     *
     * @param string $action If null, then current action
     * @param string $ext Extension of the view template (with dot) Example: ‘.xml’
     * @return string
     */
    function get_filename($action,$ext)
    {       
        $old_ext=$this->ext;
        $this->ext=$ext;
        $fn=$this->_getViewFileName($action);
        $this->ext=$old_ext;
        return $fn;
    }
   
}

/**
 * This object is the interface between MailView and MailWidgetHelper
 */
class MailAux extends Object
{
    var $charset=’utf-8′;
   
    var $from_mail=null;
    var $from_name=”;
    var $to=array();
    var $subject=”;
    var $body_text=null;
    var $body_html=null;   
    var $attachments=array();

    /**
     * SMTP Configuration
     */   
    var $host=CONFIG_SMTP_HOST;
    var $username=CONFIG_SMTP_USER;
    var $password=CONFIG_SMTP_PASS;

    /**
     * It contains mailer error message or false if no
     * error has occurred
     */   
    var $error=false;
   
   
    function send()
    {       
        vendor(PHPMAILER_SUBDIR. ‘class.phpmailer’);
        $mail = new PHPMailer();
        $mail->PluginDir = VENDORS .PHPMAILER_SUBDIR ;
        $mail->SetLanguage(‘en’,VENDORS .PHPMAILER_SUBDIR . ‘language’ . DS);
               
        $mail->CharSet= $this->charset;       
        $mail->IsSMTP();                  // send via SMTP
        $mail->Host     = $this->host; // SMTP servers
        if ($this->username !==null)
        {
            $mail->SMTPAuth = true;     // turn on SMTP authentication
            $mail->Username = $this->username;  // SMTP username
            $mail->Password = $this->password; // SMTP password           
        }       
        $mail->From = $this->from_mail;   
        $mail->FromName = $this->from_name;
        foreach ($this->to as $address)
        {           
            $mail->AddAddress($address);           
        }
        $mail->Subject  =  $this->subject;
        if ($this->body_html)
        {
            $mail->IsHTML(true); // send as HTML       
            $mail->Body     =  $this->body_html;
            $mail->AltBody  =  $this->body_text;
        }
        else
        {
            $mail->IsHtml(false);
            $mail->Body        =  $this->body_text;
        }
        //$mail->WordWrap = 50;                              // set word wrap
        foreach ($this->attachments as $attach)
        {
            $mail->AddAttachment($attach[‘path’],$attach[‘name’],’base64′,$attach[‘type’]);
           
        }       
        if (! $mail->Send())
        {
            $this->error=$mail->ErrorInfo;
            $this->log(‘Mail send:’ . $mail->ErrorInfo);
            return false;           
        }
        else
        {
            $this->error=false;
            return true;
        }
                               
    }
}
?>

Copy to app/views/helpers/mail_widget.php

<?php
/**
 * MailWidgetHelper
 *
 * For usage with MailView
 *
 * @author RosSoft
 * @version 0.1
 * @license MIT
 */
 
class MailWidgetHelper extends WidgetHelper
{   
    var $tag=array(    ‘m:from’,
                    ‘m:to’,
                    ‘m:subject’,
                    ‘m:body_text’,
                    ‘m:body_html’,
                    ‘m:attach’);

    /**
     * m:from (Mail From)
     * $attr[‘mail’] Mail
     * $attr[‘name’] Name (optional)
     */   
    function tag_m_from($attr,$inner_html)
    {
        $this->view->mail->from_name=@$attr[‘name’];
        $this->view->mail->from_mail=@$attr[‘mail’];
    }

    /**
     * m:to (Mail To)
     * $inner_html Destination address
     */   
    function tag_m_to($attr,$inner_html)
    {
        $this->view->mail->to[]=$this->_trim($inner_html);
    }
   
    /**
     * m:subject (Mail Subject)
     * $inner_html The subject
     */
    function tag_m_subject($attr,$inner_html)
    {
        $this->view->mail->subject=$this->_trim($inner_html);
    }
   
    /**
     * m:body_text  Body in plain text
     * $inner_html The body
     */
     function tag_m_body_text($attr,$inner_html)
     {
         $this->view->mail->body_text=$inner_html;
     }
       

    /**
     * m:body_html Body in html text
     * $inner_html The body
     */
     function tag_m_body_html($attr,$inner_html)
     {
         $this->view->mail->body_html=$inner_html;
     }

    /**
     * m:attach Adds an attachment
     * $attr[‘path’] The path of the file
     * $attr[‘name’] Overrides the attachment name
     * $attr[‘type’] MIME type
     */
     function tag_m_attach($attr,$inner_html)
     {
         $path=$attr[‘path’];
         $name=($attr[‘name’])? $attr[‘name’] : ”;
         $type=($attr[‘type’])? $attr[‘type’] : ‘application/octet-stream’;
        
         $this->view->mail->attachments[]=array(    ‘path’=>$path,
                                                 ‘name’=>$name,
                                                 ‘type’=>$type);
     }

     /**
      * Removes the spaces, tabs, newlines from
      * the beginning and ending of the string
      * @param string $string
      * @return string
      */     
     function _trim($string)
     {
         preg_match(‘/^[\n\t\s]*(.*)[\n\t\s]*$/’,$string,$matches);
         return $matches[1];        
     }

}
?>

Blog tutorial Chapter 3 done June 24, 2006

Posted by rossoft in CakePHP.
356 comments

Continuation of the extension blog tutorial.

Chapter 3: Ajax and Cookies. Saving a draft of the editing post in cookies through transparent periodicall ajax calls with feedback effects to the user.

Now you can get a .tgz file that containsthe tutorial in .PDF and .ODT formats, and the complete application before the chapter 1, before the chapter 3 and after the chapter 3. You can’t get stuck anywhere.

Download directly PDF version or download the complete .tgz with source code.

Working on a extension of the blog tutorial June 23, 2006

Posted by rossoft in CakePHP.
40 comments

I’m building a ampliation of the blog tutorial of the manual. It starts between the manual tutorial and the 2nd tutorial in the wiki.
It will do a walk through some cool things in Cake. It has a lot of screenshots.

This is the first two chapters, cool flash messages + some error handling.

They’re a recopilation of useful blog posts on the well-known CakePHP blogs.

Download directly PDF version or download the complete .tgz with source code.