MDL-55224 output: Support automatic flipping to RTL styles

Part of MDL-55071
This commit is contained in:
Frederic Massart 2016-07-28 20:08:16 +08:00 committed by Dan Poltawski
parent fbe18cc022
commit 1d987cae5f
5 changed files with 206 additions and 16 deletions

81
lib/classes/cssparser.php Normal file
View File

@ -0,0 +1,81 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Moodle implementation of CSS parsing.
*
* @package core
* @copyright 2016 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
// TODO MDL-53016 Remove this when the latter is implemented.
require_once($CFG->libdir . '/php-css-parser/Comment/Commentable.php');
require_once($CFG->libdir . '/php-css-parser/Renderable.php');
require_once($CFG->libdir . '/php-css-parser/Property/AtRule.php');
require_once($CFG->libdir . '/php-css-parser/RuleSet/RuleSet.php');
require_once($CFG->libdir . '/php-css-parser/RuleSet/AtRuleSet.php');
require_once($CFG->libdir . '/php-css-parser/Parsing/SourceException.php');
require_once($CFG->libdir . '/php-css-parser/CSSList/CSSList.php');
require_once($CFG->libdir . '/php-css-parser/CSSList/CSSBlockList.php');
require_once($CFG->libdir . '/php-css-parser/Value/Value.php');
require_once($CFG->libdir . '/php-css-parser/Value/ValueList.php');
require_once($CFG->libdir . '/php-css-parser/Value/CSSFunction.php');
require_once($CFG->libdir . '/php-css-parser/Comment/Comment.php');
require_once($CFG->libdir . '/php-css-parser/Value/PrimitiveValue.php');
require_once($CFG->libdir . '/php-css-parser/CSSList/AtRuleBlockList.php');
require_once($CFG->libdir . '/php-css-parser/CSSList/Document.php');
require_once($CFG->libdir . '/php-css-parser/CSSList/KeyFrame.php');
require_once($CFG->libdir . '/php-css-parser/OutputFormat.php');
require_once($CFG->libdir . '/php-css-parser/Parser.php');
require_once($CFG->libdir . '/php-css-parser/Parsing/OutputException.php');
require_once($CFG->libdir . '/php-css-parser/Parsing/UnexpectedTokenException.php');
require_once($CFG->libdir . '/php-css-parser/Property/Charset.php');
require_once($CFG->libdir . '/php-css-parser/Property/CSSNamespace.php');
require_once($CFG->libdir . '/php-css-parser/Property/Import.php');
require_once($CFG->libdir . '/php-css-parser/Property/Selector.php');
require_once($CFG->libdir . '/php-css-parser/Rule/Rule.php');
require_once($CFG->libdir . '/php-css-parser/RuleSet/DeclarationBlock.php');
require_once($CFG->libdir . '/php-css-parser/Settings.php');
require_once($CFG->libdir . '/php-css-parser/Value/Color.php');
require_once($CFG->libdir . '/php-css-parser/Value/CSSString.php');
require_once($CFG->libdir . '/php-css-parser/Value/RuleValueList.php');
require_once($CFG->libdir . '/php-css-parser/Value/Size.php');
require_once($CFG->libdir . '/php-css-parser/Value/URL.php');
/**
* Moodle CSS parser.
*
* @package core
* @copyright 2016 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_cssparser extends \Sabberworm\CSS\Parser {
/**
* Constructor.
*
* @param string $css The CSS content.
*/
public function __construct($css) {
$settings = \Sabberworm\CSS\Settings::create();
$settings->withLenientParsing();
parent::__construct($css, $settings);
}
}

61
lib/classes/rtlcss.php Normal file
View File

@ -0,0 +1,61 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Moodle implementation of RTLCSS.
*
* @package core
* @copyright 2016 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
// TODO MDL-53016 Remove this when the latter is implemented.
require_once($CFG->libdir . '/rtlcss/RTLCSS.php');
/**
* Moodle RTLCSS class.
*
* @package core
* @copyright 2016 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_rtlcss extends \MoodleHQ\RTLCSS\RTLCSS {
/**
* Process a block declaration.
*
* This is overriden in order to provide a backwards compability with the plugins
* who already defined RTL styles the old-fashioned way. Because we do not need
* those any more we rename them so that they do not apply.
*
* @todo Remove the dir-rtl flipping when dir-rtl is fully deprecated.
* @param \Sabberworm\CSS\RuleSet\RuleSet $node The object.
* @return void
*/
protected function processDeclaration($node) {
$selectors = $node instanceof \Sabberworm\CSS\RuleSet\DeclarationBlock ? $node->getSelectors() : [];
foreach ($selectors as $selector) {
// The blocks containing .dir-rtl are always accepted as is.
if (strpos($selector->getSelector(), '.dir-rtl') !== false) {
return;
}
}
return parent::processDeclaration($node);
}
}

View File

@ -404,6 +404,12 @@ class theme_config {
*/
private $usesvg = null;
/**
* Whether in RTL mode or not.
* @var bool
*/
protected $rtlmode = false;
/**
* The LESS file to compile. When set, the theme will attempt to compile the file itself.
* @var bool
@ -748,6 +754,7 @@ class theme_config {
$separate = (core_useragent::is_ie() && !core_useragent::check_ie_version('10'));
if ($rev > -1) {
$filename = right_to_left() ? 'all-rtl' : 'all';
$url = new moodle_url("$CFG->httpswwwroot/theme/styles.php");
if (!empty($CFG->slasharguments)) {
$slashargs = '';
@ -756,13 +763,13 @@ class theme_config {
// The underscore is used to ensure that it isn't a valid theme name.
$slashargs .= '/_s'.$slashargs;
}
$slashargs .= '/'.$this->name.'/'.$rev.'/all';
$slashargs .= '/'.$this->name.'/'.$rev.'/'.$filename;
if ($separate) {
$slashargs .= '/chunk0';
}
$url->set_slashargument($slashargs, 'noparam', true);
} else {
$params = array('theme' => $this->name,'rev' => $rev, 'type' => 'all');
$params = array('theme' => $this->name,'rev' => $rev, 'type' => $filename);
if (!$svg) {
// We add an SVG param so that we know not to serve SVG images.
// We do this because all modern browsers support SVG and this param will one day be removed.
@ -784,6 +791,9 @@ class theme_config {
// We do this because all modern browsers support SVG and this param will one day be removed.
$baseurl->param('svg', '0');
}
if (right_to_left()) {
$baseurl->param('rtl', 1);
}
if ($separate) {
// We might need to chunk long files.
$baseurl->param('chunk', '0');
@ -899,14 +909,14 @@ class theme_config {
// The SCSS file of the theme is requested.
$csscontent = $this->get_css_content_from_scss(true);
if ($csscontent !== false) {
return $csscontent;
return $this->post_process($csscontent);
}
return '';
} else if ($type === 'less') {
// The LESS file of the theme is requested.
$csscontent = $this->get_css_content_from_less(true);
if ($csscontent !== false) {
return $csscontent;
return $this->post_process($csscontent);
}
return '';
}
@ -1172,8 +1182,6 @@ class theme_config {
// Compile the CSS.
$compiled = $compiler->getCss();
// Post process the entire thing.
$compiled = $this->post_process($compiled);
} catch (Less_Exception_Parser $e) {
$compiled = false;
debugging('Error while compiling LESS ' . $lessfile . ' file: ' . $e->getMessage(), DEBUG_DEVELOPER);
@ -1218,7 +1226,6 @@ class theme_config {
try {
// Compile!
$compiled = $compiler->to_css();
$compiled = $this->post_process($compiled);
} catch (\Leafo\ScssPhp\Exception $e) {
$compiled = false;
@ -1531,6 +1538,23 @@ class theme_config {
}
}
// Post processing using an object representation of CSS.
$needsparsing = !empty($this->rtlmode);
if ($needsparsing) {
$parser = new core_cssparser($css);
$csstree = $parser->parse();
unset($parser);
}
if ($this->rtlmode) {
$this->rtlize($csstree);
}
if ($needsparsing) {
$css = $csstree->render();
unset($csstree);
}
// now resolve all theme settings or do any other postprocessing
$csspostprocess = $this->csspostprocess;
if (function_exists($csspostprocess)) {
@ -1540,6 +1564,17 @@ class theme_config {
return $css;
}
/**
* Flip a stylesheet to RTL.
*
* @param Object $csstree The parsed CSS tree structure to flip.
* @return void
*/
protected function rtlize($csstree) {
$rtlcss = new core_rtlcss($csstree);
$rtlcss->flip();
}
/**
* Return the URL for an image
*
@ -1869,6 +1904,17 @@ class theme_config {
$this->usesvg = (bool)$setting;
}
/**
* Set to be in RTL mode.
*
* This will likely be used when post processing the CSS before serving it.
*
* @param bool $inrtl True when in RTL mode.
*/
public function set_rtl_mode($inrtl = true) {
$this->rtlmode = $inrtl;
}
/**
* Checks if file with any image extension exists.
*

View File

@ -66,7 +66,7 @@ if ($slashargument = min_get_slash_argument()) {
if ($type === 'editor') {
// The editor CSS is never chunked.
$chunk = null;
} else if ($type === 'all') {
} else if ($type === 'all' || $type === 'all-rtl') {
// We're fine.
} else {
css_send_css_not_found();
@ -116,6 +116,7 @@ require("$CFG->dirroot/lib/setup.php");
$theme = theme_config::load($themename);
$theme->force_svg_use($usesvg);
$theme->set_rtl_mode($type === 'all-rtl' ? true : false);
$themerev = theme_get_revision();
@ -150,7 +151,6 @@ if ($type === 'editor') {
} else {
$lock = null;
// Lock system to prevent concurrent requests to compile LESS/SCSS, which is really slow and CPU intensive.
// Each client should wait for one to finish the compilation before starting a new compiling process.
// We only do this when the file will be cached...
@ -173,19 +173,19 @@ if ($type === 'editor') {
$relroot = preg_replace('|^http.?://[^/]+|', '', $CFG->wwwroot);
if (!empty($slashargument)) {
if ($usesvg) {
$chunkurl = "{$relroot}/theme/styles.php/{$themename}/{$rev}/all";
$chunkurl = "{$relroot}/theme/styles.php/{$themename}/{$rev}/$type";
} else {
$chunkurl = "{$relroot}/theme/styles.php/_s/{$themename}/{$rev}/all";
$chunkurl = "{$relroot}/theme/styles.php/_s/{$themename}/{$rev}/$type";
}
} else {
if ($usesvg) {
$chunkurl = "{$relroot}/theme/styles.php?theme={$themename}&rev={$rev}&type=all";
$chunkurl = "{$relroot}/theme/styles.php?theme={$themename}&rev={$rev}&type=$type";
} else {
$chunkurl = "{$relroot}/theme/styles.php?theme={$themename}&rev={$rev}&type=all&svg=0";
$chunkurl = "{$relroot}/theme/styles.php?theme={$themename}&rev={$rev}&type=$type&svg=0";
}
}
css_store_css($theme, "$candidatedir/all.css", $csscontent, true, $chunkurl);
css_store_css($theme, "$candidatedir/$type.css", $csscontent, true, $chunkurl);
// Release the lock.
if ($lock) {

View File

@ -37,6 +37,7 @@ $subtype = optional_param('subtype', '', PARAM_SAFEDIR);
$sheet = optional_param('sheet', '', PARAM_SAFEDIR);
$usesvg = optional_param('svg', 1, PARAM_BOOL);
$chunk = optional_param('chunk', null, PARAM_INT);
$rtl = optional_param('rtl', false, PARAM_BOOL);
if (file_exists("$CFG->dirroot/theme/$themename/config.php")) {
// The theme exists in standard location - ok.
@ -48,6 +49,7 @@ if (file_exists("$CFG->dirroot/theme/$themename/config.php")) {
$theme = theme_config::load($themename);
$theme->force_svg_use($usesvg);
$theme->set_rtl_mode($rtl);
if ($type === 'editor') {
$csscontent = $theme->get_css_content_editor();
@ -55,11 +57,11 @@ if ($type === 'editor') {
}
$chunkurl = new moodle_url($CFG->httpswwwroot . '/theme/styles_debug.php', array('theme' => $themename,
'type' => $type, 'subtype' => $subtype, 'sheet' => $sheet, 'usesvg' => $usesvg));
'type' => $type, 'subtype' => $subtype, 'sheet' => $sheet, 'usesvg' => $usesvg, 'rtl' => $rtl));
// 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";
$key = "$type $subtype $sheet $usesvg $rtl";
$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) {