--- /dev/null
+<?php
+/**
+ * @file
+ * @license https://opensource.org/licenses/Apache-2.0 Apache-2.0
+ */
+
+namespace Wikimedia\CSS\Sanitizer;
+
+use Wikimedia\CSS\Grammar\MatcherFactory;
+use Wikimedia\CSS\Objects\CSSObject;
+use Wikimedia\CSS\Objects\RuleList;
+use Wikimedia\CSS\Objects\Stylesheet;
+use Wikimedia\CSS\Util;
+
+/**
+ * Sanitizes a CSS stylesheet or rule list
+ * @see https://www.w3.org/TR/2014/CR-css-syntax-3-20140220/#css-stylesheets
+ */
+class StylesheetSanitizer extends Sanitizer {
+
+ /** @var RuleSanitizer[] */
+ protected $ruleSanitizers;
+
+ /**
+ * @param RuleSanitizer[] $ruleSanitizers Sanitizers to test rules. For
+ * each rule in the sheet, the first sanitizer that handles that rule gets
+ * to sanitize it.
+ */
+ public function __construct( array $ruleSanitizers = [] ) {
+ $this->setRuleSanitizers( $ruleSanitizers );
+ }
+
+ /**
+ * Create and return a default StylesheetSanitizer.
+ * @note This method exists more to be an example of how to put everything
+ * together than to be used directly.
+ * @return StylesheetSanitizer
+ */
+ public static function newDefault() {
+ // First, we need a matcher factory for the stuff all the sanitizers
+ // will need.
+ $matcherFactory = MatcherFactory::singleton();
+
+ // This is the sanitizer for a single "property: value", that gets used by
+ // StyleRuleSanitizer and various others.
+ $propertySanitizer = new StylePropertySanitizer( $matcherFactory );
+
+ // These are sanitizers for different types of rules that can appear in
+ // stylesheets and can be nested inside @media and @supports blocks.
+ // The keys in the array aren't used for anything by the library, but
+ // may help humans reading it.
+ $ruleSanitizers = [
+ 'style' => new StyleRuleSanitizer( $matcherFactory->cssSelectorList(), $propertySanitizer ),
+ '@font-face' => new FontFaceAtRuleSanitizer( $matcherFactory ),
+ '@font-feature-values' => new FontFeatureValuesAtRuleSanitizer( $matcherFactory ),
+ '@keyframes' => new KeyframesAtRuleSanitizer( $matcherFactory, $propertySanitizer ),
+ '@page' => new PageAtRuleSanitizer( $matcherFactory, $propertySanitizer ),
+ '@media' => new MediaAtRuleSanitizer( $matcherFactory->cssMediaQueryList() ),
+ '@supports' => new SupportsAtRuleSanitizer( $matcherFactory, [
+ 'declarationSanitizer' => $propertySanitizer,
+ ] ),
+ ];
+
+ // Inject the above list into the @media and @supports sanitizers.
+ $ruleSanitizers['@media']->setRuleSanitizers( $ruleSanitizers );
+ $ruleSanitizers['@supports']->setRuleSanitizers( $ruleSanitizers );
+
+ // Now we can put together the StylesheetSanitizer
+ $sanitizer = new StylesheetSanitizer( $ruleSanitizers + [
+ // Note there's intentionally no "@charset" sanitizer, as that at-rule
+ // was removed in the Editor's Draft in favor of special handling
+ // in the parser.
+ '@import' => new ImportAtRuleSanitizer( $matcherFactory ),
+ '@namespace' => new NamespaceAtRuleSanitizer( $matcherFactory ),
+ ] );
+
+ return $sanitizer;
+ }
+
+ /**
+ * Access the list of rule sanitizers
+ * @return RuleSanitizer[]
+ */
+ public function getRuleSanitizers() {
+ return $this->ruleSanitizers;
+ }
+
+ /**
+ * Set the list of rule sanitizers
+ * @param RuleSanitizer[] $ruleSanitizers
+ */
+ public function setRuleSanitizers( array $ruleSanitizers ) {
+ Util::assertAllInstanceOf( $ruleSanitizers, RuleSanitizer::class, '$ruleSanitizers' );
+ $this->ruleSanitizers = $ruleSanitizers;
+ }
+
+ protected function doSanitize( CSSObject $object ) {
+ $isSheet = $object instanceof Stylesheet;
+ if ( $isSheet ) {
+ $object = $object->getRuleList();
+ }
+ if ( !$object instanceof RuleList ) {
+ $this->sanitizationError( 'expected-stylesheet', $object );
+ return null;
+ }
+
+ $ret = $this->sanitizeRules( $this->ruleSanitizers, $object );
+ if ( $isSheet ) {
+ $ret = new Stylesheet( $ret );
+ }
+
+ return $ret;
+ }
+}