Sunday, March 31, 2013

Upload robots.txt file in CakePHP installation

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.

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.

Robots Meta Tag to CakePHP View File

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.

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.

Open 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.

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.

CakePHP ACL Plugin & Facebook Plugin

I am glad to get personal emails from a few of our readers. They are mostly Cake Newbies! I am really excited to find that this blog has helped some of them. I promised to share two more plugins, which is a must have in your next Cake Project. The first one will make your life happy (if you use Auth component for authorization) and the second one will make your client happy (if he has strong facebook love!) . These two plugins are:

1) ACL plugin
2) facebook plugin


As you might know, it is easy to install plugins. Download necessary files, save them under /app/plugins/plugin-name/ folder. If it needs any extra database table, fire your SQL query editor. Usually, necessary table structures are included with the plugin package. Your tables should be ready within a few seconds. Next check for a few configuration files. You can find this file under:

/app/plugins/your-plugin-name/config/config.php

READ & FOLLOW the installation direction for your new plugin verbatim. Do not skip any single character. Most of the time, I messed things up because I really skipped a few words. The cakeMagic works when you follow the conventions. So, read the instructions and follow it. Make necessary changes to set your settings. If you do everything okay, it should be ready for use within 10- 20 minutes. A single error on your part can lead to lot of frustrating hours.

Regarding these two plugins here is what I think.

ACL plugin: Well those using Auth Component might be aware of the fact that CakePHP has a built-in mechanism to set user-level access control to various pages (/controllers/action/) of your website. ACL plugin has a beautiful AJAX based GUI. You can create new ACO on the fly and set permissions.

For example, say, in your PostsController (posts_controller.php) you have created new action - 'post_by_user' (function post_by_user()). Now you want only registered users to view this section.  So, add a new ACO [Access control object, in this case post_by_user() under 'posts' controller.] ACL plugin allows you to create this new node. Now you can set group level or user specific permission using ACL component. I am just giving you a screen shot.



The facebook plugin is a cool cakePHP application. It displays facebook like button, facebook login options, facebook fan pages and many more. My simple tips are to wait for a few hours after you have installed this plugin for it to work properly. Remember you need to create your APP ID, API Key & Secret at facebook (Create Applications). It takes a few hours (in my case) to propagate. IN the meantime, your facebook login action won't work. So, do not loose your heart.

Once you have installed those plugins, you can access it using the following urls:

http://your-good-domain.com/plugin-name (if you are NOT using admin routing)
http://your-good-domain.com/admin/plugin-name (if you are using admin routing)

Remember installing and using cakePHP plugin is pretty simple. FOLLOW the steps accurately. You must not have any reason to become frustrated anymore. 
Happy Baking!

Links to downloads:

Complete User Registration with CakePHP

I was just browsing through my old posts. Hmm. it looks okay. I've started newly with Cake (CakePHP), so, I'm to really learn a lot myself. Anyway, as I'm through my process of learning, I thought it would be great to keep track of what I'm actually reading to get me into the GAME quickly.

My today's task was to learn about a simple user registration system. Once again, I had to go through the CakePHP book and some other references. Instead of listing every detailed step, I would prefer to refer to those MUST read links, which just work like a CHARM in creating a User Management/Registration System using Cake.

Step: 1 Set up CakePHP Console
The console works like a charm. If you had problem in using this console in windows environment, simply, follow the step-by-step method given here.

Step: 2 Follow the CakePHP Simple ACL Control Application
This complete tutorial will guide you through the process of creating your user management system. But before going through this tutorial, please, make sure to understand basic working principles of Cake nicely. The tutorial makes full use of different Cake's core components like Acl Component & Auth Component.

Step: 3 Set custom routing
file: // app/config/routes.php

Copy paste following codes:

Router::connect('/login', array('controller' => 'users', 'action' => 'login'));
Router::connect('/logout', array('controller' => 'users', 'action' => 'logout'));
Router::connect('/register', array('controller' => 'users', 'action' => 'register'));


This will show login form, when someone types http://caketest.local/login and likewise.


Step: 3 Create a dynamic login/logout menu
The Cakebook tutorial does not include creating a dynamic login/logout menu. So, you need to create one.
1. Create a new file.
2. Copy-paste the following code.

<?php 
if(!$session->check('Auth.User')){
echo $html->link('Login','/login');
} else {
$username = $session->read('Auth.User.username');
echo " Hello ". $username ."&nbsp;";
echo $html->link("(logout)", "/logout", array(), null, false);
}
?>

3. Save this file as '/app/views/elements/login_menu.ctp'

4. Open '/app/views/layouts/default.ctp'
5. Copy-paste the following code.

<?php echo $this-> element('login_menu'); ?>

6. Save this file.

Now you can see the login/logout option.
Notice I have used SESSION variables to control login/logout option. To learn more about CakePHP session, please visit this page. For a formatted output of contents inside session variables, use pr($_SESSION) - STRICTLY for DEBUG;


Step 4:  Ban a user account
1.Fire the following SQL query:


ALTER TABLE `users` ADD  `is_banned` TINYINT NOT NULL DEFAULT '0';

This adds a field 'is_banned' in the 'users' table. Set default values to zero.

2. Now copy-paste following code in UsersController::beforeFilter()
file:// app/controllers/users_controller.php

$this->Auth->userScope = array('User.is_banned' => 0);

3. Done. Cake will not allow users to login, when you have set 'is_banned' = 1.
Step 5: Email Validation during user registration
The code is pretty long and nicely explained here. To run with my User model (based on CakePHP's default ACL Component), I needed to make some small adjustment. So, I think it is better to give the codes intact here.

file://  app/controllers/users_controller.php


<?php
 uses('sanitize');
class UsersController extends AppController {




        var $name = 'Users';
var $components = array('Email','Auth');
                                                                    /* "Email' component will handle emailing tasks, 'Auth'    component will handle User Management */
var $helpers = array('Html', 'Form');

/* ..... member functions will go here ... */
}

function beforeFilter()

/* CakePHP CallBack methods */
function beforeFilter() {
   parent::beforeFilter(); 
$this->Email->delivery = 'debug'; /* used to debug email message */
$this->Auth->autoRedirect = false; /* this allows us to run further checks on login() action.*/
$this->Auth->allow('register', 'thanks', 'confirm', 'logout'); 
$this->Auth->userScope = array('User.is_banned' => 0); /* admin can ban a user by updating `is_banned` field of users table to '1' */
}

function register()

// Allows a user to sign up for a new account
        function register() {

                if (!empty($this->data)) {
                        // Applying Auth Components's Password Hashing Rules
/*
We have commented the following field as this was double-hashing password.
$this->Auth->password($this->data['User']['passwrd']); 

*/
                   //      $this->data['User']['passwrd'] = $this->Auth->password($this->data['User']['passwrd']);
 
                        $this->User->data = Sanitize::clean($this->data);
           
// Successfully created account – send activation email     
            
                        if ($this->User->save()) {
                                $this->__sendActivationEmail($this->User->getLastInsertID());


// pr($this->Session->read('Message.email')); /*Uncomment this code to view the content of email FOR DEBUG */


                                // this view is not show / listed – use your imagination and inform
                                // users that an activation email has been sent out to them.
                                $this->redirect('/users/thanks');
                        }
                        // Failed, clear password field
                        else {
                                $this->data['User']['passwrd'] = null;
                        }
                }
$groups = $this->User->Group->find('list');
$this->set(compact('groups'));
        }


Function login()


function login() {
                // Check for incoming login request.
//pr($this->data);
                if ($this->data) {
                        // Use the AuthComponent's login action
                        if ($this->Auth->login($this->data)) {
                                // Retrieve user data
                                $results = $this->User->find(array('User.username' => $this->data['User']['username']), array('User.active'), null, false);
                                // Check to see if the User's account isn't active
                                if ($results['User']['active'] == 0) {
                                        // Uh Oh!
                                        $this->Session->setFlash('Your account has not been activated yet!');
                                        $this->Auth->logout();
                                        $this->redirect('/users/login');
                                }
                                // Cool, user is active, redirect post login
                                else {
                                        $this->redirect('/');
                                }
                        }
                }
        }

function logout()
function logout() {
$this->Session->setFlash('Good-Bye');
$this->redirect($this->Auth->logout());
}


/* function to validate activation link

* and to set 'active' = 1
*/  

function activate()

function activate($user_id = null, $in_hash = null) {

        $this->User->id = $user_id;

if ($this->User->exists() && ($in_hash == $this->User->getActivationHash())) {
         if (empty($this->data)) {

$this->data = $this->User->read(null, $user_id);
   // Update the active flag in the database
$this->User->set('active', 1);
$this->User->save();

$this->Session->setFlash('Your account has been activated, please log in below.');
                $this->redirect('login');
}
}

     // Activation failed, render '/views/user/activate.ctp' which should tell the user.
}


function __sendActivationEmail()

/* function to send activation email */
 function __sendActivationEmail($user_id) {
                $user = $this->User->find(array('User.id' => $user_id), array('User.email', 'User.username','User.id'), null, false);
                if ($user === false) {
                        debug(__METHOD__." failed to retrieve User data for user.id: {$user_id}");
                        return false;
                }

                // Set data for the "view" of the Email
                $this->set('activate_url', 'http://' . env('SERVER_NAME') . '/users/activate/' . $user['User']['id'] . '/' . $this->User->getActivationHash());
                $this->set('username', $this->data['User']['username']);
                
                $this->Email->to = $user['User']['email'];
                $this->Email->subject = env('SERVER_NAME') . ' – Please confirm your email address';
                $this->Email->from = 'noreply@' . env('SERVER_NAME');
                $this->Email->template = 'user_confirm';
                $this->Email->sendAs = 'text';   // you probably want to use both :)    
                return $this->Email->send();

        }

Copy paste function getActivationHash at file:// app/models/user.php

function getActivationHash()
        {
                if (!isset($this->id)) {
                        return false;
                }
                return substr(Security::hash(Configure::read('Security.salt') . $this->field('created') . date('Ymd')), 0, 8);
        }
Copy-paste following code in the file:// app/app_controller.php inside the function beforeFilter()
function beforeFilter() {
$this->Auth->fields = array('username' => 'username', 'password' => 'passwrd');
       /* ... Rest of the function body goes here */
}

Now View Files


Registration form
file:// app/views/users/register.ctp



<h2>Create an Account</h2>
<?php
echo $form->create('User', array('action' => 'register'));
echo $form->input('username');
// Force the FormHelper to render a password field, and change the label.
echo $form->input('group_id', array('type' => 'hidden', 'value' => 'Insert-Default-Value'));
echo $form->input('passwrd', array('type' => 'password', 'label' => 'Password'));
echo $form->input('email', array('between' => 'We need to send you a confirmation email to check you are human.'));
echo $form->submit('Create Account');
echo $form->end();
?>

Notice replace 'Insert-Default-Value' with the actual value of your group_id.
   
Login form
file:// app/views/users/login.ctp



<?php
echo $form->create('User', array('action' => 'login'));
echo $form->input('username');
echo $form->input('passwrd', array('label' => 'Password', 'type' => 'password'));
echo $form->end('Login');
?>



user_confirm.ctp
file:// app/views/elements/email/text/user_confirm.ctp

<?php
  # /app/views/elements/email/text/user_confirm.ctp
  ?>
  Hey there <?= $username ?>, we will have you up and running in no time, but first we just need you to confirm your user account by clicking the link below:
  <?= $activate_url ?>

With all the above scripts, you should be able to get a workable user registration system.
Here, you will have groups/ users/ and you can set group level access per controller, even per action following Cake's default mechanism!

[Acknowledgements]
My sincere regards to Jonny Revees for his wonderful work on this CakePHP user registration system. It works like a charm!


Here are some more stuff I found helpful:
CakePHP Auth Component variables.
Understanding CakePHP Session
Saving data in CakePHP found in book.cakephp.org
Debuggable.com - this post explains how to debug CakePHP email.

Create Category Tree with CakePHP 'Tree' behavior

Okay! Let's try to create a category tree using CakePHP (This is something like parent category -> child category type records).

SQL:

CREATE TABLE categories (
id INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT,
parent_id INTEGER(10) DEFAULT NULL,
lft INTEGER(10) DEFAULT NULL,
rght INTEGER(10) DEFAULT NULL,
name VARCHAR(255) DEFAULT '',
PRIMARY KEY  (id)
);

Now insert some record:
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(1, 'Tutorials', NULL, 1, 8);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(2, 'PHP', 1, 2, 5);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(3, 'MySQL', 1, 6, 7);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(4, 'CakePHP', 2, 3, 4);

Now I'll create a model for this category.

1. Create a new file.
2. Copy-paste the following code:

<?php
   class Category extends AppModel {  
var $name = 'Category';
   var $actsAs = array('Tree');
}
?>

3. Save the files as app/models/category.php.

Note:
The variable $actsAs tells Cake to attach 'Tree' behavior to this model, i.e.,  Cake will generate a Tree data structure for category model. I think it is also a good time to introduce you with another fascinating feature of CakePHP - 'Behaviors'. CakePHP has built-in behaviors, like - behaviors for tree structures, translated content, access control list interaction etc., which you can attach with any model. As you might know - 'add', 'edit', 'delete' options for these type of data structures need special care. Cake takes care of it once you have specified the applicable 'behavior' in the model. Behaviors are attached with models using $actsAs variable. In this case, I have specified $actsAs = array('Tree'). This will enforce 'Tree' behavior on Category model. Simple.
To learn more about 'Behaviors', please refer to CakePHP online book.

Now I'll create CategoriesController
Step:
1. Create a new file.
2. Copy-paste the following code.

<?php
class CategoriesController extends AppController {
            var $name = 'Categories';  
             
function index() {
                  $categories = $this->Category->generatetreelist(null, null, null, '&nbsp;&nbsp;&nbsp;');
                  $this->set(compact('categories'));    
                  }
 }
?>
3. Save that file as categories_controller.php under 'app/controllers' folder.

Note: generatetreelist() method generates a tree-type views for our Categories. There are lots of options you can use with this method. For a complete guidelines on options for this method, please refer to CakePHP book.
compact(); function is used to pass variables to your views in CakePHP. Compact() method detects the variables having the same name (in this case 'categories') in your Controller and splits them as an array() of $key => value pairs. Now $this->set() is used to set those values for using them in your view file.

Now I'll create a view for our index() function.
file: '/app/views/categories/index.php'

<?php
echo $html->link("Add Category",array('action'=>'add'));
echo "<ul>";
  foreach($categories as $key=>$value){
  echo "<li>$value</li>";
  }
  echo "</ul>";
?>


Now point your browser to:
http://caketest.local/categories

And you should see following structure:

> Tutorials    
   > PHP    
> CakePHP    
> MySQL

To Add a new category to the list:
1. Open categories_controller.php (found under '/app/controllers')
2. Copy-paste the following function:


function add() {


if (!empty($this -> data) ) {
$this->Category->save($this -> data);
$this->Session->setFlash('A new category has been added');
$this->redirect(array('action' => 'index'));
} else {
$parents[0] = "[Top]";
$categories = $this->Category->generatetreelist(null,null,null," - ");
if($categories) {
foreach ($categories as $key=>$value)
$parents[$key] = $value;
}
$this->set(compact('parents'));
}


}

3. Save this file.

Now we need to create a view file for this add() method (to display the add category form).

1. Create a new file.
2. Copy-paste the following code:


<h1>Add a new category</h1>
<?php
echo $form->create('Category');
echo $form->input('parent_id',array('label'=>'Parent'));
echo $form->input('name',array('label'=>'Name'));
echo $form->end('Add');
?>

3. Save the file as '/app/views/categories/add.ctp'

Now point your browser to this location:
http://caketest.local/categories/add

You should be able to add a new category.

EDIT Category
To edit category, I'll specify a controller action. To do so:
1. Open categories_controller.php (found under '/app/controllers').
2. Copy-paste the following function:


function edit($id=null) {
if (!empty($this->data)) {
if($this->Category->save($this->data)==false)
$this->Session->setFlash('Error saving Node.');
$this->redirect(array('action'=>'index'));
} else {
if($id==null) die("No ID received");
$this->data = $this->Category->read(null, $id);
$parents[0] = "[ Top ]";
$categories = $this->Category->generatetreelist(null,null,null," - ");
if($categories) 
foreach ($categories as $key=>$value)
$parents[$key] = $value;
$this->set(compact('parents'));
}
}


3. Save that function.

Now, I'll write the view file (the form to edit a category). To do so:
1. Create a new file.
2. Copy paste the following code:


<?php
echo $html->link('Back',array('action'=>'index'));
echo $form->create('Category');
  echo $form->hidden('id');
  echo $form->input('name');
  echo $form->input('parent_id', array('selected'=>$this->data['Category']['parent_id']));
  echo $form->end('Update');
?>

3. Now save the file as '/app/views/categories/edit.ctp'.

Hold on!
There is one more thing we should do. We need to show the link to edit record. To do show:
1. Open '/app/views/categories/index.ctp' file.
2. Replace the existing code with this one:

<?php
echo $html->link("Add Category",array('action'=>'add'));
echo "<ul>";
  foreach($categories as $key=>$value){
$edit = $html->link("Edit", array('action'=>'edit', $key));
 echo "<li>$value &nbsp;[$edit]</li>";
  }
  echo "</ul>";
?>

3. Now save the file.

Now point your browser to:
http://caketest.local/categories/
You should be able to see the 'Edit' option against each category name.

Delete Category
CakePHP Format:
removeFromTree($id=null, $delete=false)

Using this method will either delete [to delete, set ($delete=true)] or move a node but retain its sub-tree, which will be re-parented one level higher.

Steps:
1. Open '/app/controllers/categories_controller.php'
2. Copy paste the following code:









function delete($id=null) {
  if($id==null)
  die("No ID received");
  $this->Category->id=$id;
  if($this->Category->removeFromTree($id,true)==false)
     $this->Session->setFlash('The Category could not be deleted.');
   $this->Session->setFlash('Category has been deleted.');
   $this->redirect(array('action'=>'index'));
}

3. Now save that file.

Next, I'll display the option to 'delete' a category.
Step:
1. Open '/app/views/categories/index.ctp
2. Replace the existing code with this one:

<?php
echo $html->link("Add Category",array('action'=>'add'));
echo "<ul>";
  foreach($categories as $key=>$value){
$edit = $html->link("Edit", array('action'=>'edit', $key));
  $delete = $html->link("Delete", array('action'=>'delete', $key));
  echo "<li>$value &nbsp;[$edit]&nbsp;[$delete]</li>";
  }
  echo "</ul>";
?>
3. Save the file.

Done
Point your browser to:
http://caketest.local/categories
Here is a screenshot of what you should see:


[ACKNOWLEDGEMENT]
The code above is mostly based on
Bram Borggreve's beautiful website - Tree Behavior
I express my sincere gratitude to Bram for his wonderful contribution.
Further, to learn more about 'Tree' behavior, please visit: CakePHP Book.





Here is the COMPLETE script files:

1. categories_controller.php ('to be saved under '/app/controllers')


<?php
  class CategoriesController extends AppController {
 var $name = 'Categories';
 function index() {
  $categories = $this->Category->generatetreelist(null, null, null, '&nbsp;&nbsp;&nbsp;');
  // debug ($this->data); die; 
  $this->set(compact('categories')); 
  
  }
  
  function add() {
  
  if (!empty($this -> data) ) {
  $this->Category->save($this -> data);
  $this->Session->setFlash('A new category has been added');
  $this->redirect(array('action' => 'index'));
  } else {
  $parents[0] = "[ Top ]";
  $categories = $this->Category->generatetreelist(null,null,null," - ");
  if($categories) {
  foreach ($categories as $key=>$value)
  $parents[$key] = $value;
  }
  $this->set(compact('parents'));
  }
  
  }

  function edit($id=null) {
  if (!empty($this->data)) {
  if($this->Category->save($this->data)==false)
  $this->Session->setFlash('Error saving Category.');
  $this->redirect(array('action'=>'index'));
  } else {
  if($id==null) die("No ID received");
  $this->data = $this->Category->read(null, $id);
  $parents[0] = "[ Top ]";
  $categories = $this->Category->generatetreelist(null,null,null," - ");
  if($categories) 
  foreach ($categories as $key=>$value)
  $parents[$key] = $value;
  $this->set(compact('parents'));
  }
  }
 function delete($id=null) {
  if($id==null)
  die("No ID received");
  $this->Category->id=$id;
  if($this->Category->removeFromTree($id,true)==false)
  $this->Session->setFlash('The Category could not be deleted.');
  $this->Session->setFlash('Category has been deleted.');
  $this->redirect(array('action'=>'index'));
  }


}
  ?>

2. Category Model (file: '/app/models/category.php')

<?php
class Category extends AppModel { 
var $name = 'Category';
var $actsAs = array('Tree'); 

?> 

3. Views for Category Files
(a)File: '/app/views/categories/index.php'

<?php
echo $html->link("Add Category",array('action'=>'add'));
echo "<ul>";
  foreach($categories as $key=>$value){
$edit = $html->link("Edit", array('action'=>'edit', $key));
  $delete = $html->link("Delete", array('action'=>'delete', $key));
  echo "<li>$value &nbsp;[$edit]&nbsp;[$delete]</li>";
  }
  echo "</ul>";
?>



(b) File: '/app/views/categories/add.php'





<h1>Add a new category</h1>
<?php
echo $form->create('Category');
echo $form->input('parent_id',array('label'=>'Parent'));
echo $form->input('name',array('label'=>'Name'));
echo $form->end('Add');
?>


(c) File: 'app/views/categories/edit.php'

<h1>Add a new category</h1>
<?php
echo $form->create('Category');
echo $form->input('parent_id',array('label'=>'Parent'));
echo $form->input('name',array('label'=>'Name'));
echo $form->end('Add');
?>



Thanks for reading the entry.

Creating HTML Text Links with CakePHP

So, we have just created our about_us page. Now we need to create a link to this page. CakePHP has defined its own class for you to create links. The basic syntax is simple.

<?php echo $html->link('Link Text', 'Link URL'); ?>

For example: I wanted to create a link to http://book.cakephp.org

My Syntax:
<?php echo $html->link('CakePHP Book', 'http://book.cakephp.org'); ?>

And output:
 <a href="http://book.cakephp.org">CakePHP Book</a>

Note:
1. To create a link you need to echo the entire statement.
2. You can change link to point to any path - even relative paths.
3. You can use additional attributes to mention CSS class

Let us add a CSS class to that link.
<p><?php echo $html->link('CakePHP Book', 'http://book.cakephp.org', array('class'=>'ext')); ?></p>

Note the part I have marked with red ink. This link is now associated with class 'ext'. (Do not forget to define the 'ext' class in your css, in my case it is cake.generic.css file). And you are done.

To open this link in a new window:
<?php echo $html->link('CakePHP Book', 'http://book.cakephp.org', array('class'=>'ext', 'target'=>'_blank')); ?>
Note I have added 'target'=>'_blank' to that statement.

To add a javascript confirmation box to that link:
<?php echo $html->link('CakePHP Book', 'http://book.cakephp.org', array('class'=>'ext', 'target'=>'_blank'), "Do you really want to visit this website?"); ?>

Note I have added just the string to display and nothing more. Cake takes care of the rest of the stuff efficiently. And there's really much more to explore!

In my CakeTest Application now I'll display a link in the footer section to 'about_us' page.

I'll edit default.ctp (found under 'app/views/layouts' and add the following lines (mark with red color).


<div id="footer">
<?php

echo $html->link('About us','about_us',array('class'=>'footer-link'));

?>
</div>


Note:
I have not used the complete path to about us page, which is:
/pages/about_us
You need to specify just that 'about_us' part in place of the 'link url'. Cake does the rest successfully.

Further, I'll add a new style to cake.generic.css file (found under 'app/webroot/css')


a.footer-link:link,a.footer-link:visited, a.footer-link:active {
color:#C1C1C1;
font-size:10px;
background-color:#ffffff;
text-decoration:none;
}
a.footer-link:hover{
color:#C1C1C1;
font-size:10px;
background-color:#ffffff;
text-decoration:underline;
}


That's it.
Here is a preview:















That's it.

Cheers!

Create a Custom CakePHP Template

So, I hope by far you have downloaded CakePHP (A.K.A. 'Cake'), installed it, changed security settings and connected to database.

You have seen the messages in the welcome screen has changed after each of your action.

Now you will note the following:
Editing this Page

1. To change the content of this page, create: APP/views/pages/home.ctp.
2. To change its layout, create: APP/views/layouts/default.ctp.
3. You can also add some CSS styles for your pages at: APP/webroot/css.
Create DEFAULT Home Page


So, I created a new folder called 'pages' under app/views/ . And then I created a new file (using any text editor) and saved that file as 'home.ctp' under 'app/views/pages/'.

Now point your browser to:
http://caketest.local/

You can see all the messages are gone!!! You can see this is a nearly empty page!


As the name says, 'home.ctp' is the default homepage for your website. You can write anything here (with HTML tags). Those will be displayed at your homepage.

Create Default LAYOUT

Now create another new file (using any text editor) and save it as 'default.ctp' under 'app/views/layouts/' folder.

'default.ctp' is the default layout to display your content. If it is empty, CakePHP will display nothing. This is exactly what you see after saving default.ctp - a completely blank page!!!

Do not worry!

Just copy and paste the following code in the newly created 'default.ctp' file.

--: BEGIN COPY :--



<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  <html xmlns="http://www.w3.org/1999/xhtml">
  <head>
  <?php echo $html->charset(); ?>
  <title>
  <?php __('My-CakePHP Tutorials'); ?>
  <?php echo $title_for_layout; ?>
  </title>
  <?php
  echo $html->meta('icon');


echo $html->css('cake.generic');


echo $scripts_for_layout;
  ?>
  </head>
  <body>
  <div id="container">
  <div id="header">
  Header Elements Will Go Here
  </div>
  <div id="content">


<?php $session->flash(); ?>


<?php echo $content_for_layout; ?>


</div>
  <div id="footer">
  Footer Text Goes Here!
  </div>
  </div>
  <?php echo $cakeDebug; ?>
  </body>
  </html>



----: END COPY :---


I have used red color to mark things you should note.
Change 'My-CakePHP Tutorials' to the title of your website. Like 'My Pet Dog Roombiq', or, anything you want.

NOTE:
There are FOUR IMPORTANT VARIABLES
$title_for_layout
$scripts_for_layout
$content_for_layout
$cakeDebug


The name suggests exactly what they do.
NOTE: 1
$title_for_layout MUST be included in between <title>  </title> tag.

<title>
  <?php echo $title_for_layout; ?>
</title>

As such, CakePHP gives a default title to every content page of your website following its own rules. But you can set a custom title as well. I'll show that in the next page.

NOTE: 2
$scripts_for_layout MUST be included before the closing </head> tag.


NOTE: 3
$content_for_layout MUST be included in between <body>
</body> tag.
<body>
<?php echo $content_for_layout; ?>
</body>

NOTE: 4
$cakeDebug SHOULD be placed before closing </body>

<?php echo $cakeDebug; ?>
</body>

AND you are done! You can add any CSS style/'div' layer to this page ('default.ctp') to give your website the layout/look you want.


For example, I tried a simple TWO Column Layout.

<div id="content">
  <div id="menu-box">
  menu items go here!
  </div>
  <div id="content-box">
<?php $session->flash(); ?>
<?php echo $content_for_layout; ?>
  </div>
</div>

I have added one div layer 'menu-box' and another one 'content-box' to display menu items and content items in two separate columns.
Now I need to add these CSS styles in a stylesheet file.

Modifying Stylesheet


As such Cake ('short-form of CakePHP) has already told us how to do that:
You can also add some CSS styles for your pages at: APP/webroot/css.
You can see the default CSS file under 'app/webroot/css' folder. The name of the file is 'cake.generic.css'. You can simply modify the content of this file (cake.generic.css).

I preferred to go to the bottom of that page and type the following lines


/* Custom CSS */

#menu-box{
width:250px;
float:left;
}
#content-box{
width:700px;
float:left;
}

It gives me a workable presentation for my custom template.

But I really need to make some more changes.

So, I just pressed (Ctrl+U) to view the source code, and I located the CSS division layers/ HTML tags being displayed in the source code, and modified them.

Here is the code: (Remember: I added the code at the bottom of cake.generic.css file.)


/* Custom CSS */

#menu-box{
width:250px;
float:left;
border-right:1px solid #CCCCCC;
}
#content-box{
margin-left:10px;
width:700px;
float:left;
border:1px solid #CCCCCC;
padding:10px;
background-color:#F3F3F3;
}
#header {
height:100px;
width:100%;
color:#000000;
background-color:#b5fad1;
font-size:2.0em;
border-bottom:1px solid #cccccc;
}
body
{
background-color:#FFFFFF;
color:#000000;
font-family:Verdana, Arial, Helvetica, sans-serif;
}

#footer
{
text-align:center;
}

..............................................................
And here is my custom template:














So, I have once again DONE that! If you are following me, you MUST have done so as well.

Congratulations!

Next, I'll try to create my 'About us' page using CakePHP. In general, I think this will give me some idea on how to create a static page with CakePHP.

Cheers!

Things You Must Know About CakePHP

Easily creating static pages

I needed to create several pages that didn't use any models and contained static data inside the default layout. My first thought was to create a controller for these pages and define an action for each static page I needed. However, this solution seemed tedious and would make it difficult to quickly add new pages. Enter the pages controller - simply create a view inside the views/pages/ folder and it'll automatically be rendered in /pages. For example, if I created /views/pages/matt.thtml it would be accessible via http://www.example.com/pages/matt

Static pages - Adjusting the page title

If you're using the pages controller and you need to change the page title, add the following to your view:
<? $this->pageTitle = 'Title of your page.'; ?>

Static pages - Adjusting other data sent to the layout

If you need to send data to the layout (such as a variable indicating what section to highlight on the nav bar), add this to your view:
<? $this->_viewVars['somedata'] = array('some','data'); ?>
That array should then be accessible as $somedata inside your layout.

Creating a simple admin center

If you need to create an administrative back-end for your CakePHP site and would like all the actions with administrative capabilities to exist under a specific folder, open up config/core.php and uncomment:
define('CAKE_ADMIN', 'admin');
This will then make all actions that are prefixed with "admin_" to be accessible via:
/admin/yourcontroller/youraction. For instance, if I created an action in my posts controller called "admin_add," I would access this via: www.example.com/admin/posts/add
From there I could simply password the admin folder to prohibit unwanted users from adding posts.

Viewing the SQL queries that are running behind the scenes

You can easily see the SQL queries that CakePHP is running by adjusting the DEBUG constant in config/core.php. 0 is production, 1 is development, 2 is full debug with SQL, and 3 is full debug with SQL and dump of the current object. I typically have debug set at 2, which renders a table at the bottom of the page that contains SQL debug information.
If rendering a table at the bottom of your site is constantly breaking your layout during development (especially if you're making AJAX calls and you're getting SQL inside your pages, not just the bottom), you can easily style this table to be hidden by adding this to your CSS:
.cakeSqlLog { display: none; }
This will allow you to view debug information in the HTML source code without your layout getting mangled, just don't forget to set debug back to 0 when your site goes live.

Multiple sources of documentation

Don't just rely on the manual. The wiki and the API are invaluable sources of information. The tutorials in the wiki are especially useful, and the API may be daunting at first, but you'll quickly find the information in there is crucial to building a site with CakePHP.

Using bake.php

Bake is a command line PHP script that will automagically generate a model, controller, and views based on the design of your database. I highly recommend using scaffolding to get a prototype going of a table that may change a lot in the beginning. If you're fairly certain the data is not subject to any drastic change, I recommend using bake instead. With bake all the files are generated and written to disk and you can make modifications from there. It saves a lot of time doing the repetitive tasks such as creating associations, views, and the basic CRUD controller operations.
Using bake is really easy. Once you have a table(s) in your database created, change directories to the /cake/scripts/ folder and run:
php bake.php
If you choose to bake interactively it'll walk you through the steps required to create your model, controller, and views. Once everything has been baked I usually go through all the generated code and make custom modifications as needed.

Mind permissions when moving cake around

When I changed from the development server to the live server I tarred up my entire cake directory and scp'd it to the new server. Immediately I started having an issue where any time the debug level was set to 0 (production mode), data would not be returned for certain database calls. This was a bit of a catch 22 since I needed to view debug information to troubleshoot the problem.
Someone in #cakephp kindly pointed out that permissions on the /app/tmp folder need to be writeable by apache. I changed the permissions to 777 and the issue went away.

Complex model validation

I needed to validate beyond just checking to make sure a field wasn't empty or it matched a regular expression. In particular, I needed a way to verify that the email address users registered with was unique. In the wiki I found this gem: this advanced validation tutorial, which covers some advanced methods of validation that were very useful.

Logging errors

$this->log('Something broke');
This will log your error to /tmp/logs/ (I initially made the mistake of thinking it would log it to the apache error log)

Creating a controller that uses other models

Suppose you have a controller that needs data from a bunch of different models, simply add this to the top of your controller:
class yourController extends AppController
{
var $uses = array('Post','User');
}

This controller would then have access to both the Post and the User model.

Creating a model for a table that doesn't actually exist in the database

I needed a way to create a model and controller without actually having an associated table in the database. I particularly wanted to make use of the $validate array so I could easily validate my fields and keep the validation logic in the model. CakePHP will throw an error if you create a model for a table that doesn't exist. Adding this to the model fixed the problem:
var $useTable = false;
You can use this to change tables names as well.
var $useTable = 'some_table';

Call exit() after redirecting

This should be no surprise to anyone who has done any serious web development in the past, but make sure you call exit() after running $this->redirect() if there's code afterward that you don't want to run. I've always done this in the past, but I made the assumption that $this->redirect() would make an exit call for me (which it didn't).

Advanced model functions

Unless you delve in to the API, there are some very useful model functions at your disposal you might not know exist. I highly recommend reading over the Model Class Reference at least once. Here's a few key functions I wasn't aware of that I found to be very useful:
  • generateList() - I use this function primarily to populate select boxes with data from associated tables
  • findBySql() - Sometimes you just need to write your own SQL
  • findCount() - Returns number of rows matching given SQL condition
  • hasAny() - Returns true if a record that meets the given conditions exists.
Again, I highly recommend reading over the entire model class reference, you'll be surprised at what you learn.

Inserting multiple rows in succession

I had a situation where I needed to iterate through a list of items and insert new rows for each. I quickly discovered that if you insert an item and then immediately insert another, the item that is inserted next doesn't insert at all. Instead the previously inserted row was being updated. For example:
$items = array('Item 1','Item 2','Item 3');
foreach ($items as $item) {
  $this->Post->save(array('Post' => array('title' => $item)));
}

This code will result in a single entry in the posts table: "item 3." CakePHP inserted "item 1", but then updates it to become "item 2," then "item 3" because $this->Post->id gets the value of the last inserted ID. Normally this functionality is very useful, but in this particular instance it was not. I found was to setting $this->Post->id = false after each insert solved the problem.

Inserting logic before or after controller functions

Suppose you needed an array of colors to be available to every view rendered by your controller but you don't want to have to define this data in every action. Using the beforeRender() callback will allow you to do this:
function beforeRender() {
  $this->set('colors',array('red','blue','green');
}

This would make $colors accessible in every view rendered by that controller. beforeRender() is called after the controller logic and just before a view is rendered.
There's also beforeFilter() and afterFilter(), which are called before and after every controller action. For more information, read up on callbacks in the models section of the manual.

Adding a WYSIWYG editor to CakePHP

I found this great tutorial on getting TinyMCE set up with CakePHP. Basically you just link the tiny_mce .js file to your page and then add a small bit of init code to every page that you want textareas to be converted into TinyMCE editors.

Writing your own SQL for HABTM relationships

I had an issue with trying to create a HABTM (has-and-belongs-to-many) relationship where I needed to specify my own SQL statement. According to the docs (at the time of this writing) you should set finderSql in your model, but according to the cakePHP source you should set finderQuery instead. It's just a foul-up in the docs, but I figured it'd be worth noting to save others from having to figure it out for themselves. Trac ticket here: https://trac.cakephp.org/ticket/1217

Sending email

I found two tutorials in the wiki: Sending email and Sending email with PHPMailer
I highly recommend the latter of the two, sending emails with PHPMailer is more secure and there's less of a headache because you don't have to deal with constructing the mail headers yourself.

Customizing HTML generated by the Helper

I needed to change the default <option> generated when I called $html->selectTag() to say something like "Please Select" rather than an empty space (default). I also wanted radio buttons to have labels so the user doesn't have to click exactly on the radio button itself but can instead click anywhere on the text associated with it.
Create the file /app/config/tags.ini.php and add the following:
; Tag template for a input type='radio' tag.
radio = "<input type="radio" name="data[%s][%s]" id="%s" %s /><label for="%3$s">%s</label>"
; Tag template for an empty select option tag.
selectempty = "<option value="" %s>-- Please Select --</option>"

You can get a full list of available tags in /cake/config/tags.ini.php. I wouldn't recommend modifying that file, however, because you could lose your changes when you upgrade CakePHP.

Creating a custom 404 error page

If you need to change the page that users see when a document is not found, create:
/app/views/errors/error404.thtml