-
Notifications
You must be signed in to change notification settings - Fork 17
/
compiler.php
363 lines (325 loc) · 11.4 KB
/
compiler.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
<?php
/**
* @package WordPress Dynamic CSS
* @version 1.0.5
* @author Askupa Software <[email protected]>
* @link https://github.com/askupasoftware/wp-dynamic-css
* @copyright 2016 Askupa Software
*/
/**
* Dynamic CSS Compiler Utility Class
*
*
* Dynamic CSS Syntax
* ------------------
* <pre>
* body {color: $body_color;}
* </pre>
* In the above example, the variable $body_color is replaced by a value
* retrieved by the value callback function. The function is passed the variable
* name without the dollar sign, which can be used with get_option() or
* get_theme_mod() etc.
*/
class DynamicCSSCompiler
{
/**
* @var DynamicCSSCompiler The reference to *Singleton* instance of this class
*/
private static $instance;
/**
* @var array The list of dynamic styles paths to compile
*/
private $stylesheets = array();
/**
* @var array The list of registered callbacks
*/
private $callbacks = array();
/**
* @var aray The list of registered filters
*/
private $filters = array();
/**
* Returns the *Singleton* instance of this class.
*
* @return DynamicCSSCompiler The *Singleton* instance.
*/
public static function get_instance()
{
if (null === static::$instance)
{
static::$instance = new static();
}
return static::$instance;
}
/**
* Enqueue all registered stylesheets.
*/
public function enqueue_styles()
{
foreach( $this->stylesheets as $stylesheet )
{
if( $this->callback_exists( $stylesheet['handle'] ) )
{
$this->enqueue_style( $stylesheet );
}
}
}
/**
* Enqueue a single registered stylesheet.
*
* @param array $stylesheet
*/
public function enqueue_style( $stylesheet )
{
$handle = 'wp-dynamic-css-'.$stylesheet['handle'];
$print = $stylesheet['print'];
wp_register_style(
$handle,
// Don't pass a URL if this style is to be printed
$print ? false : $this->get_ajax_callback_url( $stylesheet['handle'] )
);
wp_enqueue_style( $handle );
// Add inline styles for styles that are set to be printed
if( $print )
{
// Inline styles only work if the handle has already been registered and enqueued
wp_add_inline_style( $handle, $this->get_compiled_style( $stylesheet ) );
}
}
/**
* This is the AJAX callback used for loading styles externally via an http
* request.
*/
public function ajax_callback()
{
header( "Content-type: text/css; charset: UTF-8" );
$handle = filter_input( INPUT_GET, 'handle' );
foreach( $this->stylesheets as $stylesheet )
{
if( $handle === $stylesheet['handle'] )
{
echo $this->get_compiled_style( $stylesheet );
}
}
wp_die();
}
/**
* Add a style path to the pool of styles to be compiled
*
* @param string $handle The stylesheet's name/id
* @param string $path The absolute path to the dynamic style
* @param boolean $print Whether to print the compiled CSS to the document
* head, or include it as an external CSS file
* @param boolean $minify Whether to minify the CSS output
* @param boolean $cache Whether to store the compiled version of this
* stylesheet in cache to avoid compilation on every page load.
*/
public function register_style( $handle, $path, $print, $minify, $cache )
{
$this->stylesheets[] = array(
'handle'=> $handle,
'path' => $path,
'print' => $print,
'minify'=> $minify,
'cache' => $cache
);
}
/**
* Register a value retrieval function and associate it with the given handle
*
* @param string $handle The stylesheet's name/id
* @param callable $callback
*/
public function register_callback( $handle, $callback )
{
$this->callbacks[$handle] = $callback;
}
/**
* Register a filter function for a given stylesheet handle.
*/
public function register_filter( $handle, $filter_name, $callback )
{
if( !array_key_exists( $handle, $this->filters ) )
{
$this->filters[$handle] = array();
}
$this->filters[$handle][$filter_name] = $callback;
}
/**
* Get the compiled CSS for the given style. Skips compilation if the compiled
* version can be found in cache.
*
* @param array $style List of styles with the same structure as they are
* stored in $this->stylesheets
* @return string The compiled CSS for this stylesheet
*/
protected function get_compiled_style( $style )
{
$cache = DynamicCSSCache::get_instance();
// Use cached compiled CSS if applicable
if( $style['cache'] )
{
$cached_css = $cache->get( $style['handle'] );
if( false !== $cached_css )
{
return $cached_css;
}
}
$context_options = array(
'ssl' => array(
'verify_peer' => false,
'verify_peer_name' => false,
),
);
$css = file_get_contents( $style['path'], false, stream_context_create($context_options) );
if( $style['minify'] ) $css = $this->minify_css( $css );
// Compile the dynamic CSS
$compiled_css = $this->compile_css(
$css,
$this->callbacks[$style['handle']],
(array) @$this->filters[$style['handle']]
);
$cache->update( $style['handle'], $compiled_css );
return $this->add_meta_info( $compiled_css );
}
/**
* Add meta information to the compiled CSS
*
* @param string $compiled_css The compiled CSS
* @return string The compiled CSS with the meta information added to it
*/
protected function add_meta_info( $compiled_css )
{
return "/**\n".
" * Compiled using wp-dynamic-css\n".
" * https://github.com/askupasoftware/wp-dynamic-css\n".
" */\n\n".
$compiled_css;
}
/**
* Get the callback URL for enqueuing the stylesheet extrnally
*
* @param string $handle The stylesheet's handle
* @return string The URL for the given handle
*/
protected function get_ajax_callback_url( $handle )
{
return esc_url_raw(
add_query_arg(array(
'action' => 'wp_dynamic_css',
'handle' => $handle
),
admin_url( 'admin-ajax.php'))
);
}
/**
* Minify a given CSS string by removing comments, whitespaces and newlines
*
* @see http://stackoverflow.com/a/6630103/1096470
* @param string $css CSS style to minify
* @return string Minified CSS
*/
protected function minify_css( $css )
{
return preg_replace( '@({)\s+|(\;)\s+|/\*.+?\*\/|\R@is', '$1$2 ', $css );
}
/**
* Check if a callback function has been register for the given handle.
*
* @param string $handle
* @return boolean
*/
protected function callback_exists( $handle )
{
if( array_key_exists( $handle, $this->callbacks ) )
{
return true;
}
trigger_error(
"There is no callback function associated with the handle '$handle'. ".
"Use <b>wp_dynamic_css_set_callback()</b> to register a callback function for this handle."
);
return false;
}
/**
* Parse the given CSS string by converting the variables to their
* corresponding values retrieved by applying the callback function
*
* @param callable $callback A function that replaces the variables with
* their values. The function accepts the variable's name as a parameter
* @param string $css A string containing dynamic CSS (pre-compiled CSS with
* variables)
* @return string The compiled CSS after converting the variables to their
* corresponding values
*/
protected function compile_css( $css, $callback, $filters )
{
return preg_replace_callback(
"#". // Begin
"\\$". // Must start with $
"([\\w-]+)". // Match alphanumeric characters and dashes
"((?:\\['?[\\w-]+'?\\])*)". // Optionally match array subscripts i.e. $myVar['index']
"((?:". // Optionally match pipe filters i.e. $myVar|myFilter
"\\|[\\w-]+". // Starting with the | character
"(\([\w\.,']+\))?". // Filters can have strings and numbers i.e myFilter('string',1,2.5)
")*)". // Allow for 0 or more piped filters
"#", // End
function( $matches ) use ( $callback, $filters )
{
$subscripts = array();
// If this variable is an array, get the subscripts
if( '' !== $matches[2] )
{
preg_match_all('/[\w-]+/i', $matches[2], $subscripts);
}
$val = call_user_func_array( $callback, array( $matches[1],@$subscripts[0] ) );
// If there are filters, apply them
if( '' !== $matches[3] )
{
$val = $this->apply_filters( substr( $matches[3], 1 ), $val, $filters );
}
return $val;
}, $css
);
}
/**
* Apply the filters specified in $filters_string to the given $value.
*
* @param string $filters_string
* @param string $value
* @param array $filters Array of callback functions
* @return string The value after all filters have been applied
*/
protected function apply_filters( $filters_string, $value, $filters = array() )
{
foreach( explode( '|', $filters_string) as $filter )
{
$args = array( $value );
if( false !== strrpos( $filters_string, "(" ) )
{
$pieces = explode( '(', $filter );
$filter = $pieces[0];
$params = explode( ',', str_replace( ')', '', $pieces[1] ) );
array_walk( $params, array( $this, 'strtoval' ) ); // Convert string values to actual values
$args = array_merge( $args, $params );
}
if( key_exists( $filter, $filters ) )
{
$value = call_user_func_array( $filters[$filter], $args );
}
}
return $value;
}
/**
* Convert the given string to its actual value.
*
* @param string $str The string to be converted (passed by reference)
*/
protected function strtoval( &$str )
{
if( 'false' === strtolower($str) ) $str = false;
if( 'true' === strtolower($str) ) $str = true;
if( false !== strrpos( $str, "'" ) ) $str = str_replace ( "'", "", $str );
if( is_numeric( $str ) ) $str = floatval( $str );
}
}