How to create download links based on Custom Fields with Rewrite API

For me this is quite hard to do, but I guess that this could be easily done by an expert. 🙂

Let’s suppose I have a post, which is a custom post type resources, with the following data:

  • Post Slug: beach
  • Custom Fields:
    • dl1file: bikini
    • dl1link: assets/file1.zip
    • dl1count: 56
    • dl2file: beachball
    • dl2link: assets/file2.zip
    • dl2count: 31

The post above have more than one file to download, and all of them are stored in Custom Fields as dlNfile, where N are numbers (e.g.: dl1file). Also, each of these files have two more custom fields, the dlNlink (e.g.: dl1link) which will be used in the URL, and dlNcount (e.g.: dl1count) will store the download count number.

Now, with Rewrite API I will need to convert site.com/?resource=beach&file=bikini (or if it’s easier, site.com/resources/beach/?file=bikini) into site.com/download/beach/bikini.

When one of the links is clicked, and since I only provided the resource, which is the post slug, and the value of one of the dlNfile, WordPress will now have to know which post to retrieve and identify the correct dlNfile, which in this case is dl1file. Then it will know that the real filename is stored in dl1link and then it proceeds to the download (site.com/assets/file1.zip) and increments dl1count by 1.


EDIT: I tried Jesper’s tutorial before, but I don’t want to create a new post just for store a download file information.

I also provided another URL idea, which doesn’t have to store the URL parameter resource, which is the post slug.

1 Answer
1

The first step in making this work is to register your custom query var for the file via the query_vars filter. I’ve named it wpa82328_file, but you can change this to something more meaningful to you. file is a bit generic though, so you’ll want to prefix it with something guaranteed to be unique:

function wpa82328_query_vars( $query_vars ){
    $query_vars[] = 'wpa82328_file';
    return $query_vars;
}
add_filter( 'query_vars', 'wpa82328_query_vars' );

Next, the rewrite rule which will convert incoming requests to /download/resource/file/ from the pretty URL to query vars:

function wpa82328_rewrite_rule(){
    add_rewrite_rule(
        '^download/([^/]+)/([^/]+)/?$',
        'index.php?resource=$matches[1]&wpa82328_file=$matches[2]',
        'top'
    );
}
add_action( 'init', 'wpa82328_rewrite_rule' );

This will point everything to the main index.php file with the resource and filename query vars set.

Next, we have to capture these requests early, before the main query is run and headers are sent. If we try to do this later, headers will already be sent and downloads won’t work.

I’ve hooked the parse_request action, where a ref array of query vars is passed as an argument to the hooked function. We can then easily check for the presence of the wpa82328_file query var, which will tell us that a download is being requested. Read the comments within the code to understand what is happening in each step:

function wpa82328_parse_request( &$wp ){

    // if the wpa82328_file query var is set
    if ( array_key_exists( 'wpa82328_file', $wp->query_vars ) ){

        // query for the requested resource
        $args = array(
            'name' => $wp->query_vars['resource'],
            'post_type' => 'resource'
        );
        $resource = new WP_Query( $args );

        // get all of the custom fields for this resource
        $custom_fields = get_post_custom( $resource->post->ID );

        // check each custom field for a value that matches the file query var
        foreach( $custom_fields as $key => $value ):

            // if a custom field value matches, we have the correct key
            if( in_array( $wp->query_vars['wpa82328_file'], $value ) ){

                echo 'file key is: ' . $key;

                // increment count
                // set proper headers
                // initiate file download

                exit(); 
            }

        endforeach;
    }
    return;
}
add_action( 'parse_request', 'wpa82328_parse_request' );

The last part you’ll have to sort out is extracting the number from the key so you can get the correct count and asset keys. I would personally change it to have the number last, making it easier to just do a substr on the key to get the number. Or better, I would store them all as an array of arrays under a single key, so once you find the index that contains the requested name, you just have to go to the next index to find the count, and the next to find the asset url.

Leave a Comment