RSS
 

Archive for November, 2011

Serving views as files in Cake2

21 Nov

Actually, its not that different in Cake1.3. But as I just played around with it in 2.0, I will stick to that version for examples.

How to start

Skip this, if you want to cut to the chase.

In your routes.php you need to add Router::parseExtensions(); (or only specific ones). That tells cake that urls ending with “.xyz” will be served as files (either inline or as download attachment).

Setup

Don’t forget to add the RequestHandler to your Controller components list:

public $components = array('RequestHandler');
This will be an important part in the following auto-magic.

Let’s say you want to display an invoice as pdf. The normal url is /invoices/view/1. Now, we set a link in the view to the file like so:

$this->Html->link('View as PDF', array('action'=>'view', 'ext'=>'pdf', 1));

Since we use the RequestHandler Component cake automatically detects that this will be a pdf file, it will a) set the correct header (application/pdf) b) will try to find the specific pdf view in /View/Invoices/pdf/index.ctp and the pdf layout in /View/Layouts/pdf/default.ctp

That’ all :)

Download it right away

Files like pdf can be displayed inline. So the browser will usually not force you to download it. If you want this, though, you need to call $this->request->download($filename); in your controller action or in your layout.

Note: If your browser does not understand the file format (in this case pdf) it will probably trigger the download right away.

Browser bugs and easter eggs

Well, you could also call it a bug. But during my tryouts I found out that files served inline (Content-Disposition: inline; filename=”…”) will not use the given filename on save. They will be saved with the name in the url instead. In the above example it would be “1.pdf”. I did some research: Thats a well known browser deficiency that nobody yet fixed. Or so it seems. Ok, but nobody wants his invoice to be “1.pdf”. So what can we do about it?

I found a pretty well working workaround:

$this->Html->link('View as PDF', array('action'=>'view', 'ext'=>'pdf', 1, 'invoice-2011-11-01_some_customer_tag'));
As you can see we simply add our filename to the url – after the id (!).

Since it is only “filename cosmetics” we dont need to add this second passed param to our method:

public function view($id = null) {}
It will be ignored in the action itself.

So the generated url is /invoices/view/1/invoice-2011-11-01_some_customer_tag.pdf and will result in a file saved as “invoice-2011-11-01_some_customer_tag.pdf”. Job done.

Let me know what you think.

Example for PDFs

A pretty quick example how to output your content as pdf using DOMPDF.

In your layout (default.ctp in /pdf/):

App::import('Vendor', 'dompdf/dompdf.php');
$dompdf = new DOMPDF();
$dompdf->load_html(utf8_decode($content_for_layout), Configure::read('App.encoding'));
$dompdf->render();
echo $dompdf->output();
For me it looked like that without utf8_decode the DOMPDF lib seems to be buggy – although it claims to support utf8.

Example for ics (ical calendar) files

Just a very basic example using an IcalHelper from my Tools Plugin.

Please note: There is a pending ticket on this. Until then you either need to manually patch the ResponseClass (quick solution) by adding the missing mimetype 'ics' => 'text/calendar', or define it in your Controller via $this->response->type(array('ics' => 'text/calendar')); and set all paths manually… Hopefully the second issue for unknown mimetypes will be addressed soon, as well.

Controller code:

$reservation = ...;
$this->plugin = 'Tools'; //not necessary, only that I want to store the layout once (in the plugin)
$this->set(compact('reservation'));
$this->helpers[] = 'Tools.Ical';

Layout (in /Plugin/Tools/View/Layouts/ics/default.ctp):

<?php echo $content_for_layout; ?>

View (in /View/Reservations/ics/export.ctp):

$data = array(
    'start' => $this->Time->toAtom($reservation['Reservation']['time']),
    'end' => $this->Time->toAtom(strtotime($reservation['Reservation']['time'])+HOUR),
    'summary' => 'Reservation',
    'description' => $reservation['Reservation']['headcount'].' persons @ location foo',
    'organizer' => 'CEO',
    'class' => 'public',
    'timestamp' => '2010-10-08 22:23:34',
    'id' => 'reservation-'.$reservation['Reservation']['id'],
    'location' => $reservation['Restaurant']['address'],
);
$this->Ical->add($data);
echo $this->Ical->generate();

And all that is left again is to link this action accordingly:

echo $this->Html->link($this->Html->image('icons/calendar.gif', array('title'=>'Download reservation as ical-file')), array('action'=>'export', $reservation['Reservation']['id'], 'ext'=>'ical'), array('escape'=>false));

 
12 Comments

Posted in CakePHP