Completed
Push — milestone/2.0 ( 631282...ba0b77 )
by
unknown
02:33
created

Post_Meta_Container::is_valid_save()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
eloc 6
nc 3
nop 1
dl 0
loc 11
rs 9.2
c 0
b 0
f 0
ccs 0
cts 6
cp 0
crap 20
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
	 * @param int $post_id ID of the post against which save() is ran
110
	 * @return bool
111
	 **/
112
	public function is_valid_save( $post_id = 0 ) {
113
		if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
114
			return false;
115
		}
116
117
		if ( ! $this->verified_nonce_in_request() ) {
118
			return false;
119
		}
120
121
		return $this->is_valid_attach_for_object( $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 ) ) {
157
				continue;
158
			}
159
160
			switch ( $condition ) {
161
				case 'page_id':
162
					if ( $value < 1 || $this->post_id != $value ) {
163
						return false;
164
					}
165
					break;
166
				case 'parent_page_id':
167
					// Check if such page exists
168
					if ( $value < 1 ) {
169
						return false;
170
					}
171
					break;
172
			}
173
		}
174
175
		return true;
176
	}
177
178
	/**
179
	 * Check container attachment rules against object id
180
	 *
181
	 * @return bool
182
	 **/
183
	public function is_valid_attach_for_object( $object_id = null ) {
184
		$valid = true;
185
		$post_id = $object_id;
186
		$post = get_post( $post_id );
187
188
		if ( ! $post ) {
189
			return false;
190
		}
191
192
		// Check post type
193
		if ( ! in_array( $post->post_type, $this->settings['post_type'] ) ) {
194
			return false;
195
		}
196
197
		// Check show on conditions
198
		foreach ( $this->settings['show_on'] as $condition => $value ) {
199
			if ( is_null( $value ) ) {
200
				continue;
201
			}
202
203
			switch ( $condition ) {
204
				// show_on_post_format
205
				case 'post_formats':
206
					if ( empty( $value ) || $post->post_type != 'post' ) {
207
						break;
208
					}
209
210
					$current_format = get_post_format( $post_id );
211
					if ( ! in_array( $current_format, $value ) ) {
212
						$valid = false;
213
						break 2;
214
					}
215
216
					break;
217
218
				// show_on_taxonomy_term or show_on_category
219
				case 'category':
220
					$this->show_on_category( $value );
221
222
					/* fall-through intended */
223
				case 'tax_term_id':
224
					$current_terms = wp_get_object_terms( $post_id, $this->settings['show_on']['tax_slug'], array( 'fields' => 'ids' ) );
225
226
					if ( ! is_array( $current_terms ) || ! in_array( $this->settings['show_on']['tax_term_id'], $current_terms ) ) {
227
						$valid = false;
228
						break 2;
229
					}
230
231
					break;
232
233
				// show_on_level
234
				case 'level_limit':
235
					$post_level = count( get_post_ancestors( $post_id ) ) + 1;
236
237
					if ( $post_level != $value ) {
238
						$valid = false;
239
						break 2;
240
					}
241
242
					break;
243
244
				// show_on_page
245
				case 'page_id':
246
					if ( $post_id != $value ) {
247
						$valid = false;
248
						break 2;
249
					}
250
251
					break;
252
253
				// show_on_page_children
254
				case 'parent_page_id':
255
					if ( $post->post_parent != $value ) {
256
						$valid = false;
257
						break 2;
258
					}
259
260
					break;
261
262
				// show_on_template
263 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...
264
					if ( empty( $value ) ) {
265
						break;
266
					}
267
					$current_template = get_post_meta( $post_id, '_wp_page_template', 1 );
268
269
					if ( ! in_array( $current_template, $value ) ) {
270
						$valid = false;
271
						break 2;
272
					}
273
274
					break;
275
276
				// hide_on_template
277 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...
278
					if ( empty( $value ) ) {
279
						break;
280
					}
281
					$current_template = get_post_meta( $post_id, '_wp_page_template', 1 );
282
283
					if ( in_array( $current_template, $value ) ) {
284
						$valid = false;
285
						break 2;
286
					}
287
288
					break;
289
			}
290
		}
291
292
		return $valid;
293
	}
294
295
	/**
296
	 * Add meta box for each of the container post types
297
	 **/
298
	public function attach() {
299
		foreach ( $this->settings['post_type'] as $post_type ) {
300
			add_meta_box(
301
				$this->id,
302
				$this->title,
303
				array( $this, 'render' ),
304
				$post_type,
305
				$this->settings['panel_context'],
306
				$this->settings['panel_priority']
307
			);
308
		}
309
310
		foreach ( $this->settings['post_type'] as $post_type ) {
311
			add_filter( "postbox_classes_{$post_type}_{$this->id}", array( $this, 'add_postbox_classes' ) );
312
		}
313
	}
314
315
	/**
316
	 * Classes to add to the post meta box
317
	 */
318
	public function add_postbox_classes( $classes ) {
319
		$classes[] = 'carbon-box';
320
		return $classes;
321
	}
322
323
	/**
324
	 * Output the container markup
325
	 **/
326
	public function render() {
327
		include \Carbon_Fields\DIR . '/templates/Container/post_meta.php';
328
	}
329
330
	/**
331
	 * Set the post ID the container will operate with.
332
	 *
333
	 * @param int $post_id
334
	 **/
335
	public function set_post_id( $post_id ) {
336
		$this->post_id = $post_id;
337
		$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...
338
	}
339
340
	/**
341
	 * COMMON USAGE METHODS
342
	 */
343
344
	/**
345
	 * Show the container only on particular page referenced by it's path.
346
	 *
347
	 * @param int|string $page page ID or page path
348
	 * @return object $this
349
	 **/
350 4
	public function show_on_page( $page ) {
351 4
		$page_id = absint( $page );
352
353 4
		if ( $page_id && $page_id == $page ) {
354 2
			$page_obj = get_post( $page_id );
355 2
		} else {
356 2
			$page_obj = get_page_by_path( $page );
357
		}
358
359 4
		$this->show_on_post_type( 'page' );
360
361 4
		if ( $page_obj ) {
362 4
			$this->settings['show_on']['page_id'] = $page_obj->ID;
363 4
		} else {
364
			$this->settings['show_on']['page_id'] = -1;
365
		}
366
367 4
		return $this;
368
	}
369
370
	/**
371
	 * Show the container only on pages whose parent is referenced by $parent_page_path.
372
	 *
373
	 * @param string $parent_page_path
374
	 * @return object $this
375
	 **/
376 1
	public function show_on_page_children( $parent_page_path ) {
377 1
		$page = get_page_by_path( $parent_page_path );
378
379 1
		$this->show_on_post_type( 'page' );
380
381 1
		if ( $page ) {
382 1
			$this->settings['show_on']['parent_page_id'] = $page->ID;
383 1
		} else {
384
			$this->settings['show_on']['parent_page_id'] = -1;
385
		}
386
387 1
		return $this;
388
	}
389
390
	/**
391
	 * Show the container only on posts from the specified category.
392
	 *
393
	 * @see show_on_taxonomy_term()
394
	 *
395
	 * @param string $category_slug
396
	 * @return object $this
397
	 **/
398
	public function show_on_category( $category_slug ) {
399
		$this->settings['show_on']['category'] = $category_slug;
400
401
		return $this->show_on_taxonomy_term( $category_slug, 'category' );
402
	}
403
404
	/**
405
	 * Show the container only on pages whose template has filename $template_path.
406
	 *
407
	 * @param string|array $template_path
408
	 * @return object $this
409
	 **/
410 2
	public function show_on_template( $template_path ) {
411
		// Backwards compatibility where only pages support templates
412 2
		if ( version_compare( get_bloginfo( 'version' ), '4.7', '<' ) ) {
413 2
			$this->show_on_post_type( 'page' );
414 2
		}
415
416 2
		if ( is_array( $template_path ) ) {
417 1
			foreach ( $template_path as $path ) {
418 1
				$this->show_on_template( $path );
419 1
			}
420
421 1
			return $this;
422
		}
423
424 2
		$this->settings['show_on']['template_names'][] = $template_path;
425
426 2
		return $this;
427
	}
428
429
	/**
430
	 * Hide the container from pages whose template has filename $template_path.
431
	 *
432
	 * @param string|array $template_path
433
	 * @return object $this
434
	 **/
435
	public function hide_on_template( $template_path ) {
436
		if ( is_array( $template_path ) ) {
437
			foreach ( $template_path as $path ) {
438
				$this->hide_on_template( $path );
439
			}
440
			return $this;
441
		}
442
443
		$this->settings['show_on']['not_in_template_names'][] = $template_path;
444
445
		return $this;
446
	}
447
448
	/**
449
	 * Show the container only on hierarchical posts of level $level.
450
	 * Levels start from 1 (top level post)
451
	 *
452
	 * @param int $level
453
	 * @return object $this
454
	 **/
455
	public function show_on_level( $level ) {
456
		if ( $level < 0 ) {
457
			Incorrect_Syntax_Exception::raise( 'Invalid level limitation (' . $level . ')' );
458
		}
459
460
		$this->settings['show_on']['level_limit'] = $level;
461
462
		return $this;
463
	}
464
465
	/**
466
	 * Show the container only on posts which have term $term_slug from the $taxonomy_slug taxonomy.
467
	 *
468
	 * @param string $taxonomy_slug
469
	 * @param string $term_slug
470
	 * @return object $this
471
	 **/
472
	public function show_on_taxonomy_term( $term_slug, $taxonomy_slug ) {
473
		$term = get_term_by( 'slug', $term_slug, $taxonomy_slug );
474
475
		$this->settings['show_on']['tax_slug'] = $taxonomy_slug;
476
		$this->settings['show_on']['tax_term'] = $term_slug;
477
		$this->settings['show_on']['tax_term_id'] = $term ? $term->term_id : null;
478
479
		return $this;
480
	}
481
482
	/**
483
	 * Show the container only on posts from the specified format.
484
	 * Learn more about {@link http://codex.wordpress.org/Post_Formats Post Formats (Codex)}
485
	 *
486
	 * @param string|array $post_format Name of the format as listed on Codex
487
	 * @return object $this
488
	 **/
489
	public function show_on_post_format( $post_format ) {
490
		if ( is_array( $post_format ) ) {
491
			foreach ( $post_format as $format ) {
492
				$this->show_on_post_format( $format );
493
			}
494
			return $this;
495
		}
496
497
		if ( $post_format === 'standard' ) {
498
			$post_format = 0;
499
		}
500
501
		$this->settings['show_on']['post_formats'][] = strtolower( $post_format );
502
503
		return $this;
504
	}
505
506
	/**
507
	 * Show the container only on posts from the specified type(s).
508
	 *
509
	 * @param string|array $post_types
510
	 * @return object $this
511
	 **/
512
	public function show_on_post_type( $post_types ) {
513
		$post_types = (array) $post_types;
514
515
		$this->settings['post_type'] = $post_types;
516
517
		return $this;
518
	}
519
520
	/**
521
	 * Sets the meta box container context
522
	 *
523
	 * @see https://codex.wordpress.org/Function_Reference/add_meta_box
524
	 * @param string $context ('normal', 'advanced' or 'side')
525
	 */
526
	public function set_context( $context ) {
527
		$this->settings['panel_context'] = $context;
528
529
		return $this;
530
	}
531
532
	/**
533
	 * Sets the meta box container priority
534
	 *
535
	 * @see https://codex.wordpress.org/Function_Reference/add_meta_box
536
	 * @param string $priority ('high', 'core', 'default' or 'low')
537
	 */
538
	public function set_priority( $priority ) {
539
		$this->settings['panel_priority'] = $priority;
540
541
		return $this;
542
	}
543
}
544