Dynamically fetching css from CDN with fallback to local copy - php

I am trying to fetch my CSS from external CDN service let's say http://cdn.example.com/.
This code is suppose to check if file exists on external CDN and if it does, then get that file else get it from a local folder.
Currently this is fetching only the local copy even though the file exists on external CDN.
Please help me correct this code.
function getCSSfile($filename)
{
$externalcss = EXTERNAL_CDN.'css/'.$filename.'.css';
$localcss = LOCAL_CDN.'css/'.$filename.'.css';
$ctx = stream_context_create(array(
'http' => array(
'method' => 'HEAD'
)
));
if (file_get_contents($externalcss, false, $ctx) !== false) {
echo '<link href="' . $externalcss . '" rel="stylesheet" type="text/css" media="all"/>';
} else if (file_exists($localcss)) {
echo '<link href="' . $localcss . '" rel="stylesheet" type="text/css" media="all"/>';
} else {
echo 'Error loading CSS File';
}
}
UPDATE:
Updated the code as per #DaveRandom's suggestion. Currently code runs fine it fetches the copy from external CDN but if the external CDN is unavailable it does fetches the local copy BUT throws the following error too:
Warning: file_get_contents(http://cdn.localtest.com/css/core.css): failed to open stream: HTTP request failed! HTTP/1.1 404 Not Found in D:\xampp\htdocs\localtest2\core\core.php on line 165
and in line 165 this is the code
if (file_get_contents($externalcss, false, $ctx) !== false) {
I am testing it locally and created some domains on my XAMPP Server, hence cannot share a live link.

I don't believe you can use file_exists on a URL, this answer might shed some light on the code you could use to properly check in the URL returned a file to you.
But to summarize, you want to check the status code that is returned to you, rather than using file_exists.

The problem with checking if the file can be accessed through PHP is that the check is done from your server, not the client loading the files. Just because your server can access the CSS file from the CDN does not mean the client can.
If you are going to do a check with a fallback, it is safer to do it on the client side using javascript.
How to fallback to local stylesheet if CDN fails

Another potential problem (in addition to #Johannes' answer) would be your constants. In the question you structured your CDN URL as http://cdn.example.com. Appending this to css/ (as in your code) would produce http://cdn.example.comcss/, which isn't a valid URL.
Make sure that the constants (base URL's) are structured as http://cdn.example.com/.
Edit: To add to #Johannes' answer, I found this link from another answer on SO: http://www.php.net/manual/en/function.file-exists.php#75064
To put the code here:
$file = 'http://www.domain.com/somefile.jpg';
$file_headers = #get_headers($file);
if($file_headers[0] == 'HTTP/1.1 404 Not Found') {
$exists = false;
}
else {
$exists = true;
}

The way I look at this, you have this process the wrong way round - you should serve your local copy if it exists (and is not stale, a mechanism which needs to be defined) and fall back to the CDN. This means you are behaving like a cache.
But to answer the question directly, your best bet would be to use a HEAD request to the CDN to see whether it exists. It's tempting to use get_headers() for this, but actually this causes a GET method request unless you alter the default stream context - this creates potentially unwanted global state in your application.
So my preferred approach would be to create localised stream context and use it in conjuction with a function that will accept it as an argument. For the sake of simplicity, in the below example I'll use file_get_contents().
function getCSSfile($filename)
{
$externalcss = EXTERNAL_CDN.'css/'.$filename.'.css';
$localcss = LOCAL_CDN.'css/'.$filename.'.css';
$ctx = stream_context_create(array(
'http' => array(
'method' => 'HEAD'
)
));
if (file_get_contents($externalcss, false, $ctx) !== false) {
echo '<link href="' . $externalcss . '" rel="stylesheet" type="text/css" media="all"/>';
} else if (file_exists($localcss)) {
echo '<link href="' . $localcss . '" rel="stylesheet" type="text/css" media="all"/>';
} else {
echo 'Error loading CSS File';
}
}
Now, this is not perfect by a long way, but I've recycled a lot of mechanism from your original code. There is still global state (the use of user-defined constants in functions like this is a BIG no-no for me) and you still have a function which produces output directly instead of returning control to the caller.
I would prefer to pass $externalcss and $localcss in as arguments, and simply return the chosen URL string instead of formatting it into HTML and echoing it. This way, the code is isolated and doesn't rely on anything external, and by returning the string you allow the caller to further manipulate the data if necessary. In essence, it makes the code infinitely more re-usable.
Side note: Defining functions within functions, while possible, isn't recommended. This is because the second call will cause a Cannot redeclare function fatal error.

Okay since the error solution was not explained by anyone... I decided to answer my own question. This is the solution I found:
function getCSSfile($filename)
{
$externalcss = EXTERNAL_CDN.'css/'.$filename.'.css';
$localcss = LOCAL_CDN.'css/'.$filename.'.css';
$ctx = stream_context_create(array(
'http' => array(
'method' => 'HEAD'
)
));
if (#file_get_contents($externalcss, false, $ctx) !== false) {
echo '<link href="' . $externalcss . '" rel="stylesheet" type="text/css" media="all"/>';
} else if (file_exists($localcss)) {
echo '<link href="' . $localcss . '" rel="stylesheet" type="text/css" media="all"/>';
} else {
echo 'Error loading CSS File';
}
}
As suggested by Mark B
As stated in the docs, file_get_contents() will throw a warning if the stated resource cannot be found.
This is one of the few cases in which using the # error suppression operator may be justified, e.g.
if (#file_get_contents($externalcss, false, $ctx) !== false)
to prevent the warning from mucking up the output.

Related

Insert a CSS file within a PHP class

I have a little problem, which I can't figure out by myself.
I created sort of a "webengine", which I build out of several classes.
The main-class, getting a "theme's" internals is called "Logic".
This logic-class includes all the files, needed for a website. I made it like that, so that every developer, creating such website could use function, that are given inside this class.
I now want to create a function, that allows these developers to include a CSS file. But this turns out to be quite difficult.
So far I tried this:
public function include_css($path_to_css) {
$path_to_css = $this->project_name . THEMES . "/" . ACTIVE_THEME . "/" . $path_to_css;
if(file_exists($path_to_css)) {
?>
<html>
<head>
<link rel="stylesheet" type="text/css" href=" <?php echo $path_to_css; ?>" />
</head>
<?php
} else {
return false;
}
}
/* Idea number 2 */
public function include_css($path_to_css) {
$path_to_css = $this->project_name . THEMES . "/" . ACTIVE_THEME . "/" . $path_to_css;
if(file_exists($path_to_css)) {
echo "
<html>
<head>
<link rel='stylesheet' type='text/css' href='".$path_to_css."' />
</head>
";
} else {
return false;
}
}
Please note that unclarified attributes are declared in a complex matter, so it would be a very long post, if I would paste these here.
I am just getting this console error: http://127.0.0.1/vpm2/themes/numberOne/css/test.css 404 (Not Found), which means there is no such file. The interesting thing about that, is, that this is the exact path to the right file!
Is there anything I am missing?
I found the solution. Apparently the CSS Path seems to be wrong. It needs to look like that:
$path_to_css = THEMES . "/" . ACTIVE_THEME . "/" . $path_to_css;
So the result looks like that:
themes/numberOne/test.css
Before it looked like that:
/vpm2/themes/numberOne/test.css

Magento category redirect cuts off the querystring of an url

we are running a Magento 1.4.2.0 Webshop with google analytics.
As we all know google attaches a querystring called the "gclid-Param" to the url.
The customer clicks the following url: http://www.myshop.com/bathrooms/showersbaths.html?glicd=somevalue
The category "bathrooms" was renamed inside magento, so magento automatically created a redirect from the old categoryname to the new name "bathroom furniture".
So now we have the problem, that magento cuts off the querystring with the glic-param when it rewrites and redirects the url.
Does anybody know how to prevent this or in which core-Module we have to modify the building of the new url?
best regards
Markus
In 1.9.1.0 this problem could be solved through patching in another class Mage_Core_Model_Url_Rewrite_Request/function _processRedirectOptions().
Just add after code
$targetUrl = $this->_request->getBaseUrl() . '/' . $this->_rewrite->getTargetPath();
$storeCode = $this->_app->getStore()->getCode();
if (Mage::getStoreConfig('web/url/use_store') && !empty($storeCode)) {
$targetUrl = $this->_request->getBaseUrl() . '/' . $storeCode . '/' . $this->_rewrite->getTargetPath();
}
if ($this->_rewrite->hasOption('R') || $isPermanentRedirectOption) {
the following
$queryString = $this->_getQueryString();
if ($queryString) {
$targetUrl .= '?'.$queryString;
}
and make sure 'if' statement keep closed with
$this->_sendRedirectHeaders($targetUrl, $isPermanentRedirectOption);
}
I'm sure it's fairly enough because of you don't need to transfer query string for external redirects.
Happy coding
After some more deep research inside the chaos of magento we found the solution to solve our Problem.
In the Url-Model of the Mage_Core exists a class rewrite.php.
We created a custom model and overwrited the rewrite.php.
Inside of the function rewrite(), we added the following piece(marked as comments) of code:
//$url_params = false;
//if ($url_params = $_SERVER['QUERY_STRING'])
//$url_params = "?".$url_params;
if ($external === 'http:/' || $external === 'https:')
{
if ($isPermanentRedirectOption)
{
header('HTTP/1.1 301 Moved Permanently');
}
header("Location: ".$this->getTargetPath() //.$url_params);
exit;
}
else
{
$targetUrl = $request->getBaseUrl(). '/' . $this->getTargetPath();
}
$isRedirectOption = $this->hasOption('R');
if ($isRedirectOption || $isPermanentRedirectOption)
{
if (Mage::getStoreConfig('web/url/use_store') && $storeCode =
Mage::app()->getStore()->getCode())
{
$targetUrl = $request->getBaseUrl(). '/' . $storeCode . '/'
.$this->getTargetPath();
}
if ($isPermanentRedirectOption)
{
header('HTTP/1.1 301 Moved Permanently');
}
header('Location: '.$targetUrl //.$url_params);
exit;
}
So i hope our solution helps others, who are facing the same problem.
Best regards
Markus
I am running 2.1.0 community edition right now and am having the same issue. I tried finding the files above, but they seem to be specific to the 1.X versions of Magento (at least the implementation within the files). I've found a work around for this, but I hate hate hate the way I am doing it. That being said, I am not noticing any performance problems with the site and I didn't have to modify any Magento core files. So... here is what I did;
I already had a directory under the Magento root directory that I use to host static content.
I created two files in this directory: startscripts.js (which I use to load any custom scripts within the head element) and endscripts.js (which I use to load any custom scripts right before the end of the body element).
In the administration page, go to content, configuration, and edit your site.
In the html head section add
<script src="/[staticDirectoryYouCreate]/startscripts.js" type="text/javascript"></script>
to the scripts and stylesheets section
In the footer section on this same page, add
<script src="/[staticDirectoryYouCreate]/endscripts.js" type="text/javascript" defer></script>
to the Miscellaneous HTML section
Here is the script that I created in the endscripts.js file to append the gclid to links on the page:
try
{
var urlParameters = /gclid=([^&]+)/.exec(document.location.toString());
if(urlParameters)
{
if(urlParameters[1])
{
var siteLinks = document.getElementsByTagName("a");
for(var currentLink=0;currentLink < siteLinks.length;currentLink++)
{
if(siteLinks[currentLink].href.toString().indexOf("gclid") < 0)
{
if(siteLinks[currentLink].href.toString().indexOf("?") < 0)
{
siteLinks[currentLink].href=siteLinks[currentLink].href+"?gclid="+urlParameters[1];
}
else
{
siteLinks[currentLink].href=siteLinks[currentLink].href+"&gclid="+urlParameters[1];
}
}
}
}
}
}
catch(e){}
For this particular fix, you don't have to add the startscripts.js file, but the Google tag assistant was complaining that the Google Analytics snippet was outside of the head element, so I disabled the Magento Google Analytics implementation and put the snippet in the startscripts.js file.
I'll be curious to hear folks opinions on solving the problem this way.

file_exists() showing opposite results than expected

I have a very, very simple function:
function getUserImage($id) {
if (file_exists(SITE_ROOT . '/images/' . $id)) {
return "http://www.---.net/images/" . $id;
} else {
return "http://www.---.net/images/usericon.png";
}
}
It is being called this way:
<img src="<?php echo getUserImage($row['user_id'].".jpg"); ?>" />
What I am trying to do is show a default icon if the user does not have a profile picture. It is showing the default icon every time even though the correct path is being tested (I have confirmed this). Maybe I am using file_exists() wrong?
file_exists() works at the filesystem level. You're passing in something like /images/whatever, so it'll be looking for a whatever file in an images directory that is at the TOP of the file system, e.g. you're trying to for the equivalent of
c:\images\whatever
instead of where the image actually exists, in your site's document root, e.g.
c:\inetpub\wwwroot\example.com\html\images\whatever
You generally can NOT use a url directly in a file-system context, because the paths you see in a URL very rarely EVER directly map to what's on the server's file system.
function getUserImage($id) {
if (file_exists(SITE_ROOT . '/profile_pics/' . $id)) {
return "http://www.---.net/profile_pics/" . $id;
} else {
return "http://www.---.net/images/usericon.png";
}
}
I was searching in the wrong folder. It should have been profile_pics all along~

CakePHP 2.x using PHP in CSS

Could someone please advise me on the current methods available for using PHP in a CSS file in CakePHP 2.x
I have currently separated my stylesheet into a php file and wish to parse my data via the URL but I cannot seem to work out how to link it using
$this->Html->css('dynamic-stylesheet')
as it always appends .css to the name.
Your help is much appreciated.
You can generate the tag by using HtmlHelper::meta()
echo $this->Html->meta(array(
'link' => '/css/test.php',
'rel' => 'stylesheet',
'type' => 'text/css'
));
Note that you need to explicitly direct it to the /css directory, as you are no longer using a helper method specifically for CSS.
Looking at the Html helper file in the CakePHP library located at lib/Cake/View/Helper/HtmlHelper.php, check out line 427, looks like the .css extension is set automatically unless there are two slashes in the CSS file name. I GUESS that is to catch external resources?
From the aforementioned library file:
if (strpos($path, '//') !== false) {
$url = $path;
} else {
$url = $this->assetUrl($path, $options + array('pathPrefix' => CSS_URL, 'ext' => '.css'));
if (Configure::read('Asset.filter.css')) {
$pos = strpos($url, CSS_URL);
if ($pos !== false) {
$url = substr($url, 0, $pos) . 'ccss/' . substr($url, $pos + strlen(CSS_URL));
}
}
}
So this..
echo $this->Html->css('http://whatever.com/css/dynamic-stylesheet.php');
Would render the dynamic-stylesheet.php file rather than defaulting to 'dynamic-stylesheet.css.
Not sure that is what this was intended for but maybe that file will help you.

How is the force reload of javascript/css done in Symfony?

After reading this thread: How to force browser to reload cached CSS/JS files?
I would like to know if there is any built-in function or easy way in Symfony that automatically forces a reload by appending a random querystring or timestamp to the link when it has discovered that javascript / css file has been modified. (Normally, people use the use_javascript function to generate the <script> tag)
There is no built-in mechanism, but a little creativity means you can do this just about anywhere in your code, from view.yml to layout.php to each individual action.
The view.yml method is easy enough:
apps/frontend/config/view.yml:
stylesheets: [main?v=<?php echo time() ?>, reset?v=<?php echo time() ?>, layout?v=<?php echo time() ?>]
Although I think this is a little too active, and I tend to use either the SVN revision or a overall project version number:
stylesheets: [main?v=<?php echo sfConfig('app_project_version') ?>, reset?v=<?php echo sfConfig('app_project_version') ?>, layout?v=<?php echo sfConfig('app_project_version') ?>]
where app_project_version is set in apps/frontend/config/app.yml. Methods for layout.php and actionSuccess.php should be easy enough from here:
<?php use_stylesheet('blah?v='.sfConfig::get('app_project_version')); ?>
instead of setting a version for each stylesheet you include, it is better to have it done automatically for all included stylesheets, no matter if you use view.yml or use_stylesheet() method. You need to implement this helper method and
include the helper in your applications settings.yml, so that it becomes available to alle your actions.
`
function include_versioned_stylesheets()
{
$response = sfContext::getInstance()->getResponse();
sfConfig::set('symfony.asset.stylesheets_included', true);
$html = '';
foreach ($response->getStylesheets() as $file => $options) {
$filepath = sfConfig::get('sf_web_dir') . '/' . stylesheet_path($file);
if(file_exists($filepath)) {
$file .= '?v=' . filectime($filepath);
}
$html .= stylesheet_tag($file, $options);
}
echo $html;
}
`
in your layout.php call this inside your header area. make sure there is no further call to include_stylesheets(), as this is an extended version to it.
same can be done with include_javascripts.

Categories