Skip to content
Grav 2.0 is officially stable. Read the announcement →

Community guidelines

Please keep discussions civil and on-topic. Repeated violations may lead to a temporary ban.

Support

How can I create dynamic pdf files from a page

Solved by pamtbaau View solution

Started by Dean Bateman 1 year ago · 7 replies · 193 views
1 year ago

I am currently manually creating a pdf file of my products but, I would like to display my page as a pdf file for download when I click a button, as they are the same content.

I would like to control how it displays, can I create page-item.pdf.twig template so that if a user visits www.example.com/page-item.pdf it will display a pdf.

if this is not supported by Grav I was thinking of using TCPDF
how can I add this to Grav?

As per the documentation

Grav is a flexible platform however, and can actually serve up any content type you could wish for (xml, rss, json, pdf, etc.), you just have to provide a way to render it appropriately.

If you were to request a route with a .xml extension, for example: /blog.xml, instead of using the regular blog.html.twig template to render it, Grav looks for a template called blog.xml.twig. You would need to ensure that template outputs the appropriate XML structure.

last edited 02/27/25 by Karmalakas
1 year ago

I guess you could have something like

TWIG
{# my-page.pdf.twig #}
{{ outputPDF(page) }}

And then in some custom plugin with outputPDF($page) Twig function render the PDF using TCPDF 🤔

Or maybe this outputPDF() even could render your actual my-page.html.twig output. I used TCPDF years ago, but IIRC it can accept HTML and make a PDF. I believe it used to distort the layout then, but maybe worth trying

last edited 02/27/25 by Karmalakas
1 year ago

In your described case, it is enough to optimize your print-stylesheet - as all modern browsers are able to generate PDF files as well.

If you want to generate a completely individual looking PDF-file with specific data from your page (lets say you have built a real estate page and want the user to be able to download an expose for a building in your corporate style) I would recommend https://parall.ax/products/jspdf.
But be aware that coding a PDF-template is as much fun as creating a responsive newsletter-template with tables...

👍 1
1 year ago

ok, so I am having a go at designing a custom plugin. It works well locally, but I am having issues on the live site.

When I upload the plugin, the whole site doesn't display, it just shows "this page isn't working".

If I edit out code, it seems to be the "class MYPDF extends TCPDF" section; due to if I remove it, the site displays again.

I have installed tcpdf via composer in the plugin root folder. is there something I am missing or doing wrong?

Here is an abbreviated version of the code

PHP
<?php
namespace Grav\Plugin;

use Composer\Autoload\ClassLoader;
use Grav\Common\Plugin;
use TCPDF;

class MYPDF extends TCPDF {
    public $logoPath;
    public $product;
    public $dxfIconPath;
    public $dxfFilePath;

    public function Header() {
        // Code
    }

    public function Footer() {
        // Code
     }
}

/**
 * Class ProductPDFPlugin
 * @package Grav\Plugin
 */
class ProductPDFPlugin extends Plugin
{
    /**
     * @return array
     *
     * The getSubscribedEvents() gives the core a list of events
     *     that the plugin wants to listen to. The key of each
     *     array section is the event that the plugin listens to
     *     and the value (in the form of an array) contains the
     *     callable (or function) as well as the priority. The
     *     higher the number the higher the priority.
     */
    public static function getSubscribedEvents(): array
    {
        return [
            'onPluginsInitialized' => [
                ['onPluginsInitialized', 0]
            ]
        ];
    }

    /**
     * Composer autoload
     *
     * @return ClassLoader
     */
    public function autoload(): ClassLoader
    {
        return require __DIR__ . '/vendor/autoload.php';
    }

    /**
     * Initialize the plugin
     */
    public function onPluginsInitialized(): void
    {
        // Don't proceed if we are in the admin plugin
        if ($this->isAdmin()) {
            return;
        }

        // Enable the main events we are interested in
        $this->enable([
            'onPageInitialized' => ['onPageInitialized', 0]
        ]);
    }

    /**
     * Handle the page initialization event
     */
    public function onPageInitialized(): void
    {      
         // Check if the current route is for generating a PDF
         $uri = $this->grav['uri'];
         $path = $uri->path();
         if (preg_match('/^\/products\/.+\.pdf$/', $path)) {
             $this->generatePDF($path);
         }
    }

    /**
     * Generate and display a PDF using TCPDF
     */
    private function generatePDF(string $path): void
    {
        // Extract product name from path
        $productName = basename($path, '.pdf');

        // Remove the .pdf extension to find the original page
        $originalPath = preg_replace('/\.pdf$/', '', $path);

        // Retrieve the original page
        $page = $this->grav['pages']->dispatch($originalPath, true);

        // Get product data from page frontmatter
        $product = $page->header()->product;

        // Create new PDF document
        $pdf = new MYPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false);

        // Set document information
        $pdf->SetCreator(PDF_CREATOR);
        $pdf->SetAuthor('Your Name');
        $pdf->SetTitle($product['code'] . '[' . $product['name'] . ']');
        $pdf->SetSubject('Product PDF Document');
        $pdf->SetKeywords('TCPDF, PDF, product, guide');

        //HTML PDF CODE

        // Output PDF document
        $pdf->Output( $product['code'] . '[' . $product['name'] . ']'.'.pdf', 'I');

        // Stop Grav from further processing
        exit();
    }
}

1 year ago

Further to this, it seems locally i have got tcpdf installed in the root composer, when removed it acts the same. so my question is how do i make sure it uses TCPDF from the plugin composer?

1 year ago

here is the solution

in your terminal in the plugin root folder

BASH
composer required tecnickcom/tcpdf

then in your plugin.php file add the require line

PHP
use Composer\Autoload\ClassLoader;
use Grav\Common\Plugin;
require __DIR__ . '/vendor/autoload.php';
use TCPDF;
1 year ago Solution

@dean_007, Yes, it may have solved the error, but it is a bit crude...

I would do the following to better align with PHP PSR-1 codestyle, Composer autoloading and Grav itself:

  • Create a plugin using $ bin/plugin devtools newplugin and name it ProductPDF
    Judging from your code, it seems you've used this utility.
  • $ cd user/plugins/product-pdf
  • $ composer require tecnickcom/tcpdf
    Note require instead of required
  • Create file /user/plugins/product-pdf/classes/MyPDF.pdf, with the following content:

    PHP
    <?php
    namespace Grav\Plugin\ProductPDF;
    
    use TCPDF;
    
    class MyPDF extends TCPDF {
      public $logoPath;
      public $product;
      public $dxfIconPath;
      public $dxfFilePath;
    
      public function Header() {
          // Code
      }
    
      public function Footer() {
          // Code
       }
    }
    

    Note the CamelCase naming convention for both the file and class.

  • Make the following changes in file /user/plugins/product-pdf/product-pdf.php:
    • Add use Grav\Plugin\ProductPDF\MyPDF;
    • Don't use require __DIR__ . '/vendor/autoload.php';
      public function autoload() will be called by Grav when plugin is initialized.
    • Remove use TCPDF;
    • Remove entire class MYPDF
    • Replace $pdf = new MYPDF(...) with $pdf = new MyPDF(...)
1 year ago

You, sir, are great! Thank you. I have updated it, and it works like a dream. Now I understand a little better how to add extra classes.

Hopefully someone else can learn from this too.

Suggested topics

Topic Participants Replies Views Activity
Support · by Thomas, 1 week ago
2 54 12 hours ago
Support · by Anna, 3 days ago
2 60 15 hours ago
Support · by Justin Young, 16 hours ago
1 30 15 hours ago
Support · by Duc , 1 week ago
2 65 5 days ago
Support · by Colin Hume, 1 week ago
2 57 5 days ago