Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 39 and 310] [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Autoprefixer.
  19   *
  20   * This autoprefixer has been developed to satisfy the basic needs of the
  21   * theme Boost when working with Bootstrap 4 alpha. We do not recommend
  22   * that this tool is shared, nor used outside of this theme.
  23   *
  24   * @package    theme_boost
  25   * @copyright  2016 Frédéric Massart - FMCorz.net
  26   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  27   */
  28  
  29  namespace theme_boost;
  30  defined('MOODLE_INTERNAL') || die();
  31  
  32  use Sabberworm\CSS\CSSList\CSSList;
  33  use Sabberworm\CSS\CSSList\Document;
  34  use Sabberworm\CSS\CSSList\KeyFrame;
  35  use Sabberworm\CSS\OutputFormat;
  36  use Sabberworm\CSS\Parser;
  37  use Sabberworm\CSS\Property\AtRule;
  38  use Sabberworm\CSS\Property\Selector;
  39  use Sabberworm\CSS\Rule\Rule;
  40  use Sabberworm\CSS\RuleSet\AtRuleSet;
  41  use Sabberworm\CSS\RuleSet\DeclarationBlock;
  42  use Sabberworm\CSS\RuleSet\RuleSet;
  43  use Sabberworm\CSS\Settings;
  44  use Sabberworm\CSS\Value\CSSFunction;
  45  use Sabberworm\CSS\Value\CSSString;
  46  use Sabberworm\CSS\Value\PrimitiveValue;
  47  use Sabberworm\CSS\Value\RuleValueList;
  48  use Sabberworm\CSS\Value\Size;
  49  use Sabberworm\CSS\Value\ValueList;
  50  
  51  
  52  /**
  53   * Autoprefixer class.
  54   *
  55   * Very basic implementation covering simple needs for Bootstrap 4.
  56   *
  57   * @package    theme_boost
  58   * @copyright  2016 Frédéric Massart - FMCorz.net
  59   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  60   */
  61  class autoprefixer {
  62  
  63      /** @var object The CSS tree. */
  64      protected $tree;
  65  
  66      /** @var string Pseudo classes regex. */
  67      protected $pseudosregex;
  68  
  69      /** @var array At rules prefixes. */
  70      protected static $atrules = [
  71          'keyframes' => ['-webkit-', '-o-']
  72      ];
  73  
  74      /** @var array Pseudo classes prefixes. */
  75      protected static $pseudos = [
  76          '::placeholder' => ['::-webkit-input-placeholder', '::-moz-placeholder', ':-ms-input-placeholder']
  77      ];
  78  
  79      /** @var array Rule properties prefixes. */
  80      protected static $rules = [
  81          'animation' => ['-webkit-'],
  82          'appearance' => ['-webkit-', '-moz-'],
  83          'backface-visibility' => ['-webkit-'],
  84          'box-sizing' => ['-webkit-'],
  85          'box-shadow' => ['-webkit-'],
  86          'background-clip' => ['-webkit-'],
  87          'background-size' => ['-webkit-'],
  88          'box-shadow' => ['-webkit-'],
  89          'column-count' => ['-webkit-', '-moz-'],
  90          'column-gap' => ['-webkit-', '-moz-'],
  91          'perspective' => ['-webkit-'],
  92          'touch-action' => ['-ms-'],
  93          'transform' => ['-webkit-', '-moz-', '-ms-', '-o-'],
  94          'transition' => ['-webkit-', '-o-'],
  95          'transition-timing-function' => ['-webkit-', '-o-'],
  96          'transition-duration' => ['-webkit-', '-o-'],
  97          'transition-property' => ['-webkit-', '-o-'],
  98          'user-select' => ['-webkit-', '-moz-', '-ms-'],
  99      ];
 100  
 101      /**
 102       * Constructor.
 103       *
 104       * @param Document $tree The CSS tree.
 105       */
 106      public function __construct(Document $tree) {
 107          $this->tree = $tree;
 108  
 109          $pseudos = array_map(function($pseudo) {
 110              return '(' . preg_quote($pseudo) . ')';
 111          }, array_keys(self::$pseudos));
 112          $this->pseudosregex = '(' . implode('|', $pseudos) . ')';
 113      }
 114  
 115      /**
 116       * Manipulate an array of rules to adapt their values.
 117       *
 118       * @param array $rules The rules.
 119       * @return New array of rules.
 120       */
 121      protected function manipulateRuleValues(array $rules) {
 122          $finalrules = [];
 123  
 124          foreach ($rules as $rule) {
 125              $property = $rule->getRule();
 126              $value = $rule->getValue();
 127  
 128              if ($property === 'position' && $value === 'sticky') {
 129                  $newrule = clone $rule;
 130                  $newrule->setValue('-webkit-sticky');
 131                  $finalrules[] = $newrule;
 132  
 133              } else if ($property === 'background-image' &&
 134                      $value instanceof CSSFunction &&
 135                      $value->getName() === 'linear-gradient') {
 136  
 137                  foreach (['-webkit-', '-o-'] as $prefix) {
 138                      $newfunction = clone $value;
 139                      $newfunction->setName($prefix . $value->getName());
 140                      $newrule = clone $rule;
 141                      $newrule->setValue($newfunction);
 142                      $finalrules[] = $newrule;
 143                  }
 144              }
 145  
 146              $finalrules[] = $rule;
 147          }
 148  
 149          return $finalrules;
 150      }
 151  
 152      /**
 153       * Prefix all the things!
 154       */
 155      public function prefix() {
 156          $this->processBlock($this->tree);
 157      }
 158  
 159      /**
 160       * Process block.
 161       *
 162       * @param object $block A block.
 163       * @param object $parent The parent of the block.
 164       */
 165      protected function processBlock($block) {
 166          foreach ($block->getContents() as $node) {
 167              if ($node instanceof AtRule) {
 168  
 169                  $name = $node->atRuleName();
 170                  if (isset(self::$atrules[$name])) {
 171                      foreach (self::$atrules[$name] as $prefix) {
 172                          $newname = $prefix . $name;
 173                          $newnode = clone $node;
 174  
 175                          if ($node instanceof KeyFrame) {
 176                              $newnode->setVendorKeyFrame($newname);
 177                              $block->insert($newnode, $node);
 178  
 179                          } else {
 180                              debugging('Unhandled atRule prefixing.', DEBUG_DEVELOPER);
 181                          }
 182                      }
 183                  }
 184              }
 185  
 186              if ($node instanceof CSSList) {
 187                  $this->processBlock($node);
 188  
 189              } else if ($node instanceof RuleSet) {
 190                  $this->processDeclaration($node, $block);
 191              }
 192          }
 193      }
 194  
 195      /**
 196       * Process declaration.
 197       *
 198       * @param object $node The declaration block.
 199       * @param object $parent The parent.
 200       */
 201      protected function processDeclaration($node, $parent) {
 202          $rules = [];
 203  
 204          foreach ($node->getRules() as $key => $rule) {
 205              $name = $rule->getRule();
 206              $seen[$name] = true;
 207  
 208              if (!isset(self::$rules[$name])) {
 209                  $rules[] = $rule;
 210                  continue;
 211              }
 212  
 213              foreach (self::$rules[$name] as $prefix) {
 214                  $newname = $prefix . $name;
 215                  if (isset($seen[$newname])) {
 216                      continue;
 217                  }
 218                  $newrule = clone $rule;
 219                  $newrule->setRule($newname);
 220                  $rules[] = $newrule;
 221              }
 222  
 223              $rules[] = $rule;
 224          }
 225  
 226          $node->setRules($this->manipulateRuleValues($rules));
 227  
 228          if ($node instanceof DeclarationBlock) {
 229              $selectors = $node->getSelectors();
 230              foreach ($selectors as $key => $selector) {
 231  
 232                  $matches = [];
 233                  if (preg_match($this->pseudosregex, $selector->getSelector(), $matches)) {
 234  
 235                      $newnode = clone $node;
 236                      foreach (self::$pseudos[$matches[1]] as $newpseudo) {
 237                          $newselector = new Selector(str_replace($matches[1], $newpseudo, $selector->getSelector()));
 238                          $selectors[$key] = $newselector;
 239                          $newnode = clone $node;
 240                          $newnode->setSelectors($selectors);
 241                          $parent->insert($newnode, $node);
 242                      }
 243  
 244                      // We're only expecting one affected pseudo class per block.
 245                      break;
 246                  }
 247              }
 248          }
 249      }
 250  }