Passed
Push — master ( 203c84...bd5512 )
by Paul
04:35
created

Attributes   B

Complexity

Total Complexity 38

Size/Duplication

Total Lines 268
Duplicated Lines 0 %

Test Coverage

Coverage 81.63%

Importance

Changes 0
Metric Value
dl 0
loc 268
ccs 80
cts 98
cp 0.8163
rs 8.3999
c 0
b 0
f 0
wmc 38

15 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
A getPermanentAttributes() 0 7 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 getQuoteChar() 0 5 2
A filterAttributes() 0 3 1
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_BUTTON = [
14
		'autofocus', 'disabled', 'form', 'formaction', 'formenctype', 'formmethod',
15
		'formnovalidate', 'formtarget', 'name', 'type', 'value',
16
	];
17
18
	const ATTRIBUTES_FORM = [
19
		'accept', 'accept-charset', 'action', 'autocapitalize', 'autocomplete', 'enctype', 'method',
20
		'name', 'novalidate', 'target',
21
	];
22
23
	const ATTRIBUTES_INPUT = [
24
		'accept', 'autocomplete', 'autocorrect', 'autofocus', 'capture', 'checked', 'disabled',
25
		'form', 'formaction', 'formenctype', 'formmethod', 'formnovalidate', 'formtarget', 'height',
26
		'incremental', 'inputmode', 'list', 'max', 'maxlength', 'min', 'minlength', 'multiple',
27
		'name', 'pattern', 'placeholder', 'readonly', 'results', 'required', 'selectionDirection',
28
		'selectionEnd', 'selectionStart', 'size', 'spellcheck', 'src', 'step', 'tabindex', 'type',
29
		'value', 'webkitdirectory', 'width',
30
	];
31
32
	const ATTRIBUTES_LABEL = [
33
		'for',
34
	];
35
36
	const ATTRIBUTES_OPTION = [
37
		'disabled', 'label', 'selected', 'value',
38
	];
39
40
	const ATTRIBUTES_SELECT = [
41
		'autofocus', 'disabled', 'form', 'multiple', 'name', 'required', 'size',
42
	];
43
44
	const ATTRIBUTES_TEXTAREA = [
45
		'autocapitalize', 'autocomplete', 'autofocus', 'cols', 'disabled', 'form', 'maxlength',
46
		'minlength', 'name', 'placeholder', 'readonly', 'required', 'rows', 'spellcheck', 'wrap',
47
	];
48
49
	const BOOLEAN_ATTRIBUTES = [
50
		'autofocus', 'capture', 'checked', 'disabled', 'draggable', 'formnovalidate', 'hidden',
51
		'multiple', 'novalidate', 'readonly', 'required', 'selected', 'spellcheck',
52
		'webkitdirectory',
53
	];
54
55
	const GLOBAL_ATTRIBUTES = [
56
		'accesskey', 'class', 'contenteditable', 'contextmenu', 'dir', 'draggable', 'dropzone',
57
		'hidden', 'id', 'lang', 'spellcheck', 'style', 'tabindex', 'title',
58
	];
59
60
	const GLOBAL_WILDCARD_ATTRIBUTES = [
61
		'aria-', 'data-', 'item', 'on',
62
	];
63
64
	const INPUT_TYPES = [
65
		'button', 'checkbox', 'color', 'date', 'datetime-local', 'email', 'file', 'hidden', 'image',
66
		'month', 'number', 'password', 'radio', 'range', 'reset', 'search', 'submit', 'tel', 'text',
67
		'time', 'url', 'week',
68
	];
69
70
	/**
71
	 * @var array
72
	 */
73
	protected $attributes = [];
74
75
	/**
76
	 * @param string $method
77
	 * @param array $args
78
	 * @return static
79
	 */
80 7
	public function __call( $method, $args )
81
	{
82 7
		$args += [[], false];
83 7
		$constant = 'static::ATTRIBUTES_'.strtoupper( $method );
84 7
		$allowedAttributeKeys = defined( $constant )
85 7
			? constant( $constant )
86 7
			: [];
87 7
		$this->normalize( (array)$args[0], $allowedAttributeKeys );
88 7
		$this->normalizeInputType( $method );
89 7
		return $this;
90
	}
91
92
	/**
93
	 * @return array
94
	 */
95
	public function toArray()
96
	{
97
		return $this->attributes;
98
	}
99
100
	/**
101
	 * @return string
102
	 */
103 7
	public function toString()
104
	{
105 7
		$attributes = [];
106 7
		foreach( $this->attributes as $attribute => $value ) {
107 7
			$quote = $this->getQuoteChar( $attribute );
108 7
			$attributes[] = in_array( $attribute, static::BOOLEAN_ATTRIBUTES )
109
				? $attribute
110 7
				: $attribute.'='.$quote.implode( ',', (array)$value ).$quote;
111
		}
112 7
		return implode( ' ', $attributes );
113
	}
114
115
	/**
116
	 * @return array
117
	 */
118 7
	protected function filterAttributes( array $allowedAttributeKeys )
119
	{
120 7
		return array_intersect_key( $this->attributes, array_flip( $allowedAttributeKeys ));
121
	}
122
123
	/**
124
	 * @return array
125
	 */
126 7
	protected function filterGlobalAttributes()
127
	{
128 7
		$globalAttributes = $this->filterAttributes( static::GLOBAL_ATTRIBUTES );
129 7
		$wildcards = [];
130 7
		foreach( static::GLOBAL_WILDCARD_ATTRIBUTES as $wildcard ) {
131 7
			$newWildcards = array_filter( $this->attributes, function( $key ) use( $wildcard ) {
132 7
				return glsr( Helper::class )->startsWith( $wildcard, $key );
133 7
			}, ARRAY_FILTER_USE_KEY );
134 7
			$wildcards = array_merge( $wildcards, $newWildcards );
135
		}
136 7
		return array_merge( $globalAttributes, $wildcards );
137
	}
138
139
	/**
140
	 * @return array
141
	 */
142 7
	protected function getPermanentAttributes()
143
	{
144 7
		$permanentAttributes = [];
145 7
		if( array_key_exists( 'value', $this->attributes )) {
146 7
			$permanentAttributes['value'] = $this->attributes['value'];
147
		}
148 7
		return $permanentAttributes;
149
	}
150
151
	/**
152
	 * @param string $attribute
153
	 * @return string
154
	 */
155 7
	protected function getQuoteChar( $attribute )
156
	{
157 7
		return glsr( Helper::class )->startsWith( 'data-', $attribute )
158
			? '\''
159 7
			: '"';
160
	}
161
162
	/**
163
	 * @param string $key
164
	 * @param mixed $value
165
	 * @return bool
166
	 */
167 7
	protected function isAttributeKeyNumeric( $key, $value )
168
	{
169 7
		return is_string( $value )
170 7
			&& is_numeric( $key )
171 7
			&& !array_key_exists( $value, $this->attributes );
172
	}
173
174
	/**
175
	 * @return void
176
	 */
177 7
	protected function normalize( array $args, array $allowedAttributeKeys )
178
	{
179 7
		$this->attributes = array_change_key_case( $args, CASE_LOWER );
180 7
		$this->normalizeBooleanAttributes();
181 7
		$this->normalizeDataAttributes();
182 7
		$this->normalizeStringAttributes();
183 7
		$this->removeEmptyAttributes();
184 7
		$this->removeIndexedAttributes();
185 7
		$this->attributes = array_merge(
186 7
			$this->filterGlobalAttributes(),
187 7
			$this->filterAttributes( $allowedAttributeKeys )
188
		);
189 7
	}
190
191
	/**
192
	 * @return void
193
	 */
194 7
	protected function normalizeBooleanAttributes()
195
	{
196 7
		foreach( $this->attributes as $key => $value ) {
197 7
			if( $this->isAttributeKeyNumeric( $key, $value )) {
198
				$key = $value;
199
				$value = true;
200
			}
201 7
			if( !in_array( $key, static::BOOLEAN_ATTRIBUTES ))continue;
202
			$this->attributes[$key] = wp_validate_boolean( $value );
203
		}
204 7
	}
205
206
	/**
207
	 * @return void
208
	 */
209 7
	protected function normalizeDataAttributes()
210
	{
211 7
		foreach( $this->attributes as $key => $value ) {
212 7
			if( $this->isAttributeKeyNumeric( $key, $value )) {
213
				$key = $value;
214
				$value = '';
215
			}
216 7
			if( !glsr( Helper::class )->startsWith( 'data-', $key ))continue;
217
			if( is_array( $value )) {
218
				$value = json_encode( $value, JSON_HEX_APOS | JSON_NUMERIC_CHECK | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES );
219
			}
220
			$this->attributes[$key] = $value;
221
		}
222 7
	}
223
224
	/**
225
	 * @return void
226
	 */
227 7
	protected function normalizeStringAttributes()
228
	{
229 7
		foreach( $this->attributes as $key => $value ) {
230 7
			if( !is_string( $value ))continue;
231 7
			$this->attributes[$key] = trim( $value );
232
		}
233 7
	}
234
235
	/**
236
	 * @param string $method
237
	 * @return void
238
	 */
239 7
	protected function normalizeInputType( $method )
240
	{
241 7
		if( $method != 'input' )return;
242
		$attributes = wp_parse_args( $this->attributes, ['type' => ''] );
243
		if( !in_array( $attributes['type'], static::INPUT_TYPES )) {
244
			$this->attributes['type'] = 'text';
245
		}
246
	}
247
248
	/**
249
	 * @return void
250
	 */
251 7
	protected function removeEmptyAttributes()
252
	{
253 7
		$attributes = $this->attributes;
254 7
		$permanentAttributes = $this->getPermanentAttributes();
255 7
		foreach( $this->attributes as $key => $value ) {
256 7
			if( in_array( $key, static::BOOLEAN_ATTRIBUTES ) && !$value ) {
257
				unset( $attributes[$key] );
258
			}
259 7
			if( glsr( Helper::class )->startsWith( 'data-', $key )) {
260
				$permanentAttributes[$key] = $value;
261 7
				unset( $attributes[$key] );
262
			}
263
		}
264 7
		$this->attributes = array_merge( array_filter( $attributes ), $permanentAttributes );
265 7
	}
266
267
	/**
268
	 * @return void
269
	 */
270 7
	protected function removeIndexedAttributes()
271
	{
272 7
		$this->attributes = array_diff_key(
273 7
			$this->attributes,
274 7
			array_filter( $this->attributes, 'is_numeric', ARRAY_FILTER_USE_KEY )
275
		);
276 7
	}
277
}
278