Completed
Push — milestone/2.0 ( d39960...87395a )
by
unknown
03:12
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 checks whether the current save() request is valid
85
	 * Possible errors are triggering save() for autosave requests
86
	 * or performing post save outside of the post edit page (like Quick Edit)
87
	 *
88
	 * @param int $post_id ID of the post against which save() is ran
89
	 * @return bool
90
	 **/
91
	public function is_valid_save( $post_id = 0 ) {
92
		if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
93
			return false;
94
		}
95
96
		if ( ! $this->verified_nonce_in_request() ) {
97
			return false;
98
		}
99
100
		return $this->is_valid_attach_for_object( $post_id );
101
	}
102
103
	/**
104
	 * Perform save operation after successful is_valid_save() check.
105
	 * The call is propagated to all fields in the container.
106
	 *
107
	 * @param int $post_id ID of the post against which save() is ran
108
	 **/
109 View Code Duplication
	public function save( $post_id ) {
110
		// Unhook action to garantee single save
111
		remove_action( 'save_post', array( $this, '_save' ) );
112
113
		$this->set_post_id( $post_id );
114
115
		foreach ( $this->fields as $field ) {
116
			$field->set_value_from_input();
117
			$field->save();
118
		}
119
120
		do_action( 'carbon_after_save_custom_fields', $post_id );
121
		do_action( 'carbon_after_save_post_meta', $post_id );
122
	}
123
124
	/**
125
	 * Perform checks whether the container should be attached during the current request
126
	 *
127
	 * @return bool True if the container is allowed to be attached
128
	 **/
129
	public function is_valid_attach_for_request() {
130
		global $pagenow;
131
132
		if ( $pagenow !== 'post.php' && $pagenow !== 'post-new.php' ) {
133
			return false;
134
		}
135
136
		// Post types check
137
		if ( ! empty( $this->settings['post_type'] ) ) {
138
			$post_type = '';
139
			$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...
140
141
			if ( $this->post_id ) {
142
				$post_type = get_post_type( $this->post_id );
143
			} elseif ( ! empty( $request_post_type ) ) {
144
				$post_type = $request_post_type;
145
			} elseif ( $pagenow === 'post-new.php' ) {
146
				$post_type = 'post';
147
			}
148
149
			if ( ! $post_type || ! in_array( $post_type, $this->settings['post_type'] ) ) {
150
				return false;
151
			}
152
		}
153
154
		// Check show on conditions
155
		foreach ( $this->settings['show_on'] as $condition => $value ) {
156
			if ( is_null( $value ) || $condition !== 'page_id' ) {
157
				continue;
158
			}
159
160
			$post = get_post( $this->post_id );
161
			if ( ! $this->is_condition_fullfilled( $condition, $value, $post ) ) {
162
				return false;
163
			}
164
		}
165
166
		return true;
167
	}
168
169
	/**
170
	 * Check container attachment rules against object id
171
	 *
172
	 * @param int $object_id
173
	 * @return bool
174
	 **/
175
	public function is_valid_attach_for_object( $object_id = null ) {
176
		$post = get_post( intval( $object_id ) );
177
178
		if ( ! $post ) {
179
			return false;
180
		}
181
182
		// Check post type
183
		if ( ! in_array( $post->post_type, $this->settings['post_type'] ) ) {
184
			return false;
185
		}
186
187
		// Check show on conditions
188
		foreach ( $this->settings['show_on'] as $condition => $value ) {
189
			if ( is_null( $value ) ) {
190
				continue;
191
			}
192
193
			if ( ! $this->is_condition_fullfilled( $condition, $value, $post ) ) {
194
				return false;
195
			}
196
		}
197
198
		return true;
199
	}
200
201
	/**
202
	 * Add meta box for each of the container post types
203
	 **/
204
	public function attach() {
205
		foreach ( $this->settings['post_type'] as $post_type ) {
206
			add_meta_box(
207
				$this->id,
208
				$this->title,
209
				array( $this, 'render' ),
210
				$post_type,
211
				$this->settings['panel_context'],
212
				$this->settings['panel_priority']
213
			);
214
		}
215
216
		foreach ( $this->settings['post_type'] as $post_type ) {
217
			add_filter( "postbox_classes_{$post_type}_{$this->id}", array( $this, 'add_postbox_classes' ) );
218
		}
219
	}
220
221
	/**
222
	 * Classes to add to the post meta box
223
	 */
224
	public function add_postbox_classes( $classes ) {
225
		$classes[] = 'carbon-box';
226
		return $classes;
227
	}
228
229
	/**
230
	 * Output the container markup
231
	 **/
232
	public function render() {
233
		include \Carbon_Fields\DIR . '/templates/Container/post_meta.php';
234
	}
235
236
	/**
237
	 * Set the post ID the container will operate with.
238
	 *
239
	 * @param int $post_id
240
	 **/
241
	public function set_post_id( $post_id ) {
242
		$this->post_id = $post_id;
243
		$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...
244
	}
245
246
	/**
247
	 * CONDITION TOOLS
248
	 */
249
	
250
	/**
251
	 * Check a condition against all supported conditions
252
	 * 
253
	 * @param string $condition
254
	 * @param mixed $value
255
	 * @param WP_Post $post
256
	 * @return bool
257
	 */
258
	protected function is_condition_fullfilled( $condition, $value, $post ) {
259
		switch ( $condition ) {
260
			// show_on_post_format
261
			case 'post_formats':
262
				if ( ! $this->condition_is_post_of_format( $post, $value ) ) {
263
					return false;
264
				}
265
				break;
266
267
			// show_on_taxonomy_term or show_on_category
268
			case 'category':
269
				$this->show_on_category( $value );
270
271
				/* fall-through intended */
272
			case 'tax_term_id':
273
				$has_term = has_term(
274
					intval( $this->settings['show_on']['tax_term_id'] ),
275
					$this->settings['show_on']['tax_slug'],
276
					$post->ID
277
				);
278
				if ( ! $has_term ) {
279
					return false;
280
				}
281
				break;
282
283
			// show_on_level
284
			case 'level_limit':
285
				if ( ! $this->condition_is_post_on_level( $post, $value ) ) {
286
					return false;
287
				}
288
				break;
289
290
			// show_on_page
291
			case 'page_id':
292
				if ( $post->ID !== intval( $value ) ) {
293
					return false;
294
				}
295
				break;
296
297
			// show_on_page_children
298
			case 'parent_page_id':
299
				if ( $post->post_parent !== $value ) {
300
					return false;
301
				}
302
				break;
303
304
			// show_on_template
305
			case 'template_names':
306
				if ( ! empty( $value ) && ! $this->condition_is_post_using_template( $post, $value ) ) {
307
					return false;
308
				}
309
				break;
310
311
			// hide_on_template
312
			case 'not_in_template_names':
313
				if ( ! empty( $value ) && $this->condition_is_post_using_template( $post, $value ) ) {
314
					return false;
315
				}
316
				break;
317
		}
318
		return true;
319
	}
320
	
321
	/**
322
	 * Check if a post is of a given post format
323
	 * 
324
	 * @param WP_Post $post
325
	 * @param string $format
326
	 * @return bool
327
	 */
328
	protected function condition_is_post_of_format( $post, $format ) {
0 ignored issues
show
Unused Code introduced by
The parameter $format is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
329
		if ( empty( $value ) || $post->post_type !== 'post' ) {
0 ignored issues
show
Bug introduced by
The variable $value seems to never exist, and therefore empty should always return true. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
330
			return true; // this doesn't make sense - returning true for a post that does not support formats (kept for backwards compatibility)
331
		}
332
333
		$current_format = get_post_format( $post_id );
0 ignored issues
show
Bug introduced by
The variable $post_id does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
334
		if ( ! in_array( $current_format, $value ) ) {
335
			return false;
336
		}
337
338
		return true;
339
	}
340
	
341
	/**
342
	 * Check if a post is on a specific level in it's hierarchy
343
	 * 
344
	 * @param WP_Post $post
345
	 * @param int $level
346
	 * @return bool
347
	 */
348
	protected function condition_is_post_on_level( $post, $level ) {
349
		$level = intval( $level );
0 ignored issues
show
Unused Code introduced by
$level is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
350
		$post_level = count( get_post_ancestors( $post->ID ) ) + 1;
351
		return ( $post_level === $value );
0 ignored issues
show
Bug introduced by
The variable $value does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

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