Skip to content

Instantly share code, notes, and snippets.

@Robbertdk
Last active October 17, 2019 04:19
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save Robbertdk/dbab8f2a4e38bdac067b2f2105d6977e to your computer and use it in GitHub Desktop.
Save Robbertdk/dbab8f2a4e38bdac067b2f2105d6977e to your computer and use it in GitHub Desktop.
<?php
/**
* Kirki Sass Compiler
*
* Create a CSS file based on a SCSS-file and Kirki variables
* File gets saved in the public folder with a cache buster.
*/
namespace App\Kirki;
use ScssPhp\ScssPhp\Compiler;
class DynamicCSS {
private $css_dir;
private $css_file_name;
private $css_file_url;
public function __construct($settings) {
if (!class_exists( 'Kirki' )) return;
$this->css_dir = $this->get_file_dir($settings['foldername']);
$this->css_file_name = $this->get_file_name($settings['filename']);
$this->css_file_url = $this->get_cached_file_url($settings['foldername']);
// Create an CSS file with the initial values on theme change
add_action('switch_theme', array( $this, 'remove_css_directory' ), 999);
add_action('after_switch_theme', array( $this, 'refresh_css'));
add_action( 'init', [$this, 'init'], 999 );
}
/**
* Hooks up the compiler
*
* @return void
*/
public function init() {
global $wp_customize;
// If whe're in the customizer, add the styles directly into the head
// Thereby we see the changes on partial refresh, before the customize_save_after hook is triggered
if ( $wp_customize ) {
add_action( 'wp_enqueue_scripts', array( $this, 'get_inline_styles' ), 999 );
// Create a new CSS file on customizer settings save
add_action( 'customize_save_after', array( $this, 'refresh_css' ) );
return;
}
// Enqueue the script
add_action( 'wp_enqueue_scripts', array($this, 'enqueue_dynamic_css'), 999);
}
/**
* Compiles CSS and adds it as inline style.
*
* Used during style changes in the Customizer
*
* @return void
*/
public function get_inline_styles() {
$styles = $this->compile_scss();
if ( !empty($styles) ) {
wp_enqueue_style( 'dynamic-css', $this->css_file_url );
wp_add_inline_style( 'dynamic-css', $styles );
}
}
/**
* Returns the path to the css file directory.
*
* @return string
*/
private function get_file_dir($folder = 'dynamic-css') {
$upload_dir = wp_upload_dir();
return $upload_dir['basedir'] . DIRECTORY_SEPARATOR . $folder;
}
/**
* Removes the css folder
*
* @return void
*/
public function remove_css_directory() {
global $wp_filesystem;
if ( empty( $wp_filesystem ) ) {
require_once( ABSPATH . '/wp-admin/includes/file.php' );
WP_Filesystem();
}
return $wp_filesystem->rmdir( $this->css_dir, true);
}
/**
* Returns the file name adjusted for multisite.
*
* @return string
*/
private function get_file_name($basename = 'styles') {
// Add blog ID if on multisite
global $blog_id;
$blog_id = ( is_multisite() && $blog_id > 1 ) ? '_blog-' . $blog_id : null;
return $basename . $blog_id . '.css';
}
/**
* Returns the path to the css file with the cache buster in the filename.
*
* @param string cachebuster
* @return string
*/
private function get_cached_file_path($buster = false) {
// Get cache buster
if (!$buster) {
$buster = $this->get_cache_buster();
}
$file_name = str_replace('.css', '-' .$buster . '.css', $this->css_file_name);
return $this->css_dir . DIRECTORY_SEPARATOR . $file_name;
}
/**
* Returns the url to the css file name, adjusted for multisite.
* @param string foldername
* @return string
*/
private function get_cached_file_url($foldername = 'dynamic-styles'){
$upload_dir = wp_upload_dir();
$cache_buster = $this->get_cache_buster();
$file_name = str_replace('.css', '-' . $cache_buster . '.css', $this->css_file_name);
$css_url = trailingslashit( $upload_dir['baseurl'] ) . $foldername . '/' . $file_name;
// Take care of domain mapping
if ( defined( 'DOMAIN_MAPPING' ) && DOMAIN_MAPPING ) {
if ( function_exists( 'domain_mapping_siteurl' ) && function_exists( 'get_original_url' ) ) {
$mapped_domain = domain_mapping_siteurl( false );
$original_domain = get_original_url( 'siteurl' );
$css_url = str_replace( $original_domain, $mapped_domain, $css_url );
}
}
// Strip protocols
$css_url = str_replace( 'https://', '//', $css_url );
$css_url = str_replace( 'http://', '//', $css_url );
return $css_url;
}
/**
* Returns the cache buster id
* @return String
*/
private function get_cache_buster() {
$filename = $this->css_dir . DIRECTORY_SEPARATOR . 'assets.json';
$buster = file_exists($filename) ? json_decode(file_get_contents($filename), true) : false;
return $buster ? $buster['assets'] : Null;
}
/**
* Creates a json file with the cache buster
*
* @param string Cache buster id
* @return String
*/
private function set_cache_buster($id) {
global $wp_filesystem;
// Initialize the Wordpress filesystem.
if ( empty( $wp_filesystem ) ) {
require_once( ABSPATH . '/wp-admin/includes/file.php' );
WP_Filesystem();
}
$buster = ['assets' => $id];
$buster = json_encode($buster);
if ( ! $wp_filesystem->put_contents( $this->css_dir . DIRECTORY_SEPARATOR . 'assets.json', $buster, FS_CHMOD_FILE ) ) {
error_log('Can\'t create Cache buster. Something went wrong');
return false;
}
}
/**
* Delete all css-files, except the one with the new cache buster
* @return String
*/
private function delete_old_styles($new_file_buster) {
$files = glob( $this->css_dir . DIRECTORY_SEPARATOR . '*.css' );
foreach ( $files as $file ) {
// If the file has the new buster, don't delete it (caus its new)!
if (is_file( $file ) && strpos(basename($file), $new_file_buster) === false) {
unlink( $file );
}
}
}
/**
* Enqueue the dynamic CSS.
*/
public function enqueue_dynamic_css() {
wp_enqueue_style( 'dynamic-css', $this->css_file_url );
}
public function refresh_css() {
global $wp_filesystem;
// Initialize the Wordpress filesystem.
if ( empty( $wp_filesystem ) ) {
require_once( ABSPATH . '/wp-admin/includes/file.php' );
WP_Filesystem();
}
$content = "/********* Compiled - Do not edit *********/\n" . $this->compile_scss();
// Take care of domain mapping
if ( defined( 'DOMAIN_MAPPING' ) && DOMAIN_MAPPING ) {
if ( function_exists( 'domain_mapping_siteurl' ) && function_exists( 'get_original_url' ) ) {
$mapped_domain = domain_mapping_siteurl( false );
$mapped_domain = str_replace( 'https://', '//', $domain_mapping );
$mapped_domain = str_replace( 'http://', '//', $mapped_domain );
$original_domain = get_original_url( 'siteurl' );
$original_domain = str_replace( 'https://', '//', $original_domain );
$original_domain = str_replace( 'http://', '//', $original_domain );
$content = str_replace( $original_domain, $mapped_domain, $content );
}
}
// Strip protocols
$content = str_replace( 'https://', '//', $content );
$content = str_replace( 'http://', '//', $content );
// Create a unique buster
$buster = uniqid();
// Create file
if ($this->can_write()) {
if ( ! $wp_filesystem->put_contents( $this->get_cached_file_path($buster), $content, FS_CHMOD_FILE ) ) {
// Fail!
error_log('Can\'t create CSS. Something went wrong');
return false;
}
// Update the cache buster file
$this->set_cache_buster($buster);
// Delete old files
$this->delete_old_styles($buster);
} else {
error_log('Can\'t create CSS. File does not exist or is not writable');
}
}
/*
* Determines if the CSS file is writable.
*/
public function can_write() {
// Does the folder exist?
if ( file_exists( $this->css_dir ) ) {
// Folder exists, but is the folder writable?
if ( ! is_writable( $this->css_dir ) ) {
// Folder is not writable.
error_log('CSS file could not be written.');
return false;
}
} else {
// Can we create the folder?
// returns true if yes and false if not.
return wp_mkdir_p( $this->css_dir );
}
// all is well!
return true;
}
/**
* Compiles the Kirki Variables and SCSS-file to a CSS-string
*
* @return string
*/
public function compile_scss()
{
$scss = new Compiler();
$scss->setImportPaths( get_stylesheet_directory() . '/assets/styles' );
$scss->setFormatter( 'ScssPhp\ScssPhp\Formatter\Compressed' );
$variables = \Kirki_Util::get_variables();
$vars = '';
foreach ( $variables as $variable => $value ) {
if (is_string($value)) {
$vars .= '$' . $variable . ':' . $value . ';';
}
}
// Create SCSS string from Kirki variables and scss functions we've written
$scss_string = $vars . '
@import "theme"';
// Compile SCSS string to CSS string
$css = $scss->compile( $scss_string );
return $css;
}
}
$compiler = new DynamicCSS([
'filename' => 'styles',
'foldername' => 'dynamic-css',
]);
@Robbertdk
Copy link
Author

I don't get notifications for these comments, so it suprises me when I see new comments over here. So, sorry for the delay in my answers.

@ecksite good point, thanks! I've added it to the gist.

@jessekafor I don't see the problem. I use the latest version of Kirki and generated css file of this gist is added as an external script and not inlined. Can you eleborate on the problem you encounter?

@Robbertdk
Copy link
Author

Because I can't add commit messages to changes on this script, I just leave them here:

Today I've commited the following changes:

  1. The Leafo\ScssPhp composer package is archived, development of that package will be continued under the package ScssPhp\ScssPhp. So I swapped that.
  2. Remove unneeded error log message
  3. Add check for the Kirki class so it won't fail when the Kirki plugin is not activated.
  4. Add string check for variables added via the customizer as mentioned by @ecksite

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment