Completed
Pull Request — trunk (#541)
by Justin
13:08
created

CMB2_hookup::once()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 4
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
ccs 0
cts 7
cp 0
crap 6
1
<?php
2
/**
3
 * Handles hooking CMB2 forms/metaboxes into the post/attachement/user screens
4
 * and handles hooking in and saving those fields.
5
 *
6
 * @since  2.0.0
7
 *
8
 * @category  WordPress_Plugin
9
 * @package   CMB2
10
 * @author    WebDevStudios
11
 * @license   GPL-2.0+
12
 * @link      http://webdevstudios.com
13
 */
14
class CMB2_hookup extends CMB2_Hookup_Base {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
15
16
	/**
17
	 * Array of all hooks done (to be run once)
18
	 * @var   array
19
	 * @since 2.0.0
20
	 */
21
	protected static $hooks_completed = array();
22
23
	/**
24
	 * Only allow JS registration once
25
	 * @var   bool
26
	 * @since 2.0.7
27
	 */
28
	protected static $js_registration_done = false;
29
30
	/**
31
	 * Only allow CSS registration once
32
	 * @var   bool
33
	 * @since 2.0.7
34
	 */
35
	protected static $css_registration_done = false;
36
37
	/**
38
	 * @var   CMB2 object
39
	 * @since 2.0.2
40
	 */
41
	protected $cmb;
42
43
	/**
44
	 * CMB taxonomies array for term meta
45
	 * @var   array
46
	 * @since 2.2.0
47
	 */
48
	protected $taxonomies = array();
49
50
	/**
51
	 * Custom field columns.
52
	 * @var   array
53
	 * @since 2.2.2
54
	 */
55
	protected $columns = array();
56
57
	/**
58
	 * Constructor
59
	 * @since 2.0.0
60
	 * @param CMB2 $cmb The CMB2 object to hookup
61
	 */
62
	public function __construct( CMB2 $cmb ) {
63
		$this->cmb = $cmb;
64
		$this->object_type = $this->cmb->mb_object_type();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->cmb->mb_object_type() can also be of type false. However, the property $object_type is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
65
66
		$this->universal_hooks();
67
68
		if ( is_admin() ) {
69
70
			switch ( $this->object_type ) {
71
				case 'post':
72
					return $this->post_hooks();
0 ignored issues
show
Bug introduced by
Constructors do not have meaningful return values, anything that is returned from here is discarded. Are you sure this is correct?
Loading history...
73
				case 'comment':
74
					return $this->comment_hooks();
0 ignored issues
show
Bug introduced by
Constructors do not have meaningful return values, anything that is returned from here is discarded. Are you sure this is correct?
Loading history...
75
				case 'user':
76
					return $this->user_hooks();
0 ignored issues
show
Bug introduced by
Constructors do not have meaningful return values, anything that is returned from here is discarded. Are you sure this is correct?
Loading history...
77
				case 'term':
78
					return $this->term_hooks();
0 ignored issues
show
Bug introduced by
Constructors do not have meaningful return values, anything that is returned from here is discarded. Are you sure this is correct?
Loading history...
79
			}
80
81
		}
82
	}
83
84
	public function universal_hooks() {
85
		foreach ( get_class_methods( 'CMB2_Show_Filters' ) as $filter ) {
86
			add_filter( 'cmb2_show_on', array( 'CMB2_Show_Filters', $filter ), 10, 3 );
87
		}
88
89
		if ( is_admin() ) {
90
			// register our scripts and styles for cmb
91
			$this->once( 'admin_enqueue_scripts', array( __CLASS__, 'register_scripts' ), 8 );
92
			$this->once( 'admin_enqueue_scripts', array( $this, 'do_scripts' ) );
93
94
			$this->maybe_enqueue_column_display_styles();
95
		}
96
	}
97
98
	public function post_hooks() {
99
		add_action( 'add_meta_boxes', array( $this, 'add_metaboxes' ) );
100
		add_action( 'add_attachment', array( $this, 'save_post' ) );
101
		add_action( 'edit_attachment', array( $this, 'save_post' ) );
102
		add_action( 'save_post', array( $this, 'save_post' ), 10, 2 );
103
104
		if ( $this->cmb->has_columns ) {
0 ignored issues
show
Documentation introduced by
The property $has_columns is declared protected in CMB2. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
105
			foreach ( $this->cmb->prop( 'object_types' ) as $post_type ) {
106
				add_filter( "manage_{$post_type}_posts_columns", array( $this, 'register_column_headers' ) );
107
				add_action( "manage_{$post_type}_posts_custom_column", array( $this, 'column_display' ), 10, 2 );
108
			}
109
		}
110
	}
111
112
	public function comment_hooks() {
113
		add_action( 'add_meta_boxes_comment', array( $this, 'add_metaboxes' ) );
114
		add_action( 'edit_comment', array( $this, 'save_comment' ) );
115
116
		if ( $this->cmb->has_columns ) {
0 ignored issues
show
Documentation introduced by
The property $has_columns is declared protected in CMB2. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
117
			add_filter( 'manage_edit-comments_columns', array( $this, 'register_column_headers' ) );
118
			add_action( 'manage_comments_custom_column', array( $this, 'column_display'  ), 10, 3 );
119
		}
120
	}
121
122
	public function user_hooks() {
123
		$priority = $this->get_priority();
124
125
		add_action( 'show_user_profile', array( $this, 'user_metabox' ), $priority );
126
		add_action( 'edit_user_profile', array( $this, 'user_metabox' ), $priority );
127
		add_action( 'user_new_form', array( $this, 'user_new_metabox' ), $priority );
128
129
		add_action( 'personal_options_update', array( $this, 'save_user' ) );
130
		add_action( 'edit_user_profile_update', array( $this, 'save_user' ) );
131
		add_action( 'user_register', array( $this, 'save_user' ) );
132
133 View Code Duplication
		if ( $this->cmb->has_columns ) {
0 ignored issues
show
Documentation introduced by
The property $has_columns is declared protected in CMB2. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
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...
134
			add_filter( 'manage_users_columns', array( $this, 'register_column_headers' ) );
135
			add_filter( 'manage_users_custom_column', array( $this, 'return_column_display'  ), 10, 3 );
136
		}
137
	}
138
139
	public function term_hooks() {
140
		if ( ! function_exists( 'get_term_meta' ) ) {
141
			wp_die( __( 'Term Metadata is a WordPress > 4.4 feature. Please upgrade your WordPress install.', 'cmb2' ) );
142
		}
143
144
		if ( ! $this->cmb->prop( 'taxonomies' ) ) {
145
			wp_die( __( 'Term metaboxes configuration requires a \'taxonomies\' parameter', 'cmb2' ) );
146
		}
147
148
		$this->taxonomies = (array) $this->cmb->prop( 'taxonomies' );
149
		$show_on_term_add = $this->cmb->prop( 'new_term_section' );
150
		$priority         = $this->get_priority( 8 );
151
152
		foreach ( $this->taxonomies as $taxonomy ) {
153
			// Display our form data
154
			add_action( "{$taxonomy}_edit_form", array( $this, 'term_metabox' ), $priority, 2 );
155
156
			$show_on_add = is_array( $show_on_term_add )
157
				? in_array( $taxonomy, $show_on_term_add )
158
				: (bool) $show_on_term_add;
159
160
			$show_on_add = apply_filters( "cmb2_show_on_term_add_form_{$this->cmb->cmb_id}", $show_on_add, $this->cmb );
161
162
			// Display form in add-new section (unless specified not to)
163
			if ( $show_on_add ) {
164
				add_action( "{$taxonomy}_add_form_fields", array( $this, 'term_metabox' ), $priority, 2 );
165
			}
166
167 View Code Duplication
			if ( $this->cmb->has_columns ) {
0 ignored issues
show
Documentation introduced by
The property $has_columns is declared protected in CMB2. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
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...
168
				add_filter( "manage_edit-{$taxonomy}_columns", array( $this, 'register_column_headers' ) );
169
				add_filter( "manage_{$taxonomy}_custom_column", array( $this, 'return_column_display'  ), 10, 3 );
170
			}
171
		}
172
173
		add_action( 'created_term', array( $this, 'save_term' ), 10, 3 );
174
		add_action( 'edited_terms', array( $this, 'save_term' ), 10, 2 );
175
		add_action( 'delete_term', array( $this, 'delete_term' ), 10, 3 );
176
177
	}
178
179
	/**
180
	 * Registers styles for CMB2
181
	 * @since 2.0.7
182
	 */
183
	protected static function register_styles() {
184
		if ( self::$css_registration_done ) {
185 1
			return;
186 1
		}
187
188
		// Only use minified files if SCRIPT_DEBUG is off
189
		$min   = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
190
		$front = is_admin() ? '' : '-front';
191 1
		$rtl   = is_rtl() ? '-rtl' : '';
192 1
193 1
		// Filter required styles and register stylesheet
194
		$dependencies = apply_filters( 'cmb2_style_dependencies', array() );
195
		wp_register_style( 'cmb2-styles', cmb2_utils()->url( "css/cmb2{$front}{$rtl}{$min}.css" ), $dependencies );
196 1
		wp_register_style( 'cmb2-display-styles', cmb2_utils()->url( "css/cmb2-display{$rtl}{$min}.css" ), $dependencies );
197 1
198 1
		self::$css_registration_done = true;
199
	}
200 1
201 1
	/**
202
	 * Registers scripts for CMB2
203
	 * @since  2.0.7
204
	 */
205
	protected static function register_js() {
206
		if ( self::$js_registration_done ) {
207 1
			return;
208 1
		}
209
210
		$hook = is_admin() ? 'admin_footer' : 'wp_footer';
211
		add_action( $hook, array( 'CMB2_JS', 'enqueue' ), 8 );
212 1
213 1
		self::$js_registration_done = true;
214
	}
215 1
216 1
	/**
217
	 * Registers scripts and styles for CMB2
218
	 * @since  1.0.0
219
	 */
220
	public static function register_scripts() {
221
		self::register_styles();
222
		self::register_js();
223
	}
224
225
	/**
226
	 * Enqueues scripts and styles for CMB2 in admin_head.
227
	 * @since  1.0.0
228
	 */
229
	public function do_scripts( $hook ) {
230
		$hooks = array(
231
			'post.php',
232
			'post-new.php',
233
			'page-new.php',
234
			'page.php',
235
			'comment.php',
236
			'edit-tags.php',
237
			'term.php',
238
			'user-new.php',
239
			'profile.php',
240
			'user-edit.php',
241
		);
242
		// only pre-enqueue our scripts/styles on the proper pages
243
		// show_form_for_type will have us covered if we miss something here.
244
		if ( in_array( $hook, $hooks, true ) ) {
245
			if ( $this->cmb->prop( 'cmb_styles' ) ) {
246
				self::enqueue_cmb_css();
247
			}
248
			if ( $this->cmb->prop( 'enqueue_js' ) ) {
249
				self::enqueue_cmb_js();
250
			}
251
		}
252
	}
253
254
	/**
255
	 * Register the CMB2 field column headers.
256
	 * @since 2.2.2
257
	 */
258
	public function register_column_headers( $columns ) {
259
		$fields = $this->cmb->prop( 'fields' );
260
261
		foreach ( $fields as $key => $field ) {
262
			if ( ! isset( $field['column'] ) ) {
263
				continue;
264
			}
265
266
			$column = $field['column'];
267
268
			if ( false === $column['position'] ) {
269
270
				$columns[ $field['id'] ] = $column['name'];
271
272
			} else {
273
274
				$before = array_slice( $columns, 0, absint( $column['position'] ) );
275
				$before[ $field['id'] ] = $column['name'];
276
				$columns = $before + $columns;
277
			}
278
279
			$column['field'] = $field;
280
			$this->columns[ $field['id'] ] = $column;
281
		}
282
283
		return $columns;
284
	}
285
286
	/**
287
	 * The CMB2 field column display output.
288
	 * @since 2.2.2
289
	 */
290
	public function column_display( $column_name, $object_id ) {
291
		if ( isset( $this->columns[ $column_name ] ) ) {
292
 			$field = new CMB2_Field( array(
293
				'field_args'  => $this->columns[ $column_name ]['field'],
294
				'object_type' => $this->object_type,
295
				'object_id'   => $this->cmb->object_id( $object_id ),
296
				'cmb_id'      => $this->cmb->cmb_id,
297
			) );
298
299
			$this->cmb->get_field( $field )->render_column();
300
		}
301
	}
302
303
	/**
304
	 * Returns the column display.
305
	 * @since 2.2.2
306
	 */
307
	public function return_column_display( $empty, $custom_column, $object_id ) {
308
		ob_start();
309
		$this->column_display( $custom_column, $object_id );
310
		$column = ob_get_clean();
311
312
		return $column ? $column : $empty;
313
	}
314
315
	/**
316
	 * Add metaboxes (to 'post' or 'comment' object types)
317
	 * @since 1.0.0
318
	 */
319
	public function add_metaboxes() {
320
321
		if ( ! $this->show_on() ) {
322
			return;
323
		}
324
325
		/**
326
		 * To keep from registering an actual post-screen metabox,
327
		 * omit the 'title' attribute from the metabox registration array.
328
		 *
329
		 * (WordPress will not display metaboxes without titles anyway)
330
		 *
331
		 * This is a good solution if you want to output your metaboxes
332
		 * Somewhere else in the post-screen
333
		 */
334
		if ( ! $this->cmb->prop( 'title' ) ) {
335
			return;
336
		}
337
338
		foreach ( $this->cmb->prop( 'object_types' ) as $post_type ) {
339
			if ( $this->cmb->prop( 'closed' ) ) {
340
				add_filter( "postbox_classes_{$post_type}_{$this->cmb->cmb_id}", array( $this, 'close_metabox_class' ) );
341
			}
342
343
			add_meta_box( $this->cmb->cmb_id, $this->cmb->prop( 'title' ), array( $this, 'metabox_callback' ), $post_type, $this->cmb->prop( 'context' ), $this->cmb->prop( 'priority' ) );
344
		}
345
	}
346
347
	/**
348
	 * Add 'closed' class to metabox
349
	 * @since  2.0.0
350
	 * @param  array  $classes Array of classes
351
	 * @return array           Modified array of classes
352
	 */
353
	public function close_metabox_class( $classes ) {
354
		$classes[] = 'closed';
355
		return $classes;
356
	}
357
358
	/**
359
	 * Display metaboxes for a post or comment object
360
	 * @since  1.0.0
361
	 */
362
	public function metabox_callback() {
363
		$object_id = 'comment' == $this->object_type ? get_comment_ID() : get_the_ID();
364
		$this->cmb->show_form( $object_id, $this->object_type );
365
	}
366
367
	/**
368
	 * Display metaboxes for new user page
369
	 * @since  1.0.0
370
	 */
371
	public function user_new_metabox( $section ) {
0 ignored issues
show
Coding Style introduced by
user_new_metabox uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
372
		if ( $section == $this->cmb->prop( 'new_user_section' ) ) {
373
			$object_id = $this->cmb->object_id();
374
			$this->cmb->object_id( isset( $_REQUEST['user_id'] ) ? $_REQUEST['user_id'] : $object_id );
375
			$this->user_metabox();
376
		}
377
	}
378
379
	/**
380
	 * Display metaboxes for a user object
381
	 * @since  1.0.0
382
	 */
383
	public function user_metabox() {
384
		$this->show_form_for_type( 'user' );
385
	}
386
387
	/**
388
	 * Display metaboxes for a taxonomy term object
389
	 * @since  2.2.0
390
	 */
391
	public function term_metabox() {
392
		$this->show_form_for_type( 'term' );
393
	}
394
395
	/**
396
	 * Display metaboxes for an object type
397
	 * @since  2.2.0
398
	 * @param  string $type Object type
399
	 * @return void
400
	 */
401
	public function show_form_for_type( $type ) {
402
		if ( $type != $this->cmb->mb_object_type() ) {
403
			return;
404
		}
405
406
		if ( ! $this->show_on() ) {
407
			return;
408
		}
409
410
		if ( $this->cmb->prop( 'cmb_styles' ) ) {
411
			self::enqueue_cmb_css();
412
		}
413
		if ( $this->cmb->prop( 'enqueue_js' ) ) {
414
			self::enqueue_cmb_js();
415
		}
416
417
		$this->cmb->show_form( 0, $type );
418
	}
419
420
	/**
421
	 * Determines if metabox should be shown in current context
422
	 * @since  2.0.0
423
	 * @return bool Whether metabox should be added/shown
424
	 */
425
	public function show_on() {
426
		// If metabox is requesting to be conditionally shown
427
		$show = $this->cmb->should_show();
428
429
		/**
430
		 * Filter to determine if metabox should show. Default is true
431
		 *
432
		 * @param array  $show          Default is true, show the metabox
433
		 * @param mixed  $meta_box_args Array of the metabox arguments
434
		 * @param mixed  $cmb           The CMB2 instance
435
		 */
436
		$show = (bool) apply_filters( 'cmb2_show_on', $show, $this->cmb->meta_box, $this->cmb );
437
438
		return $show;
439
	}
440
441
	/**
442
	 * Get the CMB priority property set to numeric hook priority.
443
	 * @since  2.2.0
444
	 * @param  integer $default Default display hook priority.
445
	 * @return integer          Hook priority.
446
	 */
447
	public function get_priority( $default = 10 ) {
448
		$priority = $this->cmb->prop( 'priority' );
449
450
		if ( ! is_numeric( $priority ) ) {
451
			switch ( $priority ) {
452
453
				case 'high':
454
					$priority = 5;
455
					break;
456
457
				case 'low':
458
					$priority = 20;
459
					break;
460
461
				default:
462
					$priority = $default;
463
					break;
464
			}
465
		}
466
467
		return $priority;
468
	}
469
470
	/**
471
	 * Save data from post metabox
472
	 * @since  1.0.0
473
	 * @param  int    $post_id Post ID
474
	 * @param  mixed  $post    Post object
475
	 * @return null
476
	 */
477
	public function save_post( $post_id, $post = false ) {
0 ignored issues
show
Coding Style introduced by
save_post uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
478
479
		$post_type = $post ? $post->post_type : get_post_type( $post_id );
480
481
		$do_not_pass_go = (
482
			! $this->can_save( $post_type )
483
			// check user editing permissions
484
			|| ( 'page' == $post_type && ! current_user_can( 'edit_page', $post_id ) )
485
			|| ! current_user_can( 'edit_post', $post_id )
486
		);
487
488
		if ( $do_not_pass_go ) {
489
			// do not collect $200
490
			return;
491
		}
492
493
		// take a trip to reading railroad – if you pass go collect $200
494
		$this->cmb->save_fields( $post_id, 'post', $_POST );
495
	}
496
497
	/**
498
	 * Save data from comment metabox
499
	 * @since  2.0.9
500
	 * @param  int    $comment_id Comment ID
501
	 * @return null
502
	 */
503
	public function save_comment( $comment_id ) {
0 ignored issues
show
Coding Style introduced by
save_comment uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
504
505
		$can_edit = current_user_can( 'moderate_comments', $comment_id );
506
507
		if ( $this->can_save( get_comment_type( $comment_id ) ) && $can_edit ) {
508
			$this->cmb->save_fields( $comment_id, 'comment', $_POST );
509
		}
510
	}
511
512
	/**
513
	 * Save data from user fields
514
	 * @since  1.0.x
515
	 * @param  int   $user_id  User ID
516
	 * @return null
517
	 */
518
	public function save_user( $user_id ) {
0 ignored issues
show
Coding Style introduced by
save_user uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
519
		// check permissions
520
		if ( $this->can_save( 'user' ) ) {
521
			$this->cmb->save_fields( $user_id, 'user', $_POST );
522
		}
523
	}
524
525
	/**
526
	 * Save data from term fields
527
	 * @since  2.2.0
528
	 * @param  int    $term_id  Term ID
529
	 * @param  int    $tt_id    Term Taxonomy ID
530
	 * @param  string $taxonomy Taxonomy
531
	 * @return null
532
	 */
533
	public function save_term( $term_id, $tt_id, $taxonomy = '' ) {
0 ignored issues
show
Coding Style introduced by
save_term uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
534
		$taxonomy = $taxonomy ? $taxonomy : $tt_id;
535
536
		// check permissions
537
		if ( $this->taxonomy_can_save( $taxonomy ) && $this->can_save( 'term' ) ) {
538
			$this->cmb->save_fields( $term_id, 'term', $_POST );
539
		}
540
	}
541
542
	/**
543
	 * Delete term meta when a term is deleted.
544
	 * @since  2.2.0
545
	 * @param  int    $term_id  Term ID
546
	 * @param  int    $tt_id    Term Taxonomy ID
547
	 * @param  string $taxonomy Taxonomy
548
	 * @return null
549
	 */
550
	public function delete_term( $term_id, $tt_id, $taxonomy = '' ) {
551
		if ( $this->taxonomy_can_save( $taxonomy ) ) {
552
553
			foreach ( $this->cmb->prop( 'fields' ) as $field ) {
554
				$data_to_delete[ $field['id'] ] = '';
0 ignored issues
show
Coding Style Comprehensibility introduced by
$data_to_delete was never initialized. Although not strictly required by PHP, it is generally a good practice to add $data_to_delete = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
555
			}
556
557
			$this->cmb->save_fields( $term_id, 'term', $data_to_delete );
0 ignored issues
show
Bug introduced by
The variable $data_to_delete does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
558
		}
559
	}
560
561
	/**
562
	 * Determines if the current object is able to be saved
563
	 * @since  2.0.9
564
	 * @param  string  $type Current post_type or comment_type
565
	 * @return bool          Whether object can be saved
566
	 */
567
	public function can_save( $type = '' ) {
0 ignored issues
show
Coding Style introduced by
can_save uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
568
		return (
569
			$this->cmb->prop( 'save_fields' )
570
			// check nonce
571
			&& isset( $_POST[ $this->cmb->nonce() ] )
572
			&& wp_verify_nonce( $_POST[ $this->cmb->nonce() ], $this->cmb->nonce() )
573
			// check if autosave
574
			&& ! ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
575
			// get the metabox types & compare it to this type
576
			&& ( $type && in_array( $type, $this->cmb->prop( 'object_types' ) ) )
577
		);
578
	}
579
580
	/**
581
	 * Determine if taxonomy of term being modified is cmb2-editable.
582
	 * @since  2.2.0
583
	 * @param  string $taxonomy Taxonomy of term being modified.
584
	 * @return bool             Whether taxonomy is editable.
585
	 */
586
	public function taxonomy_can_save( $taxonomy ) {
587
		if ( empty( $this->taxonomies ) || ! in_array( $taxonomy, $this->taxonomies ) ) {
588
			return false;
589
		}
590
591
		$taxonomy_object = get_taxonomy( $taxonomy );
592
		// Can the user edit this term?
593
		if ( ! isset( $taxonomy_object->cap ) || ! current_user_can( $taxonomy_object->cap->edit_terms ) ) {
594
			return false;
595
		}
596
597
		return true;
598
	}
599
600
	/**
601
	 * Enqueues the 'cmb2-display-styles' if the conditions match (has columns, on the right page, etc).
602
	 * @since  2.2.2.1
603
	 */
604
	protected function maybe_enqueue_column_display_styles() {
605
		global $pagenow;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
606
		if (
607
			$pagenow
608
			&& $this->cmb->has_columns
0 ignored issues
show
Documentation introduced by
The property $has_columns is declared protected in CMB2. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
609
			&& $this->cmb->prop( 'cmb_styles' )
610
			&& in_array( $pagenow, array( 'edit.php', 'users.php', 'edit-comments.php', 'edit-tags.php' ), 1 )
611
			) {
612
			self::enqueue_cmb_css( 'cmb2-display-styles' );
613
		}
614
	}
615
616
	/**
617
	 * Includes CMB2 styles
618
	 * @since  2.0.0
619
	 */
620
	public static function enqueue_cmb_css( $handle = 'cmb2-styles' ) {
621
		if ( ! apply_filters( 'cmb2_enqueue_css', true ) ) {
622
			return false;
623
		}
624
625
		self::register_styles();
626
627
		/*
628
		 * White list the options as this method can be used as a hook callback
629
		 * and have a different argument passed.
630
		 */
631
		return wp_enqueue_style( 'cmb2-display-styles' === $handle ? $handle : 'cmb2-styles' );
632
	}
633
634
	/**
635
	 * Includes CMB2 JS
636
	 * @since  2.0.0
637
	 */
638
	public static function enqueue_cmb_js() {
639
		if ( ! apply_filters( 'cmb2_enqueue_js', true ) ) {
640
			return false;
641 1
		}
642 1
643
		self::register_js();
644
		return true;
645
	}
646 1
647
}
648