Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

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

   1  <?php
   2  
   3  namespace Sabberworm\CSS\Property;
   4  
   5  /**
   6   * Class representing a single CSS selector. Selectors have to be split by the comma prior to being passed into this
   7   * class.
   8   */
   9  class Selector
  10  {
  11      /**
  12       * regexp for specificity calculations
  13       *
  14       * @var string
  15       */
  16      const NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX = '/
  17          (\.[\w]+)                   # classes
  18          |
  19          \[(\w+)                     # attributes
  20          |
  21          (\:(                        # pseudo classes
  22              link|visited|active
  23              |hover|focus
  24              |lang
  25              |target
  26              |enabled|disabled|checked|indeterminate
  27              |root
  28              |nth-child|nth-last-child|nth-of-type|nth-last-of-type
  29              |first-child|last-child|first-of-type|last-of-type
  30              |only-child|only-of-type
  31              |empty|contains
  32          ))
  33          /ix';
  34  
  35      /**
  36       * regexp for specificity calculations
  37       *
  38       * @var string
  39       */
  40      const ELEMENTS_AND_PSEUDO_ELEMENTS_RX = '/
  41          ((^|[\s\+\>\~]+)[\w]+   # elements
  42          |
  43          \:{1,2}(                # pseudo-elements
  44              after|before|first-letter|first-line|selection
  45          ))
  46          /ix';
  47  
  48      /**
  49       * regexp for specificity calculations
  50       *
  51       * @var string
  52       */
  53      const SELECTOR_VALIDATION_RX = '/
  54          ^(
  55              (?:
  56                  [a-zA-Z0-9\x{00A0}-\x{FFFF}_^$|*="\'~\[\]()\-\s\.:#+>]* # any sequence of valid unescaped characters
  57                  (?:\\\\.)?                                              # a single escaped character
  58                  (?:([\'"]).*?(?<!\\\\)\2)?                              # a quoted text like [id="example"]
  59              )*
  60          )$
  61          /ux';
  62  
  63      /**
  64       * @var string
  65       */
  66      private $sSelector;
  67  
  68      /**
  69       * @var int|null
  70       */
  71      private $iSpecificity;
  72  
  73      /**
  74       * @param string $sSelector
  75       *
  76       * @return bool
  77       */
  78      public static function isValid($sSelector)
  79      {
  80          return preg_match(static::SELECTOR_VALIDATION_RX, $sSelector);
  81      }
  82  
  83      /**
  84       * @param string $sSelector
  85       * @param bool $bCalculateSpecificity
  86       */
  87      public function __construct($sSelector, $bCalculateSpecificity = false)
  88      {
  89          $this->setSelector($sSelector);
  90          if ($bCalculateSpecificity) {
  91              $this->getSpecificity();
  92          }
  93      }
  94  
  95      /**
  96       * @return string
  97       */
  98      public function getSelector()
  99      {
 100          return $this->sSelector;
 101      }
 102  
 103      /**
 104       * @param string $sSelector
 105       *
 106       * @return void
 107       */
 108      public function setSelector($sSelector)
 109      {
 110          $this->sSelector = trim($sSelector);
 111          $this->iSpecificity = null;
 112      }
 113  
 114      /**
 115       * @return string
 116       */
 117      public function __toString()
 118      {
 119          return $this->getSelector();
 120      }
 121  
 122      /**
 123       * @return int
 124       */
 125      public function getSpecificity()
 126      {
 127          if ($this->iSpecificity === null) {
 128              $a = 0;
 129              /// @todo should exclude \# as well as "#"
 130              $aMatches = null;
 131              $b = substr_count($this->sSelector, '#');
 132              $c = preg_match_all(self::NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX, $this->sSelector, $aMatches);
 133              $d = preg_match_all(self::ELEMENTS_AND_PSEUDO_ELEMENTS_RX, $this->sSelector, $aMatches);
 134              $this->iSpecificity = ($a * 1000) + ($b * 100) + ($c * 10) + $d;
 135          }
 136          return $this->iSpecificity;
 137      }
 138  }