Esempio n. 1
1
/**
 * Stores CSS in a file at the given path.
 *
 * This function either succeeds or throws an exception.
 *
 * @param theme_config $theme The theme that the CSS belongs to.
 * @param string $csspath The path to store the CSS at.
 * @param string $csscontent the complete CSS in one string
 * @param bool $chunk If set to true these files will be chunked to ensure
 *      that no one file contains more than 4095 selectors.
 * @param string $chunkurl If the CSS is be chunked then we need to know the URL
 *      to use for the chunked files.
 */
function css_store_css(theme_config $theme, $csspath, $csscontent, $chunk = false, $chunkurl = null)
{
    global $CFG;
    clearstatcache();
    if (!file_exists(dirname($csspath))) {
        @mkdir(dirname($csspath), $CFG->directorypermissions, true);
    }
    // Prevent serving of incomplete file from concurrent request,
    // the rename() should be more atomic than fwrite().
    ignore_user_abort(true);
    // First up write out the single file for all those using decent browsers.
    css_write_file($csspath, $csscontent);
    if ($chunk) {
        // If we need to chunk the CSS for browsers that are sub-par.
        $css = css_chunk_by_selector_count($csscontent, $chunkurl);
        $files = count($css);
        $count = 1;
        foreach ($css as $content) {
            if ($count === $files) {
                // If there is more than one file and this IS the last file.
                $filename = preg_replace('#\\.css$#', '.0.css', $csspath);
            } else {
                // If there is more than one file and this is not the last file.
                $filename = preg_replace('#\\.css$#', '.' . $count . '.css', $csspath);
            }
            $count++;
            css_write_file($filename, $content);
        }
    }
    ignore_user_abort(false);
    if (connection_aborted()) {
        die;
    }
}
Esempio n. 2
0
 /**
  * Test CSS chunking
  */
 public function test_css_chunking()
 {
     // Test with an even number of styles.
     $css = 'a{}b{}c{}d{}e{}f{}';
     $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2);
     $this->assertInternalType('array', $chunks);
     $this->assertCount(3, $chunks);
     $this->assertArrayHasKey(0, $chunks);
     $this->assertArrayHasKey(1, $chunks);
     $this->assertArrayHasKey(2, $chunks);
     $this->assertSame('a{}b{}', $chunks[0]);
     $this->assertSame('c{}d{}', $chunks[1]);
     $this->assertSame("@import url(styles.php?type=test&chunk=1);\n@import url(styles.php?type=test&chunk=2);\ne{}f{}", $chunks[2]);
     // Test with an odd number of styles.
     $css = 'a{}b{}c{}d{}e{}';
     $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2);
     $this->assertInternalType('array', $chunks);
     $this->assertCount(3, $chunks);
     $this->assertArrayHasKey(0, $chunks);
     $this->assertArrayHasKey(1, $chunks);
     $this->assertArrayHasKey(2, $chunks);
     $this->assertSame('a{}b{}', $chunks[0]);
     $this->assertSame('c{}d{}', $chunks[1]);
     $this->assertSame("@import url(styles.php?type=test&chunk=1);\n@import url(styles.php?type=test&chunk=2);\ne{}", $chunks[2]);
     // Test well placed commas.
     $css = 'a,b{}c,d{}e,f{}';
     $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2);
     $this->assertInternalType('array', $chunks);
     $this->assertCount(3, $chunks);
     $this->assertArrayHasKey(0, $chunks);
     $this->assertArrayHasKey(1, $chunks);
     $this->assertArrayHasKey(2, $chunks);
     $this->assertSame('a,b{}', $chunks[0]);
     $this->assertSame('c,d{}', $chunks[1]);
     $this->assertSame("@import url(styles.php?type=test&chunk=1);\n@import url(styles.php?type=test&chunk=2);\ne,f{}", $chunks[2]);
     // Test unfortunately placed commas.
     $css = 'a{}b,c{color:red;}d{}e{}f{}';
     $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2);
     $this->assertInternalType('array', $chunks);
     $this->assertCount(4, $chunks);
     $this->assertArrayHasKey(0, $chunks);
     $this->assertArrayHasKey(1, $chunks);
     $this->assertArrayHasKey(2, $chunks);
     $this->assertArrayHasKey(3, $chunks);
     $this->assertSame('a{}', $chunks[0]);
     $this->assertSame('b,c{color:red;}', $chunks[1]);
     $this->assertSame('d{}e{}', $chunks[2]);
     $this->assertSame("@import url(styles.php?type=test&chunk=1);\n@import url(styles.php?type=test&chunk=2);\n@import url(styles.php?type=test&chunk=3);\nf{}", $chunks[3]);
     // Test unfortunate CSS.
     $css = 'a,b,c,d,e,f{color:red;}';
     $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2, 0);
     $this->assertInternalType('array', $chunks);
     $this->assertCount(1, $chunks);
     $this->assertArrayHasKey(0, $chunks);
     $this->assertSame('a,b,c,d,e,f{color:red;}', $chunks[0]);
     $this->assertDebuggingCalled('Could not find a safe place to split at offset(s): 6. Those were ignored.');
     // Test to make sure invalid CSS isn't totally ruined.
     $css = 'a{},,,e{},';
     $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2);
     // Believe it or not we want to care what comes out here as this will be parsed correctly
     // by a browser.
     $this->assertInternalType('array', $chunks);
     $this->assertCount(3, $chunks);
     $this->assertArrayHasKey(0, $chunks);
     $this->assertArrayHasKey(1, $chunks);
     $this->assertArrayHasKey(2, $chunks);
     $this->assertSame('a{}', $chunks[0]);
     $this->assertSame(',,,e{}', $chunks[1]);
     $this->assertSame("@import url(styles.php?type=test&chunk=1);\n@import url(styles.php?type=test&chunk=2);\n,", $chunks[2]);
     $this->assertDebuggingCalled('Could not find a safe place to split at offset(s): 6. Those were ignored.');
     // Test utter crap CSS to make sure we don't loop to our deaths.
     $css = 'a,b,c,d,e,f';
     $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2);
     $this->assertInternalType('array', $chunks);
     $this->assertCount(1, $chunks);
     $this->assertArrayHasKey(0, $chunks);
     $this->assertSame($css, $chunks[0]);
     $this->assertDebuggingCalled('Could not find a safe place to split at offset(s): 6. Those were ignored.');
     // Test another death situation to make sure we're invincible.
     $css = 'a,,,,,e';
     $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2);
     $this->assertInternalType('array', $chunks);
     $this->assertDebuggingCalled('Could not find a safe place to split at offset(s): 4. Those were ignored.');
     // I don't care what the outcome is, I just want to make sure it doesn't die.
     // Test media queries.
     $css = '@media (min-width: 980px) { .a,.b{} }';
     $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2);
     $this->assertCount(1, $chunks);
     $this->assertSame('@media (min-width: 980px) { .a,.b{} }', $chunks[0]);
     // Test media queries, with commas.
     $css = '.a{} @media (min-width: 700px), handheld and (orientation: landscape) { .b{} }';
     $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2);
     $this->assertCount(1, $chunks);
     $this->assertSame($css, $chunks[0]);
     // Test special rules.
     $css = 'a,b{ background-image: linear-gradient(to bottom, #ffffff, #cccccc);}d,e{}';
     $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2);
     $this->assertCount(2, $chunks);
     $this->assertSame('a,b{ background-image: linear-gradient(to bottom, #ffffff, #cccccc);}', $chunks[0]);
     $this->assertSame("@import url(styles.php?type=test&chunk=1);\nd,e{}", $chunks[1]);
     // Test media queries with too many selectors.
     $css = '@media (min-width: 980px) { a,b,c,d{} }';
     $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2);
     $this->assertCount(1, $chunks);
     $this->assertSame('@media (min-width: 980px) { a,b,c,d{} }', $chunks[0]);
     $this->assertDebuggingCalled('Could not find a safe place to split at offset(s): 34. Those were ignored.');
     // Complex test.
     $css = '@media (a) {b{}} c{} d,e{} f,g,h{} i,j{x:a,b,c} k,l{} @media(x){l,m{ y: a,b,c}} n{}';
     $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 3);
     $this->assertCount(6, $chunks);
     $this->assertSame('@media (a) {b{}} c{}', $chunks[0]);
     $this->assertSame(' d,e{}', $chunks[1]);
     $this->assertSame(' f,g,h{}', $chunks[2]);
     $this->assertSame(' i,j{x:a,b,c}', $chunks[3]);
     $this->assertSame(' k,l{}', $chunks[4]);
     $this->assertSame("@import url(styles.php?type=test&chunk=1);\n@import url(styles.php?type=test&chunk=2);\n@import url(styles.php?type=test&chunk=3);\n@import url(styles.php?type=test&chunk=4);\n@import url(styles.php?type=test&chunk=5);\n @media(x){l,m{ y: a,b,c}} n{}", $chunks[5]);
     // Multiple offset errors.
     $css = 'a,b,c{} d,e,f{}';
     $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2);
     $this->assertCount(2, $chunks);
     $this->assertSame('a,b,c{}', $chunks[0]);
     $this->assertSame("@import url(styles.php?type=test&chunk=1);\n d,e,f{}", $chunks[1]);
     $this->assertDebuggingCalled('Could not find a safe place to split at offset(s): 6, 14. Those were ignored.');
     // Test the split according to IE.
     $css = str_repeat('a{}', 4100);
     $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test');
     $this->assertCount(2, $chunks);
     $this->assertSame(str_repeat('a{}', 4095), $chunks[0]);
     $this->assertSame("@import url(styles.php?type=test&chunk=1);\n" . str_repeat('a{}', 5), $chunks[1]);
     // Test strip out comments.
     $css = ".a {/** a\nb\nc */} /** a\nb\nc */ .b{} /** .c,.d{} */ e{}";
     $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2);
     $this->assertCount(2, $chunks);
     $this->assertSame('.a {}  .b{}', $chunks[0]);
     $this->assertSame("@import url(styles.php?type=test&chunk=1);\n  e{}", $chunks[1]);
     // Test something with unicode characters.
     $css = 'a,b{} nav a:hover:after { content: "↓"; } b{ color:test;}';
     $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2, true);
     $this->assertCount(2, $chunks);
     $this->assertSame('a,b{}', $chunks[0]);
     $this->assertSame("@import url(styles.php?type=test&chunk=1);\n nav a:hover:after { content: \"↓\"; } b{ color:test;}", $chunks[1]);
     // Test that if there is broken CSS with too many close brace symbols,
     // media rules after that point are still kept together.
     $mediarule = '@media (width=480) {a{}b{}}';
     $css = 'c{}}' . $mediarule . 'd{}';
     $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2);
     $this->assertCount(3, $chunks);
     $this->assertEquals($mediarule, $chunks[1]);
     // Test that this still works even with too many close brace symbols
     // inside a media query (note: that broken media query may be split
     // after the break, but any following ones should not be).
     $brokenmediarule = '@media (width=480) {c{}}d{}}';
     $css = $brokenmediarule . 'e{}' . $mediarule . 'f{}';
     $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2);
     $this->assertCount(4, $chunks);
     $this->assertEquals($mediarule, $chunks[2]);
 }
Esempio n. 3
0
/**
 * Stores CSS in a file at the given path.
 *
 * This function either succeeds or throws an exception.
 *
 * @param theme_config $theme The theme that the CSS belongs to.
 * @param string $csspath The path to store the CSS at.
 * @param array $cssfiles The CSS files to store.
 * @param bool $chunk If set to true these files will be chunked to ensure
 *      that no one file contains more than 4095 selectors.
 * @param string $chunkurl If the CSS is be chunked then we need to know the URL
 *      to use for the chunked files.
 */
function css_store_css(theme_config $theme, $csspath, array $cssfiles, $chunk = false, $chunkurl = null)
{
    global $CFG;
    // Check if both the CSS optimiser is enabled and the theme supports it.
    if (!empty($CFG->enablecssoptimiser) && $theme->supportscssoptimisation) {
        // This is an experimental feature introduced in Moodle 2.3
        // The CSS optimiser organises the CSS in order to reduce the overall number
        // of rules and styles being sent to the client. It does this by collating
        // the CSS before it is cached removing excess styles and rules and stripping
        // out any extraneous content such as comments and empty rules.
        $optimiser = new css_optimiser();
        $css = '';
        foreach ($cssfiles as $file) {
            $css .= file_get_contents($file) . "\n";
        }
        $css = $theme->post_process($css);
        $css = $optimiser->process($css);
        // If cssoptimisestats is set then stats from the optimisation are collected
        // and output at the beginning of the CSS
        if (!empty($CFG->cssoptimiserstats)) {
            $css = $optimiser->output_stats_css() . $css;
        }
    } else {
        // This is the default behaviour.
        // The cssoptimise setting was introduced in Moodle 2.3 and will hopefully
        // in the future be changed from an experimental setting to the default.
        // The css_minify_css will method will use the Minify library remove
        // comments, additional whitespace and other minor measures to reduce the
        // the overall CSS being sent.
        // However it has the distinct disadvantage of having to minify the CSS
        // before running the post process functions. Potentially things may break
        // here if theme designers try to push things with CSS post processing.
        $css = $theme->post_process(css_minify_css($cssfiles));
    }
    if ($chunk) {
        // Chunk the CSS if requried.
        $css = css_chunk_by_selector_count($css, $chunkurl);
    } else {
        $css = array($css);
    }
    clearstatcache();
    if (!file_exists(dirname($csspath))) {
        @mkdir(dirname($csspath), $CFG->directorypermissions, true);
    }
    // Prevent serving of incomplete file from concurrent request,
    // the rename() should be more atomic than fwrite().
    ignore_user_abort(true);
    $files = count($css);
    $count = 0;
    foreach ($css as $content) {
        if ($files > 1 && $count + 1 !== $files) {
            // If there is more than one file and this is not the last file.
            $filename = preg_replace('#\\.css$#', '.' . $count . '.css', $csspath);
            $count++;
        } else {
            $filename = $csspath;
        }
        if ($fp = fopen($filename . '.tmp', 'xb')) {
            fwrite($fp, $content);
            fclose($fp);
            rename($filename . '.tmp', $filename);
            @chmod($filename, $CFG->filepermissions);
            @unlink($filename . '.tmp');
            // just in case anything fails
        }
    }
    ignore_user_abort(false);
    if (connection_aborted()) {
        die;
    }
}
 /**
  * Test CSS chunking
  */
 public function test_css_chunking()
 {
     // Test with an even number of styles.
     $css = 'a{}b{}c{}d{}e{}f{}';
     $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2, 0);
     $this->assertInternalType('array', $chunks);
     $this->assertCount(3, $chunks);
     $this->assertArrayHasKey(0, $chunks);
     $this->assertArrayHasKey(1, $chunks);
     $this->assertArrayHasKey(2, $chunks);
     $this->assertSame('a{}b{}', $chunks[0]);
     $this->assertSame('c{}d{}', $chunks[1]);
     $this->assertSame("@import url(styles.php?type=test&chunk=1);\n@import url(styles.php?type=test&chunk=2);\ne{}f{}", $chunks[2]);
     // Test with an odd number of styles.
     $css = 'a{}b{}c{}d{}e{}';
     $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2, 0);
     $this->assertInternalType('array', $chunks);
     $this->assertCount(3, $chunks);
     $this->assertArrayHasKey(0, $chunks);
     $this->assertArrayHasKey(1, $chunks);
     $this->assertArrayHasKey(2, $chunks);
     $this->assertSame('a{}b{}', $chunks[0]);
     $this->assertSame('c{}d{}', $chunks[1]);
     $this->assertSame("@import url(styles.php?type=test&chunk=1);\n@import url(styles.php?type=test&chunk=2);\ne{}", $chunks[2]);
     // Test buffering. Set a buffer that will reduce the effective sheet size back to two.
     $css = 'a{}b{}c{}d{}e{}f{}';
     $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 6, 4);
     $this->assertInternalType('array', $chunks);
     $this->assertCount(3, $chunks);
     $this->assertArrayHasKey(0, $chunks);
     $this->assertArrayHasKey(1, $chunks);
     $this->assertArrayHasKey(2, $chunks);
     $this->assertSame('a{}b{}', $chunks[0]);
     $this->assertSame('c{}d{}', $chunks[1]);
     $this->assertSame("@import url(styles.php?type=test&chunk=1);\n@import url(styles.php?type=test&chunk=2);\ne{}f{}", $chunks[2]);
     // Test well placed commas.
     $css = 'a,b{}c,d{}e,f{}';
     $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2, 0);
     $this->assertInternalType('array', $chunks);
     $this->assertCount(3, $chunks);
     $this->assertArrayHasKey(0, $chunks);
     $this->assertArrayHasKey(1, $chunks);
     $this->assertArrayHasKey(2, $chunks);
     $this->assertSame('a,b{}', $chunks[0]);
     $this->assertSame('c,d{}', $chunks[1]);
     $this->assertSame("@import url(styles.php?type=test&chunk=1);\n@import url(styles.php?type=test&chunk=2);\ne,f{}", $chunks[2]);
     // Test unfortunately placed commas.
     $css = 'a{}b,c{color:red;}d{}e{}f{}';
     $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2, 0);
     $this->assertInternalType('array', $chunks);
     $this->assertCount(3, $chunks);
     $this->assertArrayHasKey(0, $chunks);
     $this->assertArrayHasKey(1, $chunks);
     $this->assertArrayHasKey(2, $chunks);
     $this->assertSame('a{}b{color:red;}', $chunks[0]);
     $this->assertSame('c{color:red;}d{}', $chunks[1]);
     $this->assertSame("@import url(styles.php?type=test&chunk=1);\n@import url(styles.php?type=test&chunk=2);\ne{}f{}", $chunks[2]);
     // Test unfortunate CSS.
     $css = 'a,b,c,d,e,f{color:red;}';
     $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2, 0);
     $this->assertInternalType('array', $chunks);
     $this->assertCount(3, $chunks);
     $this->assertArrayHasKey(0, $chunks);
     $this->assertArrayHasKey(1, $chunks);
     $this->assertArrayHasKey(2, $chunks);
     $this->assertSame('a,b{color:red;}', $chunks[0]);
     $this->assertSame('c,d{color:red;}', $chunks[1]);
     $this->assertSame("@import url(styles.php?type=test&chunk=1);\n@import url(styles.php?type=test&chunk=2);\ne,f{color:red;}", $chunks[2]);
     // Test to make sure invalid CSS isn't totally ruined.
     $css = 'a{},,,e{},';
     $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2, 0);
     // Believe it or not we want to care what comes out here as this will be parsed correctly
     // by a browser.
     $this->assertInternalType('array', $chunks);
     $this->assertCount(2, $chunks);
     $this->assertArrayHasKey(0, $chunks);
     $this->assertArrayHasKey(1, $chunks);
     $this->assertSame('a{},{}', $chunks[0]);
     $this->assertSame("@import url(styles.php?type=test&chunk=1);\n,e{}/** Error chunking CSS **/", $chunks[1]);
     // Test utter crap CSS to make sure we don't loop to our deaths.
     $css = 'a,b,c,d,e,f';
     $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2, 0);
     $this->assertInternalType('array', $chunks);
     $this->assertCount(3, $chunks);
     $this->assertArrayHasKey(0, $chunks);
     $this->assertArrayHasKey(1, $chunks);
     $this->assertArrayHasKey(2, $chunks);
     $this->assertSame('a,b/** Error chunking CSS **/', $chunks[0]);
     $this->assertSame('c,d/** Error chunking CSS **/', $chunks[1]);
     $this->assertSame("@import url(styles.php?type=test&chunk=1);\n@import url(styles.php?type=test&chunk=2);\ne,f", $chunks[2]);
     // Test another death situation to make sure we're invincible.
     $css = 'a,,,,,e';
     $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2, 0);
     $this->assertInternalType('array', $chunks);
     // I don't care what the outcome is, I just want to make sure it doesn't die.
 }
Esempio n. 5
0
if ($type === 'editor') {
    $csscontent = $theme->get_css_content_editor();
    css_send_uncached_css($csscontent);
}
$chunkurl = new moodle_url($CFG->httpswwwroot . '/theme/styles_debug.php', array('theme' => $themename, 'type' => $type, 'subtype' => $subtype, 'sheet' => $sheet, 'usesvg' => $usesvg));
// We need some kind of caching here because otherwise the page navigation becomes
// way too slow in theme designer mode. Feel free to create full cache definition later...
$key = "{$type} {$subtype} {$sheet} {$usesvg}";
$cache = cache::make_from_params(cache_store::MODE_APPLICATION, 'core', 'themedesigner', array('theme' => $themename));
if ($content = $cache->get($key)) {
    if ($content['created'] > time() - THEME_DESIGNER_CACHE_LIFETIME) {
        $csscontent = $content['data'];
        // We need to chunk the content.
        if ($chunk !== null) {
            $chunks = css_chunk_by_selector_count($csscontent, $chunkurl->out(false));
            $csscontent = $chunk === 0 ? end($chunks) : $chunks[$chunk - 1];
        }
        css_send_uncached_css($csscontent);
    }
}
$csscontent = $theme->get_css_content_debug($type, $subtype, $sheet);
$cache->set($key, array('data' => $csscontent, 'created' => time()));
// We need to chunk the content.
if ($chunk !== null) {
    // The chunks are ordered so that the last chunk is the one containing the @import, and so
    // the first one to be included. All the other chunks are set in the array before that one.
    // See {@link css_chunk_by_selector_count()} for more details.
    $chunks = css_chunk_by_selector_count($csscontent, $chunkurl->out(false));
    $csscontent = $chunk === 0 ? end($chunks) : $chunks[$chunk - 1];
}
css_send_uncached_css($csscontent);