Completed
Push — develop ( 0b0716...7823b4 )
by Paul
02:04
created

Normalizer::input()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 2
1
<?php
2
3
namespace GeminiLabs\Castor;
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( array $args = [] )
58
	{
59
		$this->args = $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'], $this->inputTypes )) {
0 ignored issues
show
Bug introduced by
The property inputTypes does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
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