Completed
Push — development ( 54855a...7511d5 )
by
unknown
02:11
created

Fulfillable_Collection::create_collection()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Carbon_Fields\Container\Fulfillable;
4
5
use Carbon_Fields\Container\Condition\Factory;
6
use Carbon_Fields\Container\Fulfillable\Translator\Array_Translator;
7
use Carbon_Fields\Exception\Incorrect_Syntax_Exception;
8
9
class Fulfillable_Collection implements Fulfillable {
10
11
	/**
12
	 * Condition factory used to translated condition types
13
	 *
14
	 * @var Factory
15
	 */
16
	protected $condition_factory;
17
18
	/**
19
	 * Array translator used to support array representations of fulfillables
20
	 *
21
	 * @var Array_Translator
22
	 */
23
	protected $array_translator;
24
25
	/**
26
	 * Array of fulfillables in this collection
27
	 *
28
	 * @var array<array>
29
	 */
30
	protected $fulfillables = array();
31
32
	/**
33
	 * Array of supported fulfillable comparisons
34
	 *
35
	 * @var array<string>
36
	 */
37
	protected $supported_fulfillable_comparisons = array( 'AND', 'OR' );
38
39
	/**
40
	 * Array of allowed condition types which propagate to child collections
41
	 *
42
	 * @var array<string>
43
	 */
44
	protected $condition_type_list = array();
45
46
	/**
47
	 * Whether the condition type list is a whitelist or a blacklist
48
	 *
49
	 * @var bool
50
	 */
51
	protected $condition_type_list_whitelist = false;
52
53
	/**
54
	 * Constructor
55
	 *
56
	 * @param Factory          $condition_factory
57
	 * @param Array_Translator $array_translator
58
	 */
59
	public function __construct( Factory $condition_factory, Array_Translator $array_translator ) {
60
		$this->condition_factory = $condition_factory;
61
		$this->array_translator = $array_translator;
62
	}
63
64
	/**
65
	 * Create a new collection
66
	 *
67
	 * @return Fulfillable_Collection
68
	 */
69
	protected function create_collection() {
70
		return \Carbon_Fields\Carbon_Fields::resolve( 'container_condition_fulfillable_collection' );
71
	}
72
73
	/**
74
	 * Get an array of the fulfillables in this collection
75
	 *
76
	 * @return array<Fulfillable>
77
	 */
78 1
	public function get_fulfillables() {
79 1
		return $this->fulfillables;
80
	}
81
82
	/**
83
	 * Get array of allowed condition types
84
	 *
85
	 * @return array<string>
86
	 */
87
	public function get_condition_type_list() {
88
		return $this->condition_type_list;
89
	}
90
91
	/**
92
	 * Set array of allowed condition types
93
	 * WARNING: this will NOT remove already added conditions which are no longer allowed
94
	 *
95
	 * @param  array<string>          $condition_type_list
96
	 * @param  bool                   $whitelist
97
	 * @return Fulfillable_Collection $this
98
	 */
99
	public function set_condition_type_list( $condition_type_list, $whitelist ) {
100
		$this->condition_type_list_whitelist = $whitelist;
101
		$this->condition_type_list = $condition_type_list;
102
		$this->propagate_condition_type_list();
103
		return $this;
104
	}
105
106
	/**
107
	 * Check if conditions types list is a whitelist
108
	 *
109
	 * @return bool
110
	 */
111
	public function is_condition_type_list_whitelist() {
112
		return $this->condition_type_list_whitelist;
113
	}
114
115
	/**
116
	 * Check if condition type is allowed
117
	 *
118
	 * @param  string $condition_type
119
	 * @return bool
120
	 */
121
	public function is_condition_type_allowed( $condition_type ) {
122
		$in_list = in_array( $condition_type, $this->get_condition_type_list() );
123
		if ( $this->is_condition_type_list_whitelist() ) {
124
			return $in_list;
125
		}
126
		return ! $in_list;
127
	}
128
129
	/**
130
	 * Propagate allowed condition types to child collections
131
	 */
132
	protected function propagate_condition_type_list() {
133
		$condition_type_list = $this->get_condition_type_list();
134
		$fulfillables = $this->get_fulfillables();
135
		foreach ( $fulfillables as $fulfillable ) {
136
			if ( is_a( $fulfillable['fulfillable'], get_class() ) ) {
137
				$fulfillable->set_condition_type_list( $condition_type_list, $this->is_condition_type_list_whitelist() );
138
			}
139
		}
140
	}
141
142
	/**
143
	 * Shorthand for where with OR comparison
144
	 *
145
	 * @param  string|array|callable  $condition_type
146
	 * @param  string                 $comparison_operator Can be skipped. Defaults to "="
147
	 * @param  mixed                  $value
148
	 * @return Fulfillable_Collection $this
149
	 */
150
	public function or_where( $condition_type, $comparison_operator = '=', $value = null ) {
151
		$this->where( $condition_type, $comparison_operator, $value, 'OR' );
152
		return $this;
153
	}
154
155
	/**
156
	 * Add fulfillable with optional comparison_operator
157
	 * This method assumes there is no fulfillable that can be compared with literal NULL
158
	 *
159
	 * @param  string|array|callable  $condition_type
160
	 * @param  string                 $comparison_operator Can be skipped. Defaults to "="
161
	 * @param  mixed                  $value
162
	 * @param  string                 $fulfillable_comparison
163
	 * @return Fulfillable_Collection $this
164
	 */
165 9
	public function where( $condition_type, $comparison_operator = '=', $value = null, $fulfillable_comparison = 'AND' ) {
1 ignored issue
show
Comprehensibility Naming introduced by
The variable name $fulfillable_comparison exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
166 9
		$args = func_get_args();
0 ignored issues
show
Unused Code introduced by
$args is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
167 9
		if ( is_array( $condition_type ) ) {
168 2
			return $this->where_array( $condition_type, $fulfillable_comparison );
169
		}
170
171 7
		if ( $condition_type instanceof \Closure ) {
172 2
			return $this->where_collection( $condition_type, $fulfillable_comparison );
173
		}
174
175 7
		if ( ! $this->is_condition_type_allowed( $condition_type ) ) {
176
			Incorrect_Syntax_Exception::raise( 'Unsupported container condition used: ' . $condition_type );
177
			return $this;
178
		}
179
180 7
		if ( $value === null ) {
181
			// We do not have a supplied comparison_operator so we default to "="
182 5
			$value = $comparison_operator;
183 5
			$comparison_operator = '=';
184 5
		}
185
186 7
		$condition = $this->condition_factory->make( $condition_type );
187 6
		$condition->set_comparison_operator( $comparison_operator );
188 6
		$condition->set_value( $value );
189 6
		$this->add_fulfillable( $condition, $fulfillable_comparison );
190 6
		return $this;
191
	}
192
193
	/**
194
	 * Add a Fulfillable through array representation
195
	 *
196
	 * @param  array                  $fulfillable_as_array
197
	 * @param  string                 $fulfillable_comparison
198
	 * @return Fulfillable_Collection $this
199
	 */
200
	protected function where_array( $fulfillable_as_array, $fulfillable_comparison) {
1 ignored issue
show
Comprehensibility Naming introduced by
The variable name $fulfillable_comparison exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
201
		$fulfillable = $this->array_translator->foreign_to_fulfillable( $fulfillable_as_array );
202
		$this->add_fulfillable( $fulfillable, $fulfillable_comparison );
203
		return $this;
204
	}
205
206
	/**
207
	 * Add a Fulfillable_Collection for nested logic
208
	 *
209
	 * @param  callable               $collection_callable
210
	 * @param  string                 $fulfillable_comparison
211
	 * @return Fulfillable_Collection $this
212
	 */
213
	protected function where_collection( $collection_callable, $fulfillable_comparison) {
1 ignored issue
show
Comprehensibility Naming introduced by
The variable name $fulfillable_comparison exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
214
		$collection = $this->create_collection();
215
		$collection->set_condition_type_list( $this->get_condition_type_list(), $this->is_condition_type_list_whitelist() );
216
		$collection_callable( $collection );
217
		$this->add_fulfillable( $collection, $fulfillable_comparison );
218
		return $this;
219
	}
220
221
	/**
222
	 * Add fulfillable to collection
223
	 *
224
	 * @param Fulfillable $fulfillable
225
	 * @param string      $fulfillable_comparison See static::$supported_fulfillable_comparisons
226
	 */
227 2
	public function add_fulfillable( Fulfillable $fulfillable, $fulfillable_comparison ) {
1 ignored issue
show
Comprehensibility Naming introduced by
The variable name $fulfillable_comparison exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
228 2
		if ( ! in_array( $fulfillable_comparison, $this->supported_fulfillable_comparisons ) ) {
229 1
			Incorrect_Syntax_Exception::raise( 'Invalid fulfillable comparison passed: ' . $fulfillable_comparison );
230
			return;
231
		}
232
233 1
		$this->fulfillables[] = array(
234 1
			'fulfillable_comparison' => $fulfillable_comparison,
235 1
			'fulfillable' => $fulfillable,
236
		);
237 1
	}
238
239
	/**
240
	 * Remove fulfillable from collection
241
	 *
242
	 * @param Fulfillable $fulfillable
243
	 * @return bool Fulfillable found and removed
244
	 */
245
	public function remove_fulfillable( Fulfillable $fulfillable ) {
246
		$fulfillables = $this->get_fulfillables();
247
		foreach ( $fulfillables as $index => $fulfillable_tuple ) {
248
			if ( $fulfillable_tuple['fulfillable'] === $fulfillable ) {
249
				$fulfillables_copy = $fulfillables; // introduce a copy array to highlight array_splice mutation
250
				array_splice( $fulfillables_copy, $index, 1 );
251
				$this->fulfillables = array_values( $fulfillables_copy ); // make sure our array is indexed cleanly
252
				return true;
253
			}
254
		}
255
		return false;
256
	}
257
258
	/**
259
	 * Get a copy of the collection with conditions not in the whitelist filtered out
260
	 *
261
	 * @param  array<string>          $condition_whitelist
262
	 * @return Fulfillable_Collection
263
	 */
264 2
	public function filter( $condition_whitelist ) {
265 2
		$fulfillables = $this->get_fulfillables();
266
267 2
		$collection = $this->create_collection();
268 2
		foreach ( $fulfillables as $fulfillable_tuple ) {
269 2
			$fulfillable = $fulfillable_tuple['fulfillable'];
270 2
			$fulfillable_comparison = $fulfillable_tuple['fulfillable_comparison'];
271
272 2
			if ( is_a( $fulfillable, get_class() ) ) {
273 1
				$filtered_collection = $fulfillable->filter( $condition_whitelist );
274 1
				$filtered_collection_fulfillables = $filtered_collection->get_fulfillables();
275 1
				if ( empty( $filtered_collection_fulfillables ) ) {
276 1
					continue; // skip empty collections to reduce clutter
277
				}
278
				$collection->add_fulfillable( $filtered_collection, $fulfillable_comparison );
279
			} else {
280 2
				$type = $this->condition_factory->get_type( get_class( $fulfillable ) );
281 2
				if ( ! in_array( $type, $condition_whitelist ) ) {
282 2
					continue;
283
				}
284
285 2
				$fulfillable_clone = clone $fulfillable;
286 2
				$collection->add_fulfillable( $fulfillable_clone, $fulfillable_comparison );
287
			}
288 2
		}
289 2
		return $collection;
290
	}
291
292
	/**
293
	 * Get a copy of the collection with passed conditions evaluated into boolean conditions
294
	 * Useful when evaluating only certain condition types but preserving the rest
295
	 * or when passing dynamic conditions to the front-end
296
	 *
297
	 * @param  array<string>          $condition_types
298
	 * @param  array|boolean          $environment Environment array or a boolean value to force on conditions
299
	 * @param  array<string>          $comparison_operators Array of comparison operators to evaluate regardless of condition type
300
	 * @param  boolean                $condition_types_blacklist Whether the condition list should act as a blacklist
301
	 * @param  boolean                $comparison_operators_blacklist Whether the comparison operators list should act as a blacklist
302
	 * @return Fulfillable_Collection
303
	 */
304 3
	public function evaluate( $condition_types, $environment, $comparison_operators = array(), $condition_types_blacklist = false, $comparison_operators_blacklist = false ) {
2 ignored issues
show
Comprehensibility Naming introduced by
The variable name $condition_types_blacklist exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
Comprehensibility Naming introduced by
The variable name $comparison_operators_blacklist exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
305 3
		$fulfillables = $this->get_fulfillables();
306
307 3
		$collection = $this->create_collection();
308 3
		foreach ( $fulfillables as $fulfillable_tuple ) {
309 3
			$fulfillable = $fulfillable_tuple['fulfillable'];
310 3
			$fulfillable_comparison = $fulfillable_tuple['fulfillable_comparison'];
311
312 3
			if ( is_a( $fulfillable, get_class() ) ) {
313
				$evaluated_collection = $fulfillable->evaluate( $condition_types, $environment, $comparison_operators, $condition_types_blacklist, $comparison_operators_blacklist );
314
				$collection->add_fulfillable( $evaluated_collection, $fulfillable_comparison );
315
			} else {
316 3
				$type = $this->condition_factory->get_type( get_class( $fulfillable ) );
317 3
				$comparison_operator = $fulfillable->get_comparison_operator();
318
319 3
				$condition_type_match = in_array( $type, $condition_types );
320 3
				if ( $condition_types_blacklist ) {
321
					$condition_type_match = ! $condition_type_match;
322
				}
323
324 3
				$comparison_operator_match = in_array( $comparison_operator, $comparison_operators );
1 ignored issue
show
Comprehensibility Naming introduced by
The variable name $comparison_operator_match exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
325 3
				if ( $comparison_operators_blacklist ) {
326
					$comparison_operator_match = ! $comparison_operator_match;
327
				}
328
329 3
				if ( $condition_type_match || $comparison_operator_match ) {
330 3
					$boolean_condition = $this->condition_factory->make( 'boolean' );
331 3
					$boolean_condition->set_comparison_operator( '=' );
332
333 3
					$value = is_bool( $environment ) ? $environment : $fulfillable->is_fulfilled( $environment );
334 3
					$boolean_condition->set_value( $value );
335 3
					$collection->add_fulfillable( $boolean_condition, $fulfillable_comparison );
336 3
				} else {
337 3
					$collection->add_fulfillable( clone $fulfillable, $fulfillable_comparison );
338
				}
339
			}
340 3
		}
341 3
		return $collection;
342
	}
343
344
	/**
345
	 * Check if all fulfillables are fulfilled taking into account their fulfillable comparison
346
	 *
347
	 * @param  array $environment
348
	 * @return bool
349
	 */
350 14
	public function is_fulfilled( $environment ) {
351 14
		$fulfilled = true; // return true for empty collections
352 14
		$fulfillables = $this->get_fulfillables();
353
354 14
		foreach ( $fulfillables as $i => $fulfillable_tuple ) {
355 13
			$fulfillable = $fulfillable_tuple['fulfillable'];
356 13
			$fulfillable_comparison = $fulfillable_tuple['fulfillable_comparison'];
357
358 13
			if ( $i === 0 ) {
359
				// Ignore first comparison as we need a base fulfillment value
360 13
				$fulfilled = $fulfillable->is_fulfilled( $environment );
361 13
				continue;
362
			}
363
364
			// minor optimization - avoid unnecessary AND check if $fulfilled is currently false
365
			// false && whatever is always false
366 4
			if ( $fulfillable_comparison === 'AND' && $fulfilled ) {
367 2
				$fulfilled = $fulfillable->is_fulfilled( $environment );
368 2
			}
369
370
			// minor optimization - avoid unnecessary OR check if $fulfilled is currently true
371
			// true || whatever is always true
372 4
			if ( $fulfillable_comparison === 'OR' && ! $fulfilled ) {
373 1
				$fulfilled = $fulfillable->is_fulfilled( $environment );
374 1
			}
375 14
		}
376
377 14
		return $fulfilled;
378
	}
379
}