Sunday, December 5, 2010

CakePHP: Tweak With Search, Managing Form Post Data

Nowadays, it has rather become a trend to let the visitors sort or filter search results. We let them search by the bestsellers, popular, newest, or, simply by price. We can use a form with a select box having options and let user hit the submit button. You can get the first result page to work pretty well with your default CakePHP setup. But the pagination fails. It is because Cake does not save the form post data. And here is a simple trick I found somewhere in the Internet.

USE SESSION VARIABLES.

In your controller setup:

function my_function() {

if(!empty($this->data)){
$this ->Session->write('search',$this->data['Model']['field']);
$search_string =$this->data['Model']['field'];
} else {
$search_string =$this ->Session->read('search');
}

$condition = array('Model.field_name'=>$search_string);
$search_data = $this->Model->find('all',array('conditions'=>$condition));
$this->set('search_data',$search_data);
}

/* in your view file use */
debug($search_data);
/* you can see the recordset, if debug mode is set to 2 or above. */

Hope this helps someone else as well.
Happy baking.

Sunday, August 29, 2010

How to Open a File (xls, doc, pdf etc) through a link in CakePHP

Well this question was discussed in Google Group.
The poster wants to open xls, doc, or pdf files using CakePHP. As such, he wants to create a link, which when clicked, should open the document.

Solution:
1. Save the file under /app/webroot/ folder. You can FTP that file for the sake of simplicity. If you need to upload files frequently, you can consider CAKEPHP file upload component. This component allows you to upload files from your computer to your server. Tweak it a bit to upload almost any file. But be sure to enforce all reasonable protections to prevent spammy uploads. Check to ensure that the file has been uploaded by pointing your browser to http://your-cake-domain.com/my_file_name.doc. Next we need to do something in the respective view file.

2. Create a variable name $file_path in the respective view files.

Say,  $file_path='my_file_name.doc';
Now simply create a link to that path.

Saturday, August 21, 2010

Something on CakePHP Notice (8): Undefined Index

I do not know what should be the perfect title for this post - amazing cake or uncanny cake. But unfortunately, CakePHP can literally drive you CRAZY at times. So, we need to develop a methodical debugging algorithm for cake.


Settings:

1. You are using proper Model/ Controller/ View setup.
2. You are firing correct SQL statement to fetch records. And database has at least one matching record.
3. Everything is just fine to the best of your knowledge and yet you don't really see any output in your view file.


To debug:


1. Set debug level to '2'.
As such my localhost is configured in that format by default. So, I do not have to change that setting too often. 



2. Read the output in the debug mode very carefully. Here lies the clue.
If you see a message like: 
Notice (8): Undefined index .. blah blah blah
CakePHP points out exactly what went wrong with the filename and line number even. This output is very reliable. "Undefined index" means you have used a variable somewhere but it has not been assigned any value. In production mode, CakePHP will ignore this warning and you will see nothing in your webpage leading to complete confusion.


3. You are more likely to fix the problem by correcting small typos in that specified file. Anyway, if you can't find anything wrong, follow the next few steps.

Check the value of the fetched recordset using debug($data) in your controller/action.
If you can see the values as per expectation, move on to your view files.
If you can't see any result, re-frame your find() conditions, examine your controller/action statements very carefully.

4. Check for typos.
Your variable names must be the same in controller set up and view files. Variable names you might have used in views without setting its value in the controller first. And remember the auto magic cake feature, which automatically converts controller variable names like:
my_funny_vars
to:
myFunnyVars

So, in your view file, you need to mention myFunnyVars and not my_funny_vars.
I wasted more than few hours to fix that.

5. Few other tips I have found elsewhere:
When using scaffolding the order your associations must be the same as the order of the keys in your table to avoid getting this error message. Thankfully, I did not face that problem till now.

6. Have confidence in your cake knowledge. Keep your cool. And move methodically (check for view files, then controller setup, and finally your great models). If you can see desired result using debug($data) in your controller, problem is really trivial. To get into the details, you can use debug($data) in your view file to trace out the contents of the array elements as well.

Sounds too complicated? That's how CakePHP goes on.
Hope it helps someone, who might have been stumbling with Notice (8): Undefined index!!!

Saturday, August 7, 2010

How to use SEO urls in CakePHP without the ID

CakePHP offers massive support to create SEO friendly URLs on the fly. You can create SEO-friendly-urls without an ID value to refer to specific post id. It works like a charm like many other Cake MAGIC!

I'll refer to the post table.
1. Create a field called 'slug' (must be UNIQUE and NOT NULL) in the post table.

2. Download and use sluggable behavior to create post slug! Follow instructions step-by-step. It works perfectly.

3. Define routing rules in config/router.php file to display SEO friendly urls.

Router::connect(


       '/posts/:slug',


       array(


               'controller' => 'posts',


               'action' => 'view'


       ),


       array(


               'slug' => '[-a-z0-9]+',


               'pass' => array('slug')


       )


);

4. In posts_controller.php view() function modify query,

function view($slug = null)
{
        if (empty($slug))
        {
                // redirect ...
        }
    
         $data = $this->Post->findBySlug($slug) ; 
      
}

You you should be able to access urls of the form:
http://example.com/your-seo-friendly-post-url/

Hope it helps someone.

Friday, June 18, 2010

How to Add Robots Meta Tag to a CakePHP View File

This is a pirated idea... and hats off to Maquel Collado for his wonder post

Adding robots meta-tag to a CakePHP view

You should check the original there. And here is a carbon copy:
Code:


<?php $html->meta('robots', null, array('name' => 'robots', 'content' => 'noindex') ,false); ?>

Add this html helper in its exact form in your view file. 

Output:

<meta name="robots" content="noindex"/> 

And it will appear within the <head> </head>   

And all the well behaved robots (Sure! Google is so) will stop indexing those pages.

Thanks Miquel. You are a genius!

Thursday, May 6, 2010

How to Create Multiple AJAX Forms on Same Page in CakePHP

Settings: Say, your posts/index.ctp page displays the latest ten posts. You want to allow user to rate each post.
Requirement: You MUST have a Post Model and a Comment Model.
In your respective view file: /app/view/posts/view.ctp
Type:

$i = 0; // a counter to create new divs

foreach ($posts as $post) :

// Code to display your post title, body etc.
// Now our comment form for each post

$i++;

$new_comment = 'new_comment_'.$i ; // for comment div id
$form_id = 'form_id_'.$i; // for form id

echo '<div id="'.  $new_comment .'"</div>';

echo $ajax->form(array('type' => 'post', 'options' => array(
'model'=>'Post', 
'update'=>$new_comment,
 'url'=>array('controller'=>'comments','action'=>'add'),
 'id'=>$form_id,'class'=>'CommentForm')
                                        ));

echo $form->input('comment',array('label'=>'Write your comment','type'=>'textarea', 'cols'=>'60','rows'=>'4'));

echo $form->end('Submit'); // close the form

// now close div
echo '</div>';
endforeach;

- - - - - - - - -
The idea is to create unique div to position each form and creating unique id for each form.

I created this post just before leaving for my office on the fly. Let me know if it helps.

Friday, April 30, 2010

Where Should I Upload robots.txt file in CakePHP installation & Why I Thanked Google Webmaster Tool Once Again

For those rather newbies (ME too!), robots.txt in a text file, which restricts access to specific folders or files in your web page. As such you create some rules using that file. Well behaved robots (like GoogleBOT) will follow these instructions before accessing any location.

We upload that file usually under /public_html/ folder of our web hosting directory, technically called root of our web servers. CakePHP has its own folder architecture, and, if you follow the default CakePHP installation, you should upload your robots.txt file under:

http://your-lovely-domain-name.com/app/webroot/robots.txt 


As such, I have subscribed to free Google Webmaster Tool. I found a notice long ago indicating my robots.txt file was not well formatted. I simply ignored it. Because, I checked that file manually many times and found nothing wrong.

Recently, Webmaster tool was displaying bunch of urls restricted by robots.txt file. I examined it and found the fault was in upload location itself. Issue was resolved finally.

Lesson today?

ALWAYS DOUBLE CHECK EVERYTHING WHEN YOU ARE GOING WITH CAKEPHP

And a big thanks to Google Webmaster Tool for pointing out to the urls restricted by robots.txt and finally, helping me to get into the problem location.

Thanks.

Thursday, February 18, 2010

CakePHP Pagination & Custom Routes Issue

Custom Routes are something that make CakePHP very interesting! If you are reading this post you must have experimented with what we call custom routing. The problem might arise when we want to paginate models which use custom routing.

The problems may be many-folded which all simplifies to one simple issue:

UNFORMATTED URL
Say, you want to display url in the following format using custom routes

CakePHP default url structure
http://example.com/categories/index/categoryID

Your WELL FORMATTED URL structure:
http://example.com/category-name-ID

Obviously, your custom route element points to 'index' action of 'categories' controller with two parameters - category-name and 'categoryID'.

Router::connect(

 '/:slug-:id/*',
  array('controller' => 'categories', 'action' => 'index'),
  array(
  // order matters
  'pass' => array('slug','id'),
 'id'=> '[0-9]+'
  )
  );

This works perfectly for URLs like
http://example.com/my-first-category-ID1
http://example.com/my-second-category-ID2

But it may cause problem when you try to paginate

Cake will not pick  your params (here 'slug' and 'id') passed through url if you do not force Cake to do so while paginating.  

To fix this issue, you MAY use:

 $paginator->options(array('url' => $this->passedArgs));  
or, as I have told here earlier following CakePHP book.

But this will format your paginated urls like
http://example.com/categories/index/param1/param2/page:xxxx

You can obviously browse pages with the above url. But it does not look decent. So, your entire effort with custom routing might just not work. 

You may still get a bad URL.

DO NOT WORRY!

While defining paginator options in your view file, follow religiously $html->link() structure. For an example I have put forward the structure of  $paginator->options() below in a view file:

file:// /app/views/categories/index.php

$paginator->options(array('url'=> array(
'controller' => 'categories', 
  'action' => 'index',
'slug'=>$this->params['slug']),
'id'=>$this->params['id'])
  ));

Now Cake will make well formatted URL automatically, and your paginated url should look like

http://example.com/category-name-id1/page:2
http://example.com/category-name-id1/page:3

Done!
I hope it helps someone.
Does it?

Please Note
I have marked asterisk symbol (*) with RED color while talking about custom route elements. This (*) MUST be there for pagination to work properly. 
Good night.

Saturday, February 13, 2010

How to Find Records using MySQL Match Against Query in CakePHP

To use MySQL Match Against Query in CakePHP:

$needle = 'Search String';

$conditions = array( 
 "MATCH(Post.title) 
  AGAINST('$needle' IN BOOLEAN MODE)" 
  );
 $matches = $this->Post->find('all', array('conditions' => $conditions));

It does not need any explanation. I hope.

Friday, February 12, 2010

CakePHP Select Empty

Say, you have a select box displaying dropdown options for categories. You want to give the user liberty to select nothing. In CakePHP it is pretty simple. Use 'empty' options in your view file.


<?php echo $form->input('category_id', array( 'empty' => '(choose one)')); ?>

Hope this helps someone.
Happy baking.

Tuesday, February 9, 2010

CakePHP hasAndBelongsToMany (HABTM) Join

Coming back to one of the most critical basics of Cake  - it is hasAndBelongsToMany (HABTM) relationship. You may know it is a type of relationship between two different database tables (Models) in CakePHP. A case study will explain its importance:

Say, you have Category model and Post model.
Each category has many posts and each post belongs to more than one categories.

Had there been only one category per post, it would be enough to use category_id field in posts table. But we are talking about posts each of which may have more than one categories. In this case besides categories and posts table, you need one extra table. By convention - this table should be named as categories_posts (note the alphabetical order of join tables). The table categories_posts will have following fields : id, category_id, post_id.

In your post model define the relationship.

<?php


class Post extends AppModel {
  var $name = 'Post';
  var $hasAndBelongsToMany = array(
  'Category' =>
  array(
  'className'              => 'Category',
  'joinTable'              => 'categories_posts',
  'foreignKey'             => 'post_id',
  'associationForeignKey'  => 'category_id'
  )
  );
  }


?>

This will show you all categories for a given post when you fire:

$this->Recipe->find();

Now the biggest question - how to SAVE records for HABTM association.

The default CakePHP functioning in this regard is not at all adequate. Say you want to add a new category to an existing post which already has a category defined by HABTM association - by default Cake will delete your existing category records for that post from categories_posts table before inserting the new record. That's an absolute mess if you do not really intend to desire existing records.  However, there is the savior.

ExtendAssociations Behavior.
This behavior allows you to easily add or delete HABTM associations!

A full detail of this Behavior can be found in Bakery!

You can download code for this behavior, and save that code as 'extend_associations.php' under '/app/models/behaviors' folder.

In your Post model, add the following code:


 <?php  
var $actsAs = 'ExtendAssociations'; 
?>  

So, our modified Post model should look like this:


 <?php  
class Post extends AppModel { 
    
var $name = 'Post'; 


var $actsAs = 'ExtendAssociations'; 
    
var $hasAndBelongsToMany = array( 
        
'Category' => array( 
            
'className' => 'Category', 
            
'joinTable' => 'categories_posts', 
            
'foreignKey' => 'post_id', 
            
'associationForeignKey' => 'category_id', 
        
), 
    
); 

?>  

Defining ACTION in PostsController
file: // app/controllers/posts_controller.php
Now in your PostsController - to ADD categories to a post use:


 <?php 
// to add only one category for a given post (say, post_id = 1)
$this->Post->habtmAdd('Category', 1, 1); 
// to add multiple categories 
$this->Post->habtmAdd('Category', 1, array(1, 2, 3)); 
?>  

// to DELETE categories for a given post (say, post_id = 1)


 <?php 
// delete a category 
$this->Post->habtmDelete('Category', 1, 1); 
// to delete multiple categories
$this->Post->habtmDelete('Category', 1, array(1, 3)); 
//  to delete all categories 
$this->Post->habtmDeleteAll('Tag', 1); 
?>   


That's it.
Good night.
Sweet Baking.

Friday, February 5, 2010

How to Remove Mailed-by Header in CakePHP Email Component

It took me quite sometime to fix this issue.

You can send emails with built-in CakePHP Email Component. But if you are sending email to any gmail address (like your-name@gmail.com), you can see an annoying 'mailed-by' header. It shows the name of your server, like mailed-by: dreamhost.com. It discloses your webhost. You may not like it. You may want to remove/hide this information. To do so, you need only one line of CakePHP code.

I have used following sendEmail() function in my /app/app_controller.php

file:// /app/app_controller.php


function sendEmail($subject, $view, $to=null, $from = null, $fromName = null) {
/* This function will be used to send email in my CakePHP application */
$this->Email->to = $to;
$this->Email->subject = env('SERVER_NAME') . ' – ' . $subject;
$this->Email->from = $fromName.' <'. $from.'>';
$this->Email->template = 'default';
$this->Email->additionalParams = '-fnoreply@yourdomain.com';
/* the above line is required to remove 'mailed-by' header' */ 
$this->Email->sendAs = 'both';   // you probably want to use both :)
return $this->Email->send($view);
}


Done.

Note again, whenever you use CakePHP Email Component, just add one extra line:

$this->Email->additionalParams = '-fnoreply@yourdomain.com';

And you can safely remove that annoying 'mailed-by' header.

That's it.

Just a guess, noreply@yourdomain.com is NOT essential. You may change it to anything you want.

Take care.

Responses awaited.

Saturday, January 30, 2010

How to Generate Options for HTML Select Tags in CakePHP

How to generate options for HTML Select Tags in CakePHP?

As such

  1. You can retrieve options from a Model (Database Table).
  2. Or, you can specify options for select tag in view files.
Just keep in mind CakePHP does not support ENUM type field. So, you may not use 'ENUM' datatype in table fields.

To retrieve options from Database Table

Say, you have two Models (tables) - Order and Size; 

Fields in 'sizes' table:
id, size
And fields in 'orders' table:
id, quantity, size_id

In your OrdersController file (/app/controllers/orders_controller.php)
$sizes = $this->Order->Size->find('all');
$this->set('sizes',$sizes);

Now in your view file: (/app/views/orders/add.ctp)

echo $form->create('Order');
echo $form->input('quantity');
echo $form->input('size_id');
echo $form->end('Submit');

And done.

Note the use of variable names. 
It is important to name the variables as per this convention. It will display all sizes available in database when you are adding a new order. To retrieve size field in table 'sizes', you need to specify variable name 'sizes' (note plural form) in your respective controller/action.  Now when you refer to 'size_id' in view file - cake will create a select tag on the fly with options retrieved from database.  

OK. It was pretty simple.

Now, how to display options for HTML Select Tag without any database table:

In this case, we have, say, only 'orders' table with following fields
id, quantity, size

In your view file (/app/views/orders/add.ctp), specify - 

$form->create('Order');
$form->input('quantity');
$sizes = array('s'=>'Small', 'm'=>'Medium', 'l'=>'Large');
echo $form->input('size', array('options'=>$sizes, 'default'=>'m'));
$form->end('submit');

That's it.

Friday, January 29, 2010

Managing Plugin URL in CakePHP

Just now I noticed my urls are getting deformed when I tried to access a plugin controller/action in my brand new CakePHP app. It took me sometime to understand that the problem was due to plugin call. When you are calling plugin controller/action - the url structure is:

 echo $html->link('Plugin Controller/Action Link', array('plugin' => 'plugin_name', 'controller' => 'controller_name', 'action' => 'action_name'));

The output will be:

http://example.com/plugin_name/controller_name/action_name

Make sure to mention 'plugin' => null in remaining urls, if you do not want to show plugin_name in other displayed links.

 echo $html->link('Non Plugin Link', array('plugin' => null, 'controller' => 'controller_name', 'action' => 'action_name'));

Output:

http://example.com/controller_name/action_name

If you do not specify 'plugin' => null, all the displayed links will show:

http://example.com/plugin_name/controller_name_1/action_name_1
http://example.com/plugin_name/controller_name_2/action_name_2
http://example.com/plugin_name/controller_name_3/action_name_3


From this what I get is that it is better to specify 'plugin' => null in case of all non-plugin links which are likely to be displayed in tandem with plugin links.



Or, there may be a short route - who knows?

Cake rocks... still learning each day...

Wednesday, January 27, 2010

How to Pass all URL Arguments to Paginator Functions

Use the following statement in view file for respective controllers/action:

$paginator->options(array('url' => $this->passedArgs));

This will retrieve all passed url arguments.

To specify which params to pass, use:

$paginator->options(array('url' =>  array("0", "1")));

Simple.

Thursday, January 14, 2010

CakePHP Pagination Change Label

Here we can change the displayed label for CakePHP Pagination:

Say, we want to sort results by user_id.

This is how it is done by default:

<?php echo $paginator->sort('user_id');?>


And to change the displayed label:
 <?php echo $paginator->sort('User', 'user_id');?>

Here User is the label which will be displayed.
user_id is the field to be sorted.