WC_CLI_Command::get_default_format_fields()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
eloc 2
nc 1
nop 0
1
<?php
2
3
/**
4
 * WooCommerce CLI Command.
5
 *
6
 * Base class that must be extended by any WooCommerce sub commands.
7
 *
8
 * @class    WC_CLI_Command
9
 * @version  2.5.0
10
 * @package  WooCommerce/CLI
11
 * @category CLI
12
 * @author   WooThemes
13
 */
14
class WC_CLI_Command extends WP_CLI_Command {
15
16
	/**
17
	 * Add common cli arguments to argument list before WP_Query is run.
18
	 *
19
	 * @since  2.5.0
20
	 * @param  array $base_args  Required arguments for the query (e.g. `post_type`, etc)
21
	 * @param  array $assoc_args Arguments provided in when invoking the command
22
	 * @return array
23
	 */
24
	protected function merge_wp_query_args( $base_args, $assoc_args ) {
25
		$args = array();
26
27
		// date
28
		if ( ! empty( $assoc_args['created_at_min'] ) || ! empty( $assoc_args['created_at_max'] ) || ! empty( $assoc_args['updated_at_min'] ) || ! empty( $assoc_args['updated_at_max'] ) ) {
29
30
			$args['date_query'] = array();
31
32
			// resources created after specified date
33 View Code Duplication
			if ( ! empty( $assoc_args['created_at_min'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
34
				$args['date_query'][] = array( 'column' => 'post_date_gmt', 'after' => $this->parse_datetime( $assoc_args['created_at_min'] ), 'inclusive' => true );
35
			}
36
37
			// resources created before specified date
38 View Code Duplication
			if ( ! empty( $assoc_args['created_at_max'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
39
				$args['date_query'][] = array( 'column' => 'post_date_gmt', 'before' => $this->parse_datetime( $assoc_args['created_at_max'] ), 'inclusive' => true );
40
			}
41
42
			// resources updated after specified date
43 View Code Duplication
			if ( ! empty( $assoc_args['updated_at_min'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
44
				$args['date_query'][] = array( 'column' => 'post_modified_gmt', 'after' => $this->parse_datetime( $assoc_args['updated_at_min'] ), 'inclusive' => true );
45
			}
46
47
			// resources updated before specified date
48 View Code Duplication
			if ( ! empty( $assoc_args['updated_at_max'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
49
				$args['date_query'][] = array( 'column' => 'post_modified_gmt', 'before' => $this->parse_datetime( $assoc_args['updated_at_max'] ), 'inclusive' => true );
50
			}
51
		}
52
53
		// Search.
54
		if ( ! empty( $assoc_args['q'] ) ) {
55
			$args['s'] = $assoc_args['q'];
56
		}
57
58
		// Number of post to show per page.
59
		if ( ! empty( $assoc_args['limit'] ) ) {
60
			$args['posts_per_page'] = $assoc_args['limit'];
61
		}
62
63
		// Number of post to displace or pass over.
64
		if ( ! empty( $assoc_args['offset'] ) ) {
65
			$args['offset'] = $assoc_args['offset'];
66
		}
67
68
		// order (ASC or DESC, DESC by default).
69
		if ( ! empty( $assoc_args['order'] ) ) {
70
			$args['order'] = $assoc_args['order'];
71
		}
72
73
		// orderby.
74
		if ( ! empty( $assoc_args['orderby'] ) ) {
75
			$args['orderby'] = $assoc_args['orderby'];
76
77
			// allow sorting by meta value
78
			if ( ! empty( $assoc_args['orderby_meta_key'] ) ) {
79
				$args['meta_key'] = $assoc_args['orderby_meta_key'];
80
			}
81
		}
82
83
		// allow post status change
84
		if ( ! empty( $assoc_args['post_status'] ) ) {
85
			$args['post_status'] = $assoc_args['post_status'];
86
			unset( $assoc_args['post_status'] );
87
		}
88
89
		// filter by a list of post ids
90
		if ( ! empty( $assoc_args['in'] ) ) {
91
			$args['post__in'] = explode( ',', $assoc_args['in'] );
92
			unset( $assoc_args['in'] );
93
		}
94
95
		// exclude by a list of post ids
96
		if ( ! empty( $assoc_args['not_in'] ) ) {
97
			$args['post__not_in'] = explode( ',', $assoc_args['not_in'] );
98
			unset( $assoc_args['not_in'] );
99
		}
100
101
		// posts page.
102
		$args['paged'] = ( isset( $assoc_args['page'] ) ) ? absint( $assoc_args['page'] ) : 1;
103
104
		$args = apply_filters( 'woocommerce_cli_query_args', $args, $assoc_args );
105
106
		return array_merge( $base_args, $args );
107
	}
108
109
	/**
110
	 * Add common cli arguments to argument list before WP_User_Query is run.
111
	 *
112
	 * @since  2.5.0
113
	 * @param  array $base_args required arguments for the query (e.g. `post_type`, etc)
114
	 * @param  array $assoc_args arguments provided in when invoking the command
115
	 * @return array
116
	 */
117
	protected function merge_wp_user_query_args( $base_args, $assoc_args ) {
118
		$args = array();
119
120
		// Custom Role
121
		if ( ! empty( $assoc_args['role'] ) ) {
122
			$args['role'] = $assoc_args['role'];
123
		}
124
125
		// Search
126
		if ( ! empty( $assoc_args['q'] ) ) {
127
			$args['search'] = $assoc_args['q'];
128
		}
129
130
		// Limit number of users returned.
131
		if ( ! empty( $assoc_args['limit'] ) ) {
132
			$args['number'] = absint( $assoc_args['limit'] );
133
		}
134
135
		// Offset
136
		if ( ! empty( $assoc_args['offset'] ) ) {
137
			$args['offset'] = absint( $assoc_args['offset'] );
138
		}
139
140
		// date
141
		if ( ! empty( $assoc_args['created_at_min'] ) || ! empty( $assoc_args['created_at_max'] ) ) {
142
143
			$args['date_query'] = array();
144
145
			// resources created after specified date
146 View Code Duplication
			if ( ! empty( $assoc_args['created_at_min'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
147
				$args['date_query'][] = array( 'after' => $this->parse_datetime( $assoc_args['created_at_min'] ), 'inclusive' => true );
148
			}
149
150
			// resources created before specified date
151
			if ( ! empty( $assoc_args['created_at_max'] ) ) {
152
				$args['date_query'][] = array( 'before' => $this->parse_datetime( $assoc_args['created_at_max'] ), 'inclusive' => true );
153
			}
154
		}
155
156
		// Order (ASC or DESC, ASC by default).
157
		if ( ! empty( $assoc_args['order'] ) ) {
158
			$args['order'] = $assoc_args['order'];
159
		}
160
161
		// Orderby.
162
		if ( ! empty( $assoc_args['orderby'] ) ) {
163
			$args['orderby'] = $assoc_args['orderby'];
164
		}
165
166
		$args = apply_filters( 'woocommerce_cli_user_query_args', $args, $assoc_args );
167
168
		return array_merge( $base_args, $args );
169
	}
170
171
	/**
172
	 * Parse an RFC3339 datetime into a MySQl datetime.
173
	 *
174
	 * Invalid dates default to unix epoch.
175
	 *
176
	 * @since  2.5.0
177
	 * @param  string $datetime RFC3339 datetime
178
	 * @return string MySQl datetime (YYYY-MM-DD HH:MM:SS)
179
	 */
180
	protected function parse_datetime( $datetime ) {
181
		// Strip millisecond precision (a full stop followed by one or more digits)
182
		if ( strpos( $datetime, '.' ) !== false ) {
183
			$datetime = preg_replace( '/\.\d+/', '', $datetime );
184
		}
185
186
		// default timezone to UTC
187
		$datetime = preg_replace( '/[+-]\d+:+\d+$/', '+00:00', $datetime );
188
189
		try {
190
191
			$datetime = new DateTime( $datetime, new DateTimeZone( 'UTC' ) );
192
193
		} catch ( Exception $e ) {
194
195
			$datetime = new DateTime( '@0' );
196
197
		}
198
199
		return $datetime->format( 'Y-m-d H:i:s' );
200
	}
201
202
	/**
203
	 * Format a unix timestamp or MySQL datetime into an RFC3339 datetime.
204
	 *
205
	 * @since  2.5.0
206
	 * @param  int|string $timestamp unix timestamp or MySQL datetime
207
	 * @param  bool $convert_to_utc
208
	 * @return string RFC3339 datetime
209
	 */
210
	protected function format_datetime( $timestamp, $convert_to_utc = false ) {
211
		if ( $convert_to_utc ) {
212
			$timezone = new DateTimeZone( wc_timezone_string() );
213
		} else {
214
			$timezone = new DateTimeZone( 'UTC' );
215
		}
216
217
		try {
218
			if ( is_numeric( $timestamp ) ) {
219
				$date = new DateTime( "@{$timestamp}" );
220
			} else {
221
				$date = new DateTime( $timestamp, $timezone );
222
			}
223
224
			// convert to UTC by adjusting the time based on the offset of the site's timezone
225
			if ( $convert_to_utc ) {
226
				$date->modify( -1 * $date->getOffset() . ' seconds' );
227
			}
228
229
		} catch ( Exception $e ) {
230
			$date = new DateTime( '@0' );
231
		}
232
233
		return $date->format( 'Y-m-d\TH:i:s\Z' );
234
	}
235
236
	/**
237
	 * Get formatter object based on supplied arguments.
238
	 *
239
	 * @since  2.5.0
240
	 * @param  array $assoc_args Associative args from CLI to determine formatting
241
	 * @return \WP_CLI\Formatter
242
	 */
243
	protected function get_formatter( $assoc_args ) {
244
		$args = $this->get_format_args( $assoc_args );
245
		return new \WP_CLI\Formatter( $args );
246
	}
247
248
	/**
249
	 * Get default fields for formatter.
250
	 *
251
	 * Class that extends WC_CLI_Command should override this method.
252
	 *
253
	 * @since  2.5.0
254
	 * @return null|string|array
255
	 */
256
	protected function get_default_format_fields() {
257
		return null;
258
	}
259
260
	/**
261
	 * Get format args that will be passed into CLI Formatter.
262
	 *
263
	 * @since  2.5.0
264
	 * @param  array $assoc_args Associative args from CLI
265
	 * @return array Formatter args
266
	 */
267
	protected function get_format_args( $assoc_args ) {
268
		$format_args = array(
269
			'fields' => $this->get_default_format_fields(),
270
			'field'  => null,
271
			'format' => 'table',
272
		);
273
274
		if ( isset( $assoc_args['fields'] ) ) {
275
			$format_args['fields'] = $assoc_args['fields'];
276
		}
277
278
		if ( isset( $assoc_args['field'] ) ) {
279
			$format_args['field'] = $assoc_args['field'];
280
		}
281
282
		if ( ! empty( $assoc_args['format'] ) && in_array( $assoc_args['format'], array( 'count', 'ids', 'table', 'csv', 'json' ) ) ) {
283
			$format_args['format'] = $assoc_args['format'];
284
		}
285
286
		return $format_args;
287
	}
288
289
	/**
290
	 * Flatten multidimensional array in which nested array will be prefixed with
291
	 * parent keys separated with dot char, e.g. given an array:
292
	 *
293
	 *     array(
294
	 *         'a' => array(
295
	 *             'b' => array(
296
	 *                 'c' => ...
297
	 *             )
298
	 *         )
299
	 *     )
300
	 *
301
	 * a flatten array would contain key 'a.b.c' => ...
302
	 *
303
	 * @since 2.5.0
304
	 * @param array  $arr    Array that may contains nested array
305
	 * @param string $prefix Prefix
306
	 *
307
	 * @return array Flattened array
308
	 */
309
	protected function flatten_array( $arr, $prefix = '' ) {
310
		$flattened = array();
311
		foreach ( $arr as $key => $value ) {
312
			if ( is_array( $value ) ) {
313
				if ( sizeof( $value ) > 0 ) {
314
315
					// Full access to whole elements if indices are numerical.
316
					$flattened[ $prefix . $key ] = $value;
317
318
					// This is naive assumption that if element with index zero
319
					// exists then array indices are numberical.
320
					if ( ! empty( $value[0] ) ) {
321
322
						// Allow size of array to be accessed, i.e., a.b.arr.size
323
						$flattened[ $prefix . $key . '.size' ] = sizeof( $value );
324
					}
325
326
					$flattened = array_merge( $flattened, $this->flatten_array( $value, $prefix . $key . '.' ) );
327
				} else {
328
					$flattened[ $prefix . $key ] = '';
329
330
					// Tells user that size of this array is zero.
331
					$flattened[ $prefix . $key . '.size' ] = 0;
332
				}
333
			} else {
334
				$flattened[ $prefix . $key ] = $value;
335
			}
336
		}
337
338
		return $flattened;
339
	}
340
341
	/**
342
	 * Unflatten array will make key 'a.b.c' becomes nested array:
343
	 *
344
	 *     array(
345
	 *         'a' => array(
346
	 *             'b' => array(
347
	 *                 'c' => ...
348
	 *             )
349
	 *         )
350
	 *     )
351
	 *
352
	 * @since  2.5.0
353
	 * @param  array $arr Flattened array
354
	 * @return array
355
	 */
356
	protected function unflatten_array( $arr ) {
357
		$unflatten = array();
358
359
		foreach ( $arr as $key => $value ) {
360
			$key_list  = explode( '.', $key );
361
			$first_key = array_shift( $key_list );
362
			$first_key = $this->get_normalized_array_key( $first_key );
363
			if ( sizeof( $key_list ) > 0 ) {
364
				$remaining_keys = implode( '.', $key_list );
365
				$subarray       = $this->unflatten_array( array( $remaining_keys => $value ) );
366
367
				foreach ( $subarray as $sub_key => $sub_value ) {
368
					$sub_key = $this->get_normalized_array_key( $sub_key );
369
					if ( ! empty( $unflatten[ $first_key ][ $sub_key ] ) ) {
370
						$unflatten[ $first_key ][ $sub_key ] = array_merge_recursive( $unflatten[ $first_key ][ $sub_key ], $sub_value );
371
					} else {
372
						$unflatten[ $first_key ][ $sub_key ] = $sub_value;
373
					}
374
				}
375
			} else {
376
				$unflatten[ $first_key ] = $value;
377
			}
378
		}
379
380
		return $unflatten;
381
	}
382
383
	/**
384
	 * Get normalized array key. If key is a numeric one it will be converted
385
	 * as absolute integer.
386
	 *
387
	 * @since  2.5.0
388
	 * @param  string $key Array key
389
	 * @return string|int
390
	 */
391
	protected function get_normalized_array_key( $key ) {
392
		if ( is_numeric( $key ) ) {
393
			$key = absint( $key );
394
		}
395
		return $key;
396
	}
397
398
	/**
399
	 * Check if the value is equal to 'yes', 'true' or '1'
400
	 *
401
	 * @since 2.5.4
402
	 * @param  string $value
403
	 * @return boolean
404
	 */
405
	protected function is_true( $value ) {
406
		return ( 'yes' === $value || 'true' === $value || '1' === $value ) ? true : false;
407
	}
408
}
409