Passed
Push — master ( a74976...b3d616 )
by Paul
03:42
created

Attributes::normalizeBooleanAttributes()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

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