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