Using CakePHP Code Sniffer with PhpStorm

When working on development, there are two things that I use on a daily basis: The CakePHP web framework and the PhpStorm IDE. Unfortunately PhpStorm has no “native” support for CakePHP yet (as it does for Symfony2 and Yii), although there is a pending issue that can still use more votes ;-) .

However, the latest PhpStorm 6.0 came with support for PHP Mess Detector and PHP Code Sniffer. The latter one also has an official CakePHP extension available, to inspect if your code meets the CakePHP defined coding standards. You can load the CakePHP template for Code Sniffer into PhpStorm and it will then highlight any code you have that does not comply with the Cake coding standards.

CakePHP Code Sniffer in action in PhpStorm 6.0

CakePHP Code Sniffer in action in PhpStorm 6.0

Neat, right? In order to get this up and running, you will need to complete some steps, which I will describe here. First of all, we will need the base package PHP_CodeSniffer. This is available through the PEAR repository. Just open up a command-line console and go to your PHP directory (unless the PHP binary is in your Environment variables (PATH), in which case it’ll work from any directory) and run:

pear install PHP_CodeSniffer

Now we have the base package, also retrieve the CakePHP template, so we can verify our code against the CakePHP standards. Run the following commands to obtain it:

pear channel-discover pear.cakephp.org
pear install cakephp/CakePHP_CodeSniffer

OK, now we’re all set to integrate CakePHP CodeSniffer into our PhpStorm installation. Fire up the IDE and open a CakePHP project. Now open the Settings page (CTRL+ALT+S). Under the Project Settings, expand the PHP item and click Code Sniffer.

Here you must set the path to the PHP_CodeSniffer binary (or when on a Windows machine, it’s batch launcher). It should be installed in your PHP directory. You can verify that you have the correct binary selected by clicking the Validate button. It should show you the installed version of PHP_CodeSniffer.

PHP_CodeSniffer Validation

Validating the PHP_CodeSniffer installation.

Now that we have linked our Code Sniffer installation to our PhpStorm project, we still need to tell it to use the CakePHP template, so it will only raise “relevant” warnings. To do this, we stay in the Project Settings, but go the Inspections screen. Expand the PHP item and place a checkmark in front of the “PHP Code Sniffer validation” option. This will enable Code Sniffing in general, but still doesn’t enforce the Cake standards. To achieve this, we need to do one final step. After the “Coding standard” dropdown, click the refresh button. This will retrieve a list of all supported standards. CakePHP should be right at the top of it. Select it, click Apply and enjoy clean code!

Setting the proper inspection settings.

Setting the proper inspection settings.

If you also set the CakePHP Coding Standards under the Code Style settings, combining the Code Sniffer with the “Reformat Code” (CTRL+ALT+L) command in PhpStorm, you have very easy and powerful code refactoring tools.

Catching errors on AJAX calls with CakePHP

In many of todays web applications, AJAX requests have a crucial role in the usability. You don’t want users to have to refresh the page in order to get the data they requested, especially not if it’s in the middle of a form. However, I often see bad error handling practices when it comes to AJAX requests (read: no error handling at all).

Today, I want to share a method with you that I use in my CakePHP projects. The key is in using Cake’s CakeResponse class, to throw an error and have your AJAX call catch it and run the proper action in response. In my case, I had an AJAX method in my InvoiceLinesController to allow users to add a line to an invoice by using an AJAX call (through a jQuery UI dialog). It essentially does this:

public function ajax_add() {
    // Save the data passed in the POST request
    $this->InvoiceLine->create();
    $this->InvoiceLine->save($this->request->data);
}


Pretty simple, right? Just take in the passed data and save it as an invoice line. Now, this works well if the user enters proper data. But what if the user enters bad data into the form that doesn’t pass the validation rules you have set in your Model? That’s right, the save will fail. But how are you going to tell your user that?

I extended the method by checking the return status of the save operation and throw the validation errors if it failed:

public function ajax_add() {
    // Save the data passed in the POST request
    $this->InvoiceLine->create();
    if (!$this->InvoiceLine->save($this->request->data)) {
        // Throw a HTTP 400 status
        $this->response->statusCode(400);
 
        // Set the message to display in our element
        $this->set('message', __('The invoice line could not be saved due to these errors:'));
 
        // Loop over all the validationErrors
        $errors = array();
        foreach($this->InvoiceLine->validationErrors as $field) {
            foreach($field as $rule) {
                array_push($errors, $rule);
            }
        }
        $this->set(compact('errors'));
 
        // Render the error_dialog element
        $this->render('/Elements/error_dialog');
    }
}


Now, whenever validation fails, the AJAX call will get the validation errors as returned data. Furthermore (and this is the important bit), it throws a HTTP 400 (Bad Request) status, so the AJAX request will fail and it’s error clause is triggered, rather than it’s success clause as it would on a 200 OK status, which would happen if you don’t set the statusCode with the CakeResponse class.

I also created an element called “error_dialog” to hold and display the HTML content to the user. It’s a very simple element that just ouputs our message and an ordered list using the HtmlHelper nestedList method:

<?php
echo $message;
echo $this->Html->nestedList($errors, array(), array(), 'ol');


OK, so now you can use the data from your AJAX call to display to the user somehow. I chose to use jQuery UI Modal Dialog message, so it’s bound to get the users’ attention.

The error dialog

The error dialog

I added the following error clause to my AJAX call:

error: function(jqXHR) {
    $("#NewInvoiceLineErrorDialog").html("<p><span class=\"ui-icon ui-icon-alert\" style=\"float: left; margin: 0 7px 50px 0;\"></span>" + jqXHR.responseText + "</p>");
    $("#NewInvoiceLineErrorDialog").dialog({
        modal: true,
        buttons: {
            "OK": {
                class: "btn btn-primary",
                text: "OK",
                click: function() { $(this).dialog("close"); }
            }
        }
    });
}


This populates the dialog div with the message thrown by our action. In order to properly display this dialog, you need to add a hidden div to your view (I called it NewInvoiceLineErrorDialog), like this:

<div id="NewInvoiceLineErrorDialog" title="<?php echo __('Error'); ?>" style="display: none;"></div>


Now this div will stay hidden until our error clause is triggered, which will open a popup with the error message. At this point, whenever a user enters bad data, the dialog will pop-up telling them what they have done wrong, so they can fix it and make the save succeed.

Upgrading a CakePHP project from 1.3 to 2.x

I am currently working on upgrading a large CakePHP 1.3 application to CakePHP 2.2. Although the upgrade shell takes care of the most basics, there are still quite a lot of things to be done manually. I was told that upgrading is eventually worth the pain it brings, let’s hope so!

Up until now I have been mostly debugging stuff from my error logs (both Cake and Apache), fixing deprecated syntaxes, upgrading used plugins, clearing out and repopulating my ACL tables and now I’m working on finding/replacing some deprecated stuff. Up until now, I have come up with 2 massive find/replace operations that I found pretty useful and would like to write up here for my own future reference, maybe helping other people facing the same upgrade issues as well.

When working with querystring parameters, with 1.3 these used to be under:

$this->request->params['url']

But they are now under:

$this->request->query

which makes more sense anyway. Therefor I did a massive find/replace on the above.

Furthermore, the Paginator’s sort method now takes it’s arguments in reverse order, which causes all your paginated views to have their raw model/field names displayed, rather than the label you once provided. I’m not sure why the upgrade shell does not fix this, but using this find/replace regexes (for PhpStorm) made life easier for me:

Find:

\$this->Paginator->sort\('([^']+)', '([^']+)'\);

Replace:

\\$this->Paginator->sort('\$2', '\$1');

This basically finds the two arguments of the sort method (matching anything that’s not the closing quote) and then swapping capture groups 2 and 1. Works like a charm. Using phpStorm’s “Replace in path” function you can replace all instances in your entire project in less than a minute.

The final step is to inspect all my views for any flaws I might see and then have my colleagues help me test everything to see if it all went well. Fingers crossed. If I encounter any other issues of general interest, I will update this post.

AJAX requests with CakePHP’s SecurityComponent

For one of my current CakePHP projects, I am builing an invoice module, in which I want the users to be able to add new invoice lines to an invoice using AJAX requests, so the page doesn’t have to be reloaded for every new line.

The problem

In itself, that’s not a very hard task. But with Cake’s SecurityComponent with CSRF protection enabled, this becomes a different story. You see, the SecurityComponent generates a token for each form that is created with the FormHelper, to prevent CSRF attacks. This becomes problematic when you want to allow a user to use the same form more then once (in my case, have them add more than 1 invoice line). Because the second time you submit the (same) form with an AJAX request, the SecurityComponent will notice you are using an expired CSRF token and thus blackholes the request (and rightly so!). Now, as you can tell, I am actually pretty fond of this security measure and even though you can disable it for certain controllers or (since CakePHP 2.3) even specific actions within a controller, this didn’t feel right to me. I wanted to keep this feature enabled and use it for my AJAX calls as well.

The solution

It took me some tries to figure out there is a pretty simple and effective solution to get the SecurityComponent to allow your form more than once. In my case, I used the jQueryUI modal dialog functionality to present the form to the user. So, I took the content of this dialog (which is basically just the form) and created a seperate view for it (InvoiceLines/add). At first, I was thinking of an element, but the problem is that you can’t call an element directly by URL, so I couldn’t use jQuery’s load() method to retrieve it. After creating the seperate view, I was now able to “reload” the content after a successful submission by using jQuery:

$("#NewInvoiceLineDialog").load("' . $this->webroot . 'invoice_lines/add/' . $invoice['Invoice']['id'] . '");

By reloading the view, I am firing a new request to the FormHelper and SecurityComponent, making it create a new form with a fresh token that will be accepted! The add action in the InvoiceLines controller can be very simplistic:

public function add($invoice_id) {
        // Use AJAX layout
        $this->layout = 'ajax';
 
        $this->set(compact('invoice_id'));
    }

And in the view, we just add the form:

<?php
echo $this->Form->create('InvoiceLine', array('action' => 'add'));
echo $this->Form->input('invoice_id', array('type' => 'hidden', 'value' => $invoice_id));
echo $this->Form->input('amount');
echo $this->Form->input('product_id', array('label' => __('Product')));
echo $this->Form->input('description');
echo $this->Form->input('price');
echo $this->Form->end();

Then, finally in the success clause of our AJAX call, we reload the form in the dialog box and we’re all set. My complete dialog code looked like this in the end:

$this->Js->buffer('
    // Load the view intially, so it is visible for the first line
    $("#NewInvoiceLineDialog").load("' . $this->webroot . 'invoice_lines/add/' . $invoice['Invoice']['id'] . '");
    $("#NewInvoiceLineDialog").dialog({
        autoOpen: false,
        buttons: {
            "' . __('Add line') . '": function() {
                $.ajax({
                    type: "POST",
                    // The ajax_add method is just a plain save() operation
                    // With $this->autoRender = false; (since it has no seperate view).
                    url: "' . $this->webroot . 'invoice_lines/ajax_add",
                    data: $("#InvoiceLineAddForm").serialize(),
                    success: function() {
                        // To make the user actually see the new line without reloading, append the HTML
                        $("#lines tbody").append(
                            "<tr>" +
                                "<td>" + $("#InvoiceLineAmount").val() + "</td>" +
                                "<td>" + $("#InvoiceLineProductId").val() + "</td>" +
                                "<td>" + $("#InvoiceLineDescription").val() + "</td>" +
                                "<td>' . CURRENCY_SYMBOL . '&nbsp;" + $("#InvoiceLinePrice").val() + "</td>" +
                                "<td>' . CURRENCY_SYMBOL . '&nbsp;" + parseInt($("#InvoiceLineAmount").val() * $("#InvoiceLinePrice").val()) + "</td>" +
                            "</tr>"
                        );
                        // Close the dialog
                        $("#NewInvoiceLineDialog").dialog("close");
                        // And here, we load a brand spanking new form with a fresh token
                        $("#NewInvoiceLineDialog").load("' . $this->webroot . 'invoice_lines/add/' . $invoice['Invoice']['id'] . '");
                    }
                });
            },
            "' . __('Close') . '": function() { $(this).dialog("close"); }
        },
        closeText: "' . __('Close window') . '",
        hide: "clip",
        modal: true
    });
');

And that’s it! I hope it helps others facing the same issue. Like always, drop any questions or comments you might have in the comment box below!

Apache Warning: DocumentRoot does not exist

After updating my server today, I noticed that Apache hadn’t come back up after the reboot. When I tried to start Apache manually, I got a whole bunch of errors for all of my vhosts, stating:

Warning: DocumentRoot [/var/www/vhosts/example.com/httpdocs] does not exist

While in fact, the directories did exist and the apache user had read/write permissions to it. As it turned out after some searching around, a new SELinux policy (that was among the server updates) was responsible for this. I found all kind of answers on the web, varying from the most stupid ones:

“I had the same problem. Turned out to be SELINUX.
Edit /etc/sysconfig/selinux and change it to disabled then reboot.”

To some more serious ones stating that you should make sure that the DocumentRoot had the httpd_sys_content_t context. But, even setting this context did not work for me.

As it turned out, after running audit2allow on my audit.log file, access was denied because the root folder for the vhosts are in most of my cases also home directories for local users, so that when I add them, my local vsftpd install allows them to connect using their credentials and by setting their home directories to their vhost, their ftp user enters at that folder level. All I needed was just a SELinux setting that allows httpd to read user’s home dirs:

setsebool -P httpd_enable_homedirs 1

Whether or not this a very good idea security-wise, I’m not too sure. But there were pretty much just hobby and testing vhosts on this specific server, so I didn’t mind too much in this case. If you’re running a pretty serious production server, I would reconsider if there are any better alternatives available to you.

SELinux can be a pain sometimes, but it’s things like this that also make me appreciate the barriers it throws up that make you think over your server’s security settings once more.

CakePHP 2.1 RSS Component

RSS Component

Currently, I’m getting familiar with the “latest” (at the time of writing) version of CakePHP, 2.1 and am building a project with it. For this project I also needed a helper that can read RSS files (rather than write them, what Cake’s core RssHelper does). I have done this before for CakePHP 1.3, but that code was not really compatible with 2.1.

During the development I came to the conclusion that the cleanest way to achieve my goal was to actually create a component, rather than a helper, because there is still some logic that needs to be done with the resulting object. And obviously, logic goes in the Controller and not in the View. So, a component it is!

The component is pretty simple and straight forward. Here is the entire source code (should go under app\Controller\Component\RssComponent.php), which we will discuss in a bit.

<?php
App::uses('Xml', 'Utility');
 
class RssComponent extends Component {
 
    /**
     * Reads an (external) RSS feed and returns it's items.
     *
     * @param $feed - The URL to the feed.
     * @param int $items - The amount of items to read.
     * @return array
     * @throws InternalErrorException
     */
    public function read($feed, $items = 5) {
        try {
            // Try to read the given RSS feed
            $xmlObject = Xml::build($feed);
        } catch (XmlException $e) {
            // Reading XML failed, throw InternalErrorException
            throw new InternalErrorException();
        }
 
        $output = array();
 
        for($i = 0;$i < $items;$i++):
            if(is_object($xmlObject->channel->item->$i)) {
                $output[] = $xmlObject->channel->item->$i;
            }
        endfor;
 
        return $output;
    }
 
}

First off, we’ll be using the Xml Utility, that is shipped with CakePHP, which we include using:

App::uses('Xml', 'Utility');

The component itself contains just 1 method, the read method. It takes 1 required argument ($feed), which holds the URL to the feed and 1 optional argument ($items) to determine the amount of items to retrieve from the RSS feed. The default is set to 5 here, but you can obviously change that if you want to.

The method itself is pretty simple, it just calls the Xml::build method to create a SimpleXMLElement of the feed. It catches any error that the Xml::build method might throw (e.g. if the feed URL is dead). We can catch that error from our controller.

In my case, the feed is part of the layout, so I called the component from the AppController in my beforeRender method, like this:

public function beforeRender() {
    try {
        $newsItems = $this->Rss->read('http://example.com/feed');
    } catch(InternalErrorException $e) {
        $newsItems = null;
    }
 
    $this->set('news', $newsItems);
}

So, basically we tell the AppController to try and fetch the news items and pass it to our views as the $news variable. If the reading of the items fails, $news will be set to null, which you can then check in your View. Also, make sure you don’t forget to load up the Component in your controller by putting it in the $components array.

public $components = array('Rss');

Finally, this is what my layout bit looks like. It will be different in your case, but just to give you an idea.

<?php
if(isset($news) && !is_null($news)) {
    $newsItems = array();
    foreach($news as $newsItem):
        $newsItems[] = $this->Html->link(
            $newsItem->title,
            $newsItem->link,
            array('class' => 'new_window')
        ) . '<p>' . $newsItem->description . '</p>';
    endforeach;
 
    echo $this->Html->nestedList($newsItems, array('class' => 'news'));
} else {
    echo $this->Html->nestedList(array('<p>News unavailable.</p>'), array('class' => 'news'));
}
?>

This bit checks whether we have the $news variable set and if it’s not null (so, it contains items). Then, the foreach loop prepares the items that will be passed to the nestedList method of the HtmlHelper. This way, we’ll get a nice unordered list containing the news items, which is exactly what I needed in my case to fit it into my layout. Again, your View/Layout will be different, no doubt. But, this should help you get going on your way with the RssComponent.

If you have any question or comments feel free to post yout comments below!

Adjusting Parallels/Plesk greylisting patterns

Today I got a mail from a customer saying that a mail that someone tried to send them kept bouncing from my Plesk/Qmail mailserver. Upon inspection of the maillog, I noticed this error at the specific e-mail message:

Jan 17 14:31:08 servername greylisting filter[539]: Starting greylisting filter…
Jan 17 14:31:08 servername greylisting filter[539]: list type: black, from: ch1outboundpool.messaging.microsoft.com, match string: dsl|pool|broadband|hsd
Jan 17 14:31:08 servername qmail-queue-handlers[538]: handlers_stderr: REJECT

Apparently, there was some kind of blacklist setting in the greylisting filter for all domains matching the regex “dsl|pool|broadband|hsd“, in layman’s terms: a domain which has either “dsl”, “pool”, “broadband” or “hsd” in them. Since our sender seemed to be using Microsoft’s cloud services and was using ch1outboundpool.messaging.microsoft.com as SMTP server, it matched the pattern.

But it’s a ridiculous pattern, as it would match perfectly valid domains like some-dslprovider.com, poolparty.com or broadbandstore.com. Obivously, I didn’t want this pattern to be in my greylisting filter and thus my quest to remove it had begun.

Some Googling got me to this command to inspect the greylisting configuration:

/usr/local/psa/bin/grey_listing --info-server

It turned out that this pattern was indeed effectively blacklisted:

Black domains patterns list:
*[0-9][0-9]-[0-9][0-9]-[0-9][0-9]*
*[0-9][0-9].[0-9][0-9].[0-9][0-9]*
*[0-9][0-9][0-9]-[0-9][0-9][0-9]-[0-9][0-9][0-9]*
*[0-9][0-9][0-9].[0-9][0-9][0-9].[0-9[0-9]][0-9]*
dsl|pool|broadband|hsd
dynamic|static|ppp|dyn-ip|dial-up

Now, there were some forum topics around suggesting that this command would remove it: /usr/local/psa/bin/grey_listing –update-server -blacklist del:”dsl|pool|broadband|hsd”. Unfortunately, that was not working:

/usr/local/psa/bin/grey_listing --update-server -blacklist del:"dsl|pool|broadband|hsd"
unable parse pattern list: incorrect pattern "dsl|pool|broadband|hsd"

It turns out that the -domains-blacklist option should be used instead and then it works fine (I went into the /usr/local/psa/bin directory before running this):

./grey_listing --update-server -domains-blacklist del:"dsl|pool|broadband|hsd"
SUCCESS: Update of server-wide settings complete.

Now, checking the greylisting config again showed that the pattern was gone:

./grey_listing -i

Which gave the following output:

Black domains patterns list:
*[0-9][0-9]-[0-9][0-9]-[0-9][0-9]*
*[0-9][0-9].[0-9][0-9].[0-9][0-9]*
*[0-9][0-9][0-9]-[0-9][0-9][0-9]-[0-9][0-9][0-9]*
*[0-9][0-9][0-9].[0-9][0-9][0-9].[0-9[0-9]][0-9]*
dynamic|static|ppp|dyn-ip|dial-up

And then finally restarted Qmail to make sure the new config was applied properly:

/etc/init.d/qmail restart
$Starting qmail: done

That was that, now the pattern is no longer applied to incoming mail and the valid mails get delivered again.

Copying a binary and it’s shared libraries to a chrooted directory

Today, I needed to construct a command for copying the rsync binary and it’s library dependencies to a chrooted directory. Since this might come in handy in the future, and might help other people to achieve the same as well, I decided to post it on here.

cp /usr/bin/rsync /var/www/vhosts/example.com/bin/;\
ldd /usr/bin/rsync|awk '{print $3}'|grep -e '^/'|xargs -I{} cp -v --parent '{}' /var/www/vhosts/example.com/

A quick breakdown. At first, we just do a plain copy of the rsync binary. After that, we fire up ldd on the rsync binary, then use awk to only return the third word of each output line of ldd (which should be the location to the binary). To make sure it’s a full path, a grep -e on that result should make sure the output starts with a slash (if ldd returns only a name rather than a full path, the binary will probably not work in the chrooted environment, so this method is not 100% fool-proof). Finally, we copy the matches into our chrooted environment using the –parent option (and -v for verbosity so you can see what is actually copied), so that the paths are entirely copied, e.g.: /lib64/somelib.so.1 would become /var/www/vhosts/example.com/lib64/somelib.so.1. Rather than putting it hardcoded into /lib or /usr/lib, the –parent option adopts the relative path the library should have within the chrooted environment.

In my case, this command worked perfectly for getting rsync to run. But again, it might not be 100% fool-proof for everything. Any additions/suggestions to make it so are welcome.

Dynamically resizing an iframe

For a website I’m currently working on, I have an iframe in place for implementing a forum into the site, so the forum always fits nicely into the layout, also whenever the layout changes. There was only one problem with this setup, the height of the iframe was statically set and whenever a visitor was viewing a larger page/topic a scrollbar would appear within the iframe, which looks pretty nasty.

The ideal situation would be for the iframe to dynamically change it’s size whenever another page was loaded. It took a bit of trial and error, but eventually I found a way to get it done. I created this JavaScript function (inspired by various similar examples out there) to get the job done for me.

function frameResize(frameObj) {
    var frameHeight;
 
    // Reset the height back to it's original (shrink the frame)
    frameObj.height = '200px';
 
    // Set the frame height to match the content
    frameHeight = frameObj.contentWindow.document.body.scrollHeight;
    frameObj.height = frameHeight + 'px';
}

In order for this to work, don’t forget to add the onLoad attribute to your iframe which calls the function, like this:

<iframe src="some_page.html" width="100%" height="200px" id="iframe" marginheight="0" frameborder="0"
onLoad="frameResize(this);"></iframe>

CakePHP contact form with validation rules

Recently I’ve discussed this code in the #cakephp IRC channel to someone who was looking to achieve to create a CakePHP contact form, but was struggling with it a little. I then put my code on the pastebin and decided to post it here as well. Might help other people with the same issue.

What we’ll be doing is creating a simple contact form following CakePHP conventions and using the benefit of Cake’s validation rules. It’s actually quite simple to achieve this.

The first thing you’ll want to be doing is to determine what fields your form will need to have and which of those are required and optional. Once you’re clear on that, you can start writing up your model. The model will not actually save the message to the database (although it could if you’d want to save a copy there), but it will just validate the submitted data. In my case the required fields were name, email and message. Here’s the model that goes with it.

class Contact extends AppModel {
 
    var $name = 'Contact';
    var $useTable = false;
 
    var $validate = array(
        'name' => array(
            'rule' => 'notEmpty',
            'message' => 'You have not entered your name.'
        ),
        'email' => array(
            'rule' => 'email',
            'message' => 'You have entered an invalid e-mail address.'
        ),
        'message' => array(
            'rule' => 'notEmpty',
            'message' => 'You did not enter a message.'
        )
    );
}

Next up, in order to actually get our validation rules to work, we’ll need to tell our controller to validate the data before e-mailing it. Furthermore, we’ll need to tell our controller to use Cake’s Email component and set the required parameters to send it. If you’re not familiar with the Email component, I suggest your read the cookbook’s article on it. Here’s what the controller looks like.

class ContactsController extends Controller {
 
    var $components = array('Email');
 
    function send() {
        if(!empty($this->data)) {
            $this->Contact->set($this->data);
 
            if($this->Contact->validates()) {
                if(!empty($this->data['Contact']['company'])) {
                    $this->Email->from = $this->data['Contact']['company'] . ' - ' . $this->data['Contact']['name'] . ' <' . $this->data['Contact']['email'] . '>';
                } else {
                    $this->Email->from = $this->data['Contact']['name'] . ' <' . $this->data['Contact']['email'] . '>';
                }
                $this->Email->to = 'email@example.com';
                $this->Email->subject = 'Website request';
                $this->Email->send($this->data['Contact']['message']);
                $this->Session->setFlash('Your message has been sent.');
                // Display the success.ctp page instead of the form again
                $this->render('success');
            } else {
                $this->render('index');
            }
        }
    }
 
    function index() {
        // Placeholder for index. No actual action here, everything is submitted to the send function.
    }
 
}

Finally, we need our views to show the form and the success message if the message was sent. This is most likely to look different in your case, but just for the complete picture, I’ll share them with you anyway.

app/views/contacts/index.ctp

<p>Thank you for your interest in our company. Leave a message.</p>
<?php echo $this->Form->create('Contact', array('action' => 'send')); ?>
<table style="border:none;">
    <tr>
        <td>Name</td>
        <td><?php echo $this->Form->input('Contact.name', array('label' => false, 'maxlength' => 100, 'size' => 40)); ?></td>
    </tr>
    <tr>
        <td>Company</td>
        <td><?php echo $this->Form->input('Contact.company', array('label' => false, 'maxlength' => 100, 'size' => 40)); ?></td>
    </tr>
    <tr>
        <td>E-Mail</td>
        <td><?php echo $this->Form->input('Contact.email', array('label' => false, 'maxlength' => 100, 'size' => 40)); ?></td>
    </tr>
    <tr>
        <td style="vertical-align: top;">Your comment</td>
        <td><?php echo $this->Form->input('Contact.message', array('label' => false, 'cols' => 50, 'rows' => 10)); ?></td>
    </tr>
    <tr>
        <td colspan="2" align="center"><br><?php echo $this->Form->end('Send'); ?></td>
    </tr>
</table>

app/views/contacts/success.ctp

<?php
echo $this->Session->flash();
?>
<p>Thank you, we'll get back to you shortly.</p>

That’s all! A nice and simple contact form with proper field validation.