Jump to content

How To Create Pdfs With Pw And Mpdf


Can
 Share

Recommended Posts

Hey guys,

got asked to share how to set up mpdf with processwire so it's time to make a little tutorial

Disclaimer: ( ^_^) This is my first tutorial and I'm not a real coder yet so every tips and hints are highly appreciated :)

  1. Grab a copy of mPDF unzip it and put it in a nice place outside of your Processwire folder(s) - (as far as I remember is has to be outside or within a module to work, but I could be wrong?!)
  2. Open the template from which you want to create the PDF from

In my case I have a "course.php" which is showing a page with a weekly schedule table and I wanted to provide this schedule as pdf next to the website version.

I turned on urlSegments for "course" template, because I find it nicer than a get variable

Then in my course.php template file after the normal website output I added the following

It's probably better to have this logic at the very top of the template file to prevent unnecessary code execution right??

Aha, now I remember why it's at the bottom. I have some more logic above it which I wanted to share for browser output and pdf creation

and this was the easiest way, probably I'll rearrange everything to make it faster

At least the if( file_exists logic could go at the very top :)

<?php
// In case you don't have other urlSegments it's better to throw a 404 when someone types a non existing url,
// could be managed better but at the moment it's easier
if($input->urlSegment1 && $sanitizer->pageName($input->urlSegment1) != 'pdf') throw new Wire404Exception();

// if urlSegment /pdf/ output course table as PDF file
if($sanitizer->pageName($input->urlSegment1) === 'pdf') {

	// $config->paths->files = PWs /assets/files/ folder, page ID and the title of the page could be any field or anything else
        // maybe you want to add the date
	$pdfPath = $config->paths->files . $page->id . "/" . $page->title.'.pdf';

	// Options for wireSendFile because they're needed more then once
	$wireSendOptions = array('exit' => true, 'forceDownload' => true, 'downloadFilename' => '');

	// If file already exists force download it and stop further execution which is handled $wireSendOptions 'exit' => true
	if(file_exists( $pdfPath )) wireSendFile($config->paths->files . $page->id . "/" . $headline.'.pdf', $wireSendOptions );

	// include mPDF.php conditionally for localhost and productive website, you probably don't need it if structure is the same
	// included it at this point because we don't need it to provide an existing file
        // you could even include it after your output logic just before PDF creation..
	if($config->httpHost == 'localhost') {
            include("../../../../classes/MPDF57/mpdf.php")
        }
        else {
            include("../../../classes/MPDF57/mpdf.php");
        }

	// at this point you create the logic for your output
	// I'm not sure wether mPDF understands single 'quotes' properly
        // TCPDF doesn't really liked them so I stick to double "quotes" for now
	// just a little example how it might look you can of course have a loop populate everything from $page or somewhere else ;-)
	$pdfOutput = '<table class="data">
					  <tr>
					    <th>Entry Header 1</th>
					    <th>Entry Header 2</th>
					    <th>Entry Header 3</th>
					    <th>Entry Header 4</th>
					  </tr>
					  <tr>
					    <td>Entry First Line 1</td>
					    <td>Entry First Line 2</td>
					    <td>Entry First Line 3</td>
					    <td>Entry First Line 4</td>
					  </tr>
					  <tr>
					    <td>Entry Line 1</td>
					    <td>Entry Line 2</td>
					    <td>Entry Line 3</td>
					    <td>Entry Line 4</td>
					  </tr>
					  <tr>
					    <td>Entry Last Line 1</td>
					    <td>Entry Last Line 2</td>
					    <td>Entry Last Line 3</td>
					    <td>Entry Last Line 4</td>
					  </tr>
				</table>';

	// now the best part
	// the next comment explains the mPDF() parameters as in the manual http://mpdf1.com/manual/index.php?tid=184
	// mode, format, default_font_size, default_font, margin_left, margin_right,
        // margin_top, margin_bottom, margin_header, margin_footer, orientation
	$mpdf = new mPDF('utf-8','A4-L', 0, '', 5, 5, 16, 6, 5, 5, 'L');

	// The header is repeated on all pages, it's possibly to have different headers (even/odd) and other fancy stuff
	// You can have images as well, just have a look at the documentation it's pretty good
	$mpdf->SetHTMLHeader("<h2>{$page->title}</h2>");
	
	// this one puts your table, created above, into the file
	$mpdf->WriteHTML($pdfOutput);

	// I'm saving the PDF file to the disc first as set in line 9
        // 'D' would output the file inline in the browser window but then we're not able to store it for faster serving
	$mpdf->Output($pdfPath,'F');
	
	// Thanks to Ryan and his nice functions we can use wireSendFile (as above) to get the created file and force download it
	wireSendFile($pdfPath, $wireSendOptions );

} // End if /pdf/

If you want to create the file everytime it's called and don't want the it to get stored just strip line 12, 15 and 67 and change the 'F' in line 64 to 'D'

If you want to have a newly created file each time you change the page you have to install a little module to hookAfter page save

It's just one file, I called it like hooks.module because maybe I will add some hooks later and there is no need to have each hook in a separate module.

<?php
class Hooks extends WireData implements Module {
  public static function getModuleInfo() {
    return array(
      'title' => 'Custom Hooks',
      'summary' => 'Hooks, PDF deletion on page save for example',
      'version' => 1,
      'autoload' => true, // You want it to autoload, otherwise it would never get called
    );
  }

  public function init() {
    $this->pages->addHookAfter('save', $this, 'deletePDF'); //initialise the hook
  }

  // Most of it is just copied from Pages2PDF module from Wanze, a little 'simplified' though^^
  public function deletePDF(HookEvent $event) {
    $page = $event->arguments[0];

    $pdf = wire('config')->paths->files . $page->id . "/" . $page->title.'.pdf';
    if (file_exists($pdf) && is_file($pdf)) {
      $name = $page->title.'.pdf';
      if (@unlink($pdf)) {
        $this->message($this->_(sprintf("The following file got deleted '%s'", $name)));
      } else {
        $this->error($this->_(sprintf("Failed to delete '%s'", $name)));
      }
    }
  }
}

Hope you can follow the steps. Please let me know if not and why  ^_^

Any improvements are welcome because I would love to learn more as well

Edited by Can
  • Like 5
Link to comment
Share on other sites

This is great and very useful. Thanks a lot Can!

May have spotted an error. In line 33 you define the HTML table as $pdfOutput. And the in line 71 you go

$mpdf->WriteHTML($table);

I think it should read

$mpdf->WriteHTML($pdfOutput);

I will most likely put this to use. Only waiting for the customer to finally confirm that they want this feature. I will add the cart logic as discussed in the other thread and publish my code here. Will take another month more or so.

Cheers

Gerhard

  • Like 2
Link to comment
Share on other sites

Thank you Gerhard, really fast :D

As I mentioned I changed it a bit for this tut and missed this one, thanks to you it's corrected :)

I would like to know how it works for you in the end, so hopefully your customer likes the feature  :grin:

Link to comment
Share on other sites

Dude!

I've always wanted to do on-the-fly PDF brochures of some pages on a site I have, but have always put it off as thinking I don't have enough spare time to justify it, and don;t really want to fight stupid PDF libraries to get a decent result..

Well, after reading your tut I decided it looked easier than I have previously thought. 

In about 2 hours this morning I've nailed this with some outstanding results, sooo easy! I thought there would be issues getting PDF data to display right etc (these things are never straight-forward) but no, mpdf is the bomb!

So thanks for the enticing post that got me going with this, and guys, if you like me think PDF generation will be more trouble than it's worth, I encourage you to give it a go.

  • Like 3
Link to comment
Share on other sites

Yeah thanks Can. Only thing I'm coming unwound on is wrapping text around a right-floated image. Looks like this is something that cannot be done with mpdf. If anyone has an solution to this I'd love to know.

Cheers

Link to comment
Share on other sites

Thanks for that Can, You're right, images do seem to float, unfortunately when I replace the image with a div it fails. Looks like all pdf classes have similar issues and this is obviously not an easy thing to get working reliably

<div>
   <div style="float:right;width:100px;height:100px">
      <p>Small div text</p>
   </div>
   <p>text in here should wrap floated internal div. When internal div is replaced with a floated image it works, but as a div it fails. Bummer</p>
</div>
Link to comment
Share on other sites

@muzzer

jsPDF library can handle floating divs and images pretty well.

I set up a quick and dirty test page at http://jspdf.webseiten-fuer-alle.de/

Underneath "Choose examples" select the last one "** NEW addHTML()" and you'll instantly see the result.

For floating divs with mpdf have a read here: http://mpdf1.com/manual/index.php?tid=385

As far as I understand it in your example the text wrapping the div also needs to be wrapped with float and width (but then it won't wrap the div anymore).

  • Like 1
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...