David Golding



Installing Cake Outside Root

By David Golding

There are some benefits to running the Cake libraries outside the main root folder. Since the index page of a web site I work on got compromised last week due to a PHP exploit, I decided it was a good time to begin finding ways to run Cake outside the root and to have all my Cake apps run off the same cake folder.

This is actually pretty simple to do. Here’s how I go about doing it.

Let’s say that my server setup has a root folder named “public_html” and that this folder is housed in another folder named after my account name (I’ll use “account”). Now when you download the Cake release, everything comes bundled into one folder named cake_1.2.x.x.x or something like that. It’s designed to work right out of the box, so we will have to change some things since we’ll be moving some folders around.

What I now do is place the “cake” folder in the account directory, not the “public_html” directory. I rename the cake_1.2.x.x.x folder to whatever I want the application to be named and place this folder into the “public_html” directory.

Finally, I open the app/webroot/index.php file and change line 56:

define('CAKE_CORE_INCLUDE_PATH', dirname(dirname(ROOT)));

Simple as that. This is described in the Cake Manual but I have had enough people ask me how I do it, so I figured I lay it out here.

I recommend this method because if you want to upgrade Cake, you need only swap out the Cake libraries rather than replace all of them throughout your whole server. Also, whatever you can do to minimize the amount of PHP exploitation that could be done by third parties is worth the time.


Ajax File Uploading with Cake and jQuery

By David Golding

Here’s how to upload files in CakePHP using jQuery.

First, make sure you download jQuery 1.2.2 or greater plus the jQuery Form plugin. Place them in the webroot/js folder and link them in your layout file:

<?=$javascript->link(array('jquery.js','jquery.form.js'));?>

Now, in this example, I have a table named “Stories” in the database. For each Story, I want to make a text upload option available in the add and edit views. What will happen is that the user will upload a plain-text file, then Cake will place the contents of the file into a <textarea> field, allowing the user to easily upload a text file but still be able to edit its contents before saving the Story.

In the views/stories folder, I’ve created the edit.ctp file. You will need to begin the form using the Form helper. Don’t forget to specify the type as file so that the <form> tag contains the necessary enctype="multipart/form-data" attribute for the form submission to work correctly.

<h1>Edit Story</h1>

<?=$form->create('Story',array('name'=>'storyEditForm','id'=>'storyEditForm','type'=>'file'));?>

You can put whatever input fields you want in the form. I’m just going to include here the actual uploading part:

<div id="storyTextUpload">
<?=$form->input('Story.text', array('label'=>'Current Text ','rows'=>'15','cols'=>'75'));?>
<?=$form->input('upload_text',array('label'=>'Upload Text File ','type'=>'file'));?>
<?=$form->button('Upload Text',array('onClick'=>'$(\'#storyEditForm\').ajaxSubmit({target: \'#storyTextUpload\',url: \''.$html->url('/stories/text').'\'}); return false;'));?>
</div>

For the Ajax part of the upload, I’ll need to tell jQuery where to update the Ajax request. In this case, it’s going to be the <div> tag with id="storyTextUpload". Notice that the onClick event is written in escaped jQuery Form plugin syntax. It’s essentially saying to submit the form with id="storyEditForm" and make the update target the element with an id of "storyTextUpload". I’ve linked the Ajax form submission to a controller action I haven’t written yet called “text”.

I can now finish the form with whatever else I want.

<?=$form->end('Submit');?>

All I have to do now is create the text action in the Stories controller that will handle the upload and then create a view to be used in the final Ajax returned response.

In the Controller

When the file is chosen and the user clicks the “Upload Text” button, jQuery will submit the whole form to the text action in the stories controller because of the URL parameter I wrote in the onClick event.

So, in the controllers/stories_controller.php file, I need to include the following function:

function text() {
if (!$this->data['Story']['upload_text']) { //check for uploaded file in $this->data
$this->set('error','You must select a text (.txt) file before you can upload.');
$this->render('text','ajax');
} else {
$file = new File($this->data['Story']['upload_text']['tmp_name']); //the uploaded file sent by jQuery and parsed by Cake
if ($this->data['Story']['upload_text']['type'] != 'text/plain') {
$this->set('error','You may only upload text (.txt) files.');
$this->render('text','ajax');
} else {
$data = h($file->read()); //read file contents and pass through htmlspecialchars function
$file->close();
$this->set('text',$data);
$this->render('text','ajax');
}
}
}

Notice that once the action fetches the file contents, it assigns it to a Cake variable named $text to be used in the views/stories/text.ctp view.

In the View

In the views/stories/text.ctp file, I’ve included the following code:

<? if (!empty($error)): ?>
<?=$form->input('Story.text', array('label'=>'Current Text ','rows'=>'15','cols'=>'75'));?>
<?=$form->input('Story.upload_text',array('label'=>'Upload Text File ','type'=>'file'));?>
<?=$form->button('Upload Text',array('onClick'=>'$(\'#storyEditForm\').ajaxSubmit({target: \'#storyTextUpload\',url: \''.$html->url('/stories/text').'\'}); return false;'));?>
<p><?=$error;?></p>
<? else: ?>
<?=$form->input('Story.text',array('label'=>'Current Text ','value'=>$text,'rows'=>'15','cols'=>'75'));?>
<p>Upload successful</p>
<?=$form->input('Story.upload_text',array('label'=>'Upload Text File ','type'=>'file'));?>
<?=$form->button('Upload Text',array('onClick'=>'$(\'#storyEditForm\').ajaxSubmit({target: \'#storyTextUpload\',url: \''.$html->url('/stories/text').'\'}); return false;'));?>
<? endif; ?>

That’s it… Now this handy set of functions will take a file and upload its contents the plop the contents into a text field which will be saved to the database after the user clicks the Submit button. Of course you will need to create your own edit/add functions but this much will get the file uploaded for you.

A File Transfer Method

Actually getting the data was shown above. But say I wanted to put the file contents not into a form field but do an actual file transfer to the server. This is simple. Instead of putting $data into a Cake variable, I could make a new file somewhere on the server like so:

$file = new File(WWW_ROOT.'/files/'.intval(rand()).'_text_upload.txt');
$file->write($data);
$file->close();

A Note on Ajax Submissions

The reason why I’ve used jQuery here is that it is the only Ajax framework I’ve been able to find that will serialize the form elements and actually send to the server as a post variable the <input type="file"> element. Usually, in PHP, the file is accessible in the $_FILES variable, but thanks to Cake, I can get the content moved over much more easily by making use of $this->data and the File functions, which you have seen me do. To spare you time trying to get this to work in Prototype (the Ajax framework that the Cake Ajax Helper uses), I hope you’ll stick with my example here and use jQuery. Prototype doesn’t serialize file inputs.

Maybe someone could build a jQuery set for the Ajax helper :) Until then, hope this helps.


Beginning CakePHP: From Novice to Professional by David Golding

David Golding

A blog about CakePHP, web design, and grad studies in religion. © 2008, D. Golding