Completed
Push — development ( 2fadcd...18958b )
by
unknown
03:13
created

Key_Toolset::get_storage_key_root()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 5
rs 10
c 0
b 0
f 0
ccs 0
cts 4
cp 0
crap 2
1
<?php
2
3
namespace Carbon_Fields\Toolset;
4
5
use Carbon_Fields\Value_Set\Value_Set;
6
use Carbon_Fields\Exception\Incorrect_Syntax_Exception;
7
8
/**
9
 * Provides common tools when dealing with storage keys
10
 *
11
 * Key schema:
12
 * _[root_field_name]|[parent:field:names:separated:with:colons]|[complex:group:indexes:separated:with:colons]|[value_index]|[property]
13
 *
14
 * Example:
15
 * _countries|major_cities:name|0:1|0|value
16
 * This key is for a field called "name" inside the complex field "major_cities" with group index 1, which is inside the complex field "countries" with group index 0
17
 */
18
class Key_Toolset {
19
20
	/**
21
	 * Prefix appended to all keys
22
	 */
23
	const KEY_PREFIX = '_';
24
25
	/**
26
	 * Value property to use for fields which need to be kept "alive" when they have no values stored (e.g. Set field with 0 checkboxes checked)
27
	 * Required to determine whether a field should use its default value or stay blank
28
	 *
29
	 * @var string
30
	 */
31
	const KEEPALIVE_PROPERTY = '_empty';
32
33
	/**
34
	 * Glue characters between segments in keys
35
	 */
36
	const SEGMENT_GLUE = '|';
37
38
	/**
39
	 * Glue characters between segments values in keys
40
	 */
41
	const SEGMENT_VALUE_GLUE = ':';
42
43
	/**
44
	 * Number of segments in a key
45
	 */
46
	const TOTAL_SEGMENTS = 5;
47
48
	/**
49
	 * "Equal" storage key pattern comparison type constant
50
	 */
51
	const PATTERN_COMPARISON_EQUAL = '=';
52
53
	/**
54
	 * "Starts with" storage key pattern comparison type constant
55
	 */
56
	const PATTERN_COMPARISON_STARTS_WITH = '^';
57
58
	/**
59
	 * Get the KEEPALIVE_PROPERTY
60
	 * Needed since php 5.3 cannot parse $instance->property::constant ( but parses $instance::constant )
61
	 *
62
	 * @return string
63
	 */
64
	public function get_keepalive_property() {
65
		return static::KEEPALIVE_PROPERTY;
66
	}
67
68
	/**
69
	 * Get sanitized hierarchy index for hierarchy
70
	 *
71
	 * @param array<string> $full_hierarchy
72
	 * @param array<int> $full_hierarchy_index
73
	 * @return array<int>
74
	 */
75 3
	public function get_sanitized_hierarchy_index( $full_hierarchy, $full_hierarchy_index ) {
76 3
		$full_hierarchy_index = array_slice( $full_hierarchy_index, 0, count( $full_hierarchy ) - 1 );
77 3
		if ( empty( $full_hierarchy_index ) ) {
78
			$full_hierarchy_index = array();
79
		}
80 3
		$full_hierarchy_index = array_map( 'intval', $full_hierarchy_index );
81 3
		return $full_hierarchy_index;
82
	}
83
84
	/**
85
	 * Get a storage key for a simple root field
86
	 *
87
	 * @param string $field_base_name
88
	 * @return string
89
	 */
90
	protected function get_storage_key_for_simple_root_field( $field_base_name ) {
91
		$storage_key = static::KEY_PREFIX . $field_base_name;
92
		return $storage_key;
93
	}
94
95
	/**
96
	 * Get a storage key for the root field
97
	 * Suitable for deleting entire trees of values (e.g. Complex_Field)
98
	 *
99
	 * @param array $full_hierarchy
100
	 * @return string
101
	 */
102
	protected function get_storage_key_root( $full_hierarchy ) {
103
		$first_parent = array_shift( $full_hierarchy );
104
		$storage_key = static::KEY_PREFIX . $first_parent . static::SEGMENT_GLUE;
105
		return $storage_key;
106
	}
107
108
	/**
109
	 * Get a storage key up to the hierarchy index segment
110
	 * Suitable for getting and deleting multiple values for a single field
111
	 *
112
	 * @param array<string> $full_hierarchy
113
	 * @param array<int> $full_hierarchy_index
114
	 * @return string
115
	 */
116
	protected function get_storage_key_prefix( $full_hierarchy, $full_hierarchy_index ) {
117
		$full_hierarchy_index = $this->get_sanitized_hierarchy_index( $full_hierarchy, $full_hierarchy_index );
118
		$parents = $full_hierarchy;
119
		$first_parent = array_shift( $parents );
120
121
		$storage_key = static::KEY_PREFIX .
122
			$first_parent .
123
			static::SEGMENT_GLUE .
124
			implode( static::SEGMENT_VALUE_GLUE, $parents ) .
125
			static::SEGMENT_GLUE .
126
			implode( static::SEGMENT_VALUE_GLUE, $full_hierarchy_index ) .
127
			static::SEGMENT_GLUE;
128
129
		return $storage_key;
130
	}
131
132
	/**
133
	 * Get a full storage key for a single field value
134
	 *
135
	 * @param bool $is_simple_root_field
136
	 * @param array<string> $full_hierarchy
137
	 * @param array<int> $full_hierarchy_index
138
	 * @param int $value_group_index
139
	 * @param string $property
140
	 * @return string
141
	 */
142 6
	public function get_storage_key( $is_simple_root_field, $full_hierarchy, $full_hierarchy_index, $value_group_index, $property ) {
143 6
		$full_hierarchy_index = $this->get_sanitized_hierarchy_index( $full_hierarchy, $full_hierarchy_index );
144 6
		if ( $is_simple_root_field && $property === Value_Set::VALUE_PROPERTY ) {
145 2
			return $this->get_storage_key_for_simple_root_field( array_shift( $full_hierarchy ) );
146
		}
147 4
		$storage_key = $this->get_storage_key_prefix( $full_hierarchy, $full_hierarchy_index ) . intval( $value_group_index ) . static::SEGMENT_GLUE . $property;
148 4
		return $storage_key;
149
	}
150
151
	/**
152
	 * Get a full storage key with optional wildcards for entry and value indexes
153
	 *
154
	 * @param bool $is_simple_root_field
155
	 * @param array<string> $full_hierarchy
156
	 * @param string $property
157
	 * @param string $wildcard
158
	 * @return string
159
	 */
160 3
	public function get_storage_key_with_index_wildcards( $is_simple_root_field, $full_hierarchy, $property = Value_Set::VALUE_PROPERTY, $wildcard = '%' ) {
161 3
		if ( $is_simple_root_field && $property === Value_Set::VALUE_PROPERTY ) {
162 1
			return $this->get_storage_key_for_simple_root_field( array_shift( $full_hierarchy ) );
163
		}
164
165 2
		$parents = $full_hierarchy;
166 2
		$first_parent = array_shift( $parents );
167 2
		$hierarchy = array_slice( $full_hierarchy, 0, -1 );
168 2
		$hierarchy_index = ! empty( $hierarchy ) ? $wildcard : '';
169 2
		$value_group_index = $wildcard;
170
171 2
		$storage_key = static::KEY_PREFIX .
172 2
			$first_parent .
173 2
			static::SEGMENT_GLUE .
174 2
			implode( static::SEGMENT_VALUE_GLUE, $parents ) .
175 2
			static::SEGMENT_GLUE .
176 2
			$hierarchy_index .
177 2
			static::SEGMENT_GLUE .
178 2
			$value_group_index .
179 2
			static::SEGMENT_GLUE .
180 2
			$property;
181 2
		return $storage_key;
182
	}
183
184
	/**
185
	 * Get an array of storage key patterns for use when getting values from storage
186
	 *
187
	 * @param bool $is_simple_root_field
188
	 * @param array<string> $full_hierarchy
189
	 * @return array
190
	 */
191 3
	public function get_storage_key_getter_patterns( $is_simple_root_field, $full_hierarchy ) {
192 3
		$patterns = array();
193
194 3 View Code Duplication
		if ( $is_simple_root_field ) {
195 1
			$key = $this->get_storage_key_for_simple_root_field( $full_hierarchy[ count( $full_hierarchy ) - 1 ] );
196 1
			$patterns[ $key ] = static::PATTERN_COMPARISON_EQUAL;
197
		}
198
199 3
		$parents = $full_hierarchy;
200 3
		$first_parent = array_shift( $parents );
201
202 3
		$storage_key = static::KEY_PREFIX . $first_parent . static::SEGMENT_GLUE;
203 3
		if ( empty( $parents ) ) {
204 2
			$patterns[ $storage_key ] = static::PATTERN_COMPARISON_STARTS_WITH;
205
		} else {
206 1
			$key = $storage_key . implode( static::SEGMENT_VALUE_GLUE, $parents ) . static::SEGMENT_GLUE;
207 1
			$patterns[ $key ] = static::PATTERN_COMPARISON_STARTS_WITH;
208
209 1
			$key = $storage_key . implode( static::SEGMENT_VALUE_GLUE, $parents ) . static::SEGMENT_VALUE_GLUE;
210 1
			$patterns[ $key ] = static::PATTERN_COMPARISON_STARTS_WITH;
211
		}
212
213 3
		return $patterns;
214
	}
215
216
	/**
217
	 * Get an array of storage key patterns for use when deleting values from storage
218
	 *
219
	 * @param bool $is_complex_field
220
	 * @param bool $is_simple_root_field
221
	 * @param array<string> $full_hierarchy
222
	 * @param array<int> $full_hierarchy_index
223
	 * @return array
224
	 */
225 4
	public function get_storage_key_deleter_patterns( $is_complex_field, $is_simple_root_field, $full_hierarchy, $full_hierarchy_index ) {
226 4
		$full_hierarchy_index = $this->get_sanitized_hierarchy_index( $full_hierarchy, $full_hierarchy_index );
227 4
		$patterns = array();
228
229 4 View Code Duplication
		if ( $is_simple_root_field ) {
230 1
			$key = $this->get_storage_key_for_simple_root_field( $full_hierarchy[ count( $full_hierarchy ) - 1 ] );
231 1
			$patterns[ $key ] = static::PATTERN_COMPARISON_EQUAL;
232
		}
233
234 4
		if ( $is_complex_field ) {
235 2
			$patterns[ $this->get_storage_key_root( $full_hierarchy ) ] = static::PATTERN_COMPARISON_STARTS_WITH;
236
		} else {
237 2
			$patterns[ $this->get_storage_key_prefix( $full_hierarchy, $full_hierarchy_index ) ] = static::PATTERN_COMPARISON_STARTS_WITH;
238
		}
239
240 4
		return $patterns;
241
	}
242
243
	/**
244
	 * Convert an array of storage key patterns to a parentheses-enclosed sql comparison
245
	 *
246
	 * @param string $table_column
247
	 * @param array $patterns
248
	 * @return string
249
	 */
250 2
	public function storage_key_patterns_to_sql( $table_column, $patterns ) {
251 2
		$sql = array();
252
253 2
		foreach ( $patterns as $storage_key => $type ) {
254 2
			$comparison = '';
255
			switch ( $type ) {
256 2
				case static::PATTERN_COMPARISON_EQUAL:
257 1
					$comparison = $table_column . ' = "' . esc_sql( $storage_key ) . '"';
258 1
					break;
259 2
				case static::PATTERN_COMPARISON_STARTS_WITH:
260 1
					$comparison = $table_column . ' LIKE "' . esc_sql( $storage_key ) . '%"';
261 1
					break;
262
				default:
263 1
					Incorrect_Syntax_Exception::raise( 'Unsupported storage key pattern type used: "' . $type . '"' );
264
					break;
265
			}
266
267 1
			$sql[] = $comparison;
268
		}
269
270 1
		return ' ( ' . implode( ' OR ', $sql ) . ' ) ';
271
	}
272
273
	/**
274
	 * Check if a storage key matches any pattern
275
	 *
276
	 * @param string $storage_key
277
	 * @param array $patterns
278
	 * @return bool
279
	 */
280 8
	public function storage_key_matches_any_pattern( $storage_key, $patterns ) {
281 8
		foreach ( $patterns as $key => $type ) {
282
			switch ( $type ) {
283 8
				case static::PATTERN_COMPARISON_EQUAL:
284 5
					if ( $storage_key === $key ) {
285 3
						return true;
286
					}
287 3
					break;
288 4
				case static::PATTERN_COMPARISON_STARTS_WITH:
289 3
					$key_length = strlen( $key );
290 3
					if ( substr( $storage_key, 0, $key_length ) === $key ) {
291 1
						return true;
292
					}
293 2
					break;
294
				default:
295 1
					Incorrect_Syntax_Exception::raise( 'Unsupported storage key pattern type used: "' . $type . '"' );
296 4
					break;
297
			}
298
		}
299 3
		return false;
300
	}
301
302
	/**
303
	 * Check if an array of segments has all of its segments
304
	 *
305
	 * @param array<string> $segments_array
306
	 * @return bool
307
	 */
308 1
	public function is_segments_array_full( $segments_array ) {
309 1
		return ( count( $segments_array ) === static::TOTAL_SEGMENTS );
310
	}
311
312
	/**
313
	 * Convert a storage key to an array of its segments
314
	 *
315
	 * @param string $storage_key
316
	 * @return array<string>
317
	 */
318 1
	public function storage_key_to_segments_array( $storage_key ) {
319 1
		$key = substr( $storage_key, strlen( static::KEY_PREFIX ) ); // drop the key prefix
320 1
		$segments = explode( static::SEGMENT_GLUE, $key );
321 1
		return $segments;
322
	}
323
324
	/**
325
	 * Convert a segment to an array of its values
326
	 *
327
	 * @param string $segment
328
	 * @return array<mixed>
329
	 */
330 1
	public function segment_to_segment_values_array( $segment ) {
331 1
		return explode( static::SEGMENT_VALUE_GLUE , $segment );
332
	}
333
334
	/**
335
	 * Get a parsed array of storage key segments and values
336
	 *
337
	 * @param string $storage_key
338
	 * @return array
339
	 */
340 3
	public function parse_storage_key( $storage_key ) {
341
		$parsed = array(
342 3
			'root' => '',
343
			'hierarchy' => array(),
344
			'hierarchy_index' => array(),
345 3
			'value_index' => 0,
346 3
			'property' => Value_Set::VALUE_PROPERTY,
347
		);
348
349 3
		$segments = $this->storage_key_to_segments_array( $storage_key );
350
351 3
		$parsed['root'] = $segments[0];
352 3
		if ( $this->is_segments_array_full( $segments ) ) {
353 2
			$parsed['hierarchy'] = array_filter( explode( static::SEGMENT_VALUE_GLUE, $segments[1] ) );
354
355 2
			if ( ! empty( $segments[2] ) ) {
356 2
				$parsed['hierarchy_index'] = array_map( 'intval', explode( static::SEGMENT_VALUE_GLUE, $segments[2] ) );
357
			}
358
359 2
			$parsed['value_index'] = intval( $segments[3] );
360
361 2
			$parsed['property'] = $segments[4];
362
		}
363
364
		// Add utility values
365 3
		$parsed['full_hierarchy'] = array_merge( array( $parsed['root'] ), $parsed['hierarchy'] );
366
367 3
		return $parsed;
368
	}
369
}
370