Test Failed
Push — master ( d665bf...454e63 )
by Devin
05:52
created

Give_Shortcode_Generator   B

Complexity

Total Complexity 40

Size/Duplication

Total Lines 402
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
dl 0
loc 402
rs 8.2608
c 0
b 0
f 0
wmc 40
lcom 1
cbo 1

11 Methods

Rating   Name   Duplication   Size   Complexity  
A generate_container() 0 12 2
A __construct() 0 7 1
B init() 0 30 3
A define_fields() 0 4 1
B generate_fields() 0 33 5
B get_fields() 0 26 5
C generate_listbox() 0 42 8
B generate_post() 0 32 4
B generate_textbox() 0 27 3
A return_textbox_value() 0 3 1
C validate() 0 47 7

How to fix   Complexity   

Complex Class

Complex classes like Give_Shortcode_Generator often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Give_Shortcode_Generator, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Shortcode Dialog Generator abstract class
4
 *
5
 * @package     Give/Admin
6
 * @author      Paul Ryley
7
 * @copyright   Copyright (c) 2016, WordImpress
8
 * @license     https://opensource.org/licenses/gpl-license GNU Public License
9
 * @version     1.0
10
 * @since       1.3
11
 */
12
13
// Exit if accessed directly.
14
if ( ! defined( 'ABSPATH' ) ) {
15
	exit;
16
}
17
18
/**
19
 * Class Give_Shortcode_Generator
20
 */
21
abstract class Give_Shortcode_Generator {
22
23
	/**
24
	 * The current class name
25
	 *
26
	 * @since 1.0
27
	 */
28
	public $self;
29
30
	/**
31
	 * The current shortcode
32
	 *
33
	 * @since 1.0
34
	 */
35
	public $shortcode;
36
37
	/**
38
	 * The current shortcode tag
39
	 *
40
	 * @since 1.0
41
	 */
42
	public $shortcode_tag;
43
44
	/**
45
	 * Shortcode field errors
46
	 *
47
	 * @since 1.0
48
	 */
49
	protected $errors;
50
51
	/**
52
	 * Required shortcode fields
53
	 *
54
	 * @since 1.0
55
	 */
56
	protected $required;
57
58
	/**
59
	 * Class constructor
60
	 *
61
	 * @param string $shortcode The shortcode tag
62
	 *
63
	 * @since 1.0
64
	 */
65
	public function __construct( $shortcode ) {
66
67
		$this->shortcode_tag = $shortcode;
68
69
		add_action( 'admin_init', array( $this, 'init' ) );
70
71
	}
72
73
	/**
74
	 * Kick things off for the shortcode generator
75
	 *
76
	 * @since 1.3.0.2
77
	 */
78
	public function init() {
79
80
		if ( $this->shortcode_tag ) {
81
82
			$this->self = get_class( $this );
83
84
			$this->errors   = array();
85
			$this->required = array();
86
87
			// Generate the fields, errors, and requirements
88
			$fields = $this->get_fields();
89
90
			$defaults = array(
91
				'btn_close' => esc_html__( 'Close', 'give' ),
92
				'btn_okay'  => esc_html__( 'Insert Shortcode', 'give' ),
93
				'errors'    => $this->errors,
94
				'fields'    => $fields,
95
				'label'     => '[' . $this->shortcode_tag . ']',
96
				'required'  => $this->required,
97
				'title'     => esc_html__( 'Insert Shortcode', 'give' ),
98
			);
99
100
			if ( user_can_richedit() ) {
101
102
				Give_Shortcode_Button::$shortcodes[ $this->shortcode_tag ] = wp_parse_args( $this->shortcode, $defaults );
103
104
			}
105
		}
106
107
	}
108
109
110
	/**
111
	 * Define the shortcode attribute fields
112
	 *
113
	 * @return false|array
114
	 *
115
	 * @since 1.0
116
	 */
117
	public function define_fields() {
118
119
		return false;
120
	}
121
122
	/**
123
	 * Generate the shortcode dialog fields
124
	 *
125
	 * @param array $defined_fields
126
	 *
127
	 * @return array
128
	 *
129
	 * @since 1.0
130
	 */
131
	protected function generate_fields( $defined_fields ) {
132
133
		$fields = array();
134
135
		if ( is_array( $defined_fields ) ) {
136
137
			foreach ( $defined_fields as $field ) {
138
139
				$defaults = array(
140
					'label'       => false,
141
					'name'        => false,
142
					'options'     => array(),
143
					'placeholder' => false,
144
					'tooltip'     => false,
145
					'type'        => '',
146
				);
147
148
				$field  = wp_parse_args( (array) $field, $defaults );
149
				$method = 'generate_' . strtolower( $field['type'] );
150
151
				if ( method_exists( $this, $method ) ) {
152
153
					$field = call_user_func( array( $this, $method ), $field );
154
155
					if ( $field ) {
156
						$fields[] = $field;
157
					}
158
				}
159
			}
160
		}
161
162
		return $fields;
163
	}
164
165
	/**
166
	 * Get the generated shortcode dialog fields
167
	 *
168
	 * @return array
169
	 *
170
	 * @since 1.0
171
	 */
172
	protected function get_fields() {
173
174
		$defined_fields   = $this->define_fields();
175
		$generated_fields = $this->generate_fields( $defined_fields );
0 ignored issues
show
Security Bug introduced by
It seems like $defined_fields defined by $this->define_fields() on line 174 can also be of type false; however, Give_Shortcode_Generator::generate_fields() does only seem to accept array, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
176
177
		$errors = array();
178
179
		if ( ! empty( $this->errors ) ) {
180
			foreach ( $this->required as $name => $alert ) {
181
				// Using WordPress function in place of array_column wp_list_pluck as it support older version as well.
182
				if ( false === array_search( $name, give_list_pluck( $generated_fields, 'name' ) ) ) {
183
184
					$errors[] = $this->errors[ $name ];
185
				}
186
			}
187
188
			$this->errors = $errors;
189
		}
190
191
		if ( ! empty( $errors ) ) {
192
193
			return $errors;
194
		}
195
196
		return $generated_fields;
197
	}
198
199
	/**
200
	 * Generate a TinyMCE container field
201
	 *
202
	 * @param array $field
203
	 *
204
	 * @return array|false
205
	 *
206
	 * @since 1.0
207
	 */
208
	protected function generate_container( $field ) {
209
210
		if ( array_key_exists( 'html', $field ) ) {
211
212
			return array(
213
				'type' => $field['type'],
214
				'html' => $field['html'],
215
			);
216
		}
217
218
		return false;
219
	}
220
221
	/**
222
	 * Generate a TinyMCE listbox field
223
	 *
224
	 * @param array $field
225
	 *
226
	 * @return array|false
227
	 *
228
	 * @since 1.0
229
	 */
230
	protected function generate_listbox( $field ) {
231
232
		$listbox = shortcode_atts( array(
233
			'label'    => '',
234
			'minWidth' => '',
235
			'name'     => false,
236
			'tooltip'  => '',
237
			'type'     => '',
238
			'value'    => '',
239
			'classes'  => ''
0 ignored issues
show
introduced by
Each line in an array declaration must end in a comma
Loading history...
240
		), $field );
241
242
		if ( $this->validate( $field ) ) {
243
244
			$new_listbox = array();
245
246
			foreach ( $listbox as $key => $value ) {
247
248
				if ( $key == 'value' && empty( $value ) ) {
0 ignored issues
show
introduced by
Found "== '". Use Yoda Condition checks, you must
Loading history...
249
					$new_listbox[ $key ] = $listbox['name'];
250
				} else if ( $value ) {
251
					$new_listbox[ $key ] = $value;
252
				}
253
			}
254
255
			// do not reindex array!
256
			$field['options'] = array(
257
				                    '' => ( $field['placeholder'] ? $field['placeholder'] : esc_attr__( '- Select -', 'give' ) ),
258
			                    ) + $field['options'];
259
260
			foreach ( $field['options'] as $value => $text ) {
261
				$new_listbox['values'][] = array(
262
					'text'  => $text,
263
					'value' => $value,
264
				);
265
			}
266
267
			return $new_listbox;
268
		}
269
270
		return false;
271
	}
272
273
	/**
274
	 * Generate a TinyMCE listbox field for a post_type.
275
	 *
276
	 * @param array $field
277
	 *
278
	 * @return array|false
279
	 *
280
	 * @since 1.0
281
	 */
282
	protected function generate_post( $field ) {
283
284
		$args = array(
285
			'post_type'      => 'post',
286
			'orderby'        => 'title',
287
			'order'          => 'ASC',
288
			'posts_per_page' => 30,
289
		);
290
291
		$args    = wp_parse_args( (array) $field['query_args'], $args );
292
		$posts   = new WP_Query( $args );
293
		$options = array();
294
295
		if ( $posts->have_posts() ) {
296
			while ( $posts->have_posts() ) {
297
				$posts->the_post();
298
				$post_title = get_the_title();
299
				$post_id = get_the_ID();
300
				$options[ absint( $post_id ) ] = ( empty( $post_title ) ? sprintf( __( 'Untitled (#%s)', 'give' ), $post_id ) : $post_title );
301
			}
302
303
			$field['type']    = 'listbox';
304
			$field['options'] = $options;
305
306
			return $this->generate_listbox( $field );
307
		}
308
309
		// perform validation here before returning false
310
		$this->validate( $field );
311
312
		return false;
313
	}
314
315
	/**
316
	 * Generate a TinyMCE textbox field
317
	 *
318
	 * @param array $field
319
	 *
320
	 * @return array|false
321
	 *
322
	 * @since 1.0
323
	 */
324
	protected function generate_textbox( $field ) {
325
326
		$textbox = shortcode_atts( array(
327
			'label'       => '',
328
			'maxLength'   => '',
329
			'minHeight'   => '',
330
			'minWidth'    => '',
331
			'multiline'   => false,
332
			'name'        => false,
333
			'tooltip'     => '',
334
			'type'        => '',
335
			'value'       => '',
336
			'classes'     => '',
337
			'placeholder' => ''
0 ignored issues
show
introduced by
Each line in an array declaration must end in a comma
Loading history...
338
		), $field );
339
340
		// Remove empty placeholder.
341
		if( empty( $textbox['placeholder'] ) ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
342
			unset( $textbox['placeholder'] );
343
		}
344
345
		if ( $this->validate( $field ) ) {
346
			return array_filter( $textbox, array( $this, 'return_textbox_value' ) );
347
		}
348
349
		return false;
350
	}
351
352
	/**
353
	 * Validate Textbox Value
354
	 *
355
	 * @param $value
356
	 *
357
	 * @return bool
358
	 */
359
	function return_textbox_value( $value ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
360
		return $value !== '';
361
	}
362
363
	/**
364
	 * Perform validation for a single field
365
	 *
366
	 * Returns true or false depending on whether the field has a 'name' attribute.
367
	 * This method also populates the shortcode's $errors and $required arrays.
368
	 *
369
	 * @param array $field
370
	 *
371
	 * @return bool
372
	 *
373
	 * @since 1.0
374
	 */
375
	protected function validate( $field ) {
376
377
		extract( shortcode_atts(
0 ignored issues
show
introduced by
extract() usage is highly discouraged, due to the complexity and unintended issues it might cause.
Loading history...
378
				array(
0 ignored issues
show
Coding Style introduced by
This line of the multi-line function call does not seem to be indented correctly. Expected 12 spaces, but found 16.
Loading history...
379
					'name'     => false,
380
					'required' => false,
381
					'label'    => '',
382
				), $field )
0 ignored issues
show
Coding Style introduced by
This line of the multi-line function call does not seem to be indented correctly. Expected 8 spaces, but found 16.
Loading history...
383
		);
384
385
		if ( $name ) {
386
387
			if ( isset( $required['error'] ) ) {
388
389
				$error = array(
390
					'type' => 'container',
391
					'html' => $required['error'],
392
				);
393
394
				$this->errors[ $name ] = $this->generate_container( $error );
395
			}
396
397
			if ( ! ! $required || is_array( $required ) ) {
398
399
				$alert = esc_html__( 'Some of the shortcode options are required.', 'give' );
400
401
				if ( isset( $required['alert'] ) ) {
402
403
					$alert = $required['alert'];
404
405
				} else if ( ! empty( $label ) ) {
406
407
					$alert = sprintf(
408
					/* translators: %s: option label */
0 ignored issues
show
Coding Style introduced by
This line of the multi-line function call does not seem to be indented correctly. Expected 24 spaces, but found 20.
Loading history...
409
						esc_html__( 'The "%s" option is required.', 'give' ),
410
						str_replace( ':', '', $label )
411
					);
412
				}
413
414
				$this->required[ $name ] = $alert;
415
			}
416
417
			return true;
418
		}
419
420
		return false;
421
	}
422
}
423