~ubuntu-branches/ubuntu/utopic/moodle/utopic

« back to all changes in this revision

Viewing changes to lib/csslib.php

  • Committer: Package Import Robot
  • Author(s): Thijs Kinkhorst
  • Date: 2014-05-12 16:10:38 UTC
  • mfrom: (36.1.3 sid)
  • Revision ID: package-import@ubuntu.com-20140512161038-puyqf65k4e0s8ytz
Tags: 2.6.3-1
New upstream release.

Show diffs side-by-side

added added

removed removed

Lines of Context:
20
20
 * Please see the {@link css_optimiser} class for greater detail.
21
21
 *
22
22
 * @package core
23
 
 * @category css
 
23
 * @subpackage cssoptimiser
24
24
 * @copyright 2012 Sam Hemelryk
25
25
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26
26
 */
27
27
 
28
 
// NOTE: do not verify MOODLE_INTERNAL here, this is used from themes too
 
28
defined('MOODLE_INTERNAL') || die();
29
29
 
30
30
/**
31
31
 * Stores CSS in a file at the given path.
43
43
function css_store_css(theme_config $theme, $csspath, array $cssfiles, $chunk = false, $chunkurl = null) {
44
44
    global $CFG;
45
45
 
 
46
    $css = '';
 
47
    foreach ($cssfiles as $file) {
 
48
        $css .= file_get_contents($file)."\n";
 
49
    }
 
50
 
46
51
    // Check if both the CSS optimiser is enabled and the theme supports it.
47
52
    if (!empty($CFG->enablecssoptimiser) && $theme->supportscssoptimisation) {
48
53
        // This is an experimental feature introduced in Moodle 2.3
51
56
        // the CSS before it is cached removing excess styles and rules and stripping
52
57
        // out any extraneous content such as comments and empty rules.
53
58
        $optimiser = new css_optimiser;
54
 
        $css = '';
55
 
        foreach ($cssfiles as $file) {
56
 
            $css .= file_get_contents($file)."\n";
57
 
        }
58
59
        $css = $theme->post_process($css);
59
60
        $css = $optimiser->process($css);
60
61
 
61
62
        // If cssoptimisestats is set then stats from the optimisation are collected
62
 
        // and output at the beginning of the CSS
 
63
        // and output at the beginning of the CSS.
63
64
        if (!empty($CFG->cssoptimiserstats)) {
64
65
            $css = $optimiser->output_stats_css().$css;
65
66
        }
73
74
        // However it has the distinct disadvantage of having to minify the CSS
74
75
        // before running the post process functions. Potentially things may break
75
76
        // here if theme designers try to push things with CSS post processing.
76
 
        $css = $theme->post_process(css_minify_css($cssfiles));
77
 
    }
78
 
 
79
 
    if ($chunk) {
80
 
        // Chunk the CSS if requried.
81
 
        $css = css_chunk_by_selector_count($css, $chunkurl);
82
 
    } else {
83
 
        $css = array($css);
 
77
        $css = $theme->post_process($css);
 
78
        $css = core_minify::css($css);
84
79
    }
85
80
 
86
81
    clearstatcache();
92
87
    // the rename() should be more atomic than fwrite().
93
88
    ignore_user_abort(true);
94
89
 
95
 
    $files = count($css);
96
 
    $count = 0;
97
 
    foreach ($css as $content) {
98
 
        if ($files > 1 && ($count+1) !== $files) {
99
 
            // If there is more than one file and this is not the last file.
100
 
            $filename = preg_replace('#\.css$#', '.'.$count.'.css', $csspath);
 
90
    // First up write out the single file for all those using decent browsers.
 
91
    css_write_file($csspath, $css);
 
92
 
 
93
    if ($chunk) {
 
94
        // If we need to chunk the CSS for browsers that are sub-par.
 
95
        $css = css_chunk_by_selector_count($css, $chunkurl);
 
96
        $files = count($css);
 
97
        $count = 1;
 
98
        foreach ($css as $content) {
 
99
            if ($count === $files) {
 
100
                // If there is more than one file and this IS the last file.
 
101
                $filename = preg_replace('#\.css$#', '.0.css', $csspath);
 
102
            } else {
 
103
                // If there is more than one file and this is not the last file.
 
104
                $filename = preg_replace('#\.css$#', '.'.$count.'.css', $csspath);
 
105
            }
101
106
            $count++;
102
 
        } else {
103
 
            $filename = $csspath;
104
 
        }
105
 
        if ($fp = fopen($filename.'.tmp', 'xb')) {
106
 
            fwrite($fp, $content);
107
 
            fclose($fp);
108
 
            rename($filename.'.tmp', $filename);
109
 
            @chmod($filename, $CFG->filepermissions);
110
 
            @unlink($filename.'.tmp'); // just in case anything fails
 
107
            css_write_file($filename, $content);
111
108
        }
112
109
    }
113
110
 
118
115
}
119
116
 
120
117
/**
 
118
 * Writes a CSS file.
 
119
 *
 
120
 * @param string $filename
 
121
 * @param string $content
 
122
 */
 
123
function css_write_file($filename, $content) {
 
124
    global $CFG;
 
125
    if ($fp = fopen($filename.'.tmp', 'xb')) {
 
126
        fwrite($fp, $content);
 
127
        fclose($fp);
 
128
        rename($filename.'.tmp', $filename);
 
129
        @chmod($filename, $CFG->filepermissions);
 
130
        @unlink($filename.'.tmp'); // Just in case anything fails.
 
131
    }
 
132
}
 
133
 
 
134
/**
121
135
 * Takes CSS and chunks it if the number of selectors within it exceeds $maxselectors.
122
136
 *
 
137
 * The chunking will not split a group of selectors, or a media query. That means that
 
138
 * if n > $maxselectors and there are n selectors grouped together,
 
139
 * they will not be chunked and you could end up with more selectors than desired.
 
140
 * The same applies for a media query that has more than n selectors.
 
141
 *
 
142
 * Also, as we do not split group of selectors or media queries, the chunking might
 
143
 * not be as optimal as it could be, having files with less selectors than it could
 
144
 * potentially contain.
 
145
 *
 
146
 * String functions used here are not compliant with unicode characters. But that is
 
147
 * not an issue as the syntax of CSS is using ASCII codes. Even if we have unicode
 
148
 * characters in comments, or in the property 'content: ""', it will behave correcly.
 
149
 *
 
150
 * Please note that this strips out the comments if chunking happens.
 
151
 *
123
152
 * @param string $css The CSS to chunk.
124
153
 * @param string $importurl The URL to use for import statements.
125
154
 * @param int $maxselectors The number of selectors to limit a chunk to.
126
 
 * @param int $buffer The buffer size to use when chunking. You shouldn't need to reduce this
127
 
 *      unless you are lowering the maximum selectors.
 
155
 * @param int $buffer Not used any more.
128
156
 * @return array An array of CSS chunks.
129
157
 */
130
158
function css_chunk_by_selector_count($css, $importurl, $maxselectors = 4095, $buffer = 50) {
 
159
 
131
160
    // Check if we need to chunk this CSS file.
132
161
    $count = substr_count($css, ',') + substr_count($css, '{');
133
162
    if ($count < $maxselectors) {
135
164
        return array($css);
136
165
    }
137
166
 
138
 
    // Chunk time ?!
139
 
    // Split the CSS by array, making sure to save the delimiter in the process.
140
 
    $parts = preg_split('#([,\}])#', $css, null, PREG_SPLIT_DELIM_CAPTURE + PREG_SPLIT_NO_EMPTY);
141
 
    // We need to chunk the array. Each delimiter is stored separately so we multiple by 2.
142
 
    // We also subtract 100 to give us a small buffer just in case.
143
 
    $parts = array_chunk($parts, $maxselectors * 2 - $buffer * 2);
144
 
    $css = array();
145
 
    $partcount = count($parts);
146
 
    foreach ($parts as $key => $chunk) {
147
 
        if (end($chunk) === ',') {
148
 
            // Damn last element was a comma.
149
 
            // Pretty much the only way to deal with this is to take the styles from the end of the
150
 
            // comma separated chain of selectors and apply it to the last selector we have here in place
151
 
            // of the comma.
152
 
            // Unit tests are essential for making sure this works.
153
 
            $styles = false;
154
 
            $i = $key;
155
 
            while ($styles === false && $i < ($partcount - 1)) {
156
 
                $i++;
157
 
                $nextpart = $parts[$i];
158
 
                foreach ($nextpart as $style) {
159
 
                    if (strpos($style, '{') !== false) {
160
 
                        $styles = preg_replace('#^[^\{]+#', '', $style);
161
 
                        break;
162
 
                    }
163
 
                }
164
 
            }
165
 
            if ($styles === false) {
166
 
                $styles = '/** Error chunking CSS **/';
167
 
            } else {
168
 
                $styles .= '}';
169
 
            }
170
 
            array_pop($chunk);
171
 
            array_push($chunk, $styles);
172
 
        }
173
 
        $css[] = join('', $chunk);
174
 
    }
175
 
    // The array $css now contains CSS split into perfect sized chunks.
 
167
    $chunks = array();                  // The final chunks.
 
168
    $offsets = array();                 // The indexes to chunk at.
 
169
    $offset = 0;                        // The current offset.
 
170
    $selectorcount = 0;                 // The number of selectors since the last split.
 
171
    $lastvalidoffset = 0;               // The last valid index to split at.
 
172
    $lastvalidoffsetselectorcount = 0;  // The number of selectors used at the time were could split.
 
173
    $inrule = 0;                        // The number of rules we are in, should not be greater than 1.
 
174
    $inmedia = false;                   // Whether or not we are in a media query.
 
175
    $mediacoming = false;               // Whether or not we are expeting a media query.
 
176
    $currentoffseterror = null;         // Not null when we have recorded an error for the current split.
 
177
    $offseterrors = array();            // The offsets where we found errors.
 
178
 
 
179
    // Remove the comments. Because it's easier, safer and probably a lot of other good reasons.
 
180
    $css = preg_replace('#/\*(.*?)\*/#s', '', $css);
 
181
    $strlen = strlen($css);
 
182
 
 
183
    // Walk through the CSS content character by character.
 
184
    for ($i = 1; $i <= $strlen; $i++) {
 
185
        $char = $css[$i - 1];
 
186
        $offset = $i;
 
187
 
 
188
        // Is that a media query that I see coming towards us?
 
189
        if ($char === '@') {
 
190
            if (!$inmedia && substr($css, $offset, 5) === 'media') {
 
191
                $mediacoming = true;
 
192
            }
 
193
        }
 
194
 
 
195
        // So we are entering a rule or a media query...
 
196
        if ($char === '{') {
 
197
            if ($mediacoming) {
 
198
                $inmedia = true;
 
199
                $mediacoming = false;
 
200
            } else {
 
201
                $inrule++;
 
202
                $selectorcount++;
 
203
            }
 
204
        }
 
205
 
 
206
        // Let's count the number of selectors, but only if we are not in a rule as they
 
207
        // can contain commas too.
 
208
        if (!$inrule && $char === ',') {
 
209
            $selectorcount++;
 
210
        }
 
211
 
 
212
        // We reached the end of something.
 
213
        if ($char === '}') {
 
214
            // Oh, we are in a media query.
 
215
            if ($inmedia) {
 
216
                if (!$inrule) {
 
217
                    // This is the end of the media query.
 
218
                    $inmedia = false;
 
219
                } else {
 
220
                    // We were in a rule, in the media query.
 
221
                    $inrule--;
 
222
                }
 
223
            } else {
 
224
                $inrule--;
 
225
            }
 
226
 
 
227
            // We are not in a media query, and there is no pending rule, it is safe to split here.
 
228
            if (!$inmedia && !$inrule) {
 
229
                $lastvalidoffset = $offset;
 
230
                $lastvalidoffsetselectorcount = $selectorcount;
 
231
            }
 
232
        }
 
233
 
 
234
        // Alright, this is splitting time...
 
235
        if ($selectorcount > $maxselectors) {
 
236
            if (!$lastvalidoffset) {
 
237
                // We must have reached more selectors into one set than we were allowed. That means that either
 
238
                // the chunk size value is too small, or that we have a gigantic group of selectors, or that a media
 
239
                // query contains more selectors than the chunk size. We have to ignore this because we do not
 
240
                // support split inside a group of selectors or media query.
 
241
                if ($currentoffseterror === null) {
 
242
                    $currentoffseterror = $offset;
 
243
                    $offseterrors[] = $currentoffseterror;
 
244
                }
 
245
            } else {
 
246
                // We identify the offset to split at and reset the number of selectors found from there.
 
247
                $offsets[] = $lastvalidoffset;
 
248
                $selectorcount = $selectorcount - $lastvalidoffsetselectorcount;
 
249
                $lastvalidoffset = 0;
 
250
                $currentoffseterror = null;
 
251
            }
 
252
        }
 
253
    }
 
254
 
 
255
    // Report offset errors.
 
256
    if (!empty($offseterrors)) {
 
257
        debugging('Could not find a safe place to split at offset(s): ' . implode(', ', $offseterrors) . '. Those were ignored.',
 
258
            DEBUG_DEVELOPER);
 
259
    }
 
260
 
 
261
    // Now that we have got the offets, we can chunk the CSS.
 
262
    $offsetcount = count($offsets);
 
263
    foreach ($offsets as $key => $index) {
 
264
        $start = 0;
 
265
        if ($key > 0) {
 
266
            $start = $offsets[$key - 1];
 
267
        }
 
268
        // From somewhere up to the offset.
 
269
        $chunks[] = substr($css, $start, $index - $start);
 
270
    }
 
271
    // Add the last chunk (if there is one), from the last offset to the end of the string.
 
272
    if (end($offsets) != $strlen) {
 
273
        $chunks[] = substr($css, end($offsets));
 
274
    }
 
275
 
 
276
    // The array $chunks now contains CSS split into perfect sized chunks.
176
277
    // Import statements can only appear at the very top of a CSS file.
177
278
    // Imported sheets are applied in the the order they are imported and
178
279
    // are followed by the contents of the CSS.
183
284
    // followed by the contents of the final chunk in the actual sheet.
184
285
    $importcss = '';
185
286
    $slashargs = strpos($importurl, '.php?') === false;
186
 
    $parts = count($css);
187
 
    for ($i = 0; $i < $parts - 1; $i++) {
 
287
    $parts = count($chunks);
 
288
    for ($i = 1; $i < $parts; $i++) {
188
289
        if ($slashargs) {
189
290
            $importcss .= "@import url({$importurl}/chunk{$i});\n";
190
291
        } else {
191
292
            $importcss .= "@import url({$importurl}&chunk={$i});\n";
192
293
        }
193
294
    }
194
 
    $importcss .= end($css);
195
 
    $css[key($css)] = $importcss;
196
 
 
197
 
    return $css;
198
 
}
199
 
 
200
 
/**
201
 
 * Sends IE specific CSS
202
 
 *
203
 
 * In writing the CSS parser I have a theory that we could optimise the CSS
204
 
 * then split it based upon the number of selectors to ensure we dont' break IE
205
 
 * and that we include only as many sub-stylesheets as we require.
206
 
 * Of course just a theory but may be fun to code.
207
 
 *
208
 
 * @param string $themename The name of the theme we are sending CSS for.
209
 
 * @param string $rev The revision to ensure we utilise the cache.
210
 
 * @param string $etag The revision to ensure we utilise the cache.
211
 
 * @param bool $slasharguments
212
 
 */
213
 
function css_send_ie_css($themename, $rev, $etag, $slasharguments) {
214
 
    global $CFG;
215
 
 
216
 
    $lifetime = 60*60*24*60; // 60 days only - the revision may get incremented quite often
217
 
 
218
 
    $relroot = preg_replace('|^http.?://[^/]+|', '', $CFG->wwwroot);
219
 
 
220
 
    $css  = "/** Unfortunately IE6-9 does not support more than 4096 selectors in one CSS file, which means we have to use some ugly hacks :-( **/";
221
 
    if ($slasharguments) {
222
 
        $css .= "\n@import url($relroot/styles.php/$themename/$rev/plugins);";
223
 
        $css .= "\n@import url($relroot/styles.php/$themename/$rev/parents);";
224
 
        $css .= "\n@import url($relroot/styles.php/$themename/$rev/theme);";
225
 
    } else {
226
 
        $css .= "\n@import url($relroot/styles.php?theme=$themename&rev=$rev&type=plugins);";
227
 
        $css .= "\n@import url($relroot/styles.php?theme=$themename&rev=$rev&type=parents);";
228
 
        $css .= "\n@import url($relroot/styles.php?theme=$themename&rev=$rev&type=theme);";
229
 
    }
230
 
 
231
 
    header('Etag: "'.$etag.'"');
232
 
    header('Content-Disposition: inline; filename="styles.php"');
233
 
    header('Last-Modified: '. gmdate('D, d M Y H:i:s', time()) .' GMT');
234
 
    header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
235
 
    header('Pragma: ');
236
 
    header('Cache-Control: public, max-age='.$lifetime);
237
 
    header('Accept-Ranges: none');
238
 
    header('Content-Type: text/css; charset=utf-8');
239
 
    header('Content-Length: '.strlen($css));
240
 
 
241
 
    echo $css;
242
 
    die;
 
295
    $importcss .= end($chunks);
 
296
    $chunks[key($chunks)] = $importcss;
 
297
 
 
298
    return $chunks;
243
299
}
244
300
 
245
301
/**
252
308
 * @param string $etag The revision to make sure we utilise any caches.
253
309
 */
254
310
function css_send_cached_css($csspath, $etag) {
255
 
    $lifetime = 60*60*24*60; // 60 days only - the revision may get incremented quite often
 
311
    // 60 days only - the revision may get incremented quite often.
 
312
    $lifetime = 60*60*24*60;
256
313
 
257
314
    header('Etag: "'.$etag.'"');
258
315
    header('Content-Disposition: inline; filename="styles.php"');
282
339
 *
283
340
 * @param string $css
284
341
 */
285
 
function css_send_uncached_css($css, $themesupportsoptimisation = true) {
286
 
    global $CFG;
287
 
 
 
342
function css_send_uncached_css($css) {
288
343
    header('Content-Disposition: inline; filename="styles_debug.php"');
289
344
    header('Last-Modified: '. gmdate('D, d M Y H:i:s', time()) .' GMT');
290
345
    header('Expires: '. gmdate('D, d M Y H:i:s', time() + THEME_DESIGNER_CACHE_LIFETIME) .' GMT');
295
350
    if (is_array($css)) {
296
351
        $css = implode("\n\n", $css);
297
352
    }
298
 
 
299
353
    echo $css;
300
 
 
301
354
    die;
302
355
}
303
356
 
304
357
/**
305
358
 * Send file not modified headers
 
359
 *
306
360
 * @param int $lastmodified
307
361
 * @param string $etag
308
362
 */
309
363
function css_send_unmodified($lastmodified, $etag) {
310
 
    $lifetime = 60*60*24*60; // 60 days only - the revision may get incremented quite often
 
364
    // 60 days only - the revision may get incremented quite often.
 
365
    $lifetime = 60*60*24*60;
311
366
    header('HTTP/1.1 304 Not Modified');
312
367
    header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
313
368
    header('Cache-Control: public, max-age='.$lifetime);
328
383
}
329
384
 
330
385
/**
331
 
 * Uses the minify library to compress CSS.
332
 
 *
333
 
 * This is used if $CFG->enablecssoptimiser has been turned off. This was
334
 
 * the original CSS optimisation library.
335
 
 * It removes whitespace and shrinks things but does no apparent optimisation.
336
 
 * Note the minify library is still being used for JavaScript.
337
 
 *
338
 
 * @param array $files An array of files to minify
339
 
 * @return string The minified CSS
340
 
 */
341
 
function css_minify_css($files) {
342
 
    global $CFG;
343
 
 
344
 
    if (empty($files)) {
345
 
        return '';
346
 
    }
347
 
 
348
 
    // We do not really want any 304 here!
349
 
    // There does not seem to be any better way to prevent them here.
350
 
    unset($_SERVER['HTTP_IF_NONE_MATCH']);
351
 
    unset($_SERVER['HTTP_IF_MODIFIED_SINCE']);
352
 
 
353
 
    set_include_path($CFG->libdir . '/minify/lib' . PATH_SEPARATOR . get_include_path());
354
 
    require_once('Minify.php');
355
 
 
356
 
    if (0 === stripos(PHP_OS, 'win')) {
357
 
        Minify::setDocRoot(); // IIS may need help
358
 
    }
359
 
    // disable all caching, we do it in moodle
360
 
    Minify::setCache(null, false);
361
 
 
362
 
    $options = array(
363
 
        // JSMin is not GNU GPL compatible, use the plus version instead.
364
 
        'minifiers' => array(Minify::TYPE_JS => array('JSMinPlus', 'minify')),
365
 
        'bubbleCssImports' => false,
366
 
        // Don't gzip content we just want text for storage
367
 
        'encodeOutput' => false,
368
 
        // Maximum age to cache, not used but required
369
 
        'maxAge' => (60*60*24*20),
370
 
        // The files to minify
371
 
        'files' => $files,
372
 
        // Turn orr URI rewriting
373
 
        'rewriteCssUris' => false,
374
 
        // This returns the CSS rather than echoing it for display
375
 
        'quiet' => true
376
 
    );
377
 
 
378
 
    $error = 'unknown';
379
 
    try {
380
 
        $result = Minify::serve('Files', $options);
381
 
        if ($result['success'] and $result['statusCode'] == 200) {
382
 
            return $result['content'];
383
 
        }
384
 
    } catch (Exception $e) {
385
 
        $error = $e->getMessage();
386
 
        $error = str_replace("\r", ' ', $error);
387
 
        $error = str_replace("\n", ' ', $error);
388
 
    }
389
 
 
390
 
    // minification failed - try to inform the theme developer and include the non-minified version
391
 
    $css = <<<EOD
392
 
/* Error: $error */
393
 
/* Problem detected during theme CSS minimisation, please review the following code */
394
 
/* ================================================================================ */
395
 
 
396
 
 
397
 
EOD;
398
 
    foreach ($files as $cssfile) {
399
 
        $css .= file_get_contents($cssfile)."\n";
400
 
    }
401
 
    return $css;
402
 
}
403
 
 
404
 
/**
405
386
 * Determines if the given value is a valid CSS colour.
406
387
 *
407
388
 * A CSS colour can be one of the following:
432
413
    } else if (in_array(strtolower($value), array_keys(css_optimiser::$htmlcolours))) {
433
414
        return true;
434
415
    } else if (preg_match($rgb, $value, $m) && $m[1] < 256 && $m[2] < 256 && $m[3] < 256) {
435
 
        // It is an RGB colour
 
416
        // It is an RGB colour.
436
417
        return true;
437
418
    } else if (preg_match($rgba, $value, $m) && $m[1] < 256 && $m[2] < 256 && $m[3] < 256) {
438
 
        // It is an RGBA colour
 
419
        // It is an RGBA colour.
439
420
        return true;
440
421
    } else if (preg_match($hsl, $value, $m) && $m[1] <= 360 && $m[2] <= 100 && $m[3] <= 100) {
441
 
        // It is an HSL colour
 
422
        // It is an HSL colour.
442
423
        return true;
443
424
    } else if (preg_match($hsla, $value, $m) && $m[1] <= 360 && $m[2] <= 100 && $m[3] <= 100) {
444
 
        // It is an HSLA colour
 
425
        // It is an HSLA colour.
445
426
        return true;
446
427
    }
447
428
    // Doesn't look like a colour.
484
465
}
485
466
 
486
467
/**
487
 
 * A basic CSS optimiser that strips out unwanted things and then processing the
488
 
 * CSS organising styles and moving duplicates and useless CSS.
 
468
 * A basic CSS optimiser that strips out unwanted things and then processes CSS organising and cleaning styles.
489
469
 *
490
470
 * This CSS optimiser works by reading through a CSS string one character at a
491
471
 * time and building an object structure of the CSS.
494
474
 * then combined into an optimised form to keep them as short as possible.
495
475
 *
496
476
 * @package core
497
 
 * @category css
 
477
 * @subpackage cssoptimiser
498
478
 * @copyright 2012 Sam Hemelryk
499
479
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
500
480
 */
608
588
     * @return string The optimised CSS
609
589
     */
610
590
    public function process($css) {
611
 
        global $CFG;
612
 
 
613
 
        // Easiest win there is
 
591
        // Easiest win there is.
614
592
        $css = trim($css);
615
593
 
616
594
        $this->reset_stats();
631
609
        $css = preg_replace('#\r?\n#', ' ', $css);
632
610
 
633
611
        // Next remove the comments... no need to them in an optimised world and
634
 
        // knowing they're all gone allows us to REALLY make our processing simpler
 
612
        // knowing they're all gone allows us to REALLY make our processing simpler.
635
613
        $css = preg_replace('#/\*(.*?)\*/#m', '', $css, -1, $this->commentsincss);
636
614
 
637
615
        $medias = array(
649
627
        $inbraces = false;      // {
650
628
        $inbrackets = false;    // [
651
629
        $inparenthesis = false; // (
 
630
        /* @var css_media $currentmedia */
652
631
        $currentmedia = $medias['all'];
653
632
        $currentatrule = null;
654
633
        $suspectatrule = false;
665
644
                $suspectatrule = true;
666
645
            }
667
646
            switch ($currentprocess) {
668
 
                // Start processing an @ rule e.g. @media, @page, @keyframes
 
647
                // Start processing an @ rule e.g. @media, @page, @keyframes.
669
648
                case self::PROCESSING_ATRULE:
670
649
                    switch ($char) {
671
650
                        case ';':
683
662
                                $buffer = '';
684
663
                                $currentatrule = false;
685
664
                            }
686
 
                            // continue 1: The switch processing chars
687
 
                            // continue 2: The switch processing the state
688
 
                            // continue 3: The for loop
 
665
                            // Continue 1: The switch processing chars
 
666
                            // Continue 2: The switch processing the state
 
667
                            // Continue 3: The for loop.
689
668
                            continue 3;
690
669
                        case '{':
691
 
                            if ($currentatrule == 'media' && preg_match('#\s*@media\s*([a-zA-Z0-9]+(\s*,\s*[a-zA-Z0-9]+)*)\s*{#', $buffer, $matches)) {
692
 
                                // Basic media declaration
 
670
                            $regexmediabasic = '#\s*@media\s*([a-zA-Z0-9]+(\s*,\s*[a-zA-Z0-9]+)*)\s*{#';
 
671
                            $regexadvmedia = '#\s*@media\s*([^{]+)#';
 
672
                            $regexkeyframes = '#@((\-moz\-|\-webkit\-|\-ms\-|\-o\-)?keyframes)\s*([^\s]+)#';
 
673
 
 
674
                            if ($currentatrule == 'media' && preg_match($regexmediabasic, $buffer, $matches)) {
 
675
                                // Basic media declaration.
693
676
                                $mediatypes = str_replace(' ', '', $matches[1]);
694
677
                                if (!array_key_exists($mediatypes, $medias)) {
695
678
                                    $medias[$mediatypes] = new css_media($mediatypes);
697
680
                                $currentmedia = $medias[$mediatypes];
698
681
                                $currentprocess = self::PROCESSING_SELECTORS;
699
682
                                $buffer = '';
700
 
                            } else if ($currentatrule == 'media' && preg_match('#\s*@media\s*([^{]+)#', $buffer, $matches)) {
701
 
                                // Advanced media query declaration http://www.w3.org/TR/css3-mediaqueries/
 
683
                            } else if ($currentatrule == 'media' && preg_match($regexadvmedia, $buffer, $matches)) {
 
684
                                // Advanced media query declaration http://www.w3.org/TR/css3-mediaqueries/.
702
685
                                $mediatypes = $matches[1];
703
686
                                $hash = md5($mediatypes);
704
687
                                $medias[$hash] = new css_media($mediatypes);
705
688
                                $currentmedia = $medias[$hash];
706
689
                                $currentprocess = self::PROCESSING_SELECTORS;
707
690
                                $buffer = '';
708
 
                            } else if ($currentatrule == 'keyframes' && preg_match('#@((\-moz\-|\-webkit\-)?keyframes)\s*([^\s]+)#', $buffer, $matches)) {
 
691
                            } else if ($currentatrule == 'keyframes' && preg_match($regexkeyframes, $buffer, $matches)) {
709
692
                                // Keyframes declaration, we treat it exactly like a @media declaration except we don't allow
710
 
                                // them to be overridden to ensure we don't mess anything up. (means we keep everything in order)
 
693
                                // them to be overridden to ensure we don't mess anything up. (means we keep everything in order).
711
694
                                $keyframefor = $matches[1];
712
695
                                $keyframename = $matches[3];
713
696
                                $keyframe = new css_keyframe($keyframefor, $keyframename);
716
699
                                $currentprocess = self::PROCESSING_SELECTORS;
717
700
                                $buffer = '';
718
701
                            }
719
 
                            // continue 1: The switch processing chars
720
 
                            // continue 2: The switch processing the state
721
 
                            // continue 3: The for loop
 
702
                            // Continue 1: The switch processing chars
 
703
                            // Continue 2: The switch processing the state
 
704
                            // Continue 3: The for loop.
722
705
                            continue 3;
723
706
                    }
724
707
                    break;
725
 
                // Start processing selectors
 
708
                // Start processing selectors.
726
709
                case self::PROCESSING_START:
727
710
                case self::PROCESSING_SELECTORS:
 
711
                    $regexatrule = '#@(media|import|charset|(\-moz\-|\-webkit\-|\-ms\-|\-o\-)?(keyframes))\s*#';
728
712
                    switch ($char) {
729
713
                        case '[':
730
714
                            $inbrackets ++;
731
715
                            $buffer .= $char;
732
 
                            // continue 1: The switch processing chars
733
 
                            // continue 2: The switch processing the state
734
 
                            // continue 3: The for loop
 
716
                            // Continue 1: The switch processing chars
 
717
                            // Continue 2: The switch processing the state
 
718
                            // Continue 3: The for loop.
735
719
                            continue 3;
736
720
                        case ']':
737
721
                            $inbrackets --;
738
722
                            $buffer .= $char;
739
 
                            // continue 1: The switch processing chars
740
 
                            // continue 2: The switch processing the state
741
 
                            // continue 3: The for loop
 
723
                            // Continue 1: The switch processing chars
 
724
                            // Continue 2: The switch processing the state
 
725
                            // Continue 3: The for loop.
742
726
                            continue 3;
743
727
                        case ' ':
744
728
                            if ($inbrackets) {
745
 
                                // continue 1: The switch processing chars
746
 
                                // continue 2: The switch processing the state
747
 
                                // continue 3: The for loop
 
729
                                // Continue 1: The switch processing chars
 
730
                                // Continue 2: The switch processing the state
 
731
                                // Continue 3: The for loop.
748
732
                                continue 3;
749
733
                            }
750
734
                            if (!empty($buffer)) {
751
 
                                // Check for known @ rules
752
 
                                if ($suspectatrule && preg_match('#@(media|import|charset|(\-moz\-|\-webkit\-)?(keyframes))\s*#', $buffer, $matches)) {
 
735
                                // Check for known @ rules.
 
736
                                if ($suspectatrule && preg_match($regexatrule, $buffer, $matches)) {
753
737
                                    $currentatrule = (!empty($matches[3]))?$matches[3]:$matches[1];
754
738
                                    $currentprocess = self::PROCESSING_ATRULE;
755
739
                                    $buffer .= $char;
759
743
                                }
760
744
                            }
761
745
                            $suspectatrule = false;
762
 
                            // continue 1: The switch processing chars
763
 
                            // continue 2: The switch processing the state
764
 
                            // continue 3: The for loop
 
746
                            // Continue 1: The switch processing chars
 
747
                            // Continue 2: The switch processing the state
 
748
                            // Continue 3: The for loop.
765
749
                            continue 3;
766
750
                        case '{':
767
751
                            if ($inbrackets) {
768
 
                                // continue 1: The switch processing chars
769
 
                                // continue 2: The switch processing the state
770
 
                                // continue 3: The for loop
 
752
                                // Continue 1: The switch processing chars
 
753
                                // Continue 2: The switch processing the state
 
754
                                // Continue 3: The for loop.
 
755
                                continue 3;
 
756
                            }
 
757
                            // Check for known @ rules.
 
758
                            if ($suspectatrule && preg_match($regexatrule, $buffer, $matches)) {
 
759
                                // Ahh we've been in an @rule, lets rewind one and have the @rule case process this.
 
760
                                $currentatrule = (!empty($matches[3]))?$matches[3]:$matches[1];
 
761
                                $currentprocess = self::PROCESSING_ATRULE;
 
762
                                $i--;
 
763
                                $suspectatrule = false;
 
764
                                // Continue 1: The switch processing chars
 
765
                                // Continue 2: The switch processing the state
 
766
                                // Continue 3: The for loop.
771
767
                                continue 3;
772
768
                            }
773
769
                            if ($buffer !== '') {
778
774
                            $currentprocess = self::PROCESSING_STYLES;
779
775
 
780
776
                            $buffer = '';
781
 
                            // continue 1: The switch processing chars
782
 
                            // continue 2: The switch processing the state
783
 
                            // continue 3: The for loop
 
777
                            // Continue 1: The switch processing chars
 
778
                            // Continue 2: The switch processing the state
 
779
                            // Continue 3: The for loop.
784
780
                            continue 3;
785
781
                        case '}':
786
782
                            if ($inbrackets) {
787
 
                                // continue 1: The switch processing chars
788
 
                                // continue 2: The switch processing the state
789
 
                                // continue 3: The for loop
 
783
                                // Continue 1: The switch processing chars
 
784
                                // Continue 2: The switch processing the state
 
785
                                // Continue 3: The for loop.
790
786
                                continue 3;
791
787
                            }
792
788
                            if ($currentatrule == 'media') {
798
794
                                $currentatrule = false;
799
795
                                $buffer = '';
800
796
                            }
801
 
                            // continue 1: The switch processing chars
802
 
                            // continue 2: The switch processing the state
803
 
                            // continue 3: The for loop
 
797
                            // Continue 1: The switch processing chars
 
798
                            // Continue 2: The switch processing the state
 
799
                            // Continue 3: The for loop.
804
800
                            continue 3;
805
801
                        case ',':
806
802
                            if ($inbrackets) {
807
 
                                // continue 1: The switch processing chars
808
 
                                // continue 2: The switch processing the state
809
 
                                // continue 3: The for loop
 
803
                                // Continue 1: The switch processing chars
 
804
                                // Continue 2: The switch processing the state
 
805
                                // Continue 3: The for loop.
810
806
                                continue 3;
811
807
                            }
812
808
                            $currentselector->add($buffer);
813
809
                            $currentrule->add_selector($currentselector);
814
810
                            $currentselector = css_selector::init();
815
811
                            $buffer = '';
816
 
                            // continue 1: The switch processing chars
817
 
                            // continue 2: The switch processing the state
818
 
                            // continue 3: The for loop
 
812
                            // Continue 1: The switch processing chars
 
813
                            // Continue 2: The switch processing the state
 
814
                            // Continue 3: The for loop.
819
815
                            continue 3;
820
816
                    }
821
817
                    break;
822
 
                // Start processing styles
 
818
                // Start processing styles.
823
819
                case self::PROCESSING_STYLES:
824
820
                    if ($char == '"' || $char == "'") {
825
821
                        if ($inquotes === false) {
837
833
                        case ';':
838
834
                            if ($inparenthesis) {
839
835
                                $buffer .= $char;
840
 
                                // continue 1: The switch processing chars
841
 
                                // continue 2: The switch processing the state
842
 
                                // continue 3: The for loop
 
836
                                // Continue 1: The switch processing chars
 
837
                                // Continue 2: The switch processing the state
 
838
                                // Continue 3: The for loop.
843
839
                                continue 3;
844
840
                            }
845
841
                            $currentrule->add_style($buffer);
846
842
                            $buffer = '';
847
843
                            $inquotes = false;
848
 
                            // continue 1: The switch processing chars
849
 
                            // continue 2: The switch processing the state
850
 
                            // continue 3: The for loop
 
844
                            // Continue 1: The switch processing chars
 
845
                            // Continue 2: The switch processing the state
 
846
                            // Continue 3: The for loop.
851
847
                            continue 3;
852
848
                        case '}':
853
849
                            $currentrule->add_style($buffer);
861
857
                            $buffer = '';
862
858
                            $inquotes = false;
863
859
                            $inparenthesis = false;
864
 
                            // continue 1: The switch processing chars
865
 
                            // continue 2: The switch processing the state
866
 
                            // continue 3: The for loop
 
860
                            // Continue 1: The switch processing chars
 
861
                            // Continue 2: The switch processing the state
 
862
                            // Continue 3: The for loop.
867
863
                            continue 3;
868
864
                        case '(':
869
865
                            $inparenthesis = true;
870
866
                            $buffer .= $char;
871
 
                            // continue 1: The switch processing chars
872
 
                            // continue 2: The switch processing the state
873
 
                            // continue 3: The for loop
 
867
                            // Continue 1: The switch processing chars
 
868
                            // Continue 2: The switch processing the state
 
869
                            // Continue 3: The for loop.
874
870
                            continue 3;
875
871
                        case ')':
876
872
                            $inparenthesis = false;
877
873
                            $buffer .= $char;
878
 
                            // continue 1: The switch processing chars
879
 
                            // continue 2: The switch processing the state
880
 
                            // continue 3: The for loop
 
874
                            // Continue 1: The switch processing chars
 
875
                            // Continue 2: The switch processing the state
 
876
                            // Continue 3: The for loop.
881
877
                            continue 3;
882
878
                    }
883
879
                    break;
898
894
     * Produces CSS for the given charset, imports, media, and keyframes
899
895
     * @param string $charset
900
896
     * @param array $imports
901
 
     * @param array $medias
902
 
     * @param array $keyframes
 
897
     * @param css_media[] $medias
 
898
     * @param css_keyframe[] $keyframes
903
899
     * @return string
904
900
     */
905
901
    protected function produce_css($charset, array $imports, array $medias, array $keyframes) {
916
912
        $cssstandard = array();
917
913
        $csskeyframes = array();
918
914
 
919
 
        // Process each media declaration individually
 
915
        // Process each media declaration individually.
920
916
        foreach ($medias as $media) {
921
 
            // If this declaration applies to all media types
 
917
            // If this declaration applies to all media types.
922
918
            if (in_array('all', $media->get_types())) {
923
919
                // Collect all rules that represet reset rules and remove them from the media object at the same time.
924
920
                // We do this because we prioritise reset rules to the top of a CSS output. This ensures that they
928
924
                    $cssreset[] = css_writer::media('all', $resetrules);
929
925
                }
930
926
            }
931
 
            // Get the standard cSS
 
927
            // Get the standard cSS.
932
928
            $cssstandard[] = $media->out();
933
929
        }
934
930
 
944
940
            }
945
941
        }
946
942
 
947
 
        // Join it all together
 
943
        // Join it all together.
948
944
        $css .= join('', $cssreset);
949
945
        $css .= join('', $cssstandard);
950
946
        $css .= join('', $csskeyframes);
952
948
        // Record the strlenght of the now optimised CSS.
953
949
        $this->optimisedstrlen = strlen($css);
954
950
 
955
 
        // Return the now produced CSS
 
951
        // Return the now produced CSS.
956
952
        return $css;
957
953
    }
958
954
 
991
987
            'improvementrules'     => '-',
992
988
            'improvementselectors'     => '-',
993
989
        );
994
 
        // Avoid division by 0 errors by checking we have valid raw values
 
990
        // Avoid division by 0 errors by checking we have valid raw values.
995
991
        if ($this->rawstrlen > 0) {
996
992
            $stats['improvementstrlen'] = round(100 - ($this->optimisedstrlen / $this->rawstrlen) * 100, 1).'%';
997
993
        }
1022
1018
    public function get_errors($clear = false) {
1023
1019
        $errors = $this->errors;
1024
1020
        if ($clear) {
1025
 
            // Reset the error array
 
1021
            // Reset the error array.
1026
1022
            $this->errors = array();
1027
1023
        }
1028
1024
        return $errors;
1106
1102
     * This reference table is used to allow us to unify colours, and will aid
1107
1103
     * us in identifying buggy CSS using unsupported colours.
1108
1104
     *
1109
 
     * @staticvar array
1110
 
     * @var array
 
1105
     * @var string[]
1111
1106
     */
1112
1107
    public static $htmlcolours = array(
1113
1108
        'aliceblue' => '#F0F8FF',
1265
1260
 * Used to prepare CSS strings
1266
1261
 *
1267
1262
 * @package core
1268
 
 * @category css
 
1263
 * @subpackage cssoptimiser
1269
1264
 * @copyright 2012 Sam Hemelryk
1270
1265
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1271
1266
 */
1323
1318
     * Returns CSS for media
1324
1319
     *
1325
1320
     * @param string $typestring
1326
 
     * @param array $rules An array of css_rule objects
 
1321
     * @param css_rule[] $rules An array of css_rule objects
1327
1322
     * @return string
1328
1323
     */
1329
1324
    public static function media($typestring, array &$rules) {
1349
1344
     *
1350
1345
     * @param string $for The desired declaration. e.g. keyframes, -moz-keyframes, -webkit-keyframes
1351
1346
     * @param string $name The name for the keyframe
1352
 
     * @param array $rules An array of rules belonging to the keyframe
 
1347
     * @param css_rule[] $rules An array of rules belonging to the keyframe
1353
1348
     * @return string
1354
1349
     */
1355
1350
    public static function keyframe($for, $name, array &$rules) {
1356
 
        $nl = self::get_separator();
1357
 
 
1358
1351
        $output = "\n@{$for} {$name} {";
1359
1352
        foreach ($rules as $rule) {
1360
1353
            $output .= $rule->out();
1378
1371
    /**
1379
1372
     * Returns CSS for the selectors of a rule
1380
1373
     *
1381
 
     * @param array $selectors Array of css_selector objects
 
1374
     * @param css_selector[] $selectors Array of css_selector objects
1382
1375
     * @return string
1383
1376
     */
1384
1377
    public static function selectors(array $selectors) {
1403
1396
    /**
1404
1397
     * Returns a CSS string for the provided styles
1405
1398
     *
1406
 
     * @param array $styles Array of css_style objects
 
1399
     * @param css_style[] $styles Array of css_style objects
1407
1400
     * @return string
1408
1401
     */
1409
1402
    public static function styles(array $styles) {
1413
1406
            // An advanced style is a style with one or more values, and can occur in situations like background-image
1414
1407
            // where browse specific values are being used.
1415
1408
            if (is_array($style)) {
 
1409
                /* @var css_style[] $style */
1416
1410
                foreach ($style as $advstyle) {
1417
1411
                    $bits[] = $advstyle->out();
1418
1412
                }
1441
1435
}
1442
1436
 
1443
1437
/**
 
1438
 * A consolidatable style interface.
 
1439
 *
 
1440
 * Class that implement this have a short-hand notation for specifying multiple styles.
 
1441
 *
 
1442
 * @package core
 
1443
 * @subpackage cssoptimiser
 
1444
 * @copyright 2012 Sam Hemelryk
 
1445
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 
1446
 */
 
1447
interface core_css_consolidatable_style {
 
1448
    /**
 
1449
     * Used to consolidate several styles into a single "short-hand" style.
 
1450
     * @param array $styles
 
1451
     * @return mixed
 
1452
     */
 
1453
    public static function consolidate(array $styles);
 
1454
}
 
1455
 
 
1456
/**
1444
1457
 * A structure to represent a CSS selector.
1445
1458
 *
1446
1459
 * The selector is the classes, id, elements, and psuedo bits that make up a CSS
1447
1460
 * rule.
1448
1461
 *
1449
1462
 * @package core
1450
 
 * @category css
 
1463
 * @subpackage cssoptimiser
1451
1464
 * @copyright 2012 Sam Hemelryk
1452
1465
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1453
1466
 */
1483
1496
    /**
1484
1497
     * CSS selectors can only be created through the init method above.
1485
1498
     */
1486
 
    protected function __construct() {}
 
1499
    protected function __construct() {
 
1500
        // Nothing to do here by default.
 
1501
    }
1487
1502
 
1488
1503
    /**
1489
1504
     * Adds a selector to the end of the current selector
1496
1511
        if (strpos($selector, '.') !== 0 && strpos($selector, '#') !== 0) {
1497
1512
            $count ++;
1498
1513
        }
1499
 
        // If its already false then no need to continue, its not basic
 
1514
        // If its already false then no need to continue, its not basic.
1500
1515
        if ($this->isbasic !== false) {
1501
 
            // If theres more than one part making up this selector its not basic
 
1516
            // If theres more than one part making up this selector its not basic.
1502
1517
            if ($count > 1) {
1503
1518
                $this->isbasic = false;
1504
1519
            } else {
1505
 
                // Check whether it is a basic element (a-z+) with possible psuedo selector
 
1520
                // Check whether it is a basic element (a-z+) with possible psuedo selector.
1506
1521
                $this->isbasic = (bool)preg_match('#^[a-z]+(:[a-zA-Z]+)?$#', $selector);
1507
1522
            }
1508
1523
        }
1538
1553
 * A structure to represent a CSS rule.
1539
1554
 *
1540
1555
 * @package core
1541
 
 * @category css
 
1556
 * @subpackage cssoptimiser
1542
1557
 * @copyright 2012 Sam Hemelryk
1543
1558
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1544
1559
 */
1546
1561
 
1547
1562
    /**
1548
1563
     * An array of CSS selectors {@link css_selector}
1549
 
     * @var array
 
1564
     * @var css_selector[]
1550
1565
     */
1551
1566
    protected $selectors = array();
1552
1567
 
1553
1568
    /**
1554
1569
     * An array of CSS styles {@link css_style}
1555
 
     * @var array
 
1570
     * @var css_style[]
1556
1571
     */
1557
1572
    protected $styles = array();
1558
1573
 
1568
1583
     * Constructs a new css rule.
1569
1584
     *
1570
1585
     * @param string $selector The selector or array of selectors that make up this rule.
1571
 
     * @param array $styles An array of styles that belong to this rule.
 
1586
     * @param css_style[] $styles An array of styles that belong to this rule.
1572
1587
     */
1573
1588
    protected function __construct($selector = null, array $styles = array()) {
1574
1589
        if ($selector != null) {
1613
1628
        } else if ($style instanceof css_style) {
1614
1629
            // Clone the style as it may be coming from another rule and we don't
1615
1630
            // want references as it will likely be overwritten by proceeding
1616
 
            // rules
 
1631
            // rules.
1617
1632
            $style = clone($style);
1618
1633
        }
1619
1634
        if ($style instanceof css_style) {
1649
1664
     * This method simply iterates over the array and calls {@link css_rule::add_style()}
1650
1665
     * with each.
1651
1666
     *
1652
 
     * @param array $styles Adds an array of styles
 
1667
     * @param css_style[] $styles Adds an array of styles
1653
1668
     */
1654
1669
    public function add_styles(array $styles) {
1655
1670
        foreach ($styles as $style) {
1660
1675
    /**
1661
1676
     * Returns the array of selectors
1662
1677
     *
1663
 
     * @return array
 
1678
     * @return css_selector[]
1664
1679
     */
1665
1680
    public function get_selectors() {
1666
1681
        return $this->selectors;
1669
1684
    /**
1670
1685
     * Returns the array of styles
1671
1686
     *
1672
 
     * @return array
 
1687
     * @return css_style[]
1673
1688
     */
1674
1689
    public function get_styles() {
1675
1690
        return $this->styles;
1689
1704
    /**
1690
1705
     * Consolidates all styles associated with this rule
1691
1706
     *
1692
 
     * @return array An array of consolidated styles
 
1707
     * @return css_style[] An array of consolidated styles
1693
1708
     */
1694
1709
    public function get_consolidated_styles() {
 
1710
        /* @var css_style[] $organisedstyles */
1695
1711
        $organisedstyles = array();
 
1712
        /* @var css_style[] $finalstyles */
1696
1713
        $finalstyles = array();
 
1714
        /* @var core_css_consolidatable_style[] $consolidate */
1697
1715
        $consolidate = array();
 
1716
        /* @var css_style[] $advancedstyles */
1698
1717
        $advancedstyles = array();
1699
1718
        foreach ($this->styles as $style) {
1700
1719
            // If the style is an array then we are processing an advanced style. An advanced style is a style that can have
1703
1722
                $single = null;
1704
1723
                $count = 0;
1705
1724
                foreach ($style as $advstyle) {
 
1725
                    /* @var css_style $advstyle */
1706
1726
                    $key = $count++;
1707
1727
                    $advancedstyles[$key] = $advstyle;
1708
1728
                    if (!$advstyle->allows_multiple_values()) {
1716
1736
                    $style = $advancedstyles[$single];
1717
1737
 
1718
1738
                    $consolidatetoclass = $style->consolidate_to();
1719
 
                    if (($style->is_valid() || $style->is_special_empty_value()) && !empty($consolidatetoclass) && class_exists('css_style_'.$consolidatetoclass)) {
 
1739
                    if (($style->is_valid() || $style->is_special_empty_value()) && !empty($consolidatetoclass) &&
 
1740
                            class_exists('css_style_'.$consolidatetoclass)) {
1720
1741
                        $class = 'css_style_'.$consolidatetoclass;
1721
1742
                        if (!array_key_exists($class, $consolidate)) {
1722
1743
                            $consolidate[$class] = array();
1730
1751
                continue;
1731
1752
            }
1732
1753
            $consolidatetoclass = $style->consolidate_to();
1733
 
            if (($style->is_valid() || $style->is_special_empty_value()) && !empty($consolidatetoclass) && class_exists('css_style_'.$consolidatetoclass)) {
 
1754
            if (($style->is_valid() || $style->is_special_empty_value()) && !empty($consolidatetoclass) &&
 
1755
                    class_exists('css_style_'.$consolidatetoclass)) {
1734
1756
                $class = 'css_style_'.$consolidatetoclass;
1735
1757
                if (!array_key_exists($class, $consolidate)) {
1736
1758
                    $consolidate[$class] = array();
1743
1765
        }
1744
1766
 
1745
1767
        foreach ($consolidate as $class => $styles) {
1746
 
            $organisedstyles[$class] = $class::consolidate($styles);
 
1768
            $organisedstyles[$class] = call_user_func(array($class, 'consolidate'), $styles);
1747
1769
        }
1748
1770
 
1749
1771
        foreach ($organisedstyles as $style) {
1763
1785
     * Splits this rules into an array of CSS rules. One for each of the selectors
1764
1786
     * that make up this rule.
1765
1787
     *
1766
 
     * @return array(css_rule)
 
1788
     * @return css_rule[]
1767
1789
     */
1768
1790
    public function split_by_selector() {
1769
1791
        $return = array();
1777
1799
     * Splits this rule into an array of rules. One for each of the styles that
1778
1800
     * make up this rule
1779
1801
     *
1780
 
     * @return array Array of css_rule objects
 
1802
     * @return css_rule[] Array of css_rule objects
1781
1803
     */
1782
1804
    public function split_by_style() {
1783
1805
        $return = array();
1830
1852
    public function has_errors() {
1831
1853
        foreach ($this->styles as $style) {
1832
1854
            if (is_array($style)) {
 
1855
                /* @var css_style[] $style */
1833
1856
                foreach ($style as $advstyle) {
1834
1857
                    if ($advstyle->has_error()) {
1835
1858
                        return true;
1857
1880
        $errors = array();
1858
1881
        foreach ($this->styles as $style) {
1859
1882
            if (is_array($style)) {
1860
 
                foreach ($style as $s) {
1861
 
                    if ($style instanceof css_style && $style->has_error()) {
1862
 
                        $errors[] = "  * ".$style->get_last_error();
 
1883
                /* @var css_style[] $style */
 
1884
                foreach ($style as $advstyle) {
 
1885
                    if ($advstyle instanceof css_style && $advstyle->has_error()) {
 
1886
                        $errors[] = "  * ".$advstyle->get_last_error();
1863
1887
                    }
1864
1888
                }
1865
1889
            } else if ($style instanceof css_style && $style->has_error()) {
1894
1918
 * When no declaration is specified rules accumulate into @media all.
1895
1919
 *
1896
1920
 * @package core
1897
 
 * @category css
 
1921
 * @subpackage cssoptimiser
1898
1922
 * @copyright 2012 Sam Hemelryk
1899
1923
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1900
1924
 */
1901
1925
abstract class css_rule_collection {
1902
1926
    /**
1903
1927
     * An array of rules within this collection instance
1904
 
     * @var array
 
1928
     * @var css_rule[]
1905
1929
     */
1906
1930
    protected $rules = array();
1907
1931
 
1929
1953
    /**
1930
1954
     * Returns the rules used by this collection
1931
1955
     *
1932
 
     * @return array
 
1956
     * @return css_rule[]
1933
1957
     */
1934
1958
    public function get_rules() {
1935
1959
        return $this->rules;
1942
1966
     * @return bool True if the CSS was optimised by this method
1943
1967
     */
1944
1968
    public function organise_rules_by_selectors() {
1945
 
        $optimised = array();
 
1969
        /* @var css_rule[] $optimisedrules */
 
1970
        $optimisedrules = array();
1946
1971
        $beforecount = count($this->rules);
1947
1972
        $lasthash = null;
 
1973
        /* @var css_rule $lastrule */
1948
1974
        $lastrule = null;
1949
1975
        foreach ($this->rules as $rule) {
1950
1976
            $hash = $rule->get_style_hash();
1956
1982
            }
1957
1983
            $lastrule = clone($rule);
1958
1984
            $lasthash = $hash;
1959
 
            $optimised[] = $lastrule;
 
1985
            $optimisedrules[] = $lastrule;
1960
1986
        }
1961
1987
        $this->rules = array();
1962
 
        foreach ($optimised as $optimised) {
 
1988
        foreach ($optimisedrules as $optimised) {
1963
1989
            $this->rules[$optimised->get_selector_hash()] = $optimised;
1964
1990
        }
1965
1991
        $aftercount = count($this->rules);
2005
2031
    /**
2006
2032
     * Returns any errors that have happened within rules in this collection.
2007
2033
     *
2008
 
     * @return string
 
2034
     * @return string[]
2009
2035
     */
2010
2036
    public function get_errors() {
2011
2037
        $errors = array();
2022
2048
 * A media class to organise rules by the media they apply to.
2023
2049
 *
2024
2050
 * @package core
2025
 
 * @category css
 
2051
 * @subpackage cssoptimiser
2026
2052
 * @copyright 2012 Sam Hemelryk
2027
2053
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2028
2054
 */
2085
2111
 * A media class to organise rules by the media they apply to.
2086
2112
 *
2087
2113
 * @package core
2088
 
 * @category css
 
2114
 * @subpackage cssoptimiser
2089
2115
 * @copyright 2012 Sam Hemelryk
2090
2116
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2091
2117
 */
2092
2118
class css_keyframe extends css_rule_collection {
2093
2119
 
2094
 
    /** @var string $for The directive e.g. keyframes, -moz-keyframes, -webkit-keyframes  */
 
2120
    /**
 
2121
     * The directive e.g. keyframes, -moz-keyframes, -webkit-keyframes
 
2122
     * @var string
 
2123
     */
2095
2124
    protected $for;
2096
2125
 
2097
 
    /** @var string $name The name for the keyframes */
 
2126
    /**
 
2127
     * The name for the keyframes
 
2128
     * @var string
 
2129
     */
2098
2130
    protected $name;
2099
2131
    /**
2100
2132
     * Constructs a new keyframe
2136
2168
 * An absract class to represent CSS styles
2137
2169
 *
2138
2170
 * @package core
2139
 
 * @category css
 
2171
 * @subpackage cssoptimiser
2140
2172
 * @copyright 2012 Sam Hemelryk
2141
2173
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2142
2174
 */
2190
2222
     * @return css_style_generic
2191
2223
     */
2192
2224
    public static function init_automatic($name, $value) {
2193
 
        $specificclass = 'css_style_'.preg_replace('#[^a-zA-Z0-9]+#', '', $name);
 
2225
        $cleanedname = preg_replace('#[^a-zA-Z0-9]+#', '', $name);
 
2226
        $specificclass = 'css_style_'.$cleanedname;
2194
2227
        if (class_exists($specificclass)) {
2195
 
            return $specificclass::init($value);
 
2228
            $style = call_user_func(array($specificclass, 'init'), $value);
 
2229
            if ($cleanedname !== $name && !is_array($style)) {
 
2230
                $style->set_actual_name($name);
 
2231
            }
 
2232
            return $style;
2196
2233
        }
2197
2234
        return new css_style_generic($name, $value);
2198
2235
    }
2368
2405
    public function set_important($important = true) {
2369
2406
        $this->important = (bool) $important;
2370
2407
    }
 
2408
 
 
2409
    /**
 
2410
     * Sets the actual name used within the style.
 
2411
     *
 
2412
     * This method allows us to support browser hacks like *width:0;
 
2413
     *
 
2414
     * @param string $name
 
2415
     */
 
2416
    public function set_actual_name($name) {
 
2417
        $this->name = $name;
 
2418
    }
2371
2419
}
2372
2420
 
2373
2421
/**
2374
2422
 * A generic CSS style class to use when a more specific class does not exist.
2375
2423
 *
2376
2424
 * @package core
2377
 
 * @category css
 
2425
 * @subpackage cssoptimiser
2378
2426
 * @copyright 2012 Sam Hemelryk
2379
2427
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2380
2428
 */
2400
2448
 * A colour CSS style
2401
2449
 *
2402
2450
 * @package core
2403
 
 * @category css
 
2451
 * @subpackage cssoptimiser
2404
2452
 * @copyright 2012 Sam Hemelryk
2405
2453
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2406
2454
 */
2485
2533
 * A width style
2486
2534
 *
2487
2535
 * @package core
2488
 
 * @category css
 
2536
 * @subpackage cssoptimiser
2489
2537
 * @copyright 2012 Sam Hemelryk
2490
2538
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2491
2539
 */
2531
2579
 * A margin style
2532
2580
 *
2533
2581
 * @package core
2534
 
 * @category css
 
2582
 * @subpackage cssoptimiser
2535
2583
 * @copyright 2012 Sam Hemelryk
2536
2584
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2537
2585
 */
2538
 
class css_style_margin extends css_style_width {
 
2586
class css_style_margin extends css_style_width implements core_css_consolidatable_style {
2539
2587
 
2540
2588
    /**
2541
2589
     * Initialises a margin style.
2581
2629
    /**
2582
2630
     * Consolidates individual margin styles into a single margin style
2583
2631
     *
2584
 
     * @param array $styles
2585
 
     * @return array An array of consolidated styles
 
2632
     * @param css_style[] $styles
 
2633
     * @return css_style[] An array of consolidated styles
2586
2634
     */
2587
2635
    public static function consolidate(array $styles) {
2588
2636
        if (count($styles) != 4) {
2670
2718
 * A margin top style
2671
2719
 *
2672
2720
 * @package core
2673
 
 * @category css
 
2721
 * @subpackage cssoptimiser
2674
2722
 * @copyright 2012 Sam Hemelryk
2675
2723
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2676
2724
 */
2700
2748
 * A margin right style
2701
2749
 *
2702
2750
 * @package core
2703
 
 * @category css
 
2751
 * @subpackage cssoptimiser
2704
2752
 * @copyright 2012 Sam Hemelryk
2705
2753
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2706
2754
 */
2730
2778
 * A margin bottom style
2731
2779
 *
2732
2780
 * @package core
2733
 
 * @category css
 
2781
 * @subpackage cssoptimiser
2734
2782
 * @copyright 2012 Sam Hemelryk
2735
2783
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2736
2784
 */
2760
2808
 * A margin left style
2761
2809
 *
2762
2810
 * @package core
2763
 
 * @category css
 
2811
 * @subpackage cssoptimiser
2764
2812
 * @copyright 2012 Sam Hemelryk
2765
2813
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2766
2814
 */
2790
2838
 * A border style
2791
2839
 *
2792
2840
 * @package core
2793
 
 * @category css
 
2841
 * @subpackage cssoptimiser
2794
2842
 * @copyright 2012 Sam Hemelryk
2795
2843
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2796
2844
 */
2797
 
class css_style_border extends css_style {
 
2845
class css_style_border extends css_style implements core_css_consolidatable_style {
2798
2846
 
2799
2847
    /**
2800
2848
     * Initalises the border style into an array of individual style compontents
2812
2860
            if (!css_style_borderwidth::is_border_width($width)) {
2813
2861
                $width = '0';
2814
2862
            }
2815
 
            $return[] = new css_style_borderwidth('border-top-width', $width);
2816
 
            $return[] = new css_style_borderwidth('border-right-width', $width);
2817
 
            $return[] = new css_style_borderwidth('border-bottom-width', $width);
2818
 
            $return[] = new css_style_borderwidth('border-left-width', $width);
 
2863
            $return[] = css_style_bordertopwidth::init($width);
 
2864
            $return[] = css_style_borderrightwidth::init($width);
 
2865
            $return[] = css_style_borderbottomwidth::init($width);
 
2866
            $return[] = css_style_borderleftwidth::init($width);
2819
2867
        }
2820
2868
        if (count($bits) > 0) {
2821
2869
            $style = array_shift($bits);
2822
 
            $return[] = new css_style_borderstyle('border-top-style', $style);
2823
 
            $return[] = new css_style_borderstyle('border-right-style', $style);
2824
 
            $return[] = new css_style_borderstyle('border-bottom-style', $style);
2825
 
            $return[] = new css_style_borderstyle('border-left-style', $style);
 
2870
            $return[] = css_style_bordertopstyle::init($style);
 
2871
            $return[] = css_style_borderrightstyle::init($style);
 
2872
            $return[] = css_style_borderbottomstyle::init($style);
 
2873
            $return[] = css_style_borderleftstyle::init($style);
2826
2874
        }
2827
2875
        if (count($bits) > 0) {
2828
2876
            $colour = array_shift($bits);
2829
 
            $return[] = new css_style_bordercolor('border-top-color', $colour);
2830
 
            $return[] = new css_style_bordercolor('border-right-color', $colour);
2831
 
            $return[] = new css_style_bordercolor('border-bottom-color', $colour);
2832
 
            $return[] = new css_style_bordercolor('border-left-color', $colour);
 
2877
            $return[] = css_style_bordertopcolor::init($colour);
 
2878
            $return[] = css_style_borderrightcolor::init($colour);
 
2879
            $return[] = css_style_borderbottomcolor::init($colour);
 
2880
            $return[] = css_style_borderleftcolor::init($colour);
2833
2881
        }
2834
2882
        return $return;
2835
2883
    }
2837
2885
    /**
2838
2886
     * Consolidates all border styles into a single style
2839
2887
     *
2840
 
     * @param array $styles An array of border styles
2841
 
     * @return array An optimised array of border styles
 
2888
     * @param css_style[] $styles An array of border styles
 
2889
     * @return css_style[] An optimised array of border styles
2842
2890
     */
2843
2891
    public static function consolidate(array $styles) {
2844
2892
 
2905
2953
        $allstylesnull = $allstylesthesame && $nullstyles;
2906
2954
        $allcolorsnull = $allcolorsthesame && $nullcolors;
2907
2955
 
 
2956
        /* @var css_style[] $return */
2908
2957
        $return = array();
2909
2958
        if ($allwidthsnull && $allstylesnull && $allcolorsnull) {
2910
 
            // Everything is null still... boo
 
2959
            // Everything is null still... boo.
2911
2960
            return array(new css_style_border('border', ''));
2912
2961
 
2913
2962
        } else if ($allwidthsnull && $allstylesnull) {
2954
3003
                self::consolidate_styles_by_direction($return, 'css_style_bordercolor', 'border-color', $bordercolors);
2955
3004
            }
2956
3005
 
2957
 
        } else if (!$nullwidths && !$nullcolors && !$nullstyles && max(array_count_values($borderwidths)) == 3 && max(array_count_values($borderstyles)) == 3 && max(array_count_values($bordercolors)) == 3) {
 
3006
        } else if (!$nullwidths && !$nullcolors && !$nullstyles &&
 
3007
            max(array_count_values($borderwidths)) == 3 &&
 
3008
            max(array_count_values($borderstyles)) == 3 &&
 
3009
            max(array_count_values($bordercolors)) == 3) {
 
3010
 
2958
3011
            $widthkeys = array();
2959
3012
            $stylekeys = array();
2960
3013
            $colorkeys = array();
2988
3041
 
2989
3042
            if ($widthkeys == $stylekeys && $stylekeys == $colorkeys) {
2990
3043
                $key = $widthkeys[0][0];
2991
 
                self::build_style_string($return, 'css_style_border', 'border',  $borderwidths[$key], $borderstyles[$key], $bordercolors[$key]);
 
3044
                self::build_style_string($return, 'css_style_border', 'border',
 
3045
                    $borderwidths[$key], $borderstyles[$key], $bordercolors[$key]);
2992
3046
                $key = $widthkeys[1][0];
2993
 
                self::build_style_string($return, 'css_style_border'.$key, 'border-'.$key,  $borderwidths[$key], $borderstyles[$key], $bordercolors[$key]);
 
3047
                self::build_style_string($return, 'css_style_border'.$key, 'border-'.$key,
 
3048
                    $borderwidths[$key], $borderstyles[$key], $bordercolors[$key]);
2994
3049
            } else {
2995
 
                self::build_style_string($return, 'css_style_bordertop', 'border-top', $borderwidths['top'], $borderstyles['top'], $bordercolors['top']);
2996
 
                self::build_style_string($return, 'css_style_borderright', 'border-right', $borderwidths['right'], $borderstyles['right'], $bordercolors['right']);
2997
 
                self::build_style_string($return, 'css_style_borderbottom', 'border-bottom', $borderwidths['bottom'], $borderstyles['bottom'], $bordercolors['bottom']);
2998
 
                self::build_style_string($return, 'css_style_borderleft', 'border-left', $borderwidths['left'], $borderstyles['left'], $bordercolors['left']);
 
3050
                self::build_style_string($return, 'css_style_bordertop', 'border-top',
 
3051
                    $borderwidths['top'], $borderstyles['top'], $bordercolors['top']);
 
3052
                self::build_style_string($return, 'css_style_borderright', 'border-right',
 
3053
                    $borderwidths['right'], $borderstyles['right'], $bordercolors['right']);
 
3054
                self::build_style_string($return, 'css_style_borderbottom', 'border-bottom',
 
3055
                    $borderwidths['bottom'], $borderstyles['bottom'], $bordercolors['bottom']);
 
3056
                self::build_style_string($return, 'css_style_borderleft', 'border-left',
 
3057
                    $borderwidths['left'], $borderstyles['left'], $bordercolors['left']);
2999
3058
            }
3000
3059
        } else {
3001
 
            self::build_style_string($return, 'css_style_bordertop', 'border-top', $borderwidths['top'], $borderstyles['top'], $bordercolors['top']);
3002
 
            self::build_style_string($return, 'css_style_borderright', 'border-right', $borderwidths['right'], $borderstyles['right'], $bordercolors['right']);
3003
 
            self::build_style_string($return, 'css_style_borderbottom', 'border-bottom', $borderwidths['bottom'], $borderstyles['bottom'], $bordercolors['bottom']);
3004
 
            self::build_style_string($return, 'css_style_borderleft', 'border-left', $borderwidths['left'], $borderstyles['left'], $bordercolors['left']);
 
3060
            self::build_style_string($return, 'css_style_bordertop', 'border-top',
 
3061
                $borderwidths['top'], $borderstyles['top'], $bordercolors['top']);
 
3062
            self::build_style_string($return, 'css_style_borderright', 'border-right',
 
3063
                $borderwidths['right'], $borderstyles['right'], $bordercolors['right']);
 
3064
            self::build_style_string($return, 'css_style_borderbottom', 'border-bottom',
 
3065
                $borderwidths['bottom'], $borderstyles['bottom'], $bordercolors['bottom']);
 
3066
            self::build_style_string($return, 'css_style_borderleft', 'border-left',
 
3067
                $borderwidths['left'], $borderstyles['left'], $bordercolors['left']);
3005
3068
        }
3006
3069
        foreach ($return as $key => $style) {
3007
3070
            if ($style->get_value() == '') {
3034
3097
     * @param string $left The left value
3035
3098
     * @return bool
3036
3099
     */
3037
 
    public static function consolidate_styles_by_direction(&$array, $class, $style, $top, $right = null, $bottom = null, $left = null) {
 
3100
    public static function consolidate_styles_by_direction(&$array, $class, $style,
 
3101
                                                           $top, $right = null, $bottom = null, $left = null) {
3038
3102
        if (is_array($top)) {
3039
3103
            $right = $top['right'];
3040
3104
            $bottom = $top['bottom'];
3108
3172
 * A border colour style
3109
3173
 *
3110
3174
 * @package core
3111
 
 * @category css
 
3175
 * @subpackage cssoptimiser
3112
3176
 * @copyright 2012 Sam Hemelryk
3113
3177
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3114
3178
 */
3188
3252
 * A border left style
3189
3253
 *
3190
3254
 * @package core
3191
 
 * @category css
 
3255
 * @subpackage cssoptimiser
3192
3256
 * @copyright 2012 Sam Hemelryk
3193
3257
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3194
3258
 */
3231
3295
 * A border right style
3232
3296
 *
3233
3297
 * @package core
3234
 
 * @category css
 
3298
 * @subpackage cssoptimiser
3235
3299
 * @copyright 2012 Sam Hemelryk
3236
3300
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3237
3301
 */
3274
3338
 * A border top style
3275
3339
 *
3276
3340
 * @package core
3277
 
 * @category css
 
3341
 * @subpackage cssoptimiser
3278
3342
 * @copyright 2012 Sam Hemelryk
3279
3343
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3280
3344
 */
3317
3381
 * A border bottom style
3318
3382
 *
3319
3383
 * @package core
3320
 
 * @category css
 
3384
 * @subpackage cssoptimiser
3321
3385
 * @copyright 2012 Sam Hemelryk
3322
3386
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3323
3387
 */
3360
3424
 * A border width style
3361
3425
 *
3362
3426
 * @package core
3363
 
 * @category css
 
3427
 * @subpackage cssoptimiser
3364
3428
 * @copyright 2012 Sam Hemelryk
3365
3429
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3366
3430
 */
3447
3511
 * A border style style
3448
3512
 *
3449
3513
 * @package core
3450
 
 * @category css
 
3514
 * @subpackage cssoptimiser
3451
3515
 * @copyright 2012 Sam Hemelryk
3452
3516
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3453
3517
 */
3500
3564
 * A border top colour style
3501
3565
 *
3502
3566
 * @package core
3503
 
 * @category css
 
3567
 * @subpackage cssoptimiser
3504
3568
 * @copyright 2012 Sam Hemelryk
3505
3569
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3506
3570
 */
3530
3594
 * A border left colour style
3531
3595
 *
3532
3596
 * @package core
3533
 
 * @category css
 
3597
 * @subpackage cssoptimiser
3534
3598
 * @copyright 2012 Sam Hemelryk
3535
3599
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3536
3600
 */
3560
3624
 * A border right colour style
3561
3625
 *
3562
3626
 * @package core
3563
 
 * @category css
 
3627
 * @subpackage cssoptimiser
3564
3628
 * @copyright 2012 Sam Hemelryk
3565
3629
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3566
3630
 */
3590
3654
 * A border bottom colour style
3591
3655
 *
3592
3656
 * @package core
3593
 
 * @category css
 
3657
 * @subpackage cssoptimiser
3594
3658
 * @copyright 2012 Sam Hemelryk
3595
3659
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3596
3660
 */
3620
3684
 * A border width top style
3621
3685
 *
3622
3686
 * @package core
3623
 
 * @category css
 
3687
 * @subpackage cssoptimiser
3624
3688
 * @copyright 2012 Sam Hemelryk
3625
3689
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3626
3690
 */
3650
3714
 * A border width left style
3651
3715
 *
3652
3716
 * @package core
3653
 
 * @category css
 
3717
 * @subpackage cssoptimiser
3654
3718
 * @copyright 2012 Sam Hemelryk
3655
3719
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3656
3720
 */
3680
3744
 * A border width right style
3681
3745
 *
3682
3746
 * @package core
3683
 
 * @category css
 
3747
 * @subpackage cssoptimiser
3684
3748
 * @copyright 2012 Sam Hemelryk
3685
3749
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3686
3750
 */
3710
3774
 * A border width bottom style
3711
3775
 *
3712
3776
 * @package core
3713
 
 * @category css
 
3777
 * @subpackage cssoptimiser
3714
3778
 * @copyright 2012 Sam Hemelryk
3715
3779
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3716
3780
 */
3740
3804
 * A border top style
3741
3805
 *
3742
3806
 * @package core
3743
 
 * @category css
 
3807
 * @subpackage cssoptimiser
3744
3808
 * @copyright 2012 Sam Hemelryk
3745
3809
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3746
3810
 */
3770
3834
 * A border left style
3771
3835
 *
3772
3836
 * @package core
3773
 
 * @category css
 
3837
 * @subpackage cssoptimiser
3774
3838
 * @copyright 2012 Sam Hemelryk
3775
3839
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3776
3840
 */
3800
3864
 * A border right style
3801
3865
 *
3802
3866
 * @package core
3803
 
 * @category css
 
3867
 * @subpackage cssoptimiser
3804
3868
 * @copyright 2012 Sam Hemelryk
3805
3869
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3806
3870
 */
3830
3894
 * A border bottom style
3831
3895
 *
3832
3896
 * @package core
3833
 
 * @category css
 
3897
 * @subpackage cssoptimiser
3834
3898
 * @copyright 2012 Sam Hemelryk
3835
3899
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3836
3900
 */
3860
3924
 * A background style
3861
3925
 *
3862
3926
 * @package core
3863
 
 * @category css
 
3927
 * @subpackage cssoptimiser
3864
3928
 * @copyright 2012 Sam Hemelryk
3865
3929
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3866
3930
 */
3867
 
class css_style_background extends css_style {
 
3931
class css_style_background extends css_style implements core_css_consolidatable_style {
3868
3932
 
3869
3933
    /**
3870
3934
     * Initialises a background style
3873
3937
     * @return array An array of background component.
3874
3938
     */
3875
3939
    public static function init($value) {
3876
 
        // colour - image - repeat - attachment - position
3877
 
 
 
3940
        // Colour - image - repeat - attachment - position.
3878
3941
        $imageurl = null;
3879
3942
        if (preg_match('#url\(([^\)]+)\)#', $value, $matches)) {
3880
3943
            $imageurl = trim($matches[1]);
3881
3944
            $value = str_replace($matches[1], '', $value);
3882
3945
        }
3883
3946
 
3884
 
        // Switch out the brackets so that they don't get messed up when we explode
 
3947
        // Switch out the brackets so that they don't get messed up when we explode.
3885
3948
        $brackets = array();
3886
3949
        $bracketcount = 0;
3887
3950
        while (preg_match('#\([^\)\(]+\)#', $value, $matches)) {
3893
3956
 
3894
3957
        $important = (stripos($value, '!important') !== false);
3895
3958
        if ($important) {
3896
 
            // Great some genius put !important in the background shorthand property
 
3959
            // Great some genius put !important in the background shorthand property.
3897
3960
            $value = str_replace('!important', '', $value);
3898
3961
        }
3899
3962
 
3910
3973
        $attachments = array('scroll' , 'fixed', 'inherit');
3911
3974
        $positions = array('top', 'left', 'bottom', 'right', 'center');
3912
3975
 
 
3976
        /* @var css_style_background[] $return */
3913
3977
        $return = array();
3914
3978
        $unknownbits = array();
3915
3979
 
3933
3997
 
3934
3998
        $attachment = self::NULL_VALUE;
3935
3999
        if (count($bits) > 0 && in_array(reset($bits), $attachments)) {
3936
 
            // scroll , fixed, inherit
 
4000
            // Scroll , fixed, inherit.
3937
4001
            $attachment = array_shift($bits);
3938
4002
        }
3939
4003
 
3971
4035
            }
3972
4036
        }
3973
4037
 
3974
 
        if ($color === self::NULL_VALUE && $image === self::NULL_VALUE && $repeat === self::NULL_VALUE && $attachment === self::NULL_VALUE && $position === self::NULL_VALUE) {
 
4038
        if ($color === self::NULL_VALUE &&
 
4039
            $image === self::NULL_VALUE &&
 
4040
            $repeat === self::NULL_VALUE && $attachment === self::NULL_VALUE &&
 
4041
            $position === self::NULL_VALUE) {
3975
4042
            // All primaries are null, return without doing anything else. There may be advanced madness there.
3976
4043
            return $return;
3977
4044
        }
3978
4045
 
3979
 
        $return[] = new css_style_backgroundcolor('background-color', $color);
3980
 
        $return[] = new css_style_backgroundimage('background-image', $image);
3981
 
        $return[] = new css_style_backgroundrepeat('background-repeat', $repeat);
3982
 
        $return[] = new css_style_backgroundattachment('background-attachment', $attachment);
3983
 
        $return[] = new css_style_backgroundposition('background-position', $position);
 
4046
        $return[] = css_style_backgroundcolor::init($color);
 
4047
        $return[] = css_style_backgroundimage::init($image);
 
4048
        $return[] = css_style_backgroundrepeat::init($repeat);
 
4049
        $return[] = css_style_backgroundattachment::init($attachment);
 
4050
        $return[] = css_style_backgroundposition::init($position);
3984
4051
 
3985
4052
        if ($important) {
3986
4053
            foreach ($return as $style) {
4008
4075
    /**
4009
4076
     * Consolidates background styles into a single background style
4010
4077
     *
4011
 
     * @param array $styles Consolidates the provided array of background styles
4012
 
     * @return array Consolidated optimised background styles
 
4078
     * @param css_style_background[] $styles Consolidates the provided array of background styles
 
4079
     * @return css_style[] Consolidated optimised background styles
4013
4080
     */
4014
4081
    public static function consolidate(array $styles) {
4015
4082
 
4042
4109
            }
4043
4110
        }
4044
4111
 
 
4112
        /* @var css_style[] $organisedstyles */
4045
4113
        $organisedstyles = array();
 
4114
        /* @var css_style[] $advancedstyles */
4046
4115
        $advancedstyles = array();
 
4116
        /* @var css_style[] $importantstyles */
4047
4117
        $importantstyles = array();
4048
4118
        foreach ($styles as $style) {
4049
4119
            if ($style instanceof css_style_backgroundimage_advanced) {
4083
4153
            }
4084
4154
        }
4085
4155
 
 
4156
        /* @var css_style[] $consolidatetosingle */
4086
4157
        $consolidatetosingle = array();
4087
4158
        if (!is_null($color) && !is_null($image) && !is_null($repeat) && !is_null($attachment) && !is_null($position)) {
4088
4159
            // We can use the shorthand background-style!
4110
4181
        }
4111
4182
 
4112
4183
        $return = array();
4113
 
        // Single background style needs to come first;
 
4184
        // Single background style needs to come first.
4114
4185
        if (count($consolidatetosingle) > 0) {
4115
4186
            $returnstyle = new css_style_background('background', join(' ', $consolidatetosingle));
4116
4187
            if ($allimportant) {
4121
4192
        foreach ($styles as $style) {
4122
4193
            $value = null;
4123
4194
            switch ($style->get_name()) {
4124
 
                case 'background-color'      : $value = $color;      break;
4125
 
                case 'background-image'      : $value = $image;      break;
4126
 
                case 'background-repeat'     : $value = $repeat;     break;
4127
 
                case 'background-attachment' : $value = $attachment; break;
4128
 
                case 'background-position'   : $value = $position;   break;
4129
 
                case 'background-clip'       : $value = $clip;       break;
4130
 
                case 'background-origin'     : $value = $origin;     break;
4131
 
                case 'background-size'       : $value = $size;       break;
 
4195
                case 'background-color' :
 
4196
                    $value = $color;
 
4197
                    break;
 
4198
                case 'background-image' :
 
4199
                    $value = $image;
 
4200
                    break;
 
4201
                case 'background-repeat' :
 
4202
                    $value = $repeat;
 
4203
                    break;
 
4204
                case 'background-attachment' :
 
4205
                    $value = $attachment;
 
4206
                    break;
 
4207
                case 'background-position' :
 
4208
                    $value = $position;
 
4209
                    break;
 
4210
                case 'background-clip' :
 
4211
                    $value = $clip;
 
4212
                    break;
 
4213
                case 'background-origin':
 
4214
                    $value = $origin;
 
4215
                    break;
 
4216
                case 'background-size':
 
4217
                    $value = $size;
 
4218
                    break;
4132
4219
            }
4133
4220
            if (!is_null($value)) {
4134
4221
                $return[] = $style;
4143
4230
 * A advanced background style that allows multiple values to preserve unknown entities
4144
4231
 *
4145
4232
 * @package core
4146
 
 * @category css
 
4233
 * @subpackage cssoptimiser
4147
4234
 * @copyright 2012 Sam Hemelryk
4148
4235
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4149
4236
 */
4176
4263
 * Based upon the colour style.
4177
4264
 *
4178
4265
 * @package core
4179
 
 * @category css
 
4266
 * @subpackage cssoptimiser
4180
4267
 * @copyright 2012 Sam Hemelryk
4181
4268
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4182
4269
 */
4227
4314
 * A background image style.
4228
4315
 *
4229
4316
 * @package core
4230
 
 * @category css
 
4317
 * @subpackage cssoptimiser
4231
4318
 * @copyright 2012 Sam Hemelryk
4232
4319
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4233
4320
 */
4240
4327
     * @return css_style_backgroundimage
4241
4328
     */
4242
4329
    public static function init($value) {
4243
 
        if (!preg_match('#^\s*(none|inherit|url\()#i', $value)) {
 
4330
        if ($value !== self::NULL_VALUE && !preg_match('#^\s*(none|inherit|url\()#i', $value)) {
4244
4331
            return css_style_backgroundimage_advanced::init($value);
4245
4332
        }
4246
4333
        return new css_style_backgroundimage('background-image', $value);
4278
4365
}
4279
4366
 
4280
4367
/**
4281
 
 * A background image style that supports mulitple values and masquerades as a background-image
 
4368
 * A background image style that supports multiple values and masquerades as a background-image
4282
4369
 *
4283
4370
 * @package core
4284
 
 * @category css
 
4371
 * @subpackage cssoptimiser
4285
4372
 * @copyright 2012 Sam Hemelryk
4286
4373
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4287
4374
 */
4312
4399
 * A background repeat style.
4313
4400
 *
4314
4401
 * @package core
4315
 
 * @category css
 
4402
 * @subpackage cssoptimiser
4316
4403
 * @copyright 2012 Sam Hemelryk
4317
4404
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4318
4405
 */
4363
4450
 * A background attachment style.
4364
4451
 *
4365
4452
 * @package core
4366
 
 * @category css
 
4453
 * @subpackage cssoptimiser
4367
4454
 * @copyright 2012 Sam Hemelryk
4368
4455
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4369
4456
 */
4414
4501
 * A background position style.
4415
4502
 *
4416
4503
 * @package core
4417
 
 * @category css
 
4504
 * @subpackage cssoptimiser
4418
4505
 * @copyright 2012 Sam Hemelryk
4419
4506
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4420
4507
 */
4465
4552
 * A background size style.
4466
4553
 *
4467
4554
 * @package core
4468
 
 * @category css
 
4555
 * @subpackage cssoptimiser
4469
4556
 * @copyright 2012 Sam Hemelryk
4470
4557
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4471
4558
 */
4495
4582
 * A background clip style.
4496
4583
 *
4497
4584
 * @package core
4498
 
 * @category css
 
4585
 * @subpackage cssoptimiser
4499
4586
 * @copyright 2012 Sam Hemelryk
4500
4587
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4501
4588
 */
4525
4612
 * A background origin style.
4526
4613
 *
4527
4614
 * @package core
4528
 
 * @category css
 
4615
 * @subpackage cssoptimiser
4529
4616
 * @copyright 2012 Sam Hemelryk
4530
4617
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4531
4618
 */
4555
4642
 * A padding style.
4556
4643
 *
4557
4644
 * @package core
4558
 
 * @category css
 
4645
 * @subpackage cssoptimiser
4559
4646
 * @copyright 2012 Sam Hemelryk
4560
4647
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4561
4648
 */
4562
 
class css_style_padding extends css_style_width {
 
4649
class css_style_padding extends css_style_width implements core_css_consolidatable_style {
4563
4650
 
4564
4651
    /**
4565
4652
     * Initialises this padding style into several individual padding styles
4601
4688
    /**
4602
4689
     * Consolidates several padding styles into a single style.
4603
4690
     *
4604
 
     * @param array $styles Array of padding styles
4605
 
     * @return array Optimised+consolidated array of padding styles
 
4691
     * @param css_style_padding[] $styles Array of padding styles
 
4692
     * @return css_style[] Optimised+consolidated array of padding styles
4606
4693
     */
4607
4694
    public static function consolidate(array $styles) {
4608
4695
        if (count($styles) != 4) {
4653
4740
            $left = null;
4654
4741
            foreach ($styles as $style) {
4655
4742
                switch ($style->get_name()) {
4656
 
                    case 'padding-top' : $top = $style->get_value(false);break;
4657
 
                    case 'padding-right' : $right = $style->get_value(false);break;
4658
 
                    case 'padding-bottom' : $bottom = $style->get_value(false);break;
4659
 
                    case 'padding-left' : $left = $style->get_value(false);break;
 
4743
                    case 'padding-top' :
 
4744
                        $top = $style->get_value(false);
 
4745
                        break;
 
4746
                    case 'padding-right' :
 
4747
                        $right = $style->get_value(false);
 
4748
                        break;
 
4749
                    case 'padding-bottom' :
 
4750
                        $bottom = $style->get_value(false);
 
4751
                        break;
 
4752
                    case 'padding-left' :
 
4753
                        $left = $style->get_value(false);
 
4754
                        break;
4660
4755
                }
4661
4756
            }
4662
4757
            if ($top == $bottom && $left == $right) {
4682
4777
 * A padding top style.
4683
4778
 *
4684
4779
 * @package core
4685
 
 * @category css
 
4780
 * @subpackage cssoptimiser
4686
4781
 * @copyright 2012 Sam Hemelryk
4687
4782
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4688
4783
 */
4712
4807
 * A padding right style.
4713
4808
 *
4714
4809
 * @package core
4715
 
 * @category css
 
4810
 * @subpackage cssoptimiser
4716
4811
 * @copyright 2012 Sam Hemelryk
4717
4812
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4718
4813
 */
4742
4837
 * A padding bottom style.
4743
4838
 *
4744
4839
 * @package core
4745
 
 * @category css
 
4840
 * @subpackage cssoptimiser
4746
4841
 * @copyright 2012 Sam Hemelryk
4747
4842
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4748
4843
 */
4772
4867
 * A padding left style.
4773
4868
 *
4774
4869
 * @package core
4775
 
 * @category css
 
4870
 * @subpackage cssoptimiser
4776
4871
 * @copyright 2012 Sam Hemelryk
4777
4872
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4778
4873
 */
4802
4897
 * A cursor style.
4803
4898
 *
4804
4899
 * @package core
4805
 
 * @category css
 
4900
 * @subpackage cssoptimiser
4806
4901
 * @copyright 2012 Sam Hemelryk
4807
4902
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4808
4903
 */
4822
4917
     * @return string
4823
4918
     */
4824
4919
    protected function clean_value($value) {
4825
 
        // Allowed values for the cursor style
 
4920
        // Allowed values for the cursor style.
4826
4921
        $allowed = array('auto', 'crosshair', 'default', 'e-resize', 'help', 'move', 'n-resize', 'ne-resize', 'nw-resize',
4827
4922
                         'pointer', 'progress', 's-resize', 'se-resize', 'sw-resize', 'text', 'w-resize', 'wait', 'inherit');
4828
 
        // Has to be one of the allowed values of an image to use. Loosely match the image... doesn't need to be thorough
 
4923
        // Has to be one of the allowed values of an image to use. Loosely match the image... doesn't need to be thorough.
4829
4924
        if (!in_array($value, $allowed) && !preg_match('#\.[a-zA-Z0-9_\-]{1,5}$#', $value)) {
4830
4925
            $this->set_error('Invalid or unexpected cursor value specified: '.$value);
4831
4926
        }
4837
4932
 * A vertical alignment style.
4838
4933
 *
4839
4934
 * @package core
4840
 
 * @category css
 
4935
 * @subpackage cssoptimiser
4841
4936
 * @copyright 2012 Sam Hemelryk
4842
4937
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4843
4938
 */
4869
4964
 * A float style.
4870
4965
 *
4871
4966
 * @package core
4872
 
 * @category css
 
4967
 * @subpackage cssoptimiser
4873
4968
 * @copyright 2012 Sam Hemelryk
4874
4969
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4875
4970
 */