Completed
Pull Request — master (#193)
by
unknown
02:22
created

Post_Meta_Container   D

Complexity

Total Complexity 97

Size/Duplication

Total Lines 584
Duplicated Lines 9.25 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 11.03%

Importance

Changes 0
Metric Value
dl 54
loc 584
ccs 29
cts 263
cp 0.1103
rs 4.8717
c 0
b 0
f 0
wmc 97
lcom 1
cbo 3

23 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 2
C check_setup_settings() 3 35 8
A init() 0 21 4
A save() 14 14 2
B is_valid_save() 0 11 6
D is_valid_save_conditions() 24 106 26
A attach() 0 16 3
A postbox_classes() 0 4 1
D is_valid_attach() 0 47 16
A detach() 13 13 2
A render() 0 3 1
A set_post_id() 0 4 1
A show_on_page_children() 0 13 2
A show_on_page() 0 19 4
A show_on_category() 0 5 1
A show_on_template() 0 18 4
A hide_on_template() 0 12 3
A show_on_level() 0 9 2
A show_on_taxonomy_term() 0 9 2
A show_on_post_format() 0 16 4
A show_on_post_type() 0 7 1
A set_context() 0 5 1
A set_priority() 0 5 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Post_Meta_Container 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 Post_Meta_Container, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Carbon_Fields\Container;
4
5
use Carbon_Fields\Datastore\Meta_Datastore;
6
use Carbon_Fields\Datastore\Post_Meta_Datastore;
7
use Carbon_Fields\Exception\Incorrect_Syntax_Exception;
8
9
/**
10
 * Field container designed to extend WordPress custom fields functionality,
11
 * providing easier user interface to add, edit and delete text, media files,
12
 * location information and more.
13
 */
14
class Post_Meta_Container extends Container {
15
	/**
16
	 * ID of the post the container is working with
17
	 *
18
	 * @see init()
19
	 * @var int
20
	 */
21
	protected $post_id;
22
23
	/**
24
	 * List of default container settings
25
	 *
26
	 * @see init()
27
	 * @var array
28
	 */
29
	public $settings = array(
30
		'post_type' => array( 'post' ),
31
		'panel_context' => 'normal',
32
		'panel_priority' => 'high',
33
		'show_on' => array(
34
			'category' => null,
35
			'template_names' => array(),
36
			'not_in_template_names' => array(),
37
			'post_formats' => array(),
38
			'level_limit' => null,
39
			'tax_term_id' => null,
40
			'page_id' => null,
41
			'parent_page_id' => null,
42
			'post_path' => null,
43
		),
44
	);
45
46
	/**
47
	 * Create a new post meta fields container
48
	 *
49
	 * @param string $title Unique title of the container
50
	 **/
51
	public function __construct( $title ) {
52
		parent::__construct( $title );
53
54
		if ( ! $this->get_datastore() ) {
55
			$this->set_datastore( new Post_Meta_Datastore() );
56
		}
57
	}
58
59
	/**
60
	 * Check if all required container settings have been specified
61
	 *
62
	 * @param array $settings Container settings
63
	 **/
64
	public function check_setup_settings( &$settings = array() ) {
65
		if ( isset( $settings['show_on'] ) ) {
66
			$invalid_settings = array_diff_key( $settings['show_on'], $this->settings['show_on'] );
67 View Code Duplication
			if ( ! empty( $invalid_settings ) ) {
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...
68
				Incorrect_Syntax_Exception::raise( 'Invalid show_on settings supplied to setup(): "' . implode( '", "', array_keys( $invalid_settings ) ) . '"' );
69
			}
70
		}
71
72
		if ( isset( $settings['show_on']['post_formats'] ) ) {
73
			$settings['show_on']['post_formats'] = (array) $settings['show_on']['post_formats'];
74
		}
75
76
		if ( isset( $settings['show_on']['post_path'] ) ) {
77
			$page = get_page_by_path( $settings['show_on']['post_path'] );
78
79
			if ( $page ) {
80
				$settings['show_on']['page_id'] = $page->ID;
81
			} else {
82
				$settings['show_on']['page_id'] = -1;
83
			}
84
		}
85
86
		// Transform category slug to taxonomy + term slug + term id
87
		if ( isset( $settings['show_on']['category'] ) ) {
88
			$term = get_term_by( 'slug', $settings['show_on']['category'], 'category' );
89
90
			if ( $term ) {
91
				$settings['show_on']['tax_slug'] = $term->taxonomy;
92
				$settings['show_on']['tax_term'] = $term->slug;
93
				$settings['show_on']['tax_term_id'] = $term->term_id;
94
			}
95
		}
96
97
		return parent::check_setup_settings( $settings );
98
	}
99
100
	/**
101
	 * Create DataStore instance, set post ID to operate with (if such exists).
102
	 * Bind attach() and save() to the appropriate WordPress actions.
103
	 **/
104
	public function init( $id = '' ) {
105
		if ( isset( $_GET['post'] ) ) {
1 ignored issue
show
introduced by
Detected access of super global var $_GET, probably need manual inspection.
Loading history...
106
			$this->set_post_id( $_GET['post'] );
0 ignored issues
show
introduced by
Detected access of super global var $_GET, probably need manual inspection.
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_GET
Loading history...
107
		} elseif ( $id ) {
108
			$this->set_post_id( $id );
109
		}
110
111
		// force post_type to be array
112
		if ( ! is_array( $this->settings['post_type'] ) ) {
113
			$this->settings['post_type'] = array( $this->settings['post_type'] );
114
		}
115
116
		add_action( 'carbon_containers_attach', array( $this, '_attach' ) );
117
		add_action( 'carbon_containers_attach_all', array( $this, '_attach_all' ) );
118
		add_action( 'rest_api_init', array( $this, '_attach_all' ) );
119
		add_action( 'save_post', array( $this, '_save' ) );
120
121
		// support for attachments
122
		add_action( 'add_attachment', array( $this, '_save' ) );
123
		add_action( 'edit_attachment', array( $this, '_save' ) );
124
	}
125
126
	/**
127
	 * Perform save operation after successful is_valid_save() check.
128
	 * The call is propagated to all fields in the container.
129
	 *
130
	 * @param int $post_id ID of the post against which save() is ran
131
	 **/
132 View Code Duplication
	public function save( $post_id ) {
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in 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...
133
		// Unhook action to garantee single save
134
		remove_action( 'save_post', array( $this, '_save' ) );
135
136
		$this->set_post_id( $post_id );
137
138
		foreach ( $this->fields as $field ) {
139
			$field->set_value_from_input();
140
			$field->save();
141
		}
142
143
		do_action( 'carbon_after_save_custom_fields', $post_id );
144
		do_action( 'carbon_after_save_post_meta', $post_id );
145
	}
146
147
	/**
148
	 * Perform checks whether the current save() request is valid
149
	 * Possible errors are triggering save() for autosave requests
150
	 * or performing post save outside of the post edit page (like Quick Edit)
151
	 *
152
	 * @see is_valid_save_conditions()
153
	 * @param int $post_id ID of the post against which save() is ran
154
	 * @return bool
155
	 **/
156
	public function is_valid_save( $post_id = 0 ) {
157
		if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
158
			return false;
159
		} else if ( ! isset( $_REQUEST[ $this->get_nonce_name() ] ) || ! wp_verify_nonce( $_REQUEST[ $this->get_nonce_name() ], $this->get_nonce_name() ) ) { // Input var okay.
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_REQUEST
Loading history...
160
			return false;
161
		} else if ( $post_id < 1 ) {
162
			return false;
163
		}
164
165
		return $this->is_valid_save_conditions( $post_id );
166
	}
167
168
	/**
169
	 * Perform checks whether the current save() request is valid
170
	 * Possible errors are triggering save() for autosave requests
171
	 * or performing post save outside of the post edit page (like Quick Edit)
172
	 *
173
	 * @param int $post_id ID of the post against which save() is ran
174
	 * @return bool
175
	 **/
176
	public function is_valid_save_conditions( $post_id ) {
177
		$valid = true;
178
		$post = get_post( $post_id );
179
180
		// Check post type
181
		if ( ! in_array( $post->post_type, $this->settings['post_type'] ) ) {
182
			return false;
183
		}
184
185
		// Check show on conditions
186
		foreach ( $this->settings['show_on'] as $condition => $value ) {
187
			if ( is_null( $value ) ) {
188
				continue;
189
			}
190
191
			switch ( $condition ) {
192
				// show_on_post_format
193
				case 'post_formats':
194
					if ( empty( $value ) || $post->post_type != 'post' ) {
195
						break;
196
					}
197
198
					$current_format = get_post_format( $post_id );
199
					if ( ! in_array( $current_format, $value ) ) {
200
						$valid = false;
201
						break 2;
202
					}
203
204
					break;
205
206
				// show_on_taxonomy_term or show_on_category
207
				case 'category':
208
					$this->show_on_category( $value );
209
210
					/* fall-through intended */
211
				case 'tax_term_id':
212
					$current_terms = wp_get_object_terms( $post_id, $this->settings['show_on']['tax_slug'], array( 'fields' => 'ids' ) );
213
214
					if ( ! is_array( $current_terms ) || ! in_array( $this->settings['show_on']['tax_term_id'], $current_terms ) ) {
215
						$valid = false;
216
						break 2;
217
					}
218
219
					break;
220
221
				// show_on_level
222
				case 'level_limit':
223
					$post_level = count( get_post_ancestors( $post_id ) ) + 1;
224
225
					if ( $post_level != $value ) {
226
						$valid = false;
227
						break 2;
228
					}
229
230
					break;
231
232
				// show_on_page
233
				case 'page_id':
234
					if ( $post_id != $value ) {
235
						$valid = false;
236
						break 2;
237
					}
238
239
					break;
240
241
				// show_on_page_children
242
				case 'parent_page_id':
243
					if ( $post->post_parent != $value ) {
244
						$valid = false;
245
						break 2;
246
					}
247
248
					break;
249
250
				// show_on_template
251 View Code Duplication
				case 'template_names':
1 ignored issue
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...
252
					if ( empty( $value ) || $post->post_type != 'page' ) {
253
						break;
254
					}
255
					$current_template = get_post_meta( $post_id, '_wp_page_template', 1 );
256
257
					if ( ! in_array( $current_template, $value ) ) {
258
						$valid = false;
259
						break 2;
260
					}
261
262
					break;
263
264
				// hide_on_template
265 View Code Duplication
				case 'not_in_template_names':
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...
266
					if ( empty( $value ) || $post->post_type != 'page' ) {
267
						break;
268
					}
269
					$current_template = get_post_meta( $post_id, '_wp_page_template', 1 );
270
271
					if ( in_array( $current_template, $value ) ) {
272
						$valid = false;
273
						break 2;
274
					}
275
276
					break;
277
			}
278
		}
279
280
		return $valid;
281
	}
282
283
	/**
284
	 * Add meta box for each of the container post types
285
	 **/
286
	public function attach() {
287
		foreach ( $this->settings['post_type'] as $post_type ) {
288
			add_meta_box(
289
				$this->id,
290
				$this->title,
291
				array( $this, 'render' ),
292
				$post_type,
293
				$this->settings['panel_context'],
294
				$this->settings['panel_priority']
295
			);
296
		}
297
298
		foreach ( $this->settings['post_type'] as $post_type ) {
299
			add_filter( "postbox_classes_{$post_type}_{$this->id}", array( $this, 'postbox_classes' ) );
300
		}
301
	}
302
303
	/**
304
	 * Classes to add to the post meta box
305
	 */
306
	public function postbox_classes( $classes ) {
307
		$classes[] = 'carbon-box';
308
		return $classes;
309
	}
310
311
	/**
312
	 * Perform checks whether the container should be attached during the current request
313
	 *
314
	 * @return bool True if the container is allowed to be attached
315
	 **/
316
	public function is_valid_attach() {
317
		global $pagenow;
318
319
		if ( $pagenow !== 'post.php' && $pagenow !== 'post-new.php' ) {
320
			return false;
321
		}
322
323
		// Post types check
324
		if ( ! empty( $this->settings['post_type'] ) ) {
325
			$post_type = '';
326
327
			if ( $this->post_id ) {
328
				$post_type = get_post_type( $this->post_id );
329
			} elseif ( ! empty( $_GET['post_type'] ) ) {
330
				$post_type = $_GET['post_type'];
0 ignored issues
show
introduced by
Detected access of super global var $_GET, probably need manual inspection.
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_GET
Loading history...
331
			} elseif ( $pagenow === 'post-new.php' ) {
332
				$post_type = 'post';
333
			}
334
335
			if ( ! $post_type || ! in_array( $post_type, $this->settings['post_type'] ) ) {
336
				return false;
337
			}
338
		}
339
340
		// Check show on conditions
341
		foreach ( $this->settings['show_on'] as $condition => $value ) {
342
			if ( is_null( $value ) ) {
343
				continue;
344
			}
345
346
			switch ( $condition ) {
347
				case 'page_id':
348
					if ( $value < 1 || $this->post_id != $value ) {
349
						return false;
350
					}
351
					break;
352
				case 'parent_page_id':
353
					// Check if such page exists
354
					if ( $value < 1 ) {
355
						return false;
356
					}
357
					break;
358
			}
359
		}
360
361
		return true;
362
	}
363
364
	/**
365
	 * Revert the result of attach()
366
	 **/
367 View Code Duplication
	public function detach() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
368
		parent::detach();
369
370
		remove_action( 'carbon_containers_attach', array( $this, '_attach' ) );
371
		remove_action( 'carbon_containers_attach_all', array( $this, '_attach_all' ) );
372
		remove_action( 'rest_api_init', array( $this, '_attach_all' ) );
373
		remove_action( 'save_post', array( $this, '_save' ) );
374
375
		// unregister field names
376
		foreach ( $this->fields as $field ) {
377
			$this->drop_unique_field_name( $field->get_name() );
378
		}
379
	}
380
381
	/**
382
	 * Output the container markup
383
	 **/
384
	public function render() {
385
		include \Carbon_Fields\DIR . '/templates/Container/post_meta.php';
386
	}
387
388
	/**
389
	 * Set the post ID the container will operate with.
390
	 *
391
	 * @param int $post_id
392
	 **/
393
	public function set_post_id( $post_id ) {
394
		$this->post_id = $post_id;
395
		$this->store->set_id( $post_id );
396
	}
397
398
	/**
399
	 * Show the container only on pages whose parent is referenced by $parent_page_path.
400
	 *
401
	 * @param string $parent_page_path
402
	 * @return object $this
403
	 **/
404 1
	public function show_on_page_children( $parent_page_path ) {
405 1
		$page = get_page_by_path( $parent_page_path );
406
407 1
		$this->show_on_post_type( 'page' );
408
409 1
		if ( $page ) {
410 1
			$this->settings['show_on']['parent_page_id'] = $page->ID;
411 1
		} else {
412
			$this->settings['show_on']['parent_page_id'] = -1;
413
		}
414
415 1
		return $this;
416
	}
417
418
	/**
419
	 * Show the container only on particular page referenced by it's path.
420
	 *
421
	 * @param int|string $page page ID or page path
422
	 * @return object $this
423
	 **/
424 4
	public function show_on_page( $page ) {
425 4
		$page_id = absint( $page );
426
427 4
		if ( $page_id && $page_id == $page ) {
428 2
			$page_obj = get_post( $page_id );
429 2
		} else {
430 2
			$page_obj = get_page_by_path( $page );
431
		}
432
433 4
		$this->show_on_post_type( 'page' );
434
435 4
		if ( $page_obj ) {
436 4
			$this->settings['show_on']['page_id'] = $page_obj->ID;
437 4
		} else {
438
			$this->settings['show_on']['page_id'] = -1;
439
		}
440
441 4
		return $this;
442
	}
443
444
	/**
445
	 * Show the container only on posts from the specified category.
446
	 *
447
	 * @see show_on_taxonomy_term()
448
	 *
449
	 * @param string $category_slug
450
	 * @return object $this
451
	 **/
452
	public function show_on_category( $category_slug ) {
453
		$this->settings['show_on']['category'] = $category_slug;
454
455
		return $this->show_on_taxonomy_term( $category_slug, 'category' );
456
	}
457
458
	/**
459
	 * Show the container only on pages whose template has filename $template_path.
460
	 *
461
	 * @param string|array $template_path
462
	 * @return object $this
463
	 **/
464 2
	public function show_on_template( $template_path ) {
465
		// Backwards compatibility where only pages support templates
466 2
		if ( version_compare( get_bloginfo( 'version' ), '4.7', '<' ) ) {
467 2
			$this->show_on_post_type( 'page' );
468 2
		}
469
470 2
		if ( is_array( $template_path ) ) {
471 1
			foreach ( $template_path as $path ) {
472 1
				$this->show_on_template( $path );
473 1
			}
474
475 1
			return $this;
476
		}
477
478 2
		$this->settings['show_on']['template_names'][] = $template_path;
479
480 2
		return $this;
481
	}
482
483
	/**
484
	 * Hide the container from pages whose template has filename $template_path.
485
	 *
486
	 * @param string|array $template_path
487
	 * @return object $this
488
	 **/
489
	public function hide_on_template( $template_path ) {
490
		if ( is_array( $template_path ) ) {
491
			foreach ( $template_path as $path ) {
492
				$this->hide_on_template( $path );
493
			}
494
			return $this;
495
		}
496
497
		$this->settings['show_on']['not_in_template_names'][] = $template_path;
498
499
		return $this;
500
	}
501
502
	/**
503
	 * Show the container only on hierarchical posts of level $level.
504
	 * Levels start from 1 (top level post)
505
	 *
506
	 * @param int $level
507
	 * @return object $this
508
	 **/
509
	public function show_on_level( $level ) {
510
		if ( $level < 0 ) {
511
			Incorrect_Syntax_Exception::raise( 'Invalid level limitation (' . $level . ')' );
512
		}
513
514
		$this->settings['show_on']['level_limit'] = $level;
515
516
		return $this;
517
	}
518
519
	/**
520
	 * Show the container only on posts which have term $term_slug from the $taxonomy_slug taxonomy.
521
	 *
522
	 * @param string $taxonomy_slug
523
	 * @param string $term_slug
524
	 * @return object $this
525
	 **/
526
	public function show_on_taxonomy_term( $term_slug, $taxonomy_slug ) {
527
		$term = get_term_by( 'slug', $term_slug, $taxonomy_slug );
528
529
		$this->settings['show_on']['tax_slug'] = $taxonomy_slug;
530
		$this->settings['show_on']['tax_term'] = $term_slug;
531
		$this->settings['show_on']['tax_term_id'] = $term ? $term->term_id : null;
532
533
		return $this;
534
	}
535
536
	/**
537
	 * Show the container only on posts from the specified format.
538
	 * Learn more about {@link http://codex.wordpress.org/Post_Formats Post Formats (Codex)}
539
	 *
540
	 * @param string|array $post_format Name of the format as listed on Codex
541
	 * @return object $this
542
	 **/
543
	public function show_on_post_format( $post_format ) {
544
		if ( is_array( $post_format ) ) {
545
			foreach ( $post_format as $format ) {
546
				$this->show_on_post_format( $format );
547
			}
548
			return $this;
549
		}
550
551
		if ( $post_format === 'standard' ) {
552
			$post_format = 0;
553
		}
554
555
		$this->settings['show_on']['post_formats'][] = strtolower( $post_format );
556
557
		return $this;
558
	}
559
560
	/**
561
	 * Show the container only on posts from the specified type(s).
562
	 *
563
	 * @param string|array $post_types
564
	 * @return object $this
565
	 **/
566
	public function show_on_post_type( $post_types ) {
567
		$post_types = (array) $post_types;
568
569
		$this->settings['post_type'] = $post_types;
570
571
		return $this;
572
	}
573
574
	/**
575
	 * Sets the meta box container context
576
	 *
577
	 * @see https://codex.wordpress.org/Function_Reference/add_meta_box
578
	 * @param string $context ('normal', 'advanced' or 'side')
579
	 */
580
	public function set_context( $context ) {
581
		$this->settings['panel_context'] = $context;
582
583
		return $this;
584
	}
585
586
	/**
587
	 * Sets the meta box container priority
588
	 *
589
	 * @see https://codex.wordpress.org/Function_Reference/add_meta_box
590
	 * @param string $priority ('high', 'core', 'default' or 'low')
591
	 */
592
	public function set_priority( $priority ) {
593
		$this->settings['panel_priority'] = $priority;
594
595
		return $this;
596
	}
597
} // END Post_Meta_Container
598