1

So, the following might be absolutely against WordPress standards & best practices, but I'm in the process of learning.

Currently, I am calling a PHP file to generate a PDF through a third party.

The way I call the file is by visiting: https://example.com/wp-content/plugins/eventix-woocommerce/generate.php?order_id=1553&key=d5df1b20-15a9-11e9-ad15-c704673daa98

The code for generate.php:

require_once("../../../wp-load.php");
nocache_headers();

$orderId = $_GET['order_id'];
$key = $_GET['key'];

$eventixOrderId = get_post_meta( $orderId, 'eventix_order_id', true );

if($eventixOrderId != $key){
    echo "These are not the tickets you're looking for.";
    exit;
}


$accessToken = get_option('eventix_access_token');  
$curl = curl_init();

curl_setopt_array($curl, array(
    CURLOPT_RETURNTRANSFER => 1,
    CURLOPT_CUSTOMREQUEST => "GET",
    CURLOPT_URL => 'https://api.eventix.io/3.0.0/order/'.$eventixOrderId.'/downloadlink',
    CURLOPT_USERAGENT => 'example.com',
    CURLOPT_HTTPHEADER => array(                                                                          
    'Content-Type: application/json',  
    'Authorization: ' . $accessToken) 
));

$ticketPdf = curl_exec($curl);
curl_close($curl);
update_post_meta( $orderId, 'eventix_pdf_ticket', $ticketPdf ); 
header("Location: $ticketPdf");
exit;

Now, for the most part, this actually works excellent. But in a few cases this script messes up, throws me to the 404 page of example.com and doesn't load the PDF. On the other hand, if I visit the link incognito; no problems.

My first thought was that it might be caused by header cache. I tried numerous ways to clear header cache, but didn't work.

3
  • Additional comment: I added header('Content-type: application/pdf'); to the code which seems to have solved the issue. Any comments in regards to the code, and if it's a best practice to do it this way.. much much appreciated.
    – Kevster
    Commented Jan 11, 2019 at 14:56
  • I was going to mention the Content-type header, but looks like you've done that. Other than that, your code looks OK. I would probably also add the Content-disposition header so you can provide a more consistent user experience. Use "inline" for opening PDF in the browser, or "attachment" for serving a "save as" dialog. i.e. ('Content-disposition: inline'; ). As for best practices, if it doesn't need to be portable, it's OK (although better methods exist). Just be aware that if you move it, the path to wp-load.php will be invalid.
    – butlerblog
    Commented Jan 11, 2019 at 15:18
  • Changed my mind on what I said about best practices. You really should go ahead and work it in so you don't have to directly hit the file and include wp-load.php. I put in an answer below that will get you started in that direction. It might need a little work. As-is, it is a drop-in code snippet for your theme's functions.php file. But add a proper plugin header and you can load it as a plugin. I'd go the plugin route since that would make it more portable (not theme dependent).
    – butlerblog
    Commented Jan 11, 2019 at 15:42

1 Answer 1

1

Here's an approach that would be more portable (so you don't have to call wp-load.php directly, which is something you want to avoid) and also would allow you to revise the URL to not point to the /wp-content/plugins/ directory (also something you want to avoid):

add_action( 'init', 'my_pdf_generator' );
function my_pdf_generator() {

    // sniff out query args to see if this is a pdf request.
    // Note that you should probably add a unique query arg to make sure this is a ticket request.
    // Mabye something like "?eventix_order=true" or something just in case order_id and key exist in 
    // some other kind of instance.
    if ( isset( $_GET['order_id'] ) && isset( $_GET['key'] ) ) {

        nocache_headers();

        // Don't forget to sanitize 
        $orderId = $_GET['order_id'];
        $key = $_GET['key'];

        $eventixOrderId = get_post_meta( $orderId, 'eventix_order_id', true );

        if($eventixOrderId != $key){
            echo "These are not the tickets you're looking for.";
            exit;
        }

        $accessToken = get_option('eventix_access_token');  
        $curl = curl_init();

        curl_setopt_array($curl, array(
            CURLOPT_RETURNTRANSFER => 1,
            CURLOPT_CUSTOMREQUEST => "GET",
            CURLOPT_URL => 'https://api.eventix.io/3.0.0/order/'.$eventixOrderId.'/downloadlink',
            CURLOPT_USERAGENT => 'example.com',
            CURLOPT_HTTPHEADER => array(                                                                          
            'Content-Type: application/json',  
            'Authorization: ' . $accessToken) 
        ));

        $ticketPdf = curl_exec($curl);
        curl_close($curl);
        update_post_meta( $orderId, 'eventix_pdf_ticket', $ticketPdf ); 

        header( 'Content-Description: File Transfer' );
        header( 'Content-Type: application/pdf' );
        header( 'Content-Disposition: inline' );
        header( 'Content-Transfer-Encoding: binary' );
        header( 'Cache-Control: must-revalidate' );
        header( 'Pragma: public' );
        header( "Location: $ticketPdf" );
        exit();

    }
}

Doing it this way would make your link as follows:

https://example.com/?order_id=1553&key=d5df1b20-15a9-11e9-ad15-c704673daa98

What it's doing is hooking to the 'init' action to check if this is a key request, and if so, it runs your process and exits at that end (so no additional WP output is sent).

That's cleaner than putting it in a custom file, directly calling that custom file, and needing to include wp-load.php.

To use it this way, you can either just drop it in your theme's functions.php file OR add a plugin file header to it and load it as a plugin. I'd recommend loading it as a plugin so that it is portable and not theme dependent, just in case you change things theme-wise down the road.

This would also be a starting point for other learning. This example just uses a raw query arg, but with additional research and work, you could probably learn to build it into a functional WooCommerce endpoint like:

https://example.com/eventix_order/1553/d5df1b20-15a9-11e9-ad15-c704673daa98/

Obviously, I can't test it as is, so you may need to tweak it to make it fully functional. I also noted in the code comments that I would probably work in an additional query argument that is unique just in case "order_id" and "key" may exist together as query args somewhere else (something like "eventix_order=true" or something). You could add that to the "if" condition.

I also added some additional headers that I think make it cleaner - you may need to experiment and/or revise as needed since as I mentioned, I can't test it exactly. But I have personally built download plugins and this comes from that work, so I think they should be correct for your purpose.

1
  • Oh, that is absolutely excellent, never looked at it that way. Going to look into Woocommerce endpoints. Thanks a bunch!
    – Kevster
    Commented Jan 13, 2019 at 18:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.