Passed
Push — master ( 6b8ca8...3384db )
by Paul
04:57
created

Attributes   B

Complexity

Total Complexity 36

Size/Duplication

Total Lines 251
Duplicated Lines 0 %

Test Coverage

Coverage 80.65%

Importance

Changes 0
Metric Value
wmc 36
dl 0
loc 251
ccs 75
cts 93
cp 0.8065
rs 8.8
c 0
b 0
f 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
A toArray() 0 3 1
A normalize() 0 11 1
A normalizeInputType() 0 6 3
A normalizeStringAttributes() 0 5 3
A __call() 0 10 2
A filterGlobalAttributes() 0 11 2
B removeEmptyAttributes() 0 14 5
B normalizeDataAttributes() 0 12 5
A normalizeBooleanAttributes() 0 9 4
A removeIndexedAttributes() 0 5 1
A toString() 0 10 3
A filterAttributes() 0 3 1
A getQuoteChar() 0 5 2
A isAttributeKeyNumeric() 0 5 3
1
<?php
2
3
namespace GeminiLabs\SiteReviews\Modules\Html;
4
5
use GeminiLabs\SiteReviews\Helper;
6
7
class Attributes
8
{
9
	const ATTRIBUTES_A = [
10
		'download', 'href', 'hreflang', 'ping', 'referrerpolicy', 'rel', 'target', 'type',
11
	];
12
13
	const ATTRIBUTES_FORM = [
14
		'accept', 'accept-charset', 'action', 'autocapitalize', 'autocomplete', 'enctype', 'method',
15
		'name', 'novalidate', 'target',
16
	];
17
18
	const ATTRIBUTES_INPUT = [
19
		'accept', 'autocomplete', 'autocorrect', 'autofocus', 'capture', 'checked', 'disabled',
20
		'form', 'formaction', 'formenctype', 'formmethod', 'formnovalidate', 'formtarget', 'height',
21
		'incremental', 'inputmode', 'list', 'max', 'maxlength', 'min', 'minlength', 'multiple',
22
		'name', 'pattern', 'placeholder', 'readonly', 'results', 'required', 'selectionDirection',
23
		'selectionEnd', 'selectionStart', 'size', 'spellcheck', 'src', 'step', 'tabindex', 'type',
24
		'value', 'webkitdirectory', 'width',
25
	];
26
27
	const ATTRIBUTES_LABEL = [
28
		'for',
29
	];
30
31
	const ATTRIBUTES_OPTION = [
32
		'disabled', 'label', 'selected', 'value',
33
	];
34
35
	const ATTRIBUTES_SELECT = [
36
		'autofocus', 'disabled', 'form', 'multiple', 'name', 'required', 'size',
37
	];
38
39
	const ATTRIBUTES_TEXTAREA = [
40
		'autocapitalize', 'autocomplete', 'autofocus', 'cols', 'disabled', 'form', 'maxlength',
41
		'minlength', 'name', 'placeholder', 'readonly', 'required', 'rows', 'spellcheck', 'wrap',
42
	];
43
44
	const BOOLEAN_ATTRIBUTES = [
45
		'autofocus', 'capture', 'checked', 'disabled', 'draggable', 'formnovalidate', 'hidden',
46
		'multiple', 'novalidate', 'readonly', 'required', 'selected', 'spellcheck',
47
		'webkitdirectory',
48
	];
49
50
	const GLOBAL_ATTRIBUTES = [
51
		'accesskey', 'class', 'contenteditable', 'contextmenu', 'dir', 'draggable', 'dropzone',
52
		'hidden', 'id', 'lang', 'spellcheck', 'style', 'tabindex', 'title',
53
	];
54
55
	const GLOBAL_WILDCARD_ATTRIBUTES = [
56
		'aria-', 'data-', 'item', 'on',
57
	];
58
59
	const INPUT_TYPES = [
60
		'button', 'checkbox', 'color', 'date', 'datetime-local', 'email', 'file', 'hidden', 'image',
61
		'month', 'number', 'password', 'radio', 'range', 'reset', 'search', 'submit', 'tel', 'text',
62
		'time', 'url', 'week',
63
	];
64
65
	/**
66
	 * @var array
67
	 */
68
	protected $attributes = [];
69
70
	/**
71
	 * @param string $method
72
	 * @param array $args
73
	 * @return static
74
	 */
75 7
	public function __call( $method, $args )
76
	{
77 7
		$args += [[], false];
78 7
		$constant = 'static::ATTRIBUTES_'.strtoupper( $method );
79 7
		$allowedAttributeKeys = defined( $constant )
80 7
			? constant( $constant )
81 7
			: [];
82 7
		$this->normalize( (array)$args[0], $allowedAttributeKeys );
83 7
		$this->normalizeInputType( $method );
84 7
		return $this;
85
	}
86
87
	/**
88
	 * @return array
89
	 */
90
	public function toArray()
91
	{
92
		return $this->attributes;
93
	}
94
95
	/**
96
	 * @return string
97
	 */
98 7
	public function toString()
99
	{
100 7
		$attributes = [];
101 7
		foreach( $this->attributes as $attribute => $value ) {
102 7
			$quote = $this->getQuoteChar( $attribute );
103 7
			$attributes[] = in_array( $attribute, static::BOOLEAN_ATTRIBUTES )
104
				? $attribute
105 7
				: $attribute.'='.$quote.implode( ',', (array)$value ).$quote;
106
		}
107 7
		return implode( ' ', $attributes );
108
	}
109
110
	/**
111
	 * @return array
112
	 */
113 7
	protected function filterAttributes( array $allowedAttributeKeys )
114
	{
115 7
		return array_intersect_key( $this->attributes, array_flip( $allowedAttributeKeys ));
116
	}
117
118
	/**
119
	 * @return array
120
	 */
121 7
	protected function filterGlobalAttributes()
122
	{
123 7
		$globalAttributes = $this->filterAttributes( static::GLOBAL_ATTRIBUTES );
124 7
		$wildcards = [];
125 7
		foreach( static::GLOBAL_WILDCARD_ATTRIBUTES as $wildcard ) {
126 7
			$newWildcards = array_filter( $this->attributes, function( $key ) use( $wildcard ) {
127 7
				return glsr( Helper::class )->startsWith( $wildcard, $key );
128 7
			}, ARRAY_FILTER_USE_KEY );
129 7
			$wildcards = array_merge( $wildcards, $newWildcards );
130
		}
131 7
		return array_merge( $globalAttributes, $wildcards );
132
	}
133
134
	/**
135
	 * @param strong $attribute
0 ignored issues
show
Bug introduced by
The type GeminiLabs\SiteReviews\Modules\Html\strong was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
136
	 * @return string
137
	 */
138 7
	protected function getQuoteChar( $attribute )
139
	{
140 7
		return glsr( Helper::class )->startsWith( 'data-', $attribute )
141
			? '\''
142 7
			: '"';
143
	}
144
145
	/**
146
	 * @param string $key
147
	 * @param mixed $value
148
	 * @return bool
149
	 */
150 7
	protected function isAttributeKeyNumeric( $key, $value )
151
	{
152 7
		return is_string( $value )
153 7
			&& is_numeric( $key )
154 7
			&& !array_key_exists( $value, $this->attributes );
155
	}
156
157
	/**
158
	 * @return void
159
	 */
160 7
	protected function normalize( array $args, array $allowedAttributeKeys )
161
	{
162 7
		$this->attributes = array_change_key_case( $args, CASE_LOWER );
163 7
		$this->normalizeBooleanAttributes();
164 7
		$this->normalizeDataAttributes();
165 7
		$this->normalizeStringAttributes();
166 7
		$this->removeEmptyAttributes();
167 7
		$this->removeIndexedAttributes();
168 7
		$this->attributes = array_merge(
169 7
			$this->filterGlobalAttributes(),
170 7
			$this->filterAttributes( $allowedAttributeKeys )
171
		);
172 7
	}
173
174
	/**
175
	 * @return void
176
	 */
177 7
	protected function normalizeBooleanAttributes()
178
	{
179 7
		foreach( $this->attributes as $key => $value ) {
180 7
			if( $this->isAttributeKeyNumeric( $key, $value )) {
181
				$key = $value;
182
				$value = true;
183
			}
184 7
			if( !in_array( $key, static::BOOLEAN_ATTRIBUTES ))continue;
185
			$this->attributes[$key] = wp_validate_boolean( $value );
186
		}
187 7
	}
188
189
	/**
190
	 * @return void
191
	 */
192 7
	protected function normalizeDataAttributes()
193
	{
194 7
		foreach( $this->attributes as $key => $value ) {
195 7
			if( $this->isAttributeKeyNumeric( $key, $value )) {
196
				$key = $value;
197
				$value = '';
198
			}
199 7
			if( !glsr( Helper::class )->startsWith( 'data-', $key ))continue;
200
			if( is_array( $value )) {
201
				$value = json_encode( $value, JSON_HEX_APOS | JSON_NUMERIC_CHECK | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES );
202
			}
203
			$this->attributes[$key] = $value;
204
		}
205 7
	}
206
207
	/**
208
	 * @return void
209
	 */
210 7
	protected function normalizeStringAttributes()
211
	{
212 7
		foreach( $this->attributes as $key => $value ) {
213 7
			if( !is_string( $value ))continue;
214 7
			$this->attributes[$key] = trim( $value );
215
		}
216 7
	}
217
218
	/**
219
	 * @param string $method
220
	 * @return void
221
	 */
222 7
	protected function normalizeInputType( $method )
223
	{
224 7
		if( $method != 'input' )return;
225
		$attributes = wp_parse_args( $this->attributes, ['type' => ''] );
226
		if( !in_array( $attributes['type'], static::INPUT_TYPES )) {
227
			$this->attributes['type'] = 'text';
228
		}
229
	}
230
231
	/**
232
	 * @return void
233
	 */
234 7
	protected function removeEmptyAttributes()
235
	{
236 7
		$attributes = $this->attributes;
237 7
		$dataAttributes = [];
238 7
		foreach( $this->attributes as $key => $value ) {
239 7
			if( in_array( $key, static::BOOLEAN_ATTRIBUTES ) && !$value ) {
240
				unset( $attributes[$key] );
241
			}
242 7
			if( glsr( Helper::class )->startsWith( 'data-', $key )) {
243
				$dataAttributes[$key] = $value;
244 7
				unset( $attributes[$key] );
245
			}
246
		}
247 7
		$this->attributes = array_merge( array_filter( $attributes ), $dataAttributes );
248 7
	}
249
250
	/**
251
	 * @return void
252
	 */
253 7
	protected function removeIndexedAttributes()
254
	{
255 7
		$this->attributes = array_diff_key(
256 7
			$this->attributes,
257 7
			array_filter( $this->attributes, 'is_numeric', ARRAY_FILTER_USE_KEY )
258
		);
259 7
	}
260
}
261