Saving CSS files in uploads folder
Published June 27th, 2014 under General
Note: Peter Knight pointed out in the comments, that this approach will cause problems on some server setups. On testing it, I found that on one of my local servers, the files were not saved and no error was served.
Once upon a time, I used to do some rather silly things in code. Okay, I most likely do silly things now too, but I’m still oblivious to them 😛 But back then, one of the dumbest thing I did, was to attempt to load CSS via PHP.
I was creating a dynamic WordPress plugin which changed the CSS based on user input in the backend. Inline CSS is ugly and not very efficient, so logically I looked for a solution which involved loading the CSS as an external “file”.
Loading CSS via a .php file
I thought I was very smart in implementing a style.php file, which was added something like this:
<link rel='stylesheet' href='http://domain.com/wp-content/plugins/some-plugin/style.php' type='text/css' />
This seemed great. I was very proud of myself. Then people started complaining that my plugin didn’t work. It turned out that some people had disabled the ability to access .php files directly from the plugins folder. Arghh!
Loading CSS via WordPress
Briliantly, I managed to solve the access problem by outputting the CSS directly from WordPress itself. I simply added a query variable to the home URL, checked that the query variable was set, and boom! I had a funky looking CSS “file” loaded somewhat like this:
<link rel='stylesheet' href='http://domain.com/?load_css=yep' type='text/css' />
Performance problems
I was very proud of myself for solving the compatibility problem. What hadn’t occurred to me though, was that loading the entirety of WordPress was not a smart thing to do when all you need to access is a simple CSS file. WordPress is a big program, and loading it twice per page was horrendously dumb way to solve that problem.
It was WP Tavern regular Otto (now of WordPress.org) who eventually convinced me of my own stupidity.
The solution
The optimal solution was easy in concept, although it did take me a while to figure out how to do it. In the rare (and it is very rare that you would need to do this) situation in which you need to output dynamically created CSS, you need to place it into the uploads folder of the site. My first thought on how to do this was to use the file_put_contents() function, but this unfortunately is not a recommended practice as some hosts do not handle file permissions correctly when doing this. The trick, is to use the WP_Filesystem class to handle the creation of the file.
Oddly, during an extensive search on the interwebz, I never found a functioning piece of code to actually do this. There are functions which look like they should work, but did not. Or there were code snippets which did part of the job, but had some weirdness going on, or enforced unneeded authentication. So, below is a simple chunk of PHP code which allows you to automagically copy data into a file in the WordPress uploads folder. It uses the built in WP_Filesystem class to ensure the security aspects of file storage is handled correctly, and it uses wp_upload_dir() to ensure that WordPress multi-site is catered for.
Hopefully some of you find this useful 🙂
<?php // Lets create some test CSS code $css = '/* Some test CSS */ body {background:red;}'; // Stash CSS in uploads directory require_once( ABSPATH . 'wp-admin/includes/file.php' ); // We will probably need to load this file global $wp_filesystem; $upload_dir = wp_upload_dir(); // Grab uploads folder array $dir = trailingslashit( $upload_dir['basedir'] ) . 'some-folder/'; // Set storage directory path WP_Filesystem(); // Initial WP file system $wp_filesystem->mkdir( $dir ); // Make a new folder for storing our file $wp_filesystem->put_contents( $dir . 'style.css', $css, 0644 ); // Finally, store the file :) ?>
Note: It is actually very rarely that you need to do this. I’m guessing about 99% of the times I see this sort of thing being done, it is in a situation where simple inline CSS or an HTML change would be a much better option
Peter Knight (@peterrknight) says:
If you use WP_Filesystem(), doesn’t it trigger an authentication screen requiring manual login/pw entry on some servers? This limits when a file creation script can be run; it would have to be part of something a user is doing manually, rather than a neat background process. The latter is relevant if, say you’re compiling to CSS from less/scss for instance.
June 28, 2014 at 12:33 pm # //
Ryan says:
I think it only triggers that if you are manually setting the method rather than letting WordPress decide which to use. That was an issue I had with many of the other tutorials I found on this, as they tended to force FTP on the user.
June 28, 2014 at 3:07 pm # //
Peter Knight (@peterrknight) says:
Relooking at the WP docs I\’m still convinced WP_Filesystem requires authentication if it can\’t write files without one of the FTP methods. Most hosts don\’t have this problem, but it wouldn\’t be suitable for a repo plugin that wants to write files in the background. Then you either have to store credentials in constants so or use your own file writing script.
Caching plugins face the same issue I believe. To the best of my knowledge, the best devs can do in those situations is to write to the uploads folder, set the correct file permissions, set an index.php for the folder & .htaccess file. There should be a guide on how to do that properly and securely. Of course, there really needs to be a legitimate reason to go that route.
June 28, 2014 at 6:31 pm # //
Ryan says:
Hmmm. Do you know what sort of things would trigger that authentication problem?
June 29, 2014 at 3:56 pm # //
Peter Knight (@peterrknight) says:
The same kinds of instances where you try to update a plugin and it asks you for ftp user&pw. I’ve had this happen on client accounts running on shitty hosting or IIS servers or when I hadn’t completely configured my DO server to allow WP to write files.
I remember asking Otto on his tutorial post how you should deal with say, a file caching approach, but not never got a reply on that. I think all the file caching solutions use fwrite or similar instead of the WP_File_System() api.
June 30, 2014 at 10:11 am # //
Ryan says:
I have a local server with that problem, so I’ll test it out with that and see how it goes.
June 30, 2014 at 10:16 am # //
Ryan says:
Damnit! I tested this out on my broken local install, and no files were saved 🙁 I didn’t see an error message, but neither the files or folder were created 🙁
I’ve updated the post to mention what you found.
June 30, 2014 at 10:27 am # //
Peter Wilson says:
Would it be possible to server the CSS via admin-ajax.php with an extended browser cache header set?
I believe this loads a cut down version of WordPress. The content type would need to be set to CSS.
I know this isn\’t a perfect solution either.
July 2, 2014 at 12:14 am # //
Ryan Hellyer says:
I’d rather serve it as inline CSS than do that. Hitting PHP at all is going to be significantly more load inducing than a simple static file or inlined code.
For my particular situation, I unfortunately need the physical .css file on the server as another part of the system does a file_exists() check before using it :/
July 2, 2014 at 7:17 am # //
Marco says:
There’s an interesting class that I’ve written (https://github.com/marco-c/WP_Serve_File) to serve files.
It uses the filesystem if possible (checking if it can access it safely and without asking for a password), otherwise falls backs to the transient API and WordPress AJAX methods.
April 7, 2016 at 1:06 pm # //
awran5 says:
Thank you very much Reyn! That’s exactly what i was looking for.
Also, I’m testing this https://github.com/askupasoftware/wp-dynamic-css if anyone want to go deep.
May 26, 2017 at 3:34 pm # //
awran5 says:
For anyone still interesting, This method will work fine but it will keep generating a new .css file every time the page is refreshed or reloaded which is bad practice, so you need to build some cache system and create the file under conditions.
I found the whole solution in this post:
https://github.com/Codestar/codestar-framework/issues/307#issuecomment-216499418
May 27, 2017 at 3:52 pm # //
Ryan says:
You don’t really need a cache system for something like this. You just need to only generate a new file when the CSS needs to change (usually when adjusting some settings page).
May 27, 2017 at 4:56 pm # //
Ryan says:
Speaking of caching … if you have direct access to the server (and some sys admin knowledge), then you could simply serve the file via PHP (which is inefficient), and cache it at server level. Something like Varnish or an Nginx microcache would solve that problem quite nicely.
The solutions listed here are only useful for when you are bundling code for others, or when you don’t have sufficient server access privileges.
May 27, 2017 at 4:59 pm # //
awran5 says:
Yes, indeed the cache system is not necessary, you can just use some wp hooks to control the file creation:
// after customizer saves
add_action( 'customize_save_after', 'your_function' );
// after pages/posts saves
add_action( 'save_post', 'your_function' );
// after redux option panel saves (which i use)
add_action( 'redux/options/your_opt_name/saved', 'your_function' );
Thank you again Ryan 🙂
May 27, 2017 at 7:21 pm # //
nermin says:
I want to add the css files depends on enabled options if the option is enable the file regenerate with the enabled css only, how to regenerate it and delete the old one, what about if it was cached, please advice.
October 14, 2019 at 1:36 pm # //
Ryan Hellyer says:
I advise that you do what you want to do. I’m not sure how else to respond to you sorry. My advice on this topic is in the post you are responding to here, so I don’t have much else to add.
January 28, 2020 at 9:32 am # //
Ahmed says:
if i add custom css in wordpress additional css then i want To save this CSS in a file in the upload folder dynamically so how it can please reply
April 3, 2020 at 12:54 pm # //
Ryan Hellyer says:
??? That’s literally the point of this post.
April 18, 2020 at 12:50 am # //
Masroor says:
function masroors_custom_css() {
$upload_dir = wp_upload_dir();
$base_dir = wp_get_upload_dir();
$styles = wp_get_custom_css();
if ( $styles || is_customize_preview() ) :
$type_attr = current_theme_supports( ‘html5’, ‘style’ ) ? ” : ‘ type=”text/css”‘;
$file = file_put_contents($upload_dir[‘basedir’].”/masroor-file-style.css”, strip_tags( $styles ) );
// upload file to uploads folder
move_uploaded_file($file, $upload_dir[‘baseurl’]);
endif;
}
// save css code in a file masroor-file-style.css
add_action( ‘save_post’, ‘masroors_custom_css’ );
//include css file here
$upload_path = wp_upload_dir();
wp_enqueue_style (‘custom-style’,$upload_path[‘baseurl’] .’/masroor-file-style.css’);
// remove css code from head section
remove_action(‘wp_head’, ‘wp_custom_css_cb’, 101);
July 30, 2021 at 12:02 pm # //