Support » Fixing WordPress » What code to open a "Save As" window to download a file stored at the host?

Viewing 15 replies - 1 through 15 (of 24 total)
  • I think you’re looking in the wrong forum. 🙂

    There are basically two ways to force a download that I know of. The first is to use an .htaccess rule, but that’s going to affect all of your images, which you don’t want. The second is to use a PHP file to accept the file name and process the request, setting the file name as part of the query string in the link. The trick in PHP is to set the content-type as octet/stream, which makes most browsers think they need to download a file instead of trying to display it somehow themselves.

    For example, in it’s simplest form, you create a PHP file called “download-image.php” with the following in it and upload it the root directory of your blog:

    <?php
    $imageurl = filter_input(INPUT_GET, 'imageurl', FILTER_SANITIZE_URL);
    // Clean the image URL to get the path and make sure the user is just getting an image.
    $url_parsed = parse_url($imageurl);
    $file = $_SERVER['DOCUMENT_ROOT'] . $url_parsed['path'];
    // Get the extension of the file.
    $extension = substr(strrchr($file ,'.'), 1);
    // Limit the extensions in this array to those you're allowing the user to download.
    $allowed_extensions = array('jpg', 'jpeg', 'png', 'gif');
    // Make sure the file requested is in your list of allowed extensions to make sure the user isn't downloading something other than an image.
    if (in_array(strtolower($extension), $allowed_extensions)) {
    	if (file_exists($file)) {
    		header ('Content-type: octet/stream');
    		header ('Content-disposition: attachment; filename=' . basename($file) . ';');
    		header('Content-Length: ' . filesize($file));
    		readfile($file);
    	}
    }
    ?>

    Then point your links to the download-image.php, passing the URL of the image to the PHP file with links like this:
    <a class="saveimage-icon" href="<?php echo home_url() . 'download-image.php?imageurl=' . get_post_meta($post->ID,'largeimage',true); ?>">Download <?php the_title(); ?></a>

    Dear linux4me2,

    thank you very much for your helpful answer.

    I used your script on one sample image and it works.
    I had to add a “/” before the ‘download-image.php?imageurl=’ in my modified php code. I noticed it was missing by hovering the mouse over the download icon.

    What kind of rule should I insert in .htacces to obtain the same result?
    I am interested in understanding that method as well, as my website is about image sharing anyway, so I do not mind if visitors download all of it.

    BTW, what would be a more appropriate section of the forum to post this type of help requests?

    The .htaccess would be something like this:

    <FilesMatch "\.(?i:jpg|gif|png)$">
      Header set Content-Disposition attachment
    </FilesMatch>

    The thing is, you only want to force the download for your download links, not for all images, and that’s what the .htaccess would do.

    I suspect there is a fancier way to write a regex in .htacess to do it, but that’s beyond my knowledge.

    I was thinking that your question is really not WordPress-specific, which often seems to be why people don’t get answers in these forums. Somewhere like StackOverflow might have get you quicker answers to general PHP and .htaccess questions, but I realize that sometimes it’s hard to know where to go if you aren’t familiar with the implication of your questions. : )

    Would such .htaccess rule work with my initial code, as it is?

    <a class="saveimage-icon" href="<?php echo get_post_meta($post->ID,'largeimage',true); ?>">Download <?php the_title(); ?></a>

    I will try it when I have more time.

    I will also have a look at StackOverflow, thank you for the suggestion.

    All the best.

    I don’t think the .htaccess approach would work as you intend, and it would be a hassle. You’d have to put it in every folder you wanted to force the downloads for, or at least a folder that was parent to them all, and with WordPress’ image file structure, that’s going to affect the thumbnails, too.

    You’d have to test it to be sure. I just offered it as one of the ways to force downloads, not a valid solution for your question.

    You’re better off going with the PHP solution.

    I am lost again, as I have noticed 2 problems with the newly-added image download functionality. It seems to only work for images uploaded through WP media library. I have created a folder structure at the root level (where the WP files are) to store the large images. I pass the correct path via the ‘largeimage’ custom field but, instead of opening the “Save As” dialogue box, an empty page is opened. I thought this could be due to my host caching the changes I make and hence they take time to propagate, but now I am starting to suspect it may be related to where images are stored (although the full URL is passed in the custom field, so the path should not be an issue).
    I would prefer to avoid uploading images via the WP media library, as it reveals the WP nature of the website through its silly absolute URLs for images (permalinks do not mask them) and creates a lot of redundant thumbnails.

    Another (less important) issue appears when I click “Cancel” on the “Save As” dialogue box, instead of actually saving the image. After I do that, the save icon on the thumbnail no longer works (it goes into an inactive state). Only after clicking somewhere else or refreshing the page it then reverts back to work. I wonder if I can add anything to the code to perform an icon refresh operation, so to speak.

    Thanks

    If you get a blank page, I suspect it is because the file is not found; i.e., the $file variable in the code is incorrect to find the actual file. You can modify the code in download-image.php to this to debug it:

    <?php
    // Toggle this setting to debug the script.
    //define('DOWNLOAD_FILE_DEBUG_MODE', 0);	// Debug is off.
    define('DOWNLOAD_FILE_DEBUG_MODE', 1);	// Debug is on.
    $imageurl = filter_input(INPUT_GET, 'imageurl', FILTER_SANITIZE_URL);
    if (DOWNLOAD_FILE_DEBUG_MODE == 1) {
    	echo 'The URL you passed is ' . $imageurl . '<br>';
    }
    // Clean the image URL to get the path and make sure the user is just getting an image.
    $url_parsed = parse_url($imageurl);
    $file = $_SERVER['DOCUMENT_ROOT'] . $url_parsed['path'];
    if (DOWNLOAD_FILE_DEBUG_MODE == 1) {
    	echo 'The path to the file is: ' . $file . '<br>';
    }
    // Get the extension of the file.
    $extension = substr(strrchr($file ,'.'), 1);
    // Limit the extensions in this array to those you're allowing the user to download.
    $allowed_extensions = array('jpg', 'jpeg', 'png', 'gif');
    // Make sure the file requested is in your list of allowed extensions to make sure the user isn't downloading something other than an image.
    if (in_array(strtolower($extension), $allowed_extensions)) {
    	if (DOWNLOAD_FILE_DEBUG_MODE == 1) {
    		if (file_exists($file)) {
    			echo 'The file was found and includes an allowed extension. The code appears to be working correctly.';
    		} else {
    			echo 'The file was not found with the path: ' . $file . '.<br>';
    		}
    	} else {
    		if (file_exists($file)) {
    			header ('Content-type: octet/stream');
    			header ('Content-disposition: attachment; filename=' . basename($file) . ';');
    			header('Content-Length: ' . filesize($file));
    			readfile($file);
    		}
    	}
    } else {
    	if (DOWNLOAD_FILE_DEBUG_MODE == 1) {
    		echo 'The file's extension is not in the list of allowed extensions.';
    	}
    }
    ?>

    When you’re done debugging, just comment out the line:
    define('DOWNLOAD_FILE_DEBUG_MODE', 1); // Debug is on.
    and uncomment the line:
    //define('DOWNLOAD_FILE_DEBUG_MODE', 0); // Debug is off.
    The reason I set the code up that way is that you can’t debug at the same time you run the actual code or you’ll probably get an error about the headers already being sent.

    Your second issue, the button only working one time, sounds like a javascript problem. I would start by looking at the javascript for the saveimage-icon class. Without seeing the code and/or looking at your site, that’s about all I can tell you.

    I tried the debug script and I got the following error:
    Internal Server Error
    The server encountered an internal error or misconfiguration and was unable to complete your request.

    I conducted some further tests and what I found out suggests a folder position issue, probably connected to access permission limits.

    The example URL I am attempting to download from is:
    http://www.mysite.org/download-image.php?imageurl=http://www.mysite.org/large/image.jpg
    This doesn’t work if I click on the icon, but works (the dialogue box appears) if I copy and paste it into the URL field of my browser.

    The one that always works is:
    http://www.mysite.org/download-image.php?imageurl=http://www.mysite.org/wp-content/uploads/image.jpg

    As you can see, the only difference is the position of the target image.
    As a test, I moved the image around the folder structure and the problem only appears when attempting to download data that is anywhere outside the wp-content folder.

    It looks like an access permission problem. I set CHMOD permissions to 777 for the “large” folder and the images it contains, but that did not work either.
    I think something at the .htaccess must be configured for that “large” folder to be accessible in download mode.

    Some other web pages suggest that it could be possible to edit wp-config.php to change access permissions for folders, but I haven’t found out how to do that specifically.

    I just tested the script, and it works fine for me. The 500 error on the server does definitely suggest an .htaccess or other server configuration issue that may indeed be permissions or ownership related. That would explain why the code fails; you’re trying to access a file you aren’t allowed to access via the PHP script.

    If you are on a Linux/Unix server, your folders should be 755 and your files 644, with the owner:group set to the user name for your account. Since you already tested the folder permissions, I would take a look at the owner:group and make sure it’s correct compared to the files that work.

    The other thing I was wondering about was whether some redirect in your .htaccess could be causing a problem, but that is unlikely if the code works for any other folder.

    Wait a second, if the new file location pasted into the browser works, it’s not the PHP script. It must be the link in your document or the javascript for the button. What does the link for the button look like and the “click” event for the button?

    I can read and display images from any folder I create at the root level, but the downloading functionality for data outside wp-content doesn’t seem to be enabled.

    My .htaccess is a standard one, I think. This what it looks like:

    # Begin default subdomain redirect #
    RewriteEngine on
    RewriteCond %{HTTP_HOST} ^mysite.org
    RewriteRule ^(.*)$ http://www.mysite.org/$1 [R=permanent,L]

    # End default subdomain redirect #

    DirectoryIndex index.php

    # BEGIN WordPress
    <IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteBase /
    RewriteRule ^index\.php$ – [L]
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule . /index.php [L]
    </IfModule>

    # END WordPress

    I will check the owner:group thing, but as I said I set all to 777 as a test and it did not solve my problem.

    Yes, I confirm that the script calls the “Save As” dialogue box if I paste the created URL into the browser, so it’s not an issue with your script.

    The URL link structure for the button is the one I pasted above.
    Where do I find the “click” event for the button?

    I don’t see anything wrong in your .htaccess.

    I’m guessing that your button uses JQuery to manage the behavior and look of the button, but I’m only guessing. That would explain why it only fires once. For example, if there is a click event linked to the class “saveimage-icon” that changes the appearance by changing the class, you would lose the functionality of the click event once the button is clicked. The JQuery could be loaded as a separate file by your theme or it could be in the head section of the theme.

    Is it possible to give me a link to a test page on the site so I can see if I can find the JQuery associated with the button? If not, you should try the Firebug extension to Firefox to watch for errors in the javascript that could explain the button only working once, and to find out where the code resides, if any, that might be causing an issue with the button only working once. That still leaves us with the 500 error, though. I’m scratching my head on that one.

    Here is a link to a test page with 2 images:
    http://www.sharemyphoto.org/#!/portfolio/

    I renamed your script dwi.php to have a shorter URL.

    The first image can be downloaded (stored in wp-content), the second (stored in large/portfolio) fails and opens an empty window instead.

    Okay, I did find a javascript error, and I will see if I can figure out what the issue is there, but in the meantime, we should add urlencoding to the “imageurl” in the query string just to make sure that’s not causing the problem. To do so, you’d change your links to this:
    <a class="saveimage-icon" href="<?php echo home_url() . '/dwi.php?imageurl=' . urlencode(get_post_meta($post->ID,'largeimage',true)); ?>">Download <?php the_title(); ?></a>
    and then change the code block to this:

    <?php
    // Toggle this setting to debug the script.
    //define('DOWNLOAD_FILE_DEBUG_MODE', 0);	// Debug is off.
    define('DOWNLOAD_FILE_DEBUG_MODE', 1);	// Debug is on.
    $imageurl = urldecode(filter_input(INPUT_GET, 'imageurl', FILTER_SANITIZE_URL));
    if (DOWNLOAD_FILE_DEBUG_MODE == 1) {
    	echo 'The URL you passed is ' . $imageurl . '<br>';
    }
    // Clean the image URL to get the path and make sure the user is just getting an image.
    $url_parsed = parse_url($imageurl);
    $file = $_SERVER['DOCUMENT_ROOT'] . $url_parsed['path'];
    if (DOWNLOAD_FILE_DEBUG_MODE == 1) {
    	echo 'The path to the file is: ' . $file . '<br>';
    }
    // Get the extension of the file.
    $extension = substr(strrchr($file ,'.'), 1);
    // Limit the extensions in this array to those you're allowing the user to download.
    $allowed_extensions = array('jpg', 'jpeg', 'png', 'gif');
    // Make sure the file requested is in your list of allowed extensions to make sure the user isn't downloading something other than an image.
    if (in_array(strtolower($extension), $allowed_extensions)) {
    	if (DOWNLOAD_FILE_DEBUG_MODE == 1) {
    		if (file_exists($file)) {
    			echo 'The file was found and includes an allowed extension. The code appears to be working correctly.';
    		} else {
    			echo 'The file was not found with the path: ' . $file . '.<br>';
    		}
    	} else {
    		if (file_exists($file)) {
    			header ('Content-type: octet/stream');
    			header ('Content-disposition: attachment; filename=' . basename($file) . ';');
    			header('Content-Length: ' . filesize($file));
    			readfile($file);
    		}
    	}
    } else {
    	if (DOWNLOAD_FILE_DEBUG_MODE == 1) {
    		echo 'The file's extension is not in the list of allowed extensions.';
    	}
    }
    ?>

Viewing 15 replies - 1 through 15 (of 24 total)
  • The topic ‘What code to open a "Save As" window to download a file stored at the host?’ is closed to new replies.