Completed
Push — milestone/2.0 ( 77f3ad...371be2 )
by
unknown
05:56
created

Post_Meta_Container::hide_on_template()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 3
nop 1
dl 0
loc 12
rs 9.4285
c 0
b 0
f 0
ccs 0
cts 8
cp 0
crap 12
1
<?php
2
3
namespace Carbon_Fields\Container;
4
5
use Carbon_Fields\Datastore\Datastore;
6
use Carbon_Fields\Exception\Incorrect_Syntax_Exception;
7
8
/**
9
 * Field container designed to extend WordPress custom fields functionality,
10
 * providing easier user interface to add, edit and delete text, media files,
11
 * location information and more.
12
 */
13
class Post_Meta_Container extends Container {
14
	/**
15
	 * ID of the post the container is working with
16
	 *
17
	 * @see init()
18
	 * @var int
19
	 */
20
	protected $post_id;
21
22
	/**
23
	 * List of default container settings
24
	 *
25
	 * @see init()
26
	 * @var array
27
	 */
28
	public $settings = array(
29
		'post_type' => array( 'post' ),
30
		'panel_context' => 'normal',
31
		'panel_priority' => 'high',
32
		'show_on' => array(
33
			'category' => null,
34
			'template_names' => array(),
35
			'not_in_template_names' => array(),
36
			'post_formats' => array(),
37
			'level_limit' => null,
38
			'tax_term_id' => null,
39
			'page_id' => null,
40
			'parent_page_id' => null,
41
			'post_path' => null,
42
		),
43
	);
44
45
	/**
46
	 * Create a new container
47
	 *
48
	 * @param string $unique_id Unique id of the container
49
	 * @param string $title title of the container
50
	 * @param string $type Type of the container
51
	 **/
52 View Code Duplication
	public function __construct( $unique_id, $title, $type ) {
53
		parent::__construct( $unique_id, $title, $type );
54
55
		if ( ! $this->get_datastore() ) {
56
			$this->set_datastore( Datastore::make( 'post_meta' ), $this->has_default_datastore() );
57
		}
58
	}
59
60
	/**
61
	 * Create DataStore instance, set post ID to operate with (if such exists).
62
	 * Bind attach() and save() to the appropriate WordPress actions.
63
	 **/
64
	public function init() {
65
		$request_post_id = isset( $_GET['post'] ) ? intval( $_GET['post'] ) : 0;
1 ignored issue
show
introduced by
Detected access of super global var $_GET, probably need manual inspection.
Loading history...
66
		if ( $request_post_id > 0 ) {
67
			$this->set_post_id( $request_post_id );
68
		}
69
70
		// force post_type to be array
71
		if ( ! is_array( $this->settings['post_type'] ) ) {
72
			$this->settings['post_type'] = array( $this->settings['post_type'] );
73
		}
74
75
		add_action( 'admin_init', array( $this, '_attach' ) );
76
		add_action( 'save_post', array( $this, '_save' ) );
77
78
		// support for attachments
79
		add_action( 'add_attachment', array( $this, '_save' ) );
80
		add_action( 'edit_attachment', array( $this, '_save' ) );
81
	}
82
83
	/**
84
	 * Perform save operation after successful is_valid_save() check.
85
	 * The call is propagated to all fields in the container.
86
	 *
87
	 * @param int $post_id ID of the post against which save() is ran
88
	 **/
89 View Code Duplication
	public function save( $post_id ) {
90
		// Unhook action to garantee single save
91
		remove_action( 'save_post', array( $this, '_save' ) );
92
93
		$this->set_post_id( $post_id );
94
95
		foreach ( $this->fields as $field ) {
96
			$field->set_value_from_input();
97
			$field->save();
98
		}
99
100
		do_action( 'carbon_after_save_custom_fields', $post_id );
101
		do_action( 'carbon_after_save_post_meta', $post_id );
102
	}
103
104
	/**
105
	 * Perform checks whether the current save() request is valid
106
	 * Possible errors are triggering save() for autosave requests
107
	 * or performing post save outside of the post edit page (like Quick Edit)
108
	 *
109
	 * @see is_valid_save_conditions()
110
	 * @param int $post_id ID of the post against which save() is ran
111
	 * @return bool
112
	 **/
113
	public function is_valid_save( $post_id = 0 ) {
114
		if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
115
			return false;
116
		}
117
118
		if ( ! $this->verified_nonce_in_request() ) {
119
			return false;
120
		}
121
122
		if ( $post_id < 1 ) {
123
			return false;
124
		}
125
126
		return $this->is_valid_save_conditions( $post_id );
127
	}
128
129
	/**
130
	 * Perform checks whether the current save() request is valid
131
	 * Possible errors are triggering save() for autosave requests
132
	 * or performing post save outside of the post edit page (like Quick Edit)
133
	 *
134
	 * @param int $post_id ID of the post against which save() is ran
135
	 * @return bool
136
	 **/
137
	public function is_valid_save_conditions( $post_id ) {
138
		$valid = true;
139
		$post = get_post( $post_id );
140
141
		// Check post type
142
		if ( ! in_array( $post->post_type, $this->settings['post_type'] ) ) {
143
			return false;
144
		}
145
146
		// Check show on conditions
147
		foreach ( $this->settings['show_on'] as $condition => $value ) {
148
			if ( is_null( $value ) ) {
149
				continue;
150
			}
151
152
			switch ( $condition ) {
153
				// show_on_post_format
154
				case 'post_formats':
155
					if ( empty( $value ) || $post->post_type != 'post' ) {
156
						break;
157
					}
158
159
					$current_format = get_post_format( $post_id );
160
					if ( ! in_array( $current_format, $value ) ) {
161
						$valid = false;
162
						break 2;
163
					}
164
165
					break;
166
167
				// show_on_taxonomy_term or show_on_category
168
				case 'category':
169
					$this->show_on_category( $value );
170
171
					/* fall-through intended */
172
				case 'tax_term_id':
173
					$current_terms = wp_get_object_terms( $post_id, $this->settings['show_on']['tax_slug'], array( 'fields' => 'ids' ) );
174
175
					if ( ! is_array( $current_terms ) || ! in_array( $this->settings['show_on']['tax_term_id'], $current_terms ) ) {
176
						$valid = false;
177
						break 2;
178
					}
179
180
					break;
181
182
				// show_on_level
183
				case 'level_limit':
184
					$post_level = count( get_post_ancestors( $post_id ) ) + 1;
185
186
					if ( $post_level != $value ) {
187
						$valid = false;
188
						break 2;
189
					}
190
191
					break;
192
193
				// show_on_page
194
				case 'page_id':
195
					if ( $post_id != $value ) {
196
						$valid = false;
197
						break 2;
198
					}
199
200
					break;
201
202
				// show_on_page_children
203
				case 'parent_page_id':
204
					if ( $post->post_parent != $value ) {
205
						$valid = false;
206
						break 2;
207
					}
208
209
					break;
210
211
				// show_on_template
212 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...
213
					if ( empty( $value ) ) {
214
						break;
215
					}
216
					$current_template = get_post_meta( $post_id, '_wp_page_template', 1 );
217
218
					if ( ! in_array( $current_template, $value ) ) {
219
						$valid = false;
220
						break 2;
221
					}
222
223
					break;
224
225
				// hide_on_template
226 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...
227
					if ( empty( $value ) ) {
228
						break;
229
					}
230
					$current_template = get_post_meta( $post_id, '_wp_page_template', 1 );
231
232
					if ( in_array( $current_template, $value ) ) {
233
						$valid = false;
234
						break 2;
235
					}
236
237
					break;
238
			}
239
		}
240
241
		return $valid;
242
	}
243
244
	/**
245
	 * Perform checks whether the container should be attached during the current request
246
	 *
247
	 * @return bool True if the container is allowed to be attached
248
	 **/
249
	public function _is_valid_attach() {
250
		global $pagenow;
251
252
		if ( $pagenow !== 'post.php' && $pagenow !== 'post-new.php' ) {
253
			return false;
254
		}
255
256
		// Post types check
257
		if ( ! empty( $this->settings['post_type'] ) ) {
258
			$post_type = '';
259
			$request_post_type = isset( $_GET['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...
260
261
			if ( $this->post_id ) {
262
				$post_type = get_post_type( $this->post_id );
263
			} elseif ( ! empty( $request_post_type ) ) {
264
				$post_type = $request_post_type;
265
			} elseif ( $pagenow === 'post-new.php' ) {
266
				$post_type = 'post';
267
			}
268
269
			if ( ! $post_type || ! in_array( $post_type, $this->settings['post_type'] ) ) {
270
				return false;
271
			}
272
		}
273
274
		// Check show on conditions
275
		foreach ( $this->settings['show_on'] as $condition => $value ) {
276
			if ( is_null( $value ) ) {
277
				continue;
278
			}
279
280
			switch ( $condition ) {
281
				case 'page_id':
282
					if ( $value < 1 || $this->post_id != $value ) {
283
						return false;
284
					}
285
					break;
286
				case 'parent_page_id':
287
					// Check if such page exists
288
					if ( $value < 1 ) {
289
						return false;
290
					}
291
					break;
292
			}
293
		}
294
295
		return true;
296
	}
297
298
	/**
299
	 * Add meta box for each of the container post types
300
	 **/
301
	public function attach() {
302
		foreach ( $this->settings['post_type'] as $post_type ) {
303
			add_meta_box(
304
				$this->id,
305
				$this->title,
306
				array( $this, 'render' ),
307
				$post_type,
308
				$this->settings['panel_context'],
309
				$this->settings['panel_priority']
310
			);
311
		}
312
313
		foreach ( $this->settings['post_type'] as $post_type ) {
314
			add_filter( "postbox_classes_{$post_type}_{$this->id}", array( $this, 'add_postbox_classes' ) );
315
		}
316
	}
317
318
	/**
319
	 * Classes to add to the post meta box
320
	 */
321
	public function add_postbox_classes( $classes ) {
322
		$classes[] = 'carbon-box';
323
		return $classes;
324
	}
325
326
	/**
327
	 * Output the container markup
328
	 **/
329
	public function render() {
330
		include \Carbon_Fields\DIR . '/templates/Container/post_meta.php';
331
	}
332
333
	/**
334
	 * Set the post ID the container will operate with.
335
	 *
336
	 * @param int $post_id
337
	 **/
338
	public function set_post_id( $post_id ) {
339
		$this->post_id = $post_id;
340
		$this->get_datastore()->set_id( $post_id );
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Carbon_Fields\Datastore\Datastore_Interface as the method set_id() does only exist in the following implementations of said interface: Carbon_Fields\Datastore\Comment_Meta_Datastore, Carbon_Fields\Datastore\Meta_Datastore, Carbon_Fields\Datastore\Nav_Menu_Item_Datastore, Carbon_Fields\Datastore\Post_Meta_Datastore, Carbon_Fields\Datastore\Term_Meta_Datastore, Carbon_Fields\Datastore\User_Meta_Datastore.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
341
	}
342
343
	/**
344
	 * COMMON USAGE METHODS
345
	 */
346
347
	/**
348
	 * Show the container only on particular page referenced by it's path.
349
	 *
350
	 * @param int|string $page page ID or page path
351
	 * @return object $this
352
	 **/
353 4
	public function show_on_page( $page ) {
354 4
		$page_id = absint( $page );
355
356 4
		if ( $page_id && $page_id == $page ) {
357 2
			$page_obj = get_post( $page_id );
358 2
		} else {
359 2
			$page_obj = get_page_by_path( $page );
360
		}
361
362 4
		$this->show_on_post_type( 'page' );
363
364 4
		if ( $page_obj ) {
365 4
			$this->settings['show_on']['page_id'] = $page_obj->ID;
366 4
		} else {
367
			$this->settings['show_on']['page_id'] = -1;
368
		}
369
370 4
		return $this;
371
	}
372
373
	/**
374
	 * Show the container only on pages whose parent is referenced by $parent_page_path.
375
	 *
376
	 * @param string $parent_page_path
377
	 * @return object $this
378
	 **/
379 1
	public function show_on_page_children( $parent_page_path ) {
380 1
		$page = get_page_by_path( $parent_page_path );
381
382 1
		$this->show_on_post_type( 'page' );
383
384 1
		if ( $page ) {
385 1
			$this->settings['show_on']['parent_page_id'] = $page->ID;
386 1
		} else {
387
			$this->settings['show_on']['parent_page_id'] = -1;
388
		}
389
390 1
		return $this;
391
	}
392
393
	/**
394
	 * Show the container only on posts from the specified category.
395
	 *
396
	 * @see show_on_taxonomy_term()
397
	 *
398
	 * @param string $category_slug
399
	 * @return object $this
400
	 **/
401
	public function show_on_category( $category_slug ) {
402
		$this->settings['show_on']['category'] = $category_slug;
403
404
		return $this->show_on_taxonomy_term( $category_slug, 'category' );
405
	}
406
407
	/**
408
	 * Show the container only on pages whose template has filename $template_path.
409
	 *
410
	 * @param string|array $template_path
411
	 * @return object $this
412
	 **/
413 2
	public function show_on_template( $template_path ) {
414
		// Backwards compatibility where only pages support templates
415 2
		if ( version_compare( get_bloginfo( 'version' ), '4.7', '<' ) ) {
416 2
			$this->show_on_post_type( 'page' );
417 2
		}
418
419 2
		if ( is_array( $template_path ) ) {
420 1
			foreach ( $template_path as $path ) {
421 1
				$this->show_on_template( $path );
422 1
			}
423
424 1
			return $this;
425
		}
426
427 2
		$this->settings['show_on']['template_names'][] = $template_path;
428
429 2
		return $this;
430
	}
431
432
	/**
433
	 * Hide the container from pages whose template has filename $template_path.
434
	 *
435
	 * @param string|array $template_path
436
	 * @return object $this
437
	 **/
438
	public function hide_on_template( $template_path ) {
439
		if ( is_array( $template_path ) ) {
440
			foreach ( $template_path as $path ) {
441
				$this->hide_on_template( $path );
442
			}
443
			return $this;
444
		}
445
446
		$this->settings['show_on']['not_in_template_names'][] = $template_path;
447
448
		return $this;
449
	}
450
451
	/**
452
	 * Show the container only on hierarchical posts of level $level.
453
	 * Levels start from 1 (top level post)
454
	 *
455
	 * @param int $level
456
	 * @return object $this
457
	 **/
458
	public function show_on_level( $level ) {
459
		if ( $level < 0 ) {
460
			Incorrect_Syntax_Exception::raise( 'Invalid level limitation (' . $level . ')' );
461
		}
462
463
		$this->settings['show_on']['level_limit'] = $level;
464
465
		return $this;
466
	}
467
468
	/**
469
	 * Show the container only on posts which have term $term_slug from the $taxonomy_slug taxonomy.
470
	 *
471
	 * @param string $taxonomy_slug
472
	 * @param string $term_slug
473
	 * @return object $this
474
	 **/
475
	public function show_on_taxonomy_term( $term_slug, $taxonomy_slug ) {
476
		$term = get_term_by( 'slug', $term_slug, $taxonomy_slug );
477
478
		$this->settings['show_on']['tax_slug'] = $taxonomy_slug;
479
		$this->settings['show_on']['tax_term'] = $term_slug;
480
		$this->settings['show_on']['tax_term_id'] = $term ? $term->term_id : null;
481
482
		return $this;
483
	}
484
485
	/**
486
	 * Show the container only on posts from the specified format.
487
	 * Learn more about {@link http://codex.wordpress.org/Post_Formats Post Formats (Codex)}
488
	 *
489
	 * @param string|array $post_format Name of the format as listed on Codex
490
	 * @return object $this
491
	 **/
492
	public function show_on_post_format( $post_format ) {
493
		if ( is_array( $post_format ) ) {
494
			foreach ( $post_format as $format ) {
495
				$this->show_on_post_format( $format );
496
			}
497
			return $this;
498
		}
499
500
		if ( $post_format === 'standard' ) {
501
			$post_format = 0;
502
		}
503
504
		$this->settings['show_on']['post_formats'][] = strtolower( $post_format );
505
506
		return $this;
507
	}
508
509
	/**
510
	 * Show the container only on posts from the specified type(s).
511
	 *
512
	 * @param string|array $post_types
513
	 * @return object $this
514
	 **/
515
	public function show_on_post_type( $post_types ) {
516
		$post_types = (array) $post_types;
517
518
		$this->settings['post_type'] = $post_types;
519
520
		return $this;
521
	}
522
523
	/**
524
	 * Sets the meta box container context
525
	 *
526
	 * @see https://codex.wordpress.org/Function_Reference/add_meta_box
527
	 * @param string $context ('normal', 'advanced' or 'side')
528
	 */
529
	public function set_context( $context ) {
530
		$this->settings['panel_context'] = $context;
531
532
		return $this;
533
	}
534
535
	/**
536
	 * Sets the meta box container priority
537
	 *
538
	 * @see https://codex.wordpress.org/Function_Reference/add_meta_box
539
	 * @param string $priority ('high', 'core', 'default' or 'low')
540
	 */
541
	public function set_priority( $priority ) {
542
		$this->settings['panel_priority'] = $priority;
543
544
		return $this;
545
	}
546
}
547