Here's the thing.
I'm serving somewhat compressed CSS content
(`str_replace(array("\r", "\n", "\t", '\s\s+'), '', cssGoesHere)`)
via a PHP file in my page:
<link rel="stylesheet" type="text/css" href="/css/loader.css.php" />
The question is: how do I make the browser cache the css returned, BUT update the cache if the content is changed?
The PHP file is not being modified, so appending something like
<?php echo filemtime('/css/loader.css.php'); ?>
to the href attribute is not an option. Can this be solved with headers, and if so, how? Because AFAIK if I serve it like I wrote above, the browser will just cache the result and keep reusing the cache (provided, of course, the browser is enabled/capable of doing so), but I need it to know when the content is changed.
Edit: I've made a github project with my code (though I did change it alot for more flexibility since I wrote this). Here's the link: https://github.com/jurchiks/YACC
If you have any suggestions, write them to my e-mail or smth.
It can be solved by headers cache-control, BUT this have proved to be not very effective. Because some browser do overwrite or modify your forced settings.
I can't see where you getting the CSS data from, if it's a CSS file you could get the filemtime of the css file as version indicator or simply supply somewhere a version string which has to be changed each time the CSS file changes.
By the way, str_replace(array("\r", "\n", "\t", '\s\s+'), does not effectively remove all newlines. Your should use something like
$foo = nl2br($string, FALSE);
$bar = str_replace(array('<br>', '\s\s+'), '', cssGoesHere)
instead.
HTH :)
The only way to reliably make the file load upon change is the method you state. Otherwise, the page will cache the file and keep it for as long as it thinks necessary. It can't check to see whether the page has changed or not without requesting and downloading at least the headers, and if it's gone that far it might as well just download the rest of the page, as the bandwidth will be minimal.
The best alternative if you really can't append the modified date to the HTML, would be to set the cache headers via PHP.
header("Cache-Control: max-age=time-in-seconds");
This doesn't always work though, as server settings and browser settings can override this, especially in aggressive caching browsers such as Internet Explorer.
You could add a version number to the CSS link href and use mod rewrite to route it back to the original CSS file.
For example. Let's say you start with version 1 (note the version appendix to the file name):
<link rel="stylesheet" type="text/css" href="/css/loader-v001.css.php" />
The browser will cache this file. The next time you update the CSS file and you don't want the browser to use the cache file, simply change the version number like this:
<link rel="stylesheet" type="text/css" href="/css/loader-v002.css.php" />
Then, in your mod rewrite, route any request for the file loader-v{x}.css.php to loader.css.php.
I am not sure if this is 'automated' enough for your needs, as it does require manual changing of the version number. However, the different file name will ensure the browser sees it as an actual 'new' file and re-downloads it.
use this in your css php page:
header('Content-type: text/css');
header("Cache-Control: no-cache, must-revalidate");
you can see more information about headers in PHP's official documentation
i would suggest to keep somewhere the time that the cache content was generated for example
style.php which holds the CSS data was last modified at 21/06/2012
and cache content was last generated at 20/06/2012 running a comparison on dates in strtotime() integers, you see that the cached content must be generated again from the new content.
Related
There appears to be something bazaar I have just been sent.
There is one main CSS file that is actually a php file with a header to declare itself as CSS. It then includes other CSS also actually PHP files all of which also include that header.
header("Content-type: text/css; charset: UTF-8");
I can see why they have done this as they are passing PHP variables to the CSS files but suspect that that header only needs to be declared once. The site is causing a number of errors.
Would I be correct that a PHP file that declares that header writes a bit of CSS and then includes other PHP files that also contain CSS, that these other files do not need that header as well.
It sounds like you're correct, yes. You only want to send the headers one single time. Instead of having each included PHP file send the headers, I'd just have one send the headers (maybe call it css.php or styles.php or something), and then include in all the other ones that actually contain the CSS. I'd just use that main file for any logic that might be needed to decide what css needs to be included (if it's always the same, maybe it should be a static file anyway).
It'd probably be better to just have static CSS files anyway, to make your site faster and whatnot, but since that wasn't in your question, I assume you're aware of the drawbacks and for whatever reason need to do it this way.
I want to force download a pdf,doc or docx file.
With the following code,Pdf files get opened in my tab instead of getting downloaded.
I have a table having download link in every row.I want to download file on click of this link.
foreach($a as $id = > $item) {
echo '<tr><td><a href="http://staging.experiencecommerce.com/ecsite-v3/uploads/'.substr($item['f_resume'], 63).'" ">';
//Note:substr($item['f_resume'], 63) is file_name
echo '</a></td><td>'.$item['f_date'].'</td></tr>';
}
I went through some Question on SO with same problem and tried their solution,but in Vain.
When I included the solution inside foreach,the page downloads file on load and when I place the solution outside ,the Php script gets downloaded.
Where am I going wrong?
You can set headers that will force downloading:
header('Content-Type: application/force-download');
header('Content-Disposition: attachment; filename="filenamehere.pdf"');
If you're not using PHP to provide content of that files you can set headers using eg. .htaccess (requires mod_headers).
<FilesMatch ".pdf$">
FileETag None
<ifModule mod_headers.c>
Header set Content-Type "application/force-download"
</ifModule>
</FilesMatch>
After our whole chat session I think we can leave this answer here, just for future reference:
As seen in your initial post, once you click the link, you relinquish all control to the browser so it will treat the file as it sees fit. Usually this involves trying to find whatever application or plugin the system can find to treat your file.
Whenever you want to force the download of the file all you have to do is divorce the presentation itself from the task at hand. In this particular case:
1 - Create a new script that will identify the file via parameters passed and force the download on it, as seen on the examples at this site php.net/manual/en/function.readfile.php.
2 - Rework the presentation so the links do no longer point to the file itself, but to the new script with the appropriate parameters (like, for example, download_file.php?file_id=#FILE_ID#).
3 - Treat the case in which the file can not be found by, for example, die("The file could not be found") before setting the headers.
One word of advice: do not use the file location as a parameter!!!. Use instead something that you can retrieve from a database to then collect the file location. If you pass the file location itself as a parameter nothing is stopping me from doing this:
http://yoursite.com/download_file.php?file=download_file.php
http://yoursite.com/download_file.php?file=index.php
http://yoursite.com/download_file.php?file=whatever_file_there_is
With the adequate circumstances, like autodetection of the xtype for the requested file, it would allow me to access your code and exploit any possible flaws
One second and final note of advice: php can only output one thing at once. If you want it to output a website you can't output a pdf file afterwards. That's why - among other reasons - you divorce the different tasks at hand and also, that's why everything went awry when you tried directly including the download script after each link was printed.
If it helps, imagine php not as your usual real-time programming language, but as a printer. It will print everything you tell it to and serve it in reasonably sized chunks. There's no stopping it until the end is reached, there's no possible exploring two opposite branching code paths unless you call the script again with the appropriate conditions.
Hope the chat helped you.
I have a PHP file get_css.php which generates CSS code more than 60 KB long. This code does not change very often. I want this code to be cached in user's browser.
Now, when i visit a HTML page several times which includes get_css.php url to fetch css, my browser is loading all CSS contents from the server each time i visit the page.
Browsers should get the contents from server only if the CSS code is changed on server side. If the css code is not changed, browser will use the css code from the browser cache.
I cannot use any PHP function which is not allowed in Server Safe Mode.
Is it possible? How can i achieve this?
You cannot force a client to revalidate its cache so easily.
Setting a variable query string to its resource won't play well with proxies, but seems to suffice with browsers. Browsers do tend to only redownload the css file if there's a query string change.
<link rel="stylesheet" type="text/css" href="/get_css.php?v=1.2.3">
Potentially, you could play with the naming of the CSS, such as add numbers, but this isn't a great alternative.
You cannot control browser behaviour from PHP, but you can use HTTP codes to tell the browser something.
If the CSS is not changed, just reply with a 304 Not Modified response code:
if ($css_has_not_changed && $browser_has_a_copy) {
http_response_code(304);
} else {
// regenerate CSS
}
This way, the browser will ask for the document (which you cannot control), but you tell him to use the cached copy.
Of course this needs testing, as I have now idea how it will work 'the first time' a browser requests the file (perhaps the request headers can tell you more). A quick firebug test reveals that Firefox requests Cache-Control: no-cache when it is requesting a fresh copy, and Cache-Control: max-age=0 when it has cache.
add normal GET parameter when you including get_css.php like so
<link rel="stylesheet" type="text/css" href="get_css.php?v=1">
Browser will think that it is new link and will load it again.
and in get_css.php use this to make browser cache data
<?php
header("Content-type: text/css");
header('Cache-Control: public');
header('Expires: ' . gmdate('D, d M Y H:i:s', strtotime('+1 year')) . ' GMT');
ob_start("ob_gzhandler");
//echo css here
The browser wants to cache your document by default, but you have to give it enough info to make that possible. One fairly easy way is to send the Last-Modified header, containing the date/time at which your script was last changed. You'll also need to handle the browser's "revalidation" request correctly by checking the incoming Last-Modified date, comparing it to the actual modified date of your script, and returning a 304 Not Modified response (with an empty response body), if the file is unchanged.
It's also a good idea to be sure that your server isn't "magically" sending any other "no-cache" directives. The easiest way to do this is to send a Cache-Control directive that tells the browser exactly what behavior you expect.
Here is a quick explanation of each Cache-Control option.
Something like the following should do the trick:
<?php
// this must be at the top of your file, no content can be output before it
$modified = filemtime(__FILE__);
if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
$if_modified_since=strtotime($_SERVER["HTTP_IF_MODIFIED_SINCE"]);
if( $modified > $if_modified_since ) {
header('HTTP/1.0 304 Not Modified');
exit();
}
}
header('Cache-Control: must-revalidate');
header('Last-Modified: '.date("r",$modified));
// ... and the rest of your file goes here...
The above example was based heavily on the example, and writeup found here.
Like everyone else I'm storing my site`s display information in style sheet files. And I want to create back-end cms so users will be able to, for example, change < h1 > color, size etc.
So, what's the right way to embed PHP code in my CSS file(s)?
Is adding this header:
<?php header("Content-type: text/css"); ?>
And changing file extension in link:
<link rel="stylesheet" type="text/css" media="screen" href="style.php">
Valid?
And what about JavaScript? At this moment I'm echoing <script type="text/javascript"> tags, but maybe there's also a way to embed everything in the .js files?
Thanks!
Yes, this is perfectly valid.
The same can be done for Javascript too by sending
<?php header("Content-type: application/javascript"); ?>
However, this is not optimal from a performance point of view because a PHP process has to be started for serving those resources.
If you have only very few dynamically changing CSS properties or JS variables, I would consider putting them into the document's head and continuing to serve the external files statically.
Remember that usually, there are no caching headers sent for PHP files. You'll have to take care of sending the correct headers inside your PHP script! Cheers #oracle certified professional for the reminder.
What you're doing is absolutely valid.
However if you're running a bigger site with many visitors, you should concern to just let PHP "build" a "real" CSS file when your user updates his or her design to save your servers performance to better needed things:
<?php
header("Content-type: text/css");
// Your database magic here to fetch the user-specific designs
// open the cached css
$cachefile = "cachedCSS/mycss.css";
if (file_exists($cachefile)) {
// the page has been cached from an earlier request
// output the contents of the cache file
include($cachefile);
// exit the script, so that the rest isnt executed
exit;
}
$fp = fopen($cachefile, 'w');
// save the contents of output buffer to the file
fwrite($fp, ob_get_contents());
// close the file
fclose($fp);
// Send the output to the browser
ob_end_flush();
Read more about it here: http://www.theukwebdesigncompany.com/articles/php-caching.php
Make sure you are parsing the php in those files.
In .htaccess :
AddType application/x-httpd-php .php .css .js
This will ensure that any <?php ?> tags in file types other than .php will be parsed by the server and the php code isn't readable by users.
On a PHP-based web site, I want to send users a download package after they have filled out a short form. The site-initiated download should be similar to sites like download.com, which say "your download will begin in a moment."
A couple of possible approaches I know about, and browser compatibility (based on a quick test):
1) Do a window.open pointing to the new file.
- FireFox 3 blocks this.
- IE6 blocks this.
- IE7 blocks this.
2) Create an iframe pointing to the new file.
- FireFox 3 seems to think this is OK. (Maybe it's because I already accepted it once?)
- IE6 blocks this.
- IE7 blocks this.
How can I do this so that at least these three browsers will not object?
Bonus: is there a method that doesn't require browser-conditional statements?
(I believe that download.com employs both methods conditionally, but I can't get either one to work.)
Responses and Clarifications:
Q: "Why not point the current window to the file?"
A: That might work, but in this particular case, I want to show them some other content while their download starts - for example, "would you like to donate to this project?"
UPDATE: I have abandoned this approach. See my answer below for reasons.
You can also do a meta refresh, which most browsers support. Download.com places one in a noscript tag.
<meta http-equiv="refresh" content="5;url=/download.php?doc=123.zip"/>
Update: I have decided to abandon this approach, and instead just present the user with a link to the actual file. My reasoning is this:
My initial attempts at a server-initiated download were blocked by the browser. That got me thinking: "the browser is right. How does it know that this is a legitimate download? It should block a download that isn't obviously user-initiated."
Any method that I can use for a server-initiated download could also be used by someone who wants to send malware. Therefore, downloads should only happen when the user specifically requests the file by clicking on a link for it.
You're free to disagree, and if you still want to initiate a download, hopefully this thread will help you do it.
I usually just have a PHP script that outputs the file directly to the browser with the appropriate Content-Type
if(file_exists($filename)) {
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, pre-check=0");
header("Cache-Control: private", false);
header("Content-Type: " . $content-type);
header("Content-Disposition: attachment; filename=\"" . basename($filename) . "\";" );
header("Content-Transfer-Encoding: binary");
header("Content-Length: " . filesize($filename));
readfile("$filename");
}else{
print "ERROR: the file " . basename($filename) . " could not be downloaded because it did not exist.";
}
The only disadvantage is that, since this sets the HTTP header, it has be called before you have any other output.
But you can have a link to the PHP download page and it will cause the browser to pop up a download box without messing up the content of the current page.
One catch is that you may encounter issues with IE (version 6 in particular) if the headers are not set up "correctly".
Ensure you set the right Content-Type, but also consider setting the Cache options for IE (at least) to allow caching. If the file is one the user can open rather than save (e.g. an MS Word document) early versions of IE need to cache the file, as they hand off the "open" request to the applicable app, pointing to the file that was downloaded in the cache.
There's also a related issue, if the IE6 user's cache is full, it won't properly save the file (thus when the applicable app gets the hand off to open it, it will complain the file is corrupt.
You may also want to turn of any gzip actions on the downloads too (for IE)
IE6/IE7 both have issues with large downloads (e.g. 4.x Gigs...) not a likely scenario since IE doesn't even have a download manager, but something to be aware of.
Finally, IE6 sometimes doesn't nicely handle a download "push" if it was initiated from within a nested iframe. I'm not exactly sure what triggers the issue, but I find it is easier with IE6 to avoid this scenario.
Hoi!
#Nathan:
I decided to do exactly that: Have my "getfile.php" load all necessary stuff and then do a
header("Location: ./$path/$filename");
to let the browser itself and directly do whatever it thinks is correct do with the file. This even works fine in Opera with me.
But this will be a problem in environments, where no direct access to the files is allowed, in that case you will have to find a different way! (Thank Discordia my files are public PDFs!)
Best regards, Basty
How about changing the location to point to the new file? (e.g. by changing window.location)
I've always just made an iframe which points to the file.
<iframe src="/download.exe" frameborder="0" height="0" width="0">Click here to download.</iframe>
Regarding not pointing the current window to the download.
In my experience you can still show your "please donate" page, since downloads (as long as they send the correct headers) don't actually update the browser window.
I do this for csv exports on one of my sites, and as far as the user is concerned it just pops up a safe file window.
So i would recommend a simple meta-redirect as Soldarnal showed.
Just to summarise, you have 2 goals:
start download process
show user a page with a donate options
To achieve this I would do the following:
When your user submits the form, he gets the resulting page with a donate options and a text saying that his download will start in 5 seconds. And in the head section of this page you put the META code as Soldarnal said:
<meta http-equiv="refresh" content="5;url=/download.php?doc=123.zip>
And that's all.
<a href="normaldownload.zip" onclick="use_dhtml_or_ajax_to_display_page()">
Current page is unaffected if download is saved. Just ensure that download doesn't open in the same window (proper MIME type or Content-Disposition) and you'll be able to show anything.
See more complete answer
You can use Javascript/jQuery to initiate the download. Here's an example - you can get rid of the Ajax request and just use the setTimeout() block.
$("btnDownloadCSV").on('click', function() {
$.ajax({
url: "php_backend/get_download_url",
type: 'post',
contentType: "application/x-www-form-urlencoded",
data: {somedata: "somedata"},
success: function(data) {
// If iFrame already exists, remove it.
if($("[id^='iframeTempCSV_"]).length) {
$("[id^='iframeTempCSV_"]).remove();
}
setTimeout(function() {
// If I'm creating an iframe with the same id, it will permit download only the first time.
// So randHashId appended to ID to trick the browser.
var randHashId = Math.random().toString(36).substr(2);
// Create a fresh iFrame for auto-downloading CSV
$('<iframe id="iframeTempCSV_'+randHashId+'" style="display:none;" src="'+data.filepath+'"></iframe>').appendTo('body');
}, 1000);
},
error: function(xhr, textStatus, errorThrown) {
console.error("Error downloading...");
}
});
});