Soma

Flexible Downloads using pages

Recommended Posts

A quick tutorial how to create file downloads using pages

You will be able to create a new page using template "PDF" (or any you setup), upload a pdf file. You then can select this page using page fields, or links in Wysiwyg. The url will be to the page and NOT the file itself. This will allow to keep a readable permanent unique url (as you define it), unlike /site/assets/files/1239/download-1.pdf, and you'll be able to update/replace the uploaded file without worring about its filename. Further more the file will also have an id, the one of the page where it lives.

Clicking those links will download or open the file (when target="_blank") like it would be a real file on server with a path like /downloads/project/yourfile.pdf. You'll be also able to use the "view" action directly in the page list tree to view the file.

Further more you'll be able to esaily track downloads simply by adding a counter integer field to the template and increase it every time the page is viewed. Since the file is basicly a page.

This all works very well and requires only minimal setup, no modules and best of it it works in the same way for multi-language fields: Just create the language alternative fields like "pdf, pdf_de, pdf_es" and it will work without modifying any code!

Still with me? ok :)

PW setup

Download folder: Create a template "folder" or "download-folder" with only a title needed. Create pages in the root like /downloads/project/ using this template.

Setup the template for the pdf files

1. Create a new template in PW. Name it pdf

2. Goto template -> URLs tab and set the URL end with slash to no. (So we can have /path/myfile.pdf as the URL)

3. Create a new custom file field, name it pdf. Set its maximal count to 1 under -> Details tab.

4. Add the pdf field created to the pdf template. Easy.

5. Create a new "pdf" page using the pdf template under a download folder you created earlier.

6. Give it the title and in the name field add ".pdf" to the end (could also leave as is)

Template PHP file for the pdf files

1. Create the template file pdf.php in your /site/templates folder

2. add the following code:

<?php
// pdf.php

if($page->pdf){
    wireSendFile($page->pdf->filename);
}

Done. 

To see the options you have with PW's wireSendFile() you can also overwrite defaults

<?php
// pdf.php

if($page->pdf){
    $options = array(
    // boolean: halt program execution after file send
    'exit' => true, 
    // boolean|null: whether file should force download (null=let content-type header decide)
    'forceDownload' => false, 
    // string: filename you want the download to show on the user's computer, or blank to use existing.
    'downloadFilename' => '',
    );

    wireSendFile($page->pdf->filename, $options);
}

Simple and powerful isn't it? Try it out.

Some thoughts advanced

Create as many file types as you like. It might also be possible to use one "filedownload" template that isn't restricted to one field type but evaluate it when being output using $page->file->ext, or save the file extension to the page name after uploading using a hook.

One last thing. You can add other meta fields or preview images to the template and use those to create lists or detail pages. It's all open to goodness. Again all without "coding" and third-party modules.

Further more you can use the excellent TemplateDecorator to add icons per template and have a nice pdf icon for those pages.

This as a base one could also easily create a simple admin page for mass uploading files in a simple manner, and create the pages for the files automaticly. ImagesManager work in the same way.

Cheers

  • Like 33
  • Thanks 2

Share this post


Link to post
Share on other sites

Thanks for this Soma! Very elegant solution :)

3. Create a new custom field, name it pdf. Set it's maximal count to 1.

What type of field please? :D

  • Like 1

Share this post


Link to post
Share on other sites

I hadn't really thought about the advantages that could be derived from doing it this way. This could definitely work well for some sites. Maybe "ImagesManager" could be expanded to "FileManager"?  ;)

Thanks for the tutorial!

Share this post


Link to post
Share on other sites

thanks soma, just used this on a project, really solved a need.

Share this post


Link to post
Share on other sites

This is working fine, only when i use language alternative fields it is not showing the other language file. i have created both fileds pdf and pdf_de.

This is the code that i am currently using to call the files:

     <?php
    $features = $pages->get("1029")->children("limit=6, sort=sort");
    foreach($features as $feature) {
    echo "<li>" .
    "<a class='readmore4' href='{$feature->url}'><p class='texti4'>{$feature->title}</p></a>" .
    "</li>";
    }
    ?>

Share this post


Link to post
Share on other sites

I'm not sure why it's not working for you without seeing the whole. Make sure you have trailing slash disabled for the template and language are active etc. 

Share this post


Link to post
Share on other sites

I have tried to implement this on a multilanguage site and also got only the one pdf from the main language.

Here's what I did:

- created a fieldtype "file" for every language (pdf, pdf_fr, pdf_it)

- created a template "pdf" with all these fields

- created "pdf.php" with the above code

- uploaded corresponding pdf-files into these fields

- in the body text I linked to the pdf-files (which worked as expected: in all languages I could link to the corresponding files)

What happens is, that in all languages you get always the pdf-file from the main language. So I tried to modify the code for the pdf.php like this:

if ($page->pdf) {
    wireSendFile($page->pdf->filename);
} elseif ($page->pdf_fr) {
    wireSendFile($page->pdf_fr->filename);
} else {
    wireSendFile($page->pdf_it->filename);
}

Still only the one version from the main language.

So I tried this:

if ($user->language == "default") {
	
	if ($page->pdf) {
		 wireSendFile($page->pdf->filename);
	}
} elseif ($user->language == "francais") {
	
	if ($page->pdf_fr) {
		wireSendFile($page->pdf_fr->filename);
	}
} else {
	if ($page->pdf_it) {
		wireSendFile($page->pdf_it->filename);
	}
	
}

That produces a blank page when clicking on the link to the file.

I don't seem to be capable to get it right.

Share this post


Link to post
Share on other sites

What version of PW are you using? Cause there was a problem with alternative language file fields that was fixed not so long ago.

Share this post


Link to post
Share on other sites
if ($user->language == "default") {
	

Your if statements won't work because $user->language is a Language object.

So you could try:

if ($user->language->name == 'default')

// Or if your values are titles rather than names
if ($user->language->title == 'francais')
  • Like 3

Share this post


Link to post
Share on other sites

Thanks, Wanze.

I have tried your version. And lo! it works! :)

Share this post


Link to post
Share on other sites

The alternative lang fields work fine, so your doing something wrong.

Are the langs really named fr it? Are the file field limited to 1 file upload?

Share this post


Link to post
Share on other sites

Using wireSendFile() for flexible download is great to mask the real file location on the server or to select the file according to the language, but it comes with an issue that appears for large files or slow connections: until the download completes, any other access to the website from the same session is blocked.

For a user viewing a page that contains such a download link, this means that when he clicks on the download link, the download starts as expected, but until the download completes, the user can not navigate to a different page of the site, which is probably not the expected behavior.
 

The solution to avoid this issue is to call session_write_close() before wireSendFile(), like this:

$options = array(
	// boolean: halt program execution after file send
	'exit' => true, 
	// boolean|null: whether file should force download (null=let content-type header decide)
	'forceDownload' => true, 
	// string: filename you want the download to show on the user's computer, or blank to use existing.
	'downloadFilename' => $clientFilename,
         );

// Close the php session, so wireSendFile doesn't block other connections from the same session while downloading
session_write_close();
	
wireSendFile($serverFilePath, $options);

This should not have any unwanted effect as long as the 'exit' option is set to true (because wireSendFile() calls exit() just after readfile() in this case).

Note: The issue comes from PHP's session management, where "session data is locked to prevent concurrent writes only one script may operate on a session at any time" (as says PHP session_write_close documentation ). A google search "php readfile session_write_close" brings many exemple of this lock problem.

  • Like 3

Share this post


Link to post
Share on other sites

Hello,

When I try this, it downloads the files (in my test case a docx file).

It works but two things happen. 

1- It renames file to download.doc

2- It converts the extension from docx to doc

Any idea why that happens?


Hello,

When I try this, it downloads the files (in my test case a docx file).

It works but two things happen. 

1- It renames file to download.doc

2- It converts the extension from docx to doc

Any idea why that happens?

Ok just after a second later figured it out. Setting forceDownload to false causes both of them. Works perfectly fine now, many thanks Soma.

Share this post


Link to post
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


  • Recently Browsing   0 members

    No registered users viewing this page.