Completed
Push — develop ( dfa7d2...0cc879 )
by Paul
02:12
created

Normalizer   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 227
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Importance

Changes 0
Metric Value
wmc 31
lcom 1
cbo 0
dl 0
loc 227
rs 9.8
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A form() 0 6 1
A input() 0 8 1
B maybeImplode() 0 19 7
A select() 0 6 1
A textarea() 0 6 1
B filterAttributes() 0 26 6
C filterGlobalAttributes() 0 31 8
A filterInputType() 0 6 3
A parseAttributes() 0 11 2
1
<?php
2
3
namespace GeminiLabs\Castor\Services;
4
5
class Normalizer
6
{
7
	const BOOLEAN_ATTRIBUTES = [
8
		'autofocus', 'capture', 'checked', 'disabled', 'draggable', 'formnovalidate', 'hidden',
9
		'multiple', 'novalidate', 'readonly', 'required', 'selected', 'spellcheck',
10
		'webkitdirectory',
11
	];
12
13
	const FORM_ATTRIBUTES = [
14
		'accept', 'accept-charset', 'action', 'autocapitalize', 'autocomplete', 'enctype',
15
		'method', 'name', 'novalidate', 'target',
16
	];
17
18
	const GLOBAL_ATTRIBUTES = [
19
		'accesskey', 'class', 'contenteditable', 'contextmenu', 'dir', 'draggable', 'dropzone',
20
		'hidden', 'id', 'lang', 'spellcheck', 'style', 'tabindex', 'title',
21
	];
22
23
	const GLOBAL_WILDCARD_ATTRIBUTES = [
24
		'aria-', 'data-', 'item', 'on',
25
	];
26
27
	const INPUT_ATTRIBUTES = [
28
		'accept', 'autocapitalize', 'autocomplete', 'autocorrect', 'autofocus', 'capture',
29
		'checked', 'disabled', 'form', 'formaction', 'formenctype', 'formmethod',
30
		'formnovalidate', 'formtarget', 'height', 'incremental', 'inputmode', 'list', 'max',
31
		'maxlength', 'min', 'minlength', 'mozactionhint', 'multiple', 'name', 'pattern',
32
		'placeholder', 'readonly', 'required', 'results', 'selectionDirection', 'size', 'src',
33
		'step', 'type', 'value', 'webkitdirectory', 'width', 'x-moz-errormessage',
34
	];
35
36
	const INPUT_TYPES = [
37
		'button', 'checkbox', 'color', 'date', 'datetime', 'datetime-local', 'email', 'file',
38
		'hidden', 'image', 'max', 'min', 'month', 'number', 'password', 'radio', 'range',
39
		'reset', 'search', 'step', 'submit', 'tel', 'text', 'time', 'url', 'value', 'week',
40
	];
41
42
	const SELECT_ATTRIBUTES = [
43
		'autofocus', 'disabled', 'form', 'multiple', 'name', 'required', 'size',
44
	];
45
46
	const TEXTAREA_ATTRIBUTES = [
47
		'autocapitalize', 'autocomplete', 'autofocus', 'cols', 'disabled', 'form', 'maxlength',
48
		'minlength', 'name', 'placeholder', 'readonly', 'required', 'rows',
49
		'selectionDirection', 'selectionEnd', 'selectionStart', 'wrap',
50
	];
51
52
	/**
53
	 * @var array
54
	 */
55
	protected $args;
56
57
	public function __construct()
58
	{
59
		$this->args = [];
60
	}
61
62
	/**
63
	 * Normalize form attributes
64
	 *
65
	 * @return array|string
66
	 */
67
	public function form( array $args = [], $implode = false )
68
	{
69
		$attributes = $this->parseAttributes( self::FORM_ATTRIBUTES, $args );
70
71
		return $this->maybeImplode( $attributes, $implode );
72
	}
73
74
	/**
75
	 * Normalize input attributes
76
	 *
77
	 * @return array|string
78
	 */
79
	public function input( array $args = [], $implode = false )
80
	{
81
		$this->filterInputType();
82
83
		$attributes = $this->parseAttributes( self::INPUT_ATTRIBUTES, $args );
84
85
		return $this->maybeImplode( $attributes, $implode );
86
	}
87
88
	/**
89
	 * Possibly implode attributes into a string
90
	 *
91
	 * @param bool|string $implode
92
	 *
93
	 * @return array|string
94
	 */
95
	public function maybeImplode( array $attributes, $implode = true )
96
	{
97
		if( !$implode || $implode !== 'implode' ) {
98
			return $attributes;
99
		}
100
		$results = [];
101
		foreach( $attributes as $key => $value ) {
102
			// if data attributes, use single quotes in case of json encoded values
103
			$quotes = false !== stripos( $key, 'data-' ) ? "'" : '"';
104
			if( is_array( $value )) {
105
				$value = json_encode( $value );
106
				$quotes = "'";
107
			}
108
			$results[] = is_string( $key )
109
				? sprintf( '%1$s=%3$s%2$s%3$s', $key, $value, $quotes )
110
				: $value;
111
		}
112
		return implode( ' ', $results );
113
	}
114
115
	/**
116
	 * Normalize select attributes
117
	 *
118
	 * @return array|string
119
	 */
120
	public function select( array $args = [], $implode = false )
121
	{
122
		$attributes = $this->parseAttributes( self::SELECT_ATTRIBUTES, $args );
123
124
		return $this->maybeImplode( $attributes, $implode );
125
	}
126
127
	/**
128
	 * Normalize textarea attributes
129
	 *
130
	 * @return array|string
131
	 */
132
	public function textarea( array $args = [], $implode = false )
133
	{
134
		$attributes = $this->parseAttributes( self::TEXTAREA_ATTRIBUTES, $args );
135
136
		return $this->maybeImplode( $attributes, $implode );
137
	}
138
139
	/**
140
	 * Filter attributes by the provided attrribute keys and remove any non-boolean keys
141
	 * with empty values
142
	 *
143
	 * @return array
144
	 */
145
	protected function filterAttributes( array $attributeKeys )
146
	{
147
		$filtered = array_intersect_key( $this->args, array_flip( $attributeKeys ));
148
149
		// normalize truthy boolean attributes
150
		foreach( $filtered as $key => $value ) {
151
			if( !in_array( $key, self::BOOLEAN_ATTRIBUTES ))continue;
152
153
			if( $value !== false ) {
154
				$filtered[ $key ] = '';
155
				continue;
156
			}
157
158
			unset( $filtered[ $key ] );
159
		}
160
161
		$filteredKeys = array_filter( array_keys( $filtered ), function( $key ) use ( $filtered ) {
162
			return !(
163
				empty( $filtered[ $key ] )
164
				&& !is_numeric( $filtered[ $key ] )
165
				&& !in_array( $key, self::BOOLEAN_ATTRIBUTES )
166
			);
167
		});
168
169
		return array_intersect_key( $filtered, array_flip( $filteredKeys ));
170
	}
171
172
	/**
173
	 * @return array
174
	 */
175
	protected function filterGlobalAttributes()
176
	{
177
		$global = $this->filterAttributes( self::GLOBAL_ATTRIBUTES );
178
179
		$wildcards = [];
180
181
		foreach( self::GLOBAL_WILDCARD_ATTRIBUTES as $wildcard ) {
182
183
			foreach( $this->args as $key => $value ) {
184
185
				$length = strlen( $wildcard );
186
				$result = substr( $key, 0, $length) === $wildcard;
187
188
				if( $result ) {
189
					// only allow data attributes to have an empty value
190
					if( $wildcard != 'data-' && empty( $value ))continue;
191
192
					if( is_array( $value )) {
193
194
						if( $wildcard != 'data-' )continue;
195
196
						$value = json_encode( $value );
197
					}
198
199
					$wildcards[ $key ] = $value;
200
				}
201
			}
202
		}
203
204
		return array_merge( $global, $wildcards );
205
	}
206
207
	/**
208
	 * @return void
209
	 */
210
	protected function filterInputType()
211
	{
212
		if( !isset( $this->args['type'] ) || !in_array( $this->args['type'], self::INPUT_TYPES )) {
213
			$this->args['type'] = 'text';
214
		}
215
	}
216
217
	/**
218
	 * @return array
219
	 */
220
	protected function parseAttributes( array $attributes, array $args = [] )
221
	{
222
		if( !empty( $args )) {
223
			$this->args = array_change_key_case( $args );
224
		}
225
226
		$global = $this->filterGlobalAttributes();
227
		$local  = $this->filterAttributes( $attributes );
228
229
		return array_merge( $global, $local );
230
	}
231
}
232