Completed
Branch milestone/2_0/react-ui (57d10c)
by htmlBurger
02:47
created

Fulfillable_Collection::when()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 26
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 5.0488

Importance

Changes 0
Metric Value
cc 5
eloc 16
c 0
b 0
f 0
nc 5
nop 4
dl 0
loc 26
ccs 14
cts 16
cp 0.875
crap 5.0488
rs 8.439
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
	 * Get an array of the fulfillables in this collection
66
	 * 
67
	 * @return array<Fulfillable>
68
	 */
69 1
	public function get_fulfillables() {
70 1
		return $this->fulfillables;
71
	}
72
73
	/**
74
	 * Get array of allowed condition types
75
	 * 
76
	 * @return array<string>
77
	 */
78
	public function get_condition_type_list() {
79
		return $this->condition_type_list;
80
	}
81
82
	/**
83
	 * Set array of allowed condition types
84
	 * WARNING: this will NOT remove already added conditions which are no longer allowed
85
	 * 
86
	 * @param  array<string>          $condition_type_list
87
	 * @param  bool                   $whitelist
88
	 * @return Fulfillable_Collection $this
89
	 */
90
	public function set_condition_type_list( $condition_type_list, $whitelist ) {
91
		$this->condition_type_list_whitelist = $whitelist;
92
		$this->condition_type_list = $condition_type_list;
93
		$this->propagate_condition_type_list();
94
		return $this;
95
	}
96
97
	/**
98
	 * Check if conditions types list is a whitelist
99
	 * 
100
	 * @return bool
101
	 */
102
	public function is_condition_type_list_whitelist() {
103
		return $this->condition_type_list_whitelist;
104
	}
105
106
	/**
107
	 * Check if condition type is allowed
108
	 *
109
	 * @param  string $condition_type
110
	 * @return bool
111
	 */
112
	public function is_condition_type_allowed( $condition_type ) {
113
		$in_list = in_array( $condition_type, $this->get_condition_type_list() );
114
		if ( $this->is_condition_type_list_whitelist() ) {
115
			return $in_list;
116
		}
117
		return ! $in_list;
118
	}
119
120
	/**
121
	 * Propagate allowed condition types to child collections
122
	 */
123
	protected function propagate_condition_type_list() {
124
		$condition_type_list = $this->get_condition_type_list();
125
		$fulfillables = $this->get_fulfillables();
126
		foreach ( $fulfillables as $fulfillable ) {
127
			if ( is_a( $fulfillable['fulfillable'], get_class() ) ) {
128
				$fulfillable->set_condition_type_list( $condition_type_list, $this->is_condition_type_list_whitelist() );
129
			}
130
		}
131
	}
132
	
133
	/**
134
	 * Shorthand for when with AND comparison
135
	 * 
136
	 * @param  string|array|callable  $condition_type
137
	 * @param  string                 $comparison_operator Can be skipped. Defaults to "="
138
	 * @param  mixed                  $value
139
	 * @return Fulfillable_Collection $this
140
	 */
141
	public function and_when( $condition_type, $comparison_operator = '=', $value = null ) {
142
		$this->when( $condition_type, $comparison_operator, $value, 'AND' );
143
		return $this;
144
	}
145
	
146
	/**
147
	 * Shorthand for when with OR comparison
148
	 * 
149
	 * @param  string|array|callable  $condition_type
150
	 * @param  string                 $comparison_operator Can be skipped. Defaults to "="
151
	 * @param  mixed                  $value
152
	 * @return Fulfillable_Collection $this
153
	 */
154
	public function or_when( $condition_type, $comparison_operator = '=', $value = null ) {
155
		$this->when( $condition_type, $comparison_operator, $value, 'OR' );
156
		return $this;
157
	}
158
159
	/**
160
	 * Add fulfillable with optional comparison_operator
161
	 * This method assumes there is no fulfillable that can be compared with literal NULL
162
	 * 
163
	 * @param  string|array|callable  $condition_type
164
	 * @param  string                 $comparison_operator Can be skipped. Defaults to "="
165
	 * @param  mixed                  $value
166
	 * @param  string                 $fulfillable_comparison
167
	 * @return Fulfillable_Collection $this
168
	 */
169 9
	public function when( $condition_type, $comparison_operator = '=', $value = null, $fulfillable_comparison = 'AND' ) {
170 9
		if ( is_array( $condition_type ) ) {
171 2
			return $this->when_array( $condition_type, $fulfillable_comparison );
172
		}
173
174 7
		if ( is_callable( $condition_type ) ) {
175 2
			return $this->when_collection( $condition_type, $fulfillable_comparison );
176
		}
177
178 7
		if ( ! $this->is_condition_type_allowed( $condition_type ) ) {
179
			Incorrect_Syntax_Exception::raise( 'Unsupported container condition used: ' . $condition_type );
180
			return $this;
181
		}
182
183 7
		if ( $value === null ) {
184
			// We do not have a supplied comparison_operator so we default to "="
185 5
			$value = $comparison_operator;
186 5
			$comparison_operator = '=';
187
		}
188
189 7
		$condition = $this->condition_factory->make( $condition_type );
190 6
		$condition->set_comparison_operator( $comparison_operator );
191 6
		$condition->set_value( $value );
192 6
		$this->add_fulfillable( $condition, $fulfillable_comparison );
193 6
		return $this;
194
	}
195
196
	/**
197
	 * Add a Fulfillable through array representation
198
	 *
199
	 * @param  array                  $fulfillable_as_array
200
	 * @param  string                 $fulfillable_comparison
201
	 * @return Fulfillable_Collection $this
202
	 */
203
	protected function when_array( $fulfillable_as_array, $fulfillable_comparison) {
204
		$fulfillable = $this->array_translator->foreign_to_fulfillable( $fulfillable_as_array );
205
		$this->add_fulfillable( $fulfillable, $fulfillable_comparison );
206
		return $this;
207
	}
208
209
	/**
210
	 * Add a Fulfillable_Collection for nested logic
211
	 *
212
	 * @param  callable               $collection_callable
213
	 * @param  string                 $fulfillable_comparison
214
	 * @return Fulfillable_Collection $this
215
	 */
216
	protected function when_collection( $collection_callable, $fulfillable_comparison) {
217
		$collection = \Carbon_Fields\Carbon_Fields::resolve( 'container_condition_fulfillable_collection' );
218
		$collection->set_condition_type_list( $this->get_condition_type_list(), $this->is_condition_type_list_whitelist() );
219
		$collection_callable( $collection );
220
		$this->add_fulfillable( $collection, $fulfillable_comparison );
221
		return $this;
222
	}
223
224
	/**
225
	 * Add fulfillable to collection
226
	 * 
227
	 * @param Fulfillable $fulfillable
228
	 * @param string      $fulfillable_comparison See static::$supported_fulfillable_comparisons
229
	 */
230 2
	public function add_fulfillable( Fulfillable $fulfillable, $fulfillable_comparison ) {
231 2
		if ( ! in_array( $fulfillable_comparison, $this->supported_fulfillable_comparisons ) ) {
232 1
			Incorrect_Syntax_Exception::raise( 'Invalid fulfillable comparison passed: ' . $fulfillable_comparison );
233
			return;
234
		}
235
236 1
		$this->fulfillables[] = array(
237 1
			'fulfillable_comparison' => $fulfillable_comparison,
238 1
			'fulfillable' => $fulfillable,
239
		);
240 1
	}
241
242
	/**
243
	 * Remove fulfillable from collection
244
	 * 
245
	 * @param Fulfillable $fulfillable
246
	 * @return bool Fulfillable found and removed
247
	 */
248
	public function remove_fulfillable( Fulfillable $fulfillable ) {
249
		$fulfillables = $this->get_fulfillables();
250
		foreach ( $fulfillables as $index => $fulfillable_tuple ) {
251
			if ( $fulfillable_tuple['fulfillable'] === $fulfillable ) {
252
				$fulfillables_copy = $fulfillables; // introduce a copy array to highlight array_splice mutation
253
				array_splice( $fulfillables_copy, $index, 1 );
254
				$this->fulfillables = array_values( $fulfillables_copy ); // make sure our array is indexed cleanly
255
				return true;
256
			}
257
		}
258
		return false;
259
	}
260
261
	/**
262
	 * Get a copy of the collection with conditions not in the whitelist filtered out
263
	 * 
264
	 * @param  array<string>          $condition_whitelist
265
	 * @return Fulfillable_Collection
266
	 */
267 2
	public function filter( $condition_whitelist ) {
268 2
		$fulfillables = $this->get_fulfillables();
269
270 2
		$collection = \Carbon_Fields\Carbon_Fields::resolve( 'container_condition_fulfillable_collection' );
271 2
		foreach ( $fulfillables as $fulfillable_tuple ) {
272 2
			$fulfillable = $fulfillable_tuple['fulfillable'];
273 2
			$fulfillable_comparison = $fulfillable_tuple['fulfillable_comparison'];
274
275 2
			if ( is_a( $fulfillable, get_class() ) ) {
276 1
				$filtered_collection = $fulfillable->filter( $condition_whitelist );
277 1
				$filtered_collection_fulfillables = $filtered_collection->get_fulfillables();
278 1
				if ( empty( $filtered_collection_fulfillables ) ) {
279 1
					continue; // skip empty collections to reduce clutter
280
				}
281
				$collection->add_fulfillable( $filtered_collection, $fulfillable_comparison );
282
			} else {
283 2
				$type = $this->condition_factory->get_type( get_class( $fulfillable ) );
284 2
				if ( ! in_array( $type, $condition_whitelist ) ) {
285 2
					continue;
286
				}
287
288 2
				$fulfillable_clone = clone $fulfillable;
289 2
				$collection->add_fulfillable( $fulfillable_clone, $fulfillable_comparison );
290
			}
291
		}
292 2
		return $collection;
293
	}
294
295
	/**
296
	 * Get a copy of the collection with passed conditions evaluated into boolean conditions
297
	 * Useful when evaluating only certain condition types but preserving the rest
298
	 * or when passing dynamic conditions to the front-end
299
	 * 
300
	 * @param  array<string>          $condition_types
301
	 * @param  array|boolean          $environment Environment array or a boolean value to force on conditions
302
	 * @param  array                  $comparison_operators Array of comparison operators to evaluate regardless of condition type
303
	 * @return Fulfillable_Collection
304
	 */
305 3
	public function evaluate( $condition_types, $environment, $comparison_operators = array() ) {
306 3
		$fulfillables = $this->get_fulfillables();
307
308 3
		$collection = \Carbon_Fields\Carbon_Fields::resolve( 'container_condition_fulfillable_collection' );
309 3
		foreach ( $fulfillables as $fulfillable_tuple ) {
310 3
			$fulfillable = $fulfillable_tuple['fulfillable'];
311 3
			$fulfillable_comparison = $fulfillable_tuple['fulfillable_comparison'];
312
313 3
			if ( is_a( $fulfillable, get_class() ) ) {
314
				$evaluated_collection = $fulfillable->evaluate( $condition_types, $environment, $comparison_operators );
315
				$collection->add_fulfillable( $evaluated_collection, $fulfillable_comparison );
316
			} else {
317 3
				$type = $this->condition_factory->get_type( get_class( $fulfillable ) );
318 3
				$comparison_operator = $fulfillable->get_comparison_operator();
319 3
				if ( in_array( $type, $condition_types ) || in_array( $comparison_operator, $comparison_operators ) ) {
320 3
					$boolean_condition = \Carbon_Fields\Carbon_Fields::resolve( 'container_condition_type_boolean' );
321 3
					$boolean_condition->set_comparison_operator( '=' );
322
323 3
					$value = is_bool( $environment ) ? $environment : $fulfillable->is_fulfilled( $environment );
324 3
					$boolean_condition->set_value( $value );
325 3
					$collection->add_fulfillable( $boolean_condition, $fulfillable_comparison );
326
				} else {
327 3
					$collection->add_fulfillable( clone $fulfillable, $fulfillable_comparison );
328
				}
329
			}
330
		}
331 3
		return $collection;
332
	}
333
	
334
	/**
335
	 * Check if all fulfillables are fulfilled taking into account their fulfillable comparison
336
	 * 
337
	 * @param  array $environment
338
	 * @return bool
339
	 */
340 14
	public function is_fulfilled( $environment ) {
341 14
		$fulfilled = true; // return true for empty collections
342 14
		$fulfillables = $this->get_fulfillables();
343
344 14
		foreach ( $fulfillables as $i => $fulfillable_tuple ) {
345 13
			$fulfillable = $fulfillable_tuple['fulfillable'];
346 13
			$fulfillable_comparison = $fulfillable_tuple['fulfillable_comparison'];
347
348 13
			if ( $i === 0 ) {
349
				// Ignore first comparison as we need a base fulfillment value
350 13
				$fulfilled = $fulfillable->is_fulfilled( $environment );
351 13
				continue;
352
			}
353
354
			// minor optimization - avoid unnecessary AND check if $fulfilled is currently false
355
			// false && whatever is always false
356 4
			if ( $fulfillable_comparison === 'AND' && $fulfilled ) {
357 2
				$fulfilled = $fulfillable->is_fulfilled( $environment );
358
			}
359
360
			// minor optimization - avoid unnecessary OR check if $fulfilled is currently true
361
			// true || whatever is always true
362 4
			if ( $fulfillable_comparison === 'OR' && ! $fulfilled ) {
363 4
				$fulfilled = $fulfillable->is_fulfilled( $environment );
364
			}
365
		}
366
367 14
		return $fulfilled;
368
	}
369
}