Completed
Push — add/amp-pwa-experiment ( efea12 )
by
unknown
11:53
created

AMP_Style_Sanitizer::collect_styles_recursive()   C

Complexity

Conditions 7
Paths 9

Size

Total Lines 30
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 18
nc 9
nop 1
dl 0
loc 30
rs 6.7272
c 0
b 0
f 0
1
<?php
2
3
require_once( AMP__ROOT__ . '/includes/sanitizers/class-amp-base-sanitizer.php' );
4
5
/**
6
 * Collects inline styles and outputs them in the amp-custom stylesheet.
7
 */
8
class AMP_Style_Sanitizer extends AMP_Base_Sanitizer {
9
	private $styles = array();
10
11
	public function get_styles() {
12
		return $this->styles;
13
	}
14
15
	public function sanitize() {
16
		$body = $this->get_body_node();
17
		$this->collect_styles_recursive( $body );
18
	}
19
20
	private function collect_styles_recursive( $node ) {
21
		if ( $node->nodeType !== XML_ELEMENT_NODE ) {
22
			return;
23
		}
24
25
		if ( $node->hasAttributes() && $node instanceof DOMElement ) {
26
			$style = $node->getAttribute( 'style' );
27
			$class = $node->getAttribute( 'class' );
28
29
			if ( $style ) {
30
				$style = $this->process_style( $style );
31
				if ( ! empty( $style ) ) {
32
					$class_name = $this->generate_class_name( $style );
33
					$new_class  = trim( $class . ' ' . $class_name );
34
35
					$node->setAttribute( 'class', $new_class );
36
					$this->styles[ '.' . $class_name ] = $style;
37
				}
38
39
				$node->removeAttribute( 'style' );
40
			}
41
		}
42
43
		$length = $node->childNodes->length;
44
		for ( $i = $length - 1; $i >= 0; $i -- ) {
45
			$child_node = $node->childNodes->item( $i );
46
47
			$this->collect_styles_recursive( $child_node );
48
		}
49
	}
50
51
	private function process_style( $string ) {
52
		// Filter properties
53
		$string = safecss_filter_attr( esc_html( $string ) );
54
55
		if ( ! $string ) {
56
			return array();
57
		}
58
59
		// Normalize order
60
		$styles = array_map( 'trim', explode( ';', $string ) );
61
		sort( $styles );
62
63
		$processed_styles = array();
64
65
		// Normalize whitespace and filter rules
66
		foreach ( $styles as $index => $rule ) {
67
			$arr2 = array_map( 'trim', explode( ':', $rule, 2 ) );
68
			if ( 2 !== count( $arr2 ) ) {
69
				continue;
70
			}
71
72
			list( $property, $value ) = $this->filter_style( $arr2[0], $arr2[1] );
73
			if ( empty( $property ) || empty( $value ) ) {
74
				continue;
75
			}
76
77
			$processed_styles[ $index ] = $property . ':' . $value;
78
		}
79
80
		return $processed_styles;
81
	}
82
83
	private function filter_style( $property, $value ) {
84
		// Handle overflow rule
85
		// https://www.ampproject.org/docs/reference/spec.html#properties
86
		if ( 0 === strpos( $property, 'overflow' )
87
			&& ( false !== strpos( $value, 'auto' ) || false !== strpos( $value, 'scroll' ) )
88
		) {
89
			return false;
90
		}
91
92
		if ( 'width' === $property ) {
93
			$property = 'max-width';
94
		}
95
96
		// !important is not allowed
97
		if ( false !== strpos( $value, 'important' ) ) {
98
			$value = preg_replace( '/\s*\!\s*important$/', '', $value );
99
		}
100
101
		return array( $property, $value );
102
	}
103
104
	private function generate_class_name( $data ) {
105
		$string = maybe_serialize( $data );
106
		return 'amp-wp-inline-' . md5( $string );
107
	}
108
}
109