Completed
Pull Request — master (#58)
by
unknown
02:21
created

Post_Meta_Container::show_on_level()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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