Thursday, December 4, 2008

Captcha component with PhpCaptcha

By Spout (spout)

PhpCaptcha is a library for generating visual and audio CAPTCHAs (completely automated public Turing test to tell computers and humans apart).

Supported Features

* Multiple random TrueType fonts
* Character rotation
* Optional chararacter shadow support
* Optional site owner display text
* Random custom background images
* Font size selection
* Greyscale or colour lines and characters
* Character set selection
* Integration of validation function for checking the user entered code with the generated code


NB: The audio CAPTCHA requires the Flite text to speech synthesis engine.

1.- Download PhpCaptcha http://www.ejeliot.com/pages/2 and unzip archive in /vendors/phpcaptcha directory
2.- Download Vera font files: http://ftp.gnome.org/pub/GNOME/sources/ttf-bitstream-vera/1.10/ttf-bitstream-vera-1.10.zip and unzip archive in /vendors/phpcaptcha/fonts/
3.- Create Captcha component:

[Read More]

Integrate CakePHP with Kcaptcha

By Andrew Stephanoff (stephanoff)

Simple way to integrate CakePHP with Kcaptcha.

KCAPTCHA is a free and open source PHP solution to generate human validation images (CAPTCHA).

KCAPTCHA is meant to be a very strong protected one but requires no special hosting featires, only PHP with GD library.

You can download Kcaptcha from here: http://captcha.ru/en/kcaptcha/. After this, put kcaptcha folder into vendors directory.

[Read More]

Wednesday, December 3, 2008

PHPMailer with native API for PHP 5.x

By Heiner Gassen (kitten)


This tutorial on using PHPMailer with Cake (http://bakery.cakephp.org/articles/view/94) has a comment requesting a component that lets you use the native PHPMailer API. This is possible with PHP5's built-in overloading capabilities.

[Read More]

Setting up Eclipse to work with Cake

By Michael McAndrew (MilkyJoe)

You’re on the quest for painless PHP development. You’re using Cake and considering the goodness of Eclipse. Two great first steps.

All you need now is a little help getting to know Eclipse and configuring it for CakePHP. This tutorial gathers information already out there, adds a bit more, and puts in all in one place.
This tutorial is based on a standard setup.

* A recent stable build of Eclipse PHP Development Tools (3.2.100)
* Standard CakePHP install (in C:\\xampp\\htdocs\\cake) 1.1 or 1.2
* Standard XAMPP install (in C:\\xampp)
* Windows XP.

There is a lot more that could be covered. If you can fill the gaps or suggest how it could be improved, that would be great.

[Read More]

Tuesday, December 2, 2008

jQuery Helper

By Marco Pegoraro (thepeg)

I wrote an helper that allow you to enjoy jQuery library by helping in script inclusion and plugin management.
I have only wrote an italian documentation yet but code il simply, comments are in english (bad english... but english!)

Code download and documentation can be found at:
http://www.consulenza-web.com/cakephp-jquery-helper.dc-28.html
I hope someone will give me an english translation!

[Read More]

Pagination

By Andy Dawson (AD7six)

If you have more than a few results it is useful, if not vital, to provide a means of presenting the results a few at a time.
This tutorial will demonstrate how easy it is to present your data using pagination.
If part of your application includes displaying lots of results, it's a good idea to give the user the possibility to view the results in digestable chunks and possibly to be able to sort the presented data. This tutorial will explain how, after copying a few files into your application, you can achieve this in very few lines of code.
You need almost no knowledge of cake to be able to make use of this tutorial :).

If you already have a table in mind that you want to add pagination to, read on; otherwise run through http://manual.cakephp.org/appendix/blog_tutorial to have some code to play with.

[Read More]

Wildflower realease

Content management system and application platform build on CakePHP framework and jQuery Javascript library.

Download from here

jQuery is new Kind of javascript Library

jQuery is a fast and concise JavaScript Library that simplifies HTML document traversing, event handling, animating, and Ajax interactions for rapid web development. jQuery is designed to change the way that you write JavaScript.

[Read More]

Sunday, November 30, 2008

Extended beforeFilter() snippet

By Nate Constant (Natcon67)

This is a simple addition to app_controller.php to allow a more customized beforeFilter() callback.

Updated: 3/27/2007 - added isset call before calling callback with args


I set out to get a little more functionality out of the controller callback beforeFilter(). I had 2 requirements for this extended functionality:

1. Specify which actions to apply the callback to.
2. Ability to specify multiple methods beforeFilter would call.

Let me give a very simple example of how to use this snippet:
in your controller define $beforeFilter which is an array of methods to call and the parameters.

[Read More]

Friday, November 28, 2008

Reading OpenDocuments with CakePHP

By Tobias Funke

On my quest to find a simple way to read the contents of an *.odt OpenDocument Text file, better known as OpenOffice Writer files, I stumbled over a simple class that, in combination with a xsl template, converts an *.odt file to XHTML.

[Read More]

Simply storing config values in the DB

By Cameron Perry


There are many situations in web apps where site-wide configurations need to be accessible to users through admin interfaces, rather than configuration files residing on the server. It is a practical method of storing configuration values that may need changing from time to time, but without access to the core configuration file.

[Read More]

Parsing XML files with CakePHP

By Fahad Ibnay Heylaal

Simple tutorial for parsing xml files quickly using the core XML class of cakephp.

[Read More]

Reading OpenDocuments with CakePHP

By Tobias Funke

On my quest to find a simple way to read the contents of an *.odt OpenDocument Text file, better known as OpenOffice Writer files, I stumbled over a simple class that, in combination with a xsl template, converts an *.odt file to XHTML.

[Read More]

Simply storing config values in the DB

By Cameron Perry

There are many situations in web apps where site-wide configurations need to be accessible to users through admin interfaces, rather than configuration files residing on the server. It is a practical method of storing configuration values that may need changing from time to time, but without access to the core configuration file.

[Read More]

Multiple Display Field

By Daniel Albert (resshin)

The behavior allows us to use multiple display field (such as "first_name" and "last_name") as a display field for generating list from a model.
This is really one of common issues: using multiple fields as display field when using the find('list') function.

Strangely enough, there's only little hint out there about how to do it "elegantly" in CakePHP. tclineks already make a great article in http://bin.cakephp.org/saved/19252#modify, but the code just won't works. I think it's happened because CakePHP now handles find function differently than the previous versions.

So then I wrote the code below to help anyone who need to use multiple fields in their display field.
Note: The afterFind function is taken without any change from tclineks' bin.

Using it is as simple as this:

1. In your model, define that the model is acting as MultipleDisplayFieldBehavior. For example, see Figure 1.
2. To generate a list, simply use the find('list') function. For example, see Figure 2.


Note: you must define the "displayField" property. This field (which can be a virtual/non-existent field) will then holds the concatenation of display fields that you defined.

[Read More]

How to create an XML-RPC server with CakePHP

By Filippo Toso (filippo.toso)

This tutorial describes a method to create an XML-RPC server that goes a bit against Cake’s logic and conventions.


I like the way the framework “distributes” the logic of the web application between different controllers (i.e. Posts, Comments, and so on). But when I want to develop an XML-RPC server, I prefer to place all the code into a single controller so I can maintain consistency and have a single place to look for errors. The other advantage of this solution is that you don’t need to add logic in the single controllers to handle XML-RPC calls.

For the implementation of the XML-RPC protocol I decided to go with the Inutio XML-RPC Library (http://scripts.incutio.com/xmlrpc/). This library is completely object oriented, easy to use and works with both PHP 4 and PHP 5.

You can download a little modified version from the following URL:

http://www.creativepark.it/downloads/xmlrpc.zip

More

Cakephp Downloads

CakePHP: the rapid development php framework. Downloads

CakePHP Install: Multiple Subdirectories & 500 Error

So, the last few weeks, I started learning how to use the CakePHP framework. It’s been a little slow going, but it’s been pretty good. I’ve been developing on my local machine, which, for some reason, mod_rewrite just refuses to work. So, I decided to make a dev site on the server. Oh, what a disaster.

I created the directory and ftp’d all the files on to the server. When I went to test it, I kept on getting a bunch of 500 errors. I tried moving the files around, looking at the “manual”, but nothing was solving the problem. I spent all afternoon, and narrowed it down to a problem with the .htaccess file.

When I came back from dinner, I found the solution.

The Problem:

1. Installing CakePHP in a subdirectory;
2. Resolving the 500 errors.

More

Sunday, November 23, 2008

Building a blog with CakePHP

CakePHP is a rapid development framework for PHP that provides an extensible architecture for developing, maintaining, and deploying applications. Using commonly known design patterns like MVC and ORM within the convention over configuration paradigm, CakePHP reduces development costs and helps developers write less code.




This tutorial will give you a good start in the world of CakePHP, just to learn the basics of the framework. The official CakePHP Blog tutorial (For Cake 1.1) gives you a good idea, but this tutorial will take it a couple of steps further (and in the next parts way beyond the official tutorial). Don't worry: This article will perfectly guide to making your first steps on baking some code.

For More details you can view the below link:

http://www.marcofolio.net/webdesign/building_a_blog_with_cakephp_part_1_getting_started.html

Get Started With AJAX in CakePHP

Some links are available from the below link:

http://ahsanity.wordpress.com/2007/02/23/get-started-with-ajax-in-cakephp/

CakePHP Ajax Chat Plugin

CakePHP Ajax Chat Demo (Uses jQuery)

Download the helper from there

http://sandbox2.pseudocoder.com/demo/chat

Friday, November 21, 2008

Bake ROT13 Encoded "mailto:" Link

By Debugged Interactive Designs (debuggeddesigns)

Why would I want to use encoded "mailto:" anchor tags? To obfuscate e-mail addresses from spam harvesters. The helper lets you easily encode an entire anchor tag using ROT13 Encryption. The ROT13 encoding simply shifts every letter by 13 places in the alphabet while leaving non-alpha characters untouched. At run-time, javascript is used to decode the ROT13 encryption. If javascript is disabled, then the e-mail address is safely shown by reversing the e-mail address using PHP and re-reversing (versing?) it at run-time using CSS.

Step 1: Create the Mailto helper


This helper's function accepts an e-mail address and link content as it's parameters and returns javascript code containing the encrypted anchor tag.

Filename: /app/views/helpers/mailto.php
Helper Class:



class MailtoHelper extends Helper {

function createLink($addr, $link_content) {

//build the mailto link
$unencrypted_link = ''.$link_content.'';
//build this for people with js turned off
$noscript_link = '';
//put them together and encrypt
$encrypted_link = ''.$noscript_link;

return $encrypted_link;
}
}
?>


Step 2: Include the helper inside your controller


Filename: /app/controllers/tests_controller.php

Controller Class:


class TestsController extends AppController {
var $name = 'Tests';
var $helpers = array('Mailto');

function mailto() { }
}
?>

Step 3: Create the javascript

The javascript decodes the anchor tag that was encoded in the helper above.

Download this file: http://scott.yang.id.au/file/js/rot13.js
Save the file here: /app/webroot/js/rot13.js


Step 4: Include rot13.js in your layout view


Filename: /app/views/layouts/default.thtml
View Template:



Step 5: Use the helper inside a view

Filename: /app/views/tests/mailto.thtml
View Template:

createLink('spam@debuggeddesigns.com','Debugged Interactive Designs'); ?>

Step 6: View the source and be amazed

To test it out, visit the page www.yourdomain.com/tests/mailto

This is the ROT13 encoded anchor tag found in the source code:






I got this idea from a presentation by Mark Rosenthal at a BostonPHP meeting that explained this technique. He admitted that there are many different techniques for tricking spam bots, and he might not be the first to think of this one. Well, after some google searching, he wasn't: http://scott.yang.id.au/2003/06/obfuscate-email-address-with-javascript-rot13/. Believe it or not, that javascript file above was written by Scott Yang in 2003 for this exact purpose. Please leave any alternate techniques you might use in the comments below.

Update: Since creating this helper, I found the article "Nine Ways To Obfuscate E-mail Addresses Compared" at http://techblog.tilllate.com/2008/07/20/ten-methods-to-obfuscate-e-mail-addresses-compared/, which says ROT13 encoding and changing the code direction with css "...are absolutely rock-solid and keep your addresses safe from the harvesters."


Brita component with HTML Purifier

By Debugged Interactive Designs (debuggeddesigns)
Brita is a CakePHP Component wrapper class created to take advantage of the functionality provided by HTML Purifier. HTML Purifier is a standards-compliant HTML filter library written in PHP. HTML Purifier will not only remove all malicious code (better known as XSS) with a thoroughly audited, secure yet permissive whitelist, it will also make sure your documents are standards compliant, something only achievable with a comprehensive knowledge of W3C's specifications.

Step 1: Download and unzip archive

Download an HTMLPurifier archive (either .zip or .tar.gz) at http://htmlpurifier.org/download.html and unzip the archive into the directory /app/vendors/htmlpurifier/

Note: Only the contents in the /app/vendors/htmlpurifier/library/ folder are necessary, so you can remove everything else when using HTML Purifier in a production environment.

Note 2: The folder /app/vendors/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer must be writeable by the webserver.


Step 2: Create brita component
Filename: /app/controllers/components/brita.php
Component Class:


//cake's version of a require_once() call
vendor('htmlpurifier'.DS.'library'.DS.'HTMLPurifier.auto'); //use this with the 1.1 core
//App::import('Vendor','HTMLPurifier' ,array('file'=>'htmlpurifier'.DS.'library'.DS.'HTMLPurifier.auto.php')); //use this with the 1.2 core

class BritaComponent extends Object {

var $controller;

function startup( &$controller ) {

//the next few lines allow the config settings to be cached
$config = HTMLPurifier_Config::createDefault();
$config->set('HTML', 'DefinitionID', 'made by debugged interactive designs');
$config->set('HTML', 'DefinitionRev', 1);
//levels describe how aggressive the Tidy module should be when cleaning up html
//four levels: none, light, medium, heavy
$config->set('HTML', 'TidyLevel', 'heavy');
//check the top of your html file for the next two
$config->set('HTML', 'Doctype', 'XHTML 1.0 Transitional');
$config->set('Core', 'Encoding', 'ISO-8859-1');

//BritaComponent instance of controller is replaced by a htmlpurifier instance
$controller->brita =& new HTMLPurifier($config);
$controller->set('brita',$controller->brita);

}

}
?>

Step 3: Use the brita component inside a controller


Filename: /app/controllers/tests_controller.php
Controller Class:


class TestsController extends AppController {
var $name = 'Tests';
var $components = array('Brita'); //import the Brita Component

function brita() {
//fake user input that we will purify (for testing)
$dirty_html = '

testing
';
//this one line of code does all the purifying
$clean_html = $this->brita->purify( $dirty_html );
//set the before and after html for the test view
$this->set('clean_html',$clean_html);
$this->set('dirty_html',$dirty_html);
}
}
?>

Step 4: Create a test view
Filename: /app/views/tests/brita.thtml

DIRTY HTML =



CLEAN HTML =

Migrating a real-world application to CakePHP 1.2

By Martin Westin (eimermusic)

I thought I'd chronicle my work migrating my fist CakePHP application to CakePHP 1.2 (rc3 at the time of writing). The application was originally written for CakePHP 1.0 (= the good old days). It has been updated to work with the latest versions 1.1 but has not really been rewritten much.

Considering how much CakePHP has evolved and also how many beginner-mistakes I have put into this application, this migration will also describe some pretty huge optimizations that was possible because of new features and my improved knowledge of CakePHP.

About The Application

The application if called Fileshifter and it is a simple file manager and file sharing application. The original purpose was to complement an ftp server. Fileshifter is generally more simple to manage and easier for "average people" to handle. You have projects and upload files into them or download files from them. As admin you can manage user accounts and project permissions.

The English pages on the application-website are horribly out of date. Sorry. There are a few screen-casts in the Swedish section that might give some context to what I am writing about, though.
http://www.fileshifter.se/


First attempt. Aka: Oh crap, nothing works
Just dropping my old application into CakePHP 1.2 clearly didn't work. :)
I just got two errors. The second one was a fatal error which halted further execution in config/routes.php

I went through all files in the config directory. This took a little while but I wanted to get the configurations identical to those I had before. One bonus for my application is the new configuration directive to not check the userAgent. This will be very useful for my upload actions that will be used by the java applet the application uses. In earlier versions I manually hacked the core to make this possible.

core.php
Starting with a clean file from CakePHP 1.2 I changed the following things for my application. Not all as strictly necessary to get an application running.
Session.cookie - set to the same name as in the old application.
Session.checkAgent - set to false globally for the time being. I will check if I can set this just in the relevant controller without causing problems.
Security.salt - should always be altered. I kept part of the original string, moved things around and inserted some swedish words here and there just for fun.

bootstrap.php
My old file set a few constants used to configure the application. A few of these I switched over to the new Configure class. Most, I left as constants since they are nothing you would ever change at runtime. For example the path to the root of the file area.
The current language was one of the ones I switched over. See below about i18n.

database.php
Not a big problem here. The could have been left as is but I did copy over the login parameters to a fresh file. I noticed that the connect parameter does not need to be set for MySQL. I am not sure about before but now it is enough to set persistent to true.

routes.php
The few routes I had defined only needed to call the method statically to continue to work. For example:
Download code
$Route->connect ('/', array('controller'=>'efs', 'action'=>'index'));
became:
Download code
Router::connect('/', array('controller' => 'efs', 'action' => 'index'));

I also replaced all default files (index.php, .htaccess ...). I did not bother to check which had changed. It was simpler to just replace them all with the new 1.2 versions.


File model clashes with the File class
Since my application is a file manager I had a model called File. It actually took me a good while before I figured out that my File model clashes with the File class in the core. Time for some search and replace. All references to File was replaced with EfsFile (did not think of anything better). Most of it could be changed this simply, but I had to make a few manual adjustments. Cakes $useTable came in good use here. I did not have to alter the database just because I renamed the class.


Replacing authentication
Next big problem is authentication. Since all data the user can interact with flows from the authenticated user account, this basically stops my application completely until I get the new authentication up and running. Lucky enough I was using an Auth component that works kind-of similar to the current Auth component in the core. There was still a lot of work but it could have been worse.

I didn't keep any of the code from the old authentication. What I did was take copy a lot of it over from another application where I first tried some basic 1.2 authentication. The code is pretty much straight out of the tutorials available all over the web.


Permissions
In the original version I intentionally kept permissions to a minimum. Simplicity was the key and increased complexity and granularity in permissions will increase the workload for the administrator. This was of-course no bad thing when it came to updating the authentication. The old Component used a User model and a Permission model. I saw no reason to even have a model for permissions anymore since all I needed was "normal user" and "admin user". I mapped this to a simple integer field in the users table. I can imagine that trying to migrate a role-based system can be a lot more work.


Transitioning old passwords
I have managed to keep all the old passwords. They were md5 hashed without a salt so I was able to get it working by defining an authenticate object for Auth. I chose to make the User model the authenticate object. It feels natural to have password hashing there.
In AppController::beforeFilter:
Download code
$this->Auth->authenticate =& $this->Auth->getModel();
And in models/user.php I added:
Download code
function hashPasswords($data) {
if (is_array($data) && isset($data[$this->alias])) {
if (isset($data[$this->alias]['email']) && isset($data[$this->alias]['pass'])) {
$data[$this->alias]['pass'] = Security::hash($data[$this->alias]['pass'], 'md5', false);
}
}
return $data;
}

One thing still on the todo-list is that I would rather try to manage a transitional system for the first release after this migration. Something where the password can be checked against both the old and new hash. Any login where the stored password matches the old hash will be replaced by the given password hashed using the new (more secure) system using a salt. If I can't get that to work I have to make a judgement call wether to keep the old less safe passwords or require all users in all installations to renew their passwords. We'll see...


Changing the code convention of the entire application
This can be tedious if you plan to do it in one big pass. I decided to use this as an indicator of what code I had looked over and changed. Any method I altered or examined and decided not to alter was given a change in coding style to indicate that it was "approved" 1.2 code. If I had been happy with my old style I would not have done this, but since I planed to change it, this was a good way to do it.

Loading classes and other external code
All the old methods of loading files have changed. No more uses(), no more vendor(). This is now all taken care of by App::import(). For example when I load a PEAR class to archive a whole folder:


vendor('Archive/Tar');

now reads:

App::import('Vendor','Archive/Tar');

The component dealing with syncing with the filesystem nor loads the needed models in a single call, like this:

App::import('Model',array('Project','EfsFile'));


Removing requestAction()
Like so many starting out, I was seduced by requestAction(). Looking at my old code I remember believing that only the UsersController should manipulate the User model. That is partly why I turned to requestAction when one controller needed access to "another model".

My primary requestAction() was to call "/files/viewedBy/1/1" whenever a user selected a new file. This would log that the user has seen the file. By moving this code into the EfsFile model I was abel to take the request-time for these down quite a bit. With my test-data this went down to 0.15s from well over a second before. Partly because of the overhead of a request and partly because of Containable helping to optimize the data retrieval.


Hey, where did my data go?
One new behavior introduced in CakePHP 1.2 RC3 may delete some data by accident. If an association has conditions, you should use the new array notation for conditions. If you delete a record and dependent is true, you expect the related record(s) to be deleted as well. If conditions is not an array you can delete delete all records of the related model, not just related ones. I have had this happen to my data for hasOne associations. I have not done extensive test to see if this affects other associations.
This is straight out of manual as it was a while ago:
Model Class:


class User extends AppModel {
var $name = 'User';
var $hasMany = array(
'Comment' => array(
'className' => 'Comment',
'foreignKey' => 'user_id',
'conditions' => 'Comment.status = 1',
'order' => 'Comment.created DESC',
'limit' => '5',
'dependent'=> true
)
);
}
?>

And this is the small but important change:

class User extends AppModel {
var $name = 'User';
var $hasMany = array(
'Comment' => array(
'className' => 'Comment',
'foreignKey' => 'user_id',
'conditions' => array('Comment.status' => '1'),
'order' => 'Comment.created DESC',
'limit' => '5',
'dependent'=> true
)
);
}
?>

I believe even empty conditions can cause you problems:

'conditions' => '',

it should be:

'conditions' => array(),


I am not certain about that last bit but better safe than sorry, right?


Hey, where did my data go? part 2
There was a bug introduced in CakePHP 1.2 rc3 that anyone updating should be aware of. Cake can accidentally delete data in the join tables of your HABTM associations. This happens if the association is two-sided and you remove the "link" between two records. Any other record the associated record is linked to is also unlinked. I found this out the hard way. I then discovered a bug report in trac. The fix described there has worked for me in two applications for a few weeks now. See the ticket for details and example-code.
https://trac.cakephp.org/ticket/5579

Finding new data
The Model methods find(), findAll() and so on have changed a lot. This required a lot of manual work, but since I could optimize thing at the same time and speed up the application noticeably I didn't really mind the work. I also thing this new syntax is a lot easier to use.

$allProj = $this->Project->findAll(null,null,'Project.title ASC');

should now read:


$allProj = $this->Project->find('all',array(
'order' => 'Project.title ASC'
));

or better yet:


$allProj = $this->Project->find('all',array(
'order' => array('Project.title' => 'ASC')
));


Note: Keeping "ASC" in my example is not necessary but I like to have it there so I know at a glance what is going on.
OK, all find operations are now taken care of by find. The first parameter indicates the type of find to perform. Setting it to "first" will find a single record for example. The second parameter is an array, like in so many places in CakePHP 1.2. This should contain the additions you want to make to the query. As you can see order is one thing. Conditions is another. Fields is a third. I could never remember if order was parameter number 3,4 or 5. Now I don't have to. I just add a key called order and a value with the ordering field and direction I want. It does not have to come before or after the conditions... all very nice.

All of these parameters can be broken down into arrays themselves. This adds another layer of protection against sql injections. The conditions can even be broken down into ands and ors in a deep structure. Cake will construct a nice where-clause of them for you and keep track of all the parenthesis. See the manual for many examples of this. The same goes for deleteAll() and any other conditions throughout the application.


$result = $this->deleteAll( array(
$this->alias.'.created <' => $deadline
));

Notice that the comparison operator is on the left side. This is new since CakePHP 1.2 RC2, I think.


I can hardly contain myself
Am am bursting with joy! Containable behavior is one of my favorite features in CakePHP 1.2. Containable is like find() on steroids. It is like associations on acid. It is like... ok enough! The basic first step is to simple add this to the relevant models.


var $actsAs = array('Containable');


Containable is a behavior that helps you optimize your queries. Perviously all I could do was to set recursive and unbind a model. Containable sort-of does this for you, in a very intuitive way. Parts of the application has been sped up by a factor of 8. That is 8x the original request time, largely because of Containable.

It may sound like magic, but imagine this:

User: habtm Project, habtm File
Project: hasMany File, habtm User
File: habtm User, belongsTo Project

These three models can easily cause a lot of unnecessary data to be loaded since they relate to each-other in several ways. By taking advantage of some simple functionality in containable I was abel to eliminate most of the unused data from my queries. For example, loading a single file:


$data = $this->User->find('first', array(
'conditions' => array('User.id'=>$this->userData['User']['id']),
'contain' => array(
'Project'=>array(
'EfsFile'=>array(
'conditions'=>array('EfsFile.id'=>$this->params['id'])
)
)
)
));


In english: Find the currently logged in User. Contain the results to also include any related Project and to the project related File if the id of the file is the one we want.

This may look like a backwards way of loading a file. However this is quite effective since I only want the file to load is it is in a project that the current user has access to. Using the Set class (another great new feature in 1.2) it is no big problem picking out the file from the results. Just imagine the mess returned if I had set recursive to 2 instead... I would have loaded all files in all projects related to the user and I would have had to loop through the results to locate the one with the right id (making it a condition would not have worked). OK, that was an example from hell but I hope the point was made. Containable is your friend. Please check the manual for more complete details on how to use Containable.


New validation system
I must admit, I did not have a lot of validation before. The validation I had was mostly custom validation code. Some of it even in the controller (ick!). One thing I did validate before was, naturally, login data. For example, validating uniqueness is a lot better in 1.2. And there is also support for multiple validation rules per field. Apart from changing the validation I had I also added a few rules for the files and projects. I still have to change my controller code to make better use of the validation errors and messages, though.


var $validate = array(
'name'=>VALID_NOT_EMPTY,
'login'=>VALID_NOT_EMPTY,
'email'=>VALID_EMAIL
);

var $validate = array(
'name' => array(
'rule' => 'notEmpty',
'message' => 'The name can not be left blank'
),
'email' => array(
'email' => array(
'rule' => 'email',
'message' => 'This is not a valid email.'
),
'unique' => array(
'rule' => 'isUnique',
'message' => 'This email already used for another user.'
)
)
);

The error messages above have been replaced to make them more readable. They are in the form of translation keys in the real code.


Synchronizing with the filesystem
The application keeps its files and folders in the database. To enable admins to expose the files on their local network or a traditional ftp, there is a component that checks the filesystem for changes. I was able to optimize this comparison quite a bit.

My original code fetched all files and folders, queried the database and then compared the two using foreach loops. These become exponentially slower as the number of files grow. Comparing 100 files would result in 100*100 comparisons just to determine that nothing new has been added.

foreach ( $system as $sysFile ) {
foreach ( $data as $dataFile ) {
if ( $sysFile['EfsFile']['filename'] == $dataFile['EfsFile']['filename'] ) {
// OK, this file exists already
}
}
}

The goal was to remove at least one of the foreach loops to keep the speed increase in check. Since these are Cake-style multi-dimensional arrays simple comparisons like array_diff() don't work. The Set class was of-course a little slower than the foreach loops since it does basically the same thing, only a bit more advanced.

Once again I found that putting more responsibility on MySQL was the answer. My new code fetches all files and folders, loops through that data and queries the database to see if a known file can be found. Testing with about 3'000 files, I measured this code to be twice as fast as the original code. Checking 3'000 files in 14 folders now takes 4.5sec instead of 8.6sec.

foreach ( $system as $sysFile ) {
$this->EfsFile->contain();
$found = $this->EfsFile->find('first', array(
'conditions' => array(
'EfsFile.filename'=> $sysFile['filename'],
'EfsFile.project_id'=> $current_project_id
)
));
if ( $found ) {
// OK, this file exists already
}
}


Filesystem case sensitivity
I got this strange error while testing. Each sync with the filesystem would result in duplicate files in a certain project. Turned out the problem was one of case sensitivity. Some filesystems are case sensitive, some are not. PHP definitely is case sensitive when doing simple comparisons like $filename1 == $filename2. This can cause problems when files and folders are added and renamed outside of my application. Renaming a folder test => Test will still find the same folder when on a case insensitive system but it will not be considered the same folder by the sync code discussed above. I needed a way to handle this problem. Here is what I came up with:
in config/bootstrap.php

define('EFS_FILESYSTEM_TYPE', 'ci'); // filesystem is set to case insensitive

in vendors/basics.php (my file of small handy global functions)


// returns a filename or foldername in the correct case for comparison
function file_case($str) {
if ( EFS_FILESYSTEM_TYPE == 'ci' ) {
return low($str); // Filesystem in case insensitive
} else {
return $str; // Filesystem in case sensitive
}
}


That is ok and it works. But how many of my customers actually know what filesystem they are running? These are people who choose this app for its simplicity. Can't I use some function to find the filesystem type automatically? Well, yes I can. I looked for some internal php mechanism but found none. There may be plento of smarter ways to do this but here is what I did:
in vendors/basics.php (my file of small handy global functions)

// returns a filename or foldername in the correct case for comparison
function file_case($str) {
if ( !defined('EFS_FILESYSTEM_TYPE') ) {
$file_name = 'CaseTest.txt';
if ( is_file(dirname(__FILE__).DS.$file_name) && is_file(dirname(__FILE__).DS.low($file_name)) ) {
define('EFS_FILESYSTEM_TYPE', 'ci'); // Filesystem in case insensitive
} else {
define('EFS_FILESYSTEM_TYPE', 'cs'); // Filesystem in case sensitive
}
}

if ( EFS_FILESYSTEM_TYPE == 'ci' ) {
return low($str); // Filesystem in case insensitive
} else {
return $str; // Filesystem in case sensitive
}
}


I checks "the same file" twise. Once with the filename capitalized and once in all lowercase. If they both exist then the filesystem should be case insensitive. This works when tested on HFS+ volumes of both types.


Re-factoring stupid noob code
A post on the CakePHP Google group got me thinking about a component I coded for this application. It is (or was) a Ticket component for maintaining temporary tickets of the type many websites use for password resets and registration activations.

When I started looking at this old component I realized that the only reason this was a component at all was vanity. I thought a component would be "kewl" and really wanted to create a component. This was of-course horribly wrong. The component may still be published here:
http://bakery.cakephp.org/articles/view/ticket-component-resetting-user-passwords
The component is really just a proxy to the Ticket model storing the tickets. I re-factored the code in the component into the model and included the model in the controllers that previously included the component.

Unfortunately I had to rename most of the methods from the component since they clashed with internal Model methods like set() and del(). What took the most time was finding new names for these methods. Out of frustration I (temporarily) just called them setTicket() and drop(). Ugly, yes. I hate putting the class name in the method name... totally useless but my English vocabulary doesn't include a good synonym.


l10n and i18n
The original application used my own code for language management. I had a php-file for each language. All labels were defined in an associative array in those files. Like this:


$labels = array();

//-- login page
$labels['login_username'] = 'Login Id';
$labels['login_password'] = 'Password';
$labels['login_login'] = 'Login';

The first step was to convert these files into gettext po-files. It was a matter of search and replace for 90% of it. Some comments had to be moved and changed manually and things of this nature. The results looked something like this:


# login page
msgid "login_username"
msgstr "Login Id"

msgid "login_password"
msgstr "Password"

msgid "login_login"
msgstr "Login"

These files were put into the relevant folders. E.g. locale/eng/LC_MESSAGES/default.po

The second step was to replace my own translation function with the built-in __(). I have never used that function directly. This is mainly since I prefer to have my strings returned and not output. I put a proxy function into my bootstrap which alters the default of the second argument.


function ___($singular, $return = true) {
return __($singular, $return);
}


I managed to change all the calls to $lang->show('some_string') to ___('some_string') in one glorious find and replace. That's my kind of migration.

The third and final change was to alter the language switching. I kept this in my bootstrap and only change it to write the configuration for the selected language. The old code is the commented lines. I kept the rest of my old code. As you can see it still uses cookies and it works just as well as before.


if (isset($_COOKIE['EFS_LANG'])) {
//define('EFS_LANG', $_COOKIE['EFS_LANG']);
Configure::write('Config.language', $_COOKIE['EFS_LANG']);
} else {
//define('EFS_LANG', 'sv');
Configure::write('Config.language', 'sv');
}


Changing all view-files to use .ctp extension
This takes all of 5 minutes and has absolutely no effect on the functionality of the application. It just feels better and considering the work involved why not? I just ran everything in the views folder through a filename changer (NameChanger for Mac OS X) and I was done.


Changes to form and html helpers
Any previous call to html helper to generate a form element needs to be switched over to use the form helper. This did not affect my application much. Most html did not make use of either helper. I remember feeling it was less intuitive to make php calls to generate html instead of just writing the html directly. The application does not have a lot of forms either.

With the login form as an example here is some of the things to look out for in views.


$html->input('User/login',array(
'style'=>"width:150px;"
));
?>

now reads:


$form->input('User.email',array(
'label'=>___('login_username'),
'div' => false,
'style'=>'width:150px;'
));


This is one area where the new helpers really shine. Just setting the label property IS simpler than writing the whole tag manually. since my layout did not have divs before I simply set that key to false to avoid the default div around my text field.

Also notice that $html is replaced by $form and that the reference to the model field uses the new dot-notation.

I also switched to email logins, but that has nothing to do with the migration. I started out using usernames. When I created the feature to reset passwords a valid email address was necessary and the last big update changed to prefer email logins but still allowed old usernames during a transitional period. This version will deprecate the old logins once and for all.

Don't render your elements
Don't get me wrong. Keep on using your elements. It is just the name of the method has changed from renderElement() to just element(). The application was full of these but this was another one of those things that a quick find and replace could solve in minutes. I did not find any problems afterwards. The parameters appear to work the same.


Cleaning up my urls
Besides cleaning up some of my mess in config/routes.php I did some spring-cleaning of the urls in my views and controllers. This is not strictly necessary but it may come in handy if, or when, I want to create some new custom routes. Changing all urls to arrays instead of simple strings does look like a lot of complexity with little benefit.

'/users/edit'

becomes:

array('controller'=>'users', 'action'=>'edit')

Clearly harder to read, right? The benefit is that I can now decide to create an "alias" in routes.php so that links to /profile should go to the edit action of the users controller. As if by magic, all links and redirects to that editing screen will change from /users/edit to /profile. Cool!

Another little nice (new?) feature I found was for the classic "logo links back to start-page". Every website in the world has this and it can be done very nicely. This mess:

image('fileshifter_logo_bar.gif',array('alt'=>'Fileshifter logo'))?>

could be written like this:

image('fileshifter_logo_bar.gif',array('alt'=>'Fileshifter logo', 'url'=>'/')); ?>

The image method accepts an url parameter and creates a link around the image for you. It does look nice and clean. If you need some attributes set for the link you can also use the link method and just put a call to the image method where the "text label" for the link is set. See the manual, it has a clear example of it. It is a lot simpler than I make it sound.


Consolidating layouts and views.
Some of my old view code was duplicated because I did not know all the tricks back then. For example, my layouts (three of them) were almost identical but they has a few things that set them apart.

Since data for the current user is always available to the view, one simple fix was to add an if-clause that would output the correct menu-items for each situation.

The other problem I had was that $content_for_layout had to be wrapped and accompanied by different code for different sections of the application. The fix is simple but not one I knew about two years ago. I made a common base-layout:


--base.ctp--
...lots of html for menu and header...



...some more html for the footer...

And then I created two other layouts of this kind (this is the most simple one):


--default.ctp--
echo $this->renderLayout('
'.$content_for_layout.'
','base');
?>

This layout simply wraps the content in a container div and passes it along to the base layout. The other layout had a lot more html around the content but that would be less clear to display like this.


Onwards and upwards
Those were the steps I took to migrate and improve my application this time around. Not all of them are necessary but all of them help the application improve and conform to new conventions. For example, old string conditions still work but using the new array notation lets Cake protect you from SQL injection, makes it easier to modify conditions in behaviors and beforeFind() and other advantages big and small.

If you find that any aspect of the migration is lacking in detail or plain missing, please don't hesitate to drop me a line in the comments. Hopefully I will be able to cover such requests in future updates.

Debugging with FirePHP

By Heath Nail (HeathNail)
This is a quick hack to have the debugger use FirePHP for debugging.

You may have read the other article on FirePHP which deals with the query log, if not check it out here:
http://bakery.cakephp.org/articles/view/baking-cakes-with-firephp

With a little massaging of Cake's Debugger class you can have all errors logged to the Firebug console instead of having them displayed on your site.

Things you'll need for this to work:

* Firefox 3 (if ya haven't already, its time to upgrade :p) http://getfirefox.com
* FireBug http://getfirebug.com
* FirePHP (the FireFox extension and the PHP class) http://firephp.org
* And yeah I'm assuming you already have CakePHP otherwise you wouldn't be here right?

You'll need to download the FirePHP.class.php file and save it to vendors. I saved it to vendors/FirePHP/FirePHP.class.php

Then save the following file to vendors/FirePHP/FirePHP.debugger.php:



/**
* The majority of code in this file is based on CakePHP's Debugger.
* This code is designed to work with CakePHP 1.2RC2 and no warranty is given nor implied.
* @link http://www.cakephp.org
* @link http://www.firephp.org
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
if(!class_exists('Debugger')) {
App::import('Core', 'Debugger');
}
if(!class_exists('FirePHP')) {
App::import('Vendor', 'FirePHP', array ( 'file' => 'FirePHP.class.php'));
}
if(!function_exists('fb')) {
function fb() {
$instance = FirePHP::getInstance(true);
$args = func_get_args();
return call_user_func_array(array($instance,'fb'),$args);
return true;
}
}
class FirePHPDebugger extends Debugger {

/**
* holds current output format
*
* @var string
* @access private
*/
var $__outputFormat = 'fb';

/**
* FirePHP error level
*
* @var string
* @access public
*/
var $FirePHPLevel = '';

/**
* Gets a reference to the Debugger object instance
*
* @return object
* @access public
*/
function &getInstance() {
static $instance = array();

if (!isset($instance[0]) || !$instance[0]) {
$instance[0] =& new FirePHPDebugger();
if (Configure::read() > 0) {
Configure::version(); // Make sure the core config is loaded
$instance[0]->helpPath = Configure::read('Cake.Debugger.HelpPath');
}
}
return $instance[0];
}

/**
* Overrides PHP's default error handling
*
* @param integer $code Code of error
* @param string $description Error description
* @param string $file File on which error occurred
* @param integer $line Line that triggered the error
* @param array $context Context
* @return boolean true if error was handled
* @access public
*/
function handleError($code, $description, $file = null, $line = null, $context = null) {
if (error_reporting() == 0 || $code === 2048) {
return;
}

$_this = FirePHPDebugger::getInstance();

if (empty($file)) {
$file = '[internal]';
}
if (empty($line)) {
$line = '??';
}
$file = $_this->trimPath($file);

$info = compact('code', 'description', 'file', 'line');
if (!in_array($info, $_this->errors)) {
$_this->errors[] = $info;
} else {
return;
}

$level = LOG_DEBUG;
switch ($code) {
case E_PARSE:
case E_ERROR:
case E_CORE_ERROR:
case E_COMPILE_ERROR:
case E_USER_ERROR:
$error = 'Fatal Error';
$level = LOG_ERROR;
$this->FirePHPLevel = FirePHP::ERROR;
break;
case E_WARNING:
case E_USER_WARNING:
case E_COMPILE_WARNING:
case E_RECOVERABLE_ERROR:
$error = 'Warning';
$level = LOG_WARNING;
$this->FirePHPLevel = FirePHP::WARN;
break;
case E_NOTICE:
case E_USER_NOTICE:
$error = 'Notice';
$level = LOG_NOTICE;
$this->FirePHPLevel = FirePHP::INFO;
break;
default:
return false;
break;
}

$helpCode = null;
if (!empty($_this->helpPath) && preg_match('/.*\[([0-9]+)\]$/', $description, $codes)) {
if (isset($codes[1])) {
$helpCode = $codes[1];
$description = trim(preg_replace('/\[[0-9]+\]$/', '', $description));
}
}

echo $_this->__output($level, $error, $code, $helpCode, $description, $file, $line, $context);

if (Configure::read('log')) {
CakeLog::write($level, "{$error} ({$code}): {$description} in [{$file}, line {$line}]");
}

if ($error == 'Fatal Error') {
die();
}
return true;
}

/**
* Handles object conversion to debug string
*
* @param string $var Object to convert
* @access private
*/
function __output($level, $error, $code, $helpCode, $description, $file, $line, $kontext) {
$_this = FirePHPDebugger::getInstance();
if($_this->__outputFormat !== 'fb') {
return Debugger::__output($level, $error, $code, $helpCode, $description, $file, $line, $kontext);
}
$files = $_this->trace(array('start' => 2, 'format' => 'points'));
$listing = $_this->fbFormat($_this->excerpt($files[0]['file'], $files[0]['line'] - 1, 1));
$trace = $_this->fbFormat($_this->trace(array('start' => 2, 'depth' => '20')));
$context = '
';
foreach ((array)$kontext as $var => $value) {
$context.= "\${$var} = " . $_this->exportVar($value, 1)."\n";
}
$context = $_this->fbFormat($context);
$instance = FirePHP::getInstance(true);
$message = "{$error} ({$code}): {$description} [{$file}, line {$line}";
$out = array(
'trace' => $trace,
'code' => $listing,
'context' => $context,
);
call_user_func_array(array($instance,'fb'),array($out, $message, $this->FirePHPLevel));
}

/**
* Function to format data to look purdy in FireBug
*
* @param mixed $data Data to be formatted for FireBug
* @return string Formatted FireBug data
*/
function fbFormat($data = '') {
if(is_array($data)) {
$data = join($data);
}
$data = strip_tags($data);
$data = '
' . str_ireplace("\t", '  ', str_ireplace("\n", '
', $data)) . '
';
return $data;
}
}

if (!defined('DISABLE_DEFAULT_ERROR_HANDLING')) {
FirePHPDebugger::invoke(FirePHPDebugger::getInstance());
}
?>

Now add the following line to app/config/bootstrap.php:

App::import('Vendor', 'FirePHPDebugger', array('file' => 'FirePHP' . DS . 'FirePHP.debugger.php'));
?>

When an error occurs (and debug > 0) you'll see something like this in your FireBug console:

http://yoursite.com
["Notice (1024): Foo [APP\config\bootstrap.php, line 46", Array(3)]

Hover over the "Array(3)" and you'll see a new window open with the Trace, Code, and Context info.

I haven't tested this code thoroughly, so if you have an issue leave a comment here and we can work it out.

Happy Baking!
Heath

Friday, April 11, 2008

Mem Cached Component

Mem Cached Component

Component:

This is mem cached component for cakePHP. Please create a component as mem_cache.php in the componet folder .

/**
* Wrapper for Memcache, v. 0.1
*
* By Jiri Kupiainen (http://jirikupiainen.com/)
*
* You are free to do whatever you please with this code. Enjoy.
*/
define('CACHE_LITE_ERROR_RETURN', 1);
define('CACHE_LITE_ERROR_DIE', 8);
//$memcache_obj = memcache_connect(array('XX.XXX.XX.XX','XX.XXX.XX.XX'), 11211);
class MemCacheComponent extends Object {
// --- Private properties ---
/**
* Directory where to put the cache files
* (make sure to add a trailing slash)
*
* @var string $_cacheDir
*/
var $_cacheDir = '/tmp/';

/**
* Enable / disable caching
*
* (can be very usefull for the debug of cached scripts)
*
* @var boolean $_caching
*/
var $_caching = true;

/**
* Cache lifetime (in seconds)
*
* If null, the cache is valid forever.
*
* @var int $_lifeTime
*/
var $_lifeTime = 3600;

/**
* Enable / disable fileLocking
*
* (can avoid cache corruption under bad circumstances)
*
* @var boolean $_fileLocking
*/
var $_fileLocking = true;

/**
* Timestamp of the last valid cache
*
* @var int $_refreshTime
*/
var $_refreshTime;

/**
* File name (with path)
*
* @var string $_file
*/
var $_file;

/**
* File name (without path)
*
* @var string $_fileName
*/
var $_fileName;

/**
* Enable / disable write control (the cache is read just after writing to detect corrupt entries)
*
* Enable write control will lightly slow the cache writing but not the cache reading
* Write control can detect some corrupt cache files but maybe it's not a perfect control
*
* @var boolean $_writeControl
*/
var $_writeControl = true;

/**
* Enable / disable read control
*
* If enabled, a control key is embeded in cache file and this key is compared with the one
* calculated after the reading.
*
* @var boolean $_writeControl
*/
var $_readControl = true;

/**
* Type of read control (only if read control is enabled)
*
* Available values are :
* 'md5' for a md5 hash control (best but slowest)
* 'crc32' for a crc32 hash control (lightly less safe but faster, better choice)
* 'strlen' for a length only test (fastest)
*
* @var boolean $_readControlType
*/
var $_readControlType = 'crc32';

/**
* Pear error mode (when raiseError is called)
*
* (see PEAR doc)
*
* @see setToDebug()
* @var int $_pearErrorMode
*/
var $_pearErrorMode = CACHE_LITE_ERROR_RETURN;

/**
* Current cache id
*
* @var string $_id
*/
var $_id;

/**
* Current cache group
*
* @var string $_group
*/
var $_group;

/**
* Enable / Disable "Memory Caching"
*
* NB : There is no lifetime for memory caching !
*
* @var boolean $_memoryCaching
*/
var $_memoryCaching = false;

/**
* Enable / Disable "Only Memory Caching"
* (be carefull, memory caching is "beta quality")
*
* @var boolean $_onlyMemoryCaching
*/
var $_onlyMemoryCaching = false;

/**
* Memory caching array
*
* @var array $_memoryCachingArray
*/
var $_memoryCachingArray = array();

/**
* Memory caching counter
*
* @var int $memoryCachingCounter
*/
var $_memoryCachingCounter = 0;

/**
* Memory caching limit
*
* @var int $memoryCachingLimit
*/
var $_memoryCachingLimit = 1000;

/**
* File Name protection
*
* if set to true, you can use any cache id or group name
* if set to false, it can be faster but cache ids and group names
* will be used directly in cache file names so be carefull with
* special characters...
*
* @var boolean $fileNameProtection
*/
var $_fileNameProtection = true;

/**
* Enable / disable automatic serialization
*
* it can be used to save directly datas which aren't strings
* (but it's slower)
*
* @var boolean $_serialize
*/
var $_automaticSerialization = false;

/**
* Disable / Tune the automatic cleaning process
*
* The automatic cleaning process destroy too old (for the given life time)
* cache files when a new cache file is written.
* 0 => no automatic cache cleaning
* 1 => systematic cache cleaning
* x (integer) > 1 => automatic cleaning randomly 1 times on x cache write
*
* @var int $_automaticCleaning
*/
var $_automaticCleaningFactor = 0;

/**
* Nested directory level
*
* Set the hashed directory structure level. 0 means "no hashed directory
* structure", 1 means "one level of directory", 2 means "two levels"...
* This option can speed up Cache_Lite only when you have many thousands of
* cache file. Only specific benchs can help you to choose the perfect value
* for you. Maybe, 1 or 2 is a good start.
*
* @var int $_hashedDirectoryLevel
*/
var $_hashedDirectoryLevel = 0;

/**
* Umask for hashed directory structure
*
* @var int $_hashedDirectoryUmask
*/
var $_hashedDirectoryUmask = 0700;

/**
* API break for error handling in CACHE_LITE_ERROR_RETURN mode
*
* In CACHE_LITE_ERROR_RETURN mode, error handling was not good because
* for example save() method always returned a boolean (a PEAR_Error object
* would be better in CACHE_LITE_ERROR_RETURN mode). To correct this without
* breaking the API, this option (false by default) can change this handling.
*
* @var boolean
*/
var $_errorHandlingAPIBreak = false;

// --- Public methods ---

/**
* Constructor
*
* $options is an assoc. Available options are :
* $options = array(
* 'cacheDir' => directory where to put the cache files (string),
* 'caching' => enable / disable caching (boolean),
* 'lifeTime' => cache lifetime in seconds (int),
* 'fileLocking' => enable / disable fileLocking (boolean),
* 'writeControl' => enable / disable write control (boolean),
* 'readControl' => enable / disable read control (boolean),
* 'readControlType' => type of read control 'crc32', 'md5', 'strlen' (string),
* 'pearErrorMode' => pear error mode (when raiseError is called) (cf PEAR doc) (int),
* 'memoryCaching' => enable / disable memory caching (boolean),
* 'onlyMemoryCaching' => enable / disable only memory caching (boolean),
* 'memoryCachingLimit' => max nbr of records to store into memory caching (int),
* 'fileNameProtection' => enable / disable automatic file name protection (boolean),
* 'automaticSerialization' => enable / disable automatic serialization (boolean),
* 'automaticCleaningFactor' => distable / tune automatic cleaning process (int),
* 'hashedDirectoryLevel' => level of the hashed directory system (int),
* 'hashedDirectoryUmask' => umask for hashed directory structure (int),
* 'errorHandlingAPIBreak' => API break for better error handling ? (boolean)
* );
*
* @param array $options options
* @access public
*/
function Cache_Lite($options = array(NULL))
{
foreach($options as $key => $value) {
$this->setOption($key, $value);
}
}
/**
* Generic way to set a Cache_Lite option
*
* see Cache_Lite constructor for available options
*
* @var string $name name of the option
* @var mixed $value value of the option
* @access public
*/
function setOption($name, $value)
{
$availableOptions = array('errorHandlingAPIBreak', 'hashedDirectoryUmask', 'hashedDirectoryLevel', 'automaticCleaningFactor', 'automaticSerialization', 'fileNameProtection', 'memoryCaching', 'onlyMemoryCaching', 'memoryCachingLimit', 'cacheDir', 'caching', 'lifeTime', 'fileLocking', 'writeControl', 'readControl', 'readControlType', 'pearErrorMode');
if (in_array($name, $availableOptions)) {
$property = '_'.$name;
$this->$property = $value;
}
}
/**
* Test if a cache is available and (if yes) return it
*
* @param string $id cache id
* @param string $group name of the cache group
* @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested
* @return string data of the cache (else : false)
* @access public
*/
function get($id, $group = 'default', $doNotTestCacheValidity = false)
{
$this->_id = $id;
$this->_group = $group;
$data = false;
if ($this->_caching) {
$this->_setRefreshTime();
$this->_setFileName($id, $group);
clearstatcache();
if ($this->_memoryCaching) {
if (isset($this->_memoryCachingArray[$this->_file])) {
if ($this->_automaticSerialization) {
return unserialize($this->_memoryCachingArray[$this->_file]);
}
return $this->_memoryCachingArray[$this->_file];
}
if ($this->_onlyMemoryCaching) {
return false;
}
}
if(($doNotTestCacheValidity) || (is_null($this->_refreshTime))) {
if (file_exists($this->_file)) {
$data = $this->_read();
}
} else {
if ((file_exists($this->_file)) && (@filemtime($this->_file) > $this->_refreshTime)) {
$data = $this->_read();
}
}
if (($data) and ($this->_memoryCaching)) {
$this->_memoryCacheAdd($data);
}
if (($this->_automaticSerialization) and (is_string($data))) {
$data = unserialize($data);
}
return $data;
}
return false;
}
/**
* Save some data in a cache file
*
* @param string $data data to put in cache (can be another type than strings if automaticSerialization is on)
* @param string $id cache id
* @param string $group name of the cache group
* @return boolean true if no problem (else : false or a PEAR_Error object)
* @access public
*/
function save($data, $id = NULL, $group = 'default')
{
if ($this->_caching) {
if ($this->_automaticSerialization) {
$data = serialize($data);
}
if (isset($id)) {
$this->_setFileName($id, $group);
}
if ($this->_memoryCaching) {
$this->_memoryCacheAdd($data);
if ($this->_onlyMemoryCaching) {
return true;
}
}
if ($this->_automaticCleaningFactor>0) {
$rand = rand(1, $this->_automaticCleaningFactor);
if ($rand==1) {
$this->clean(false, 'old');
}
}
if ($this->_writeControl) {
$res = $this->_writeAndControl($data);
if (is_bool($res)) {
if ($res) {
return true;
}
// if $res if false, we need to invalidate the cache
@touch($this->_file, time() - 2*abs($this->_lifeTime));
return false;
}
} else {
$res = $this->_write($data);
}
if (is_object($res)) {
// $res is a PEAR_Error object
if (!($this->_errorHandlingAPIBreak)) {
return false; // we return false (old API)
}
}
return $res;
}
return false;
}

/**
* Remove a cache file
*
* @param string $id cache id
* @param string $group name of the cache group
* @return boolean true if no problem
* @access public
*/
function remove($id, $group = 'default')
{
$this->_setFileName($id, $group);
if ($this->_memoryCaching) {
if (isset($this->_memoryCachingArray[$this->_file])) {
unset($this->_memoryCachingArray[$this->_file]);
$this->_memoryCachingCounter = $this->_memoryCachingCounter - 1;
}
if ($this->_onlyMemoryCaching) {
return true;
}
}
return $this->_unlink($this->_file);
}

/**
* Clean the cache
*
* if no group is specified all cache files will be destroyed
* else only cache files of the specified group will be destroyed
*
* @param string $group name of the cache group
* @param string $mode flush cache mode : 'old', 'ingroup', 'notingroup',
* 'callback_myFunction'
* @return boolean true if no problem
* @access public
*/
function clean($group = false, $mode = 'ingroup')
{
return $this->_cleanDir($this->_cacheDir, $group, $mode);
}

/**
* Set to debug mode
*
* When an error is found, the script will stop and the message will be displayed
* (in debug mode only).
*
* @access public
*/
function setToDebug()
{
$this->setOption('pearErrorMode', CACHE_LITE_ERROR_DIE);
}

/**
* Set a new life time
*
* @param int $newLifeTime new life time (in seconds)
* @access public
*/
function setLifeTime($newLifeTime)
{
$this->_lifeTime = $newLifeTime;
$this->_setRefreshTime();
}

/**
* Save the state of the caching memory array into a cache file cache
*
* @param string $id cache id
* @param string $group name of the cache group
* @access public
*/
function saveMemoryCachingState($id, $group = 'default')
{
if ($this->_caching) {
$array = array(
'counter' => $this->_memoryCachingCounter,
'array' => $this->_memoryCachingState
);
$data = serialize($array);
$this->save($data, $id, $group);
}
}

/**
* Load the state of the caching memory array from a given cache file cache
*
* @param string $id cache id
* @param string $group name of the cache group
* @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested
* @access public
*/
function getMemoryCachingState($id, $group = 'default', $doNotTestCacheValidity = false)
{
if ($this->_caching) {
if ($data = $this->get($id, $group, $doNotTestCacheValidity)) {
$array = unserialize($data);
$this->_memoryCachingCounter = $array['counter'];
$this->_memoryCachingArray = $array['array'];
}
}
}

/**
* Return the cache last modification time
*
* BE CAREFUL : THIS METHOD IS FOR HACKING ONLY !
*
* @return int last modification time
*/
function lastModified()
{
return @filemtime($this->_file);
}

/**
* Trigger a PEAR error
*
* To improve performances, the PEAR.php file is included dynamically.
* The file is so included only when an error is triggered. So, in most
* cases, the file isn't included and perfs are much better.
*
* @param string $msg error message
* @param int $code error code
* @access public
*/
function raiseError($msg, $code)
{
include_once('PEAR.php');
return PEAR::raiseError($msg, $code, $this->_pearErrorMode);
}

/**
* Extend the life of a valid cache file
*
* see http://pear.php.net/bugs/bug.php?id=6681
*
* @access public
*/
function extendLife()
{
@touch($this->_file);
}

// --- Private methods ---

/**
* Compute & set the refresh time
*
* @access private
*/
function _setRefreshTime()
{
if (is_null($this->_lifeTime)) {
$this->_refreshTime = null;
} else {
$this->_refreshTime = time() - $this->_lifeTime;
}
}

/**
* Remove a file
*
* @param string $file complete file path and name
* @return boolean true if no problem
* @access private
*/
function _unlink($file)
{
if (!@unlink($file)) {
return $this->raiseError('Cache_Lite : Unable to remove cache !', -3);
}
return true;
}

/**
* Recursive function for cleaning cache file in the given directory
*
* @param string $dir directory complete path (with a trailing slash)
* @param string $group name of the cache group
* @param string $mode flush cache mode : 'old', 'ingroup', 'notingroup',
'callback_myFunction'
* @return boolean true if no problem
* @access private
*/
function _cleanDir($dir, $group = false, $mode = 'ingroup')
{
if ($this->_fileNameProtection) {
$motif = ($group) ? 'cache_'.md5($group).'_' : 'cache_';
} else {
$motif = ($group) ? 'cache_'.$group.'_' : 'cache_';
}
if ($this->_memoryCaching) {
while (list($key, ) = each($this->_memoryCachingArray)) {
if (strpos($key, $motif, 0)) {
unset($this->_memoryCachingArray[$key]);
$this->_memoryCachingCounter = $this->_memoryCachingCounter - 1;
}
}
if ($this->_onlyMemoryCaching) {
return true;
}
}
if (!($dh = opendir($dir))) {
return $this->raiseError('Cache_Lite : Unable to open cache directory !', -4);
}
$result = true;
while ($file = readdir($dh)) {
if (($file != '.') && ($file != '..')) {
if (substr($file, 0, 6)=='cache_') {
$file2 = $dir . $file;
if (is_file($file2)) {
switch (substr($mode, 0, 9)) {
case 'old':
// files older than lifeTime get deleted from cache
if (!is_null($this->_lifeTime)) {
if ((mktime() - @filemtime($file2)) > $this->_lifeTime) {
$result = ($result and ($this->_unlink($file2)));
}
}
break;
case 'notingrou':
if (!strpos($file2, $motif, 0)) {
$result = ($result and ($this->_unlink($file2)));
}
break;
case 'callback_':
$func = substr($mode, 9, strlen($mode) - 9);
if ($func($file2, $group)) {
$result = ($result and ($this->_unlink($file2)));
}
break;
case 'ingroup':
default:
if (strpos($file2, $motif, 0)) {
$result = ($result and ($this->_unlink($file2)));
}
break;
}
}
if((is_dir($file2)) and ($this->_hashedDirectoryLevel>0)) {
$result = ($result and ($this->_cleanDir($file2 . '/', $group, $mode)));
}
}
}
}
return $result;
}

/**
* Add some date in the memory caching array
*
* @param string $data data to cache
* @access private
*/
function _memoryCacheAdd($data)
{
$this->_memoryCachingArray[$this->_file] = $data;
if ($this->_memoryCachingCounter >= $this->_memoryCachingLimit) {
list($key, ) = each($this->_memoryCachingArray);
unset($this->_memoryCachingArray[$key]);
} else {
$this->_memoryCachingCounter = $this->_memoryCachingCounter + 1;
}
}

/**
* Make a file name (with path)
*
* @param string $id cache id
* @param string $group name of the group
* @access private
*/
function _setFileName($id, $group)
{

if ($this->_fileNameProtection) {
$suffix = 'cache_'.md5($group).'_'.md5($id);
} else {
$suffix = 'cache_'.$group.'_'.$id;
}
$root = $this->_cacheDir;
if ($this->_hashedDirectoryLevel>0) {
$hash = md5($suffix);
for ($i=0 ; $i<$this->_hashedDirectoryLevel ; $i++) {
$root = $root . 'cache_' . substr($hash, 0, $i + 1) . '/';
}
}
$this->_fileName = $suffix;
$this->_file = $root.$suffix;
}

/**
* Read the cache file and return the content
*
* @return string content of the cache file (else : false or a PEAR_Error object)
* @access private
*/
function _read()
{
$fp = @fopen($this->_file, "rb");
if ($this->_fileLocking) @flock($fp, LOCK_SH);
if ($fp) {
clearstatcache();
$length = @filesize($this->_file);
$mqr = get_magic_quotes_runtime();
set_magic_quotes_runtime(0);
if ($this->_readControl) {
$hashControl = @fread($fp, 32);
$length = $length - 32;
}
if ($length) {
$data = @fread($fp, $length);
} else {
$data = '';
}
set_magic_quotes_runtime($mqr);
if ($this->_fileLocking) @flock($fp, LOCK_UN);
@fclose($fp);
if ($this->_readControl) {
$hashData = $this->_hash($data, $this->_readControlType);
if ($hashData != $hashControl) {
if (!(is_null($this->_lifeTime))) {
@touch($this->_file, time() - 2*abs($this->_lifeTime));
} else {
@unlink($this->_file);
}
return false;
}
}
return $data;
}
return $this->raiseError('Cache_Lite : Unable to read cache !', -2);
}

/**
* Write the given data in the cache file
*
* @param string $data data to put in cache
* @return boolean true if ok (a PEAR_Error object else)
* @access private
*/
function _write($data)
{
if ($this->_hashedDirectoryLevel > 0) {
$hash = md5($this->_fileName);
$root = $this->_cacheDir;
for ($i=0 ; $i<$this->_hashedDirectoryLevel ; $i++) {
$root = $root . 'cache_' . substr($hash, 0, $i + 1) . '/';
if (!(@is_dir($root))) {
@mkdir($root, $this->_hashedDirectoryUmask);
}
}
}
$fp = @fopen($this->_file, "wb");
if ($fp) {
if ($this->_fileLocking) @flock($fp, LOCK_EX);
if ($this->_readControl) {
@fwrite($fp, $this->_hash($data, $this->_readControlType), 32);
}
$len = strlen($data);
@fwrite($fp, $data, $len);
if ($this->_fileLocking) @flock($fp, LOCK_UN);
@fclose($fp);
return true;
}
return $this->raiseError('Cache_Lite : Unable to write cache file : '.$this->_file, -1);
}

/**
* Write the given data in the cache file and control it just after to avoir corrupted cache entries
*
* @param string $data data to put in cache
* @return boolean true if the test is ok (else : false or a PEAR_Error object)
* @access private
*/
function _writeAndControl($data)
{
$result = $this->_write($data);
if (is_object($result)) {
return $result; # We return the PEAR_Error object
}
$dataRead = $this->_read();
if (is_object($dataRead)) {
return $result; # We return the PEAR_Error object
}
if ((is_bool($dataRead)) && (!$dataRead)) {
return false;
}
return ($dataRead==$data);
}

/**
* Make a control key with the string containing datas
*
* @param string $data data
* @param string $controlType type of control 'md5', 'crc32' or 'strlen'
* @return string control key
* @access private
*/
function _hash($data, $controlType)
{
switch ($controlType) {
case 'md5':
return md5($data);
case 'crc32':
return sprintf('% 32d', crc32($data));
case 'strlen':
return sprintf('% 32d', strlen($data));
default:
return $this->raiseError('Unknown controlType ! (available values are only \'md5\', \'crc32\', \'strlen\')', -5);
}
}
}

Controller :

var $components = array ('MemCache');
var $ENABLE_CACHE =1;
var $ENABLE_CACHE_LOGGING =0;
function name()
{
$memcache = new Memcache;
$memcache->connect('localhost', 11211) or die ("Could not connect");
$memcache->addServer('64.237.62.50', 11211);
$memcache->addServer('64.237.62.51', 11211);
$this->Cache_Lite = array('cacheDir'=>$_SERVER['DOCUMENT_ROOT'].'/app/tmp', 'lifeTime'=>3600,'caching'=>'enable');

// code here
$cache_id = "_videolisting";
$this->MemCache->save(serialize($Userinfo),$cache_id,'Video');
if($this->ENABLE_CACHE==1)
{
if($cdat = $this->MemCache->get($cache_id,'Video'))
{
$cdat = unserialize($cdat);
$this->set('list',$list);
}
}else{
$this->set('list',$list);
}

}

Thursday, April 3, 2008

cakePHP : Pagination

If part of your application includes displaying lots of results, it's a good idea to give the user the possibility to view the results in digestable chunks and possibly to be able to sort the presented data. This tutorial will explain how, after copying a few files into your application, you can achieve this in very few lines of code.
You need almost no knowledge of cake to be able to make use of this tutorial :).

If you already have a table in mind that you want to add pagination to, read on; otherwise run through http://manual.cakephp.org/appendix/blog_tutorial to have some code to play with.

Setting Up

All of the files necessary are available here in the bakery.

Save this file http://bakery.cakephp.org/articles/view/67 as /app/controllers/components/pagination.php

Save this file http://bakery.cakephp.org/articles/view/68 as /app/views/helpers/pagination.php

Save this file http://bakery.cakephp.org/articles/view/69 as /app/views/elements/pagination.thtml


Create/modify the Controller

The only change necessary to use pagination is to include the component, the helper and call the component method "init" before the relavent find.
Controller Class:


class PostsController extends AppController
{
var $name = 'Posts'; // for PHP4 installs
var $components = array ('Pagination'); // Added
var $helpers = array('Pagination'); // Added

function index() {
$criteria=NULL;
list($order,$limit,$page) = $this->Pagination->init($criteria); // Added
$data = $this->Post->findAll($criteria, NULL, $order, $limit, $page); // Extra parameters added

$this->set('data',$data);
}
}
?>

Create/modify the View



To make use of pagination, include the element, and optionally modify your table headers to allow changing the sort order of results:
View Template:


Paginated Posts Index



$pagination->setPaging($paging); // Initialize the pagination variables
$th = array (
$pagination->sortBy('id'),
$pagination->sortBy('title'),
$pagination->sortBy('created')
); // Generate the pagination sort links
echo $html->tableHeaders($th); // Create the table headers with sort links if desired

foreach ($data as $output)
{
$tr = array (
$output['Post']['id'],
$html->link($output['Post']['title'], "/Posts/View/{$output['Post']['id']}"),
$output['Post']['created']
);
echo $html->tableCells($tr,array('class'=>'altRow'),array('class'=>'evenRow'));
}
?>

renderElement('pagination'); // Render the pagination element ?>


Adding Ajax updates



If you include the RequestHandler component, the AJAX helper in your controller and the prototype js file is loaded in your view - you get your updates by ajax. Yes, it's that simple. The div that will be updated by default is the "content" div, you can change this by specifying in the component (either directly, or at run time) which div to update. And yes, you can disable this automatic behaviour if required.
How to add Prototype

So how do you add the prototype library..? Well...
Modify your layout

PHP Snippet:

The Prototype JavaScript library is availble at http://prototype.conio.net/
put prototype.js in /app/webroot/js/
Add the JavaScript code inside the head tag

if(isset($javascript)):
echo $javascript->link('prototype.js');
endif;
?>

Wednesday, April 2, 2008

Saturday, March 29, 2008

Simple Problem for add/edit in cakephp with data base

First You can create the user table in your database :

For example :

CREATE TABLE `users` (
`id` int(11) NOT NULL auto_increment,
`login` varchar(20) NOT NULL,
`password` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
)

Second you can create the thtml file in the view folder the path is /view/users/register.thtml











hidden('User/id',array('value' => $data['User']['id'])));?>




User Name input('User/login',array('value' => $data['User']['login']));?>
Pass word input('User/password',array('value' => $data['User']['password'])));?>





Third you can create the users_controller.php

Path is /controllers/users_controller.php

class UsersController extends AppController
{
var $name= 'Users';
var $helpers = array('Html','Javascript');
function register()
{
$this->set('data',NULL);
if(isset($this->data)){
$this->User->save($this->data);
$this->redirect('users/register');
}
}
function edituser($id=NULL)
{
$data = $this->User->findById($id);
$this->set('data',$data);
if(isset($this->data)){
if($this->User->save($this->data))
$this->redirect('users/edituser/'.$id);
}
$this->render('register');
}
}
?>

Finally You can create the model user.php

path is /models/user.php


Class User extends AppModel{
var $name = 'User';
}
?>