Got Accepted.com
I, like many grad school applicants out there, have been waiting for months for graduate school decisions to come in. At this level, it gets a little ambiguous about how one gets in. So many individual factors are used to figure out who fills the spots for a given program. More than any other factor, the competition matters. One year there might be 50 applicants, another year over 200. So the qualifications for admission go up and down dramatically.
In comes Got Accepted.com, a web application designed for pulling together admissions decisions from across the country. This tool helps applicants know what’s happening with others and how their programs are making their decisions. It runs using CakePHP and hopefully will grow to include a lot of application decisions.
I’m pleased with where it’s at, but I anticipate adding more features down the road, like user-specific tracking of schools and programs. What I think is most useful in the current release is the comments section that allow users to discuss a particular decision in more detail.
Anyhow, the beta version is up and running. Check it out and let me know what you think ;)
Ajax File Uploading with Cake and jQuery
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
1 | webroot/js |
folder and link them in your layout file:
1 <?=$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
1 | <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
1 | views/stories |
folder, I’ve created the
1 | edit.ctp |
file. You will need to begin the form using the Form helper. Don’t forget to specify the type as
1 | file |
so that the
1 | <form> |
tag contains the necessary
1 | enctype="multipart/form-data" |
attribute for the form submission to work correctly.
1
2
3 <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:
1
2
3
4
5 <div id="storyTextUpload">
<span><?=$form->input('Story.text', array('label'=>'Current Text ','rows'=>'15','cols'=>'75'));?></span>
<span><?=$form->input('upload_text',array('label'=>'Upload Text File ','type'=>'file'));?></span>
<span><?=$form->button('Upload Text',array('onClick'=>'$(\'#storyEditForm\').ajaxSubmit({target: \'#storyTextUpload\',url: \''.$html->url('/stories/text').'\'}); return false;'));?></span>
</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
1 | <div> |
tag with
1 | id="storyTextUpload" |
. Notice that the
1 | onClick |
event is written in escaped jQuery Form plugin syntax. It’s essentially saying to submit the form with
1 | id="storyEditForm" |
and make the update target the element with an id of
1 | "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.
1 <?=$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
1 | text |
action in the stories controller because of the URL parameter I wrote in the onClick event.
So, in the
1 | controllers/stories_controller.php |
file, I need to include the following function:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 function text() {
<span>if (!$this->data['Story']['upload_text']) { //check for uploaded file in $this->data</span>
<span><span>$this->set('error','You must select a text (.txt) file before you can upload.');</span></span>
<span><span>$this->render('text','ajax');</span></span>
<span>} else {</span>
<span><span>$file = new File($this->data['Story']['upload_text']['tmp_name']); //the uploaded file sent by jQuery and parsed by Cake</span></span>
<span><span>if ($this->data['Story']['upload_text']['type'] != 'text/plain') {</span></span>
<span><span><span>$this->set('error','You may only upload text (.txt) files.');</span></span></span>
<span><span><span>$this->render('text','ajax');</span></span></span>
<span><span>} else {</span></span></span>
<span><span><span>$data = h($file->read()); //read file contents and pass through htmlspecialchars function</span></span></span>
<span><span><span>$file->close();</span></span></span>
<span><span><span>$this->set('text',$data);</span>
<span><span><span>$this->render('text','ajax');</span></span></span>
<span><span>}</span></span>
<span>}</span>
}
Notice that once the action fetches the file contents, it assigns it to a Cake variable named
1 | $text |
to be used in the
1 | views/stories/text.ctp |
view.
In the View
In the
1 | views/stories/text.ctp |
file, I’ve included the following code:
1
2
3
4
5
6
7
8
9
10
11 <? if (!empty($error)): ?>
<span><?=$form->input('Story.text', array('label'=>'Current Text ','rows'=>'15','cols'=>'75'));?></span>
<span><?=$form->input('Story.upload_text',array('label'=>'Upload Text File ','type'=>'file'));?></span>
<span><?=$form->button('Upload Text',array('onClick'=>'$(\'#storyEditForm\').ajaxSubmit({target: \'#storyTextUpload\',url: \''.$html->url('/stories/text').'\'}); return false;'));?></span>
<span><p><?=$error;?></p></span>
<? else: ?>
<span><?=$form->input('Story.text',array('label'=>'Current Text ','value'=>$text,'rows'=>'15','cols'=>'75'));?></span>
<span><p>Upload successful</p></span>
<span><?=$form->input('Story.upload_text',array('label'=>'Upload Text File ','type'=>'file'));?></span>
<span><?=$form->button('Upload Text',array('onClick'=>'$(\'#storyEditForm\').ajaxSubmit({target: \'#storyTextUpload\',url: \''.$html->url('/stories/text').'\'}); return false;'));?></span>
<? 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
1 | $data |
into a Cake variable, I could make a new file somewhere on the server like so:
1
2
3 $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
1 | <input type="file"> |
element. Usually, in PHP, the file is accessible in the
1 | $_FILES |
variable, but thanks to Cake, I can get the content moved over much more easily by making use of
1 | $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.
Using the File Functions in CakePHP
In CakePHP, a very useful set of functions is included but which get little documentation. I had combed the web looking for something useful about how to use the Cake File functions but got little concise or tutorial-based help. After working with the File functions for a little bit now, I figured an explanation might be useful.
Add.ctp View File
Let’s do a simple file upload. Let’s assume that I’m creating blog posts (sorry if using blogs as examples is overkill, but overall I think everyone gets it). For each blog post entry I’d like to upload an image file to match it. In the
1 | views/posts/add.ctp |
file, I have the following basic code:
1
2
3
4
5
6
7 <h1>Add Post</h1>
<?=$form->create('Post',array('type'=>'file'));?>
<?=$form->input('date',array('label'=>'Publication Date '));?>
<?=$form->input('headline');?>
<?=$form->input('content');?>
<?=$form->input('image',array('type'=>'file'));?>
<?=$form->end('Submit');?>
In the Controller
Now, the most basic upload using the File functions is going to occur in the
1 | controllers/posts_controller.php |
file.
Everything should run like a normal Add function, except I need to actually do the upload of the file.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 if ($this->data['Post']['image']) {
<span>$file = new File($this->data['Post']['image']);</span>
<span>$ext = $file->ext();</span>
<span>if ($ext != 'jpg' && $ext != 'jpeg' && $ext != 'gif' && $ext != 'png') {</span>
<span><span>$this->Session->setFlash('You may only upload image files.');</span></span>
<span><span>$this->render();</span></span>
<span>} else {</span>
<span><span>$date = $this->data['Post']['date'];</span></span>
<span><span>$filename = $date['year'].'-'.$date['month'].'-'.$date['day'].'-post-image.'.$ext;</span></span>
<span><span>$data = $file->read();</span></span>
<span><span>$file->close();</span></span>
<span><span>$file = new File(WWW_ROOT.'/img/'.$filename,true);</span></span>
<span><span>$file->write($data);</span></span>
<span><span>$file->close();</span></span>
<span>}</span>
}
Let me explain what is happening here. When the user chooses a file to be uploaded in the HTML form, the link to that file is set through the
1 | $_POST |
variables, and in Cake, as will all form posts, these get parsed into the
1 | $this->data |
array.
The first step, then, is to create a new instance of the File class so we can use those functions.
1 $file = new File($this->data['Post']['image']);
From here on out, I can pull all sorts of information about this file by using the available Cake functions. You’ll notice that I pulled out the file extension quite easily by using the
1 | File->ext() |
function:
1 $ext = $file->ext();
If I were to use an
1 | echo |
command in PHP, I would get
1 jpg
for a JPG file upload.
To write the contents of this file to the server, I need to first fetch all the data in the file.
1 $data = $file->read();
Now all the contents of the file are held in the
1 | $data |
variable. To write this to the server, I’ll need to create a new file instance, create the file in the appropriate directory (in this case, the
1 | webroot/img |
folder), and dump the contents of
1 | $data |
into that file.
1
2
3 $file->close(); //close out of the user's file
$file = new File(WWW_ROOT.'/img'.$filename,true);
Notice that in creating the new instance for the File class, I’ve used the Cake global variable,
1 | WWW_ROOT |
and I’ve appended the
1 | /img |
directory plus the desired filename. Also, I’ve set create new file to true.
Now the
1 | $file |
variable is set to the new object, which in this case is the empty file on my server stored in the
1 | webroot/img |
directory named whatever is in
1 | $filename |
. By using the
1 | write() |
function, I can plop
1 | $data |
into that file:
1 $file->write($data);
The rest of the script is just closing out everything.
Other Functions
So how about it? Much, much easier than doing all the classic
1 | fopen |
and
1 | fread, fputs |
functions in PHP. Of course you can branch out and do more as well as error checking using other parameters in the functions themselves.
The CakePHP API contains a list of the available File and Folder functions. By using them like you’ve seen me do above, you can spare yourself superfluous coding.
For example, if I wanted to test if the file by that filename already exists on the server, I can enter:
1
2
3
4
5
6
7 $id = intval(rand());
$filename = $id.'-post.'.$file->ext();
$folder = new Folder(WWW_ROOT.'/img');
while ($folder->find($filename) == $filename) {
<span>$id++;</span>
<span>$filename = $id.'-post.'.$file->ext();</span>
}
Or if I wanted to delete a file on the server where
1 | $file |
has been set as the class object of the file to be deleted:
1 $file->delete();
So explore the other File functions and enjoy!

