Completed
Pull Request — trunk (#696)
by
unknown
03:31
created

CMB2_hookup::comment_hooks()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 2
eloc 6
nc 2
nop 0
dl 0
loc 9
ccs 5
cts 5
cp 1
crap 2
rs 9.6666
c 1
b 0
f 1
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 {
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
	 * The object type we are performing the hookup for
59
	 * @var   string
60
	 * @since 2.0.9
61
	 */
62
	protected $object_type = 'post';
63
64
	public function __construct( CMB2 $cmb ) {
65
		$this->cmb = $cmb;
66
		$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...
67
68
		$this->universal_hooks();
69
70
		if ( is_admin() ) {
71
72
			switch ( $this->object_type ) {
73
				case 'post':
74
					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...
75
				case 'comment':
76
					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...
77
				case 'user':
78
					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...
79
				case 'term':
80
					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...
81
			}
82
83
		}
84
	}
85
86
	public function universal_hooks() {
87
		foreach ( get_class_methods( 'CMB2_Show_Filters' ) as $filter ) {
88
			add_filter( 'cmb2_show_on', array( 'CMB2_Show_Filters', $filter ), 10, 3 );
89
		}
90
91
		if ( is_admin() ) {
92
			// register our scripts and styles for cmb
93
			$this->once( 'admin_enqueue_scripts', array( __CLASS__, 'register_scripts' ), 8 );
94
			$this->once( 'admin_enqueue_scripts', array( $this, 'do_scripts' ) );
95
96
			$this->maybe_enqueue_column_display_styles();
97
		}
98
	}
99
100
	public function post_hooks() {
101
		add_action( 'add_meta_boxes', array( $this, 'add_metaboxes' ) );
102
		add_action( 'add_attachment', array( $this, 'save_post' ) );
103
		add_action( 'edit_attachment', array( $this, 'save_post' ) );
104
		add_action( 'save_post', array( $this, 'save_post' ), 10, 2 );
105
106
		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...
107
			foreach ( $this->cmb->prop( 'object_types' ) as $post_type ) {
108
				add_filter( "manage_{$post_type}_posts_columns", array( $this, 'register_column_headers' ) );
109
				add_action( "manage_{$post_type}_posts_custom_column", array( $this, 'column_display' ), 10, 2 );
110 1
			}
111 1
		}
112 1
	}
113
114
	public function comment_hooks() {
115
		add_action( 'add_meta_boxes_comment', array( $this, 'add_metaboxes' ) );
116 1
		add_action( 'edit_comment', array( $this, 'save_comment' ) );
117
118 1
		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...
119
			add_filter( 'manage_edit-comments_columns', array( $this, 'register_column_headers' ) );
120 1
			add_action( 'manage_comments_custom_column', array( $this, 'column_display'  ), 10, 3 );
121 1
		}
122 1
	}
123 1
124 1
	public function user_hooks() {
125 1
		$priority = $this->get_priority();
126 1
127 1
		add_action( 'show_user_profile', array( $this, 'user_metabox' ), $priority );
128 1
		add_action( 'edit_user_profile', array( $this, 'user_metabox' ), $priority );
129
		add_action( 'user_new_form', array( $this, 'user_new_metabox' ), $priority );
130 1
131
		add_action( 'personal_options_update', array( $this, 'save_user' ) );
132
		add_action( 'edit_user_profile_update', array( $this, 'save_user' ) );
133 1
		add_action( 'user_register', array( $this, 'save_user' ) );
134
135 1 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...
136
			add_filter( 'manage_users_columns', array( $this, 'register_column_headers' ) );
137 1
			add_filter( 'manage_users_custom_column', array( $this, 'return_column_display'  ), 10, 3 );
138
		}
139 1
	}
140 1
141 1
	public function term_hooks() {
142 1
		if ( ! function_exists( 'get_term_meta' ) ) {
143 1
			wp_die( esc_html__( 'Term Metadata is a WordPress > 4.4 feature. Please upgrade your WordPress install.', 'cmb2' ) );
144 1
		}
145
146 1
		if ( ! $this->cmb->prop( 'taxonomies' ) ) {
147
			wp_die( esc_html__( 'Term metaboxes configuration requires a "taxonomies" parameter.', 'cmb2' ) );
148 1
		}
149 1
150 1
		$this->taxonomies = (array) $this->cmb->prop( 'taxonomies' );
151 1
		$show_on_term_add = $this->cmb->prop( 'new_term_section' );
152 1
		$priority         = $this->get_priority( 8 );
153 1
154 1
		foreach ( $this->taxonomies as $taxonomy ) {
155 1
			// Display our form data
156 1
			add_action( "{$taxonomy}_edit_form", array( $this, 'term_metabox' ), $priority, 2 );
157 1
158 1
			$show_on_add = is_array( $show_on_term_add )
159 1
				? in_array( $taxonomy, $show_on_term_add )
160 1
				: (bool) $show_on_term_add;
161 1
162
			$show_on_add = apply_filters( "cmb2_show_on_term_add_form_{$this->cmb->cmb_id}", $show_on_add, $this->cmb );
163 1
164 1
			// Display form in add-new section (unless specified not to)
165 1
			if ( $show_on_add ) {
166 1
				add_action( "{$taxonomy}_add_form_fields", array( $this, 'term_metabox' ), $priority, 2 );
167
			}
168 1
169 1 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...
170
				add_filter( "manage_edit-{$taxonomy}_columns", array( $this, 'register_column_headers' ) );
171 1
				add_filter( "manage_{$taxonomy}_custom_column", array( $this, 'return_column_display'  ), 10, 3 );
172 1
			}
173 1
		}
174 1
175 1
		add_action( 'created_term', array( $this, 'save_term' ), 10, 3 );
176 1
		add_action( 'edited_terms', array( $this, 'save_term' ), 10, 2 );
177 1
		add_action( 'delete_term', array( $this, 'delete_term' ), 10, 3 );
178 1
179
	}
180 1
181
	/**
182 1
	 * Registers styles for CMB2
183 1
	 * @since 2.0.7
184
	 */
185
	protected static function register_styles() {
186
		if ( self::$css_registration_done ) {
187
			return;
188
		}
189
190
		// Only use minified files if SCRIPT_DEBUG is off
191
		$min   = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
192
		$front = is_admin() ? '' : '-front';
193
		$rtl   = is_rtl() ? '-rtl' : '';
194
195
		// Filter required styles and register stylesheet
196
		$dependencies = apply_filters( 'cmb2_style_dependencies', array() );
197
		wp_register_style( 'cmb2-styles', cmb2_utils()->url( "css/cmb2{$front}{$rtl}{$min}.css" ), $dependencies );
198
		wp_register_style( 'cmb2-display-styles', cmb2_utils()->url( "css/cmb2-display{$rtl}{$min}.css" ), $dependencies );
199
200
		self::$css_registration_done = true;
201
	}
202
203
	/**
204
	 * Registers scripts for CMB2
205
	 * @since  2.0.7
206
	 */
207
	protected static function register_js() {
208
		if ( self::$js_registration_done ) {
209
			return;
210
		}
211
212
		$hook = is_admin() ? 'admin_footer' : 'wp_footer';
213
		add_action( $hook, array( 'CMB2_JS', 'enqueue' ), 8 );
214
215
		self::$js_registration_done = true;
216
	}
217
218
	/**
219
	 * Registers scripts and styles for CMB2
220
	 * @since  1.0.0
221
	 */
222
	public static function register_scripts() {
223
		self::register_styles();
224
		self::register_js();
225
	}
226
227
	/**
228
	 * Enqueues scripts and styles for CMB2 in admin_head.
229
	 * @since  1.0.0
230
	 */
231
	public function do_scripts( $hook ) {
232
		$hooks = array(
233
			'post.php',
234
			'post-new.php',
235
			'page-new.php',
236
			'page.php',
237
			'comment.php',
238
			'edit-tags.php',
239
			'term.php',
240
			'user-new.php',
241
			'profile.php',
242
			'user-edit.php',
243
		);
244
		// only pre-enqueue our scripts/styles on the proper pages
245
		// show_form_for_type will have us covered if we miss something here.
246
		if ( in_array( $hook, $hooks, true ) ) {
247
			if ( $this->cmb->prop( 'cmb_styles' ) ) {
248
				self::enqueue_cmb_css();
249
			}
250
			if ( $this->cmb->prop( 'enqueue_js' ) ) {
251
				self::enqueue_cmb_js();
252
			}
253
		}
254
	}
255
256
	/**
257
	 * Register the CMB2 field column headers.
258
	 * @since 2.2.2
259
	 */
260
	public function register_column_headers( $columns ) {
261
		$fields = $this->cmb->prop( 'fields' );
262
263
		foreach ( $fields as $key => $field ) {
264
			if ( ! isset( $field['column'] ) ) {
265
				continue;
266
			}
267
268
			$column = $field['column'];
269
270
			if ( false === $column['position'] ) {
271
272
				$columns[ $field['id'] ] = $column['name'];
273
274
			} else {
275
276
				$before = array_slice( $columns, 0, absint( $column['position'] ) );
277
				$before[ $field['id'] ] = $column['name'];
278
				$columns = $before + $columns;
279
			}
280
281
			$column['field'] = $field;
282
			$this->columns[ $field['id'] ] = $column;
283
		}
284
285
		return $columns;
286
	}
287
288
	/**
289
	 * The CMB2 field column display output.
290
	 * @since 2.2.2
291
	 */
292
	public function column_display( $column_name, $object_id ) {
293
		if ( isset( $this->columns[ $column_name ] ) ) {
294
 			$field = new CMB2_Field( array(
295
				'field_args'  => $this->columns[ $column_name ]['field'],
296
				'object_type' => $this->object_type,
297
				'object_id'   => $this->cmb->object_id( $object_id ),
298
				'cmb_id'      => $this->cmb->cmb_id,
299
			) );
300
301
			$this->cmb->get_field( $field )->render_column();
302
		}
303
	}
304
305
	/**
306
	 * Returns the column display.
307
	 * @since 2.2.2
308
	 */
309
	public function return_column_display( $empty, $custom_column, $object_id ) {
310
		ob_start();
311
		$this->column_display( $custom_column, $object_id );
312
		$column = ob_get_clean();
313
314
		return $column ? $column : $empty;
315
	}
316
317
	/**
318
	 * Add metaboxes (to 'post' or 'comment' object types)
319
	 * @since 1.0.0
320
	 */
321
	public function add_metaboxes() {
322
323
		if ( ! $this->show_on() ) {
324
			return;
325
		}
326
327
		/**
328
		 * To keep from registering an actual post-screen metabox,
329
		 * omit the 'title' attribute from the metabox registration array.
330
		 *
331
		 * (WordPress will not display metaboxes without titles anyway)
332
		 *
333
		 * This is a good solution if you want to output your metaboxes
334
		 * Somewhere else in the post-screen
335
		 */
336
		if ( ! $this->cmb->prop( 'title' ) ) {
337
			return;
338
		}
339
340
		foreach ( $this->cmb->prop( 'object_types' ) as $post_type ) {
341
			if ( $this->cmb->prop( 'closed' ) ) {
342
				add_filter( "postbox_classes_{$post_type}_{$this->cmb->cmb_id}", array( $this, 'close_metabox_class' ) );
343
			}
344
345
			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' ) );
346
		}
347
	}
348
349
	/**
350 1
	 * Add 'closed' class to metabox
351 1
	 * @since  2.0.0
352
	 * @param  array  $classes Array of classes
353 1
	 * @return array           Modified array of classes
354
	 */
355
	public function close_metabox_class( $classes ) {
356
		$classes[] = 'closed';
357
		return $classes;
358
	}
359
360 1
	/**
361 1
	 * Display metaboxes for a post or comment object
362 1
	 * @since  1.0.0
363 1
	 */
364
	public function metabox_callback() {
365
		$object_id = 'comment' == $this->object_type ? get_comment_ID() : get_the_ID();
366
		$this->cmb->show_form( $object_id, $this->object_type );
367 1
	}
368
369
	/**
370
	 * Display metaboxes for new user page
371
	 * @since  1.0.0
372
	 */
373
	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...
374
		if ( $section == $this->cmb->prop( 'new_user_section' ) ) {
375
			$object_id = $this->cmb->object_id();
376
			$this->cmb->object_id( isset( $_REQUEST['user_id'] ) ? $_REQUEST['user_id'] : $object_id );
377
			$this->user_metabox();
378
		}
379
	}
380
381
	/**
382
	 * Display metaboxes for a user object
383
	 * @since  1.0.0
384
	 */
385
	public function user_metabox() {
386
		$this->show_form_for_type( 'user' );
387
	}
388
389
	/**
390
	 * Display metaboxes for a taxonomy term object
391
	 * @since  2.2.0
392
	 */
393
	public function term_metabox() {
394
		$this->show_form_for_type( 'term' );
395
	}
396
397
	/**
398
	 * Display metaboxes for an object type
399
	 * @since  2.2.0
400
	 * @param  string $type Object type
401
	 * @return void
402
	 */
403
	public function show_form_for_type( $type ) {
404
		if ( $type != $this->cmb->mb_object_type() ) {
405
			return;
406
		}
407
408
		if ( ! $this->show_on() ) {
409
			return;
410
		}
411
412
		if ( $this->cmb->prop( 'cmb_styles' ) ) {
413
			self::enqueue_cmb_css();
414
		}
415
		if ( $this->cmb->prop( 'enqueue_js' ) ) {
416
			self::enqueue_cmb_js();
417
		}
418
419
		$this->cmb->show_form( 0, $type );
420
	}
421
422
	/**
423
	 * Determines if metabox should be shown in current context
424
	 * @since  2.0.0
425
	 * @return bool Whether metabox should be added/shown
426
	 */
427
	public function show_on() {
428
		// If metabox is requesting to be conditionally shown
429
		$show = $this->cmb->should_show();
430
431
		/**
432
		 * Filter to determine if metabox should show. Default is true
433
		 *
434
		 * @param array  $show          Default is true, show the metabox
435
		 * @param mixed  $meta_box_args Array of the metabox arguments
436
		 * @param mixed  $cmb           The CMB2 instance
437
		 */
438
		$show = (bool) apply_filters( 'cmb2_show_on', $show, $this->cmb->meta_box, $this->cmb );
439
440
		return $show;
441
	}
442
443
	/**
444
	 * Get the CMB priority property set to numeric hook priority.
445
	 * @since  2.2.0
446
	 * @param  integer $default Default display hook priority.
447
	 * @return integer          Hook priority.
448
	 */
449
	public function get_priority( $default = 10 ) {
450
		$priority = $this->cmb->prop( 'priority' );
451
452
		if ( ! is_numeric( $priority ) ) {
453
			switch ( $priority ) {
454
455
				case 'high':
456
					$priority = 5;
457
					break;
458
459
				case 'low':
460
					$priority = 20;
461
					break;
462
463
				default:
464
					$priority = $default;
465
					break;
466
			}
467
		}
468
469
		return $priority;
470
	}
471
472
	/**
473
	 * Save data from post metabox
474
	 * @since  1.0.0
475
	 * @param  int    $post_id Post ID
476
	 * @param  mixed  $post    Post object
477
	 * @return null
478
	 */
479
	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...
480
481
		$post_type = $post ? $post->post_type : get_post_type( $post_id );
482
483
		$do_not_pass_go = (
484
			! $this->can_save( $post_type )
485
			// check user editing permissions
486
			|| ( 'page' == $post_type && ! current_user_can( 'edit_page', $post_id ) )
487
			|| ! current_user_can( 'edit_post', $post_id )
488
		);
489
490
		if ( $do_not_pass_go ) {
491
			// do not collect $200
492
			return;
493
		}
494
495
		// take a trip to reading railroad – if you pass go collect $200
496
		$this->cmb->save_fields( $post_id, 'post', $_POST );
497
	}
498
499
	/**
500
	 * Save data from comment metabox
501
	 * @since  2.0.9
502
	 * @param  int    $comment_id Comment ID
503
	 * @return null
504
	 */
505
	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...
506
507
		$can_edit = current_user_can( 'moderate_comments', $comment_id );
508
509
		if ( $this->can_save( get_comment_type( $comment_id ) ) && $can_edit ) {
510
			$this->cmb->save_fields( $comment_id, 'comment', $_POST );
511
		}
512
	}
513
514
	/**
515
	 * Save data from user fields
516
	 * @since  1.0.x
517
	 * @param  int   $user_id  User ID
518
	 * @return null
519
	 */
520
	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...
521
		// check permissions
522
		if ( $this->can_save( 'user' ) ) {
523
			$this->cmb->save_fields( $user_id, 'user', $_POST );
524
		}
525
	}
526
527
	/**
528
	 * Save data from term fields
529
	 * @since  2.2.0
530
	 * @param  int    $term_id  Term ID
531
	 * @param  int    $tt_id    Term Taxonomy ID
532
	 * @param  string $taxonomy Taxonomy
533
	 * @return null
534
	 */
535
	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...
536
		$taxonomy = $taxonomy ? $taxonomy : $tt_id;
537
538
		// check permissions
539
		if ( $this->taxonomy_can_save( $taxonomy ) && $this->can_save( 'term' ) ) {
540
			$this->cmb->save_fields( $term_id, 'term', $_POST );
541
		}
542
	}
543
544
	/**
545
	 * Delete term meta when a term is deleted.
546
	 * @since  2.2.0
547
	 * @param  int    $term_id  Term ID
548
	 * @param  int    $tt_id    Term Taxonomy ID
549
	 * @param  string $taxonomy Taxonomy
550
	 * @return null
551
	 */
552
	public function delete_term( $term_id, $tt_id, $taxonomy = '' ) {
553
		if ( $this->taxonomy_can_save( $taxonomy ) ) {
554
555
			foreach ( $this->cmb->prop( 'fields' ) as $field ) {
556
				$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...
557
			}
558
559
			$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...
560
		}
561
	}
562
563
	/**
564
	 * Determines if the current object is able to be saved
565
	 * @since  2.0.9
566
	 * @param  string  $type Current post_type or comment_type
567
	 * @return bool          Whether object can be saved
568
	 */
569
	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...
570
		return (
571
			$this->cmb->prop( 'save_fields' )
572
			// check nonce
573
			&& isset( $_POST[ $this->cmb->nonce() ] )
574
			&& wp_verify_nonce( $_POST[ $this->cmb->nonce() ], $this->cmb->nonce() )
575
			// check if autosave
576
			&& ! ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
577
			// get the metabox types & compare it to this type
578
			&& ( $type && in_array( $type, $this->cmb->prop( 'object_types' ) ) )
579
		);
580
	}
581
582
	/**
583
	 * Determine if taxonomy of term being modified is cmb2-editable.
584
	 * @since  2.2.0
585
	 * @param  string $taxonomy Taxonomy of term being modified.
586
	 * @return bool             Whether taxonomy is editable.
587
	 */
588
	public function taxonomy_can_save( $taxonomy ) {
589
		if ( empty( $this->taxonomies ) || ! in_array( $taxonomy, $this->taxonomies ) ) {
590
			return false;
591
		}
592
593
		$taxonomy_object = get_taxonomy( $taxonomy );
594
		// Can the user edit this term?
595
		if ( ! isset( $taxonomy_object->cap ) || ! current_user_can( $taxonomy_object->cap->edit_terms ) ) {
596
			return false;
597
		}
598
599
		return true;
600
	}
601
602
	/**
603
	 * Ensures WordPress hook only gets fired once
604
	 * @since  2.0.0
605
	 * @param string   $action        The name of the filter to hook the $hook callback to.
606
	 * @param callback $hook          The callback to be run when the filter is applied.
607
	 * @param integer  $priority      Order the functions are executed
608
	 * @param int      $accepted_args The number of arguments the function accepts.
609
	 */
610
	public function once( $action, $hook, $priority = 10, $accepted_args = 1 ) {
611
		$key = md5( serialize( func_get_args() ) );
612
613
		if ( in_array( $key, self::$hooks_completed ) ) {
614
			return;
615
		}
616
617
		self::$hooks_completed[] = $key;
618
		add_filter( $action, $hook, $priority, $accepted_args );
619
	}
620
621
	/**
622
	 * Enqueues the 'cmb2-display-styles' if the conditions match (has columns, on the right page, etc).
623
	 * @since  2.2.2.1
624
	 */
625
	protected function maybe_enqueue_column_display_styles() {
626
		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...
627
		if (
628
			$pagenow
629
			&& $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...
630
			&& $this->cmb->prop( 'cmb_styles' )
631
			&& in_array( $pagenow, array( 'edit.php', 'users.php', 'edit-comments.php', 'edit-tags.php' ), 1 )
632
			) {
633
			self::enqueue_cmb_css( 'cmb2-display-styles' );
634
		}
635
	}
636
637
	/**
638
	 * Includes CMB2 styles
639
	 * @since  2.0.0
640
	 */
641
	public static function enqueue_cmb_css( $handle = 'cmb2-styles' ) {
642
		if ( ! apply_filters( 'cmb2_enqueue_css', true ) ) {
643
			return false;
644
		}
645
646
		self::register_styles();
647
648
		/*
649
		 * White list the options as this method can be used as a hook callback
650
		 * and have a different argument passed.
651
		 */
652
		return wp_enqueue_style( 'cmb2-display-styles' === $handle ? $handle : 'cmb2-styles' );
653
	}
654
655
	/**
656
	 * Includes CMB2 JS
657
	 * @since  2.0.0
658
	 */
659
	public static function enqueue_cmb_js() {
660
		if ( ! apply_filters( 'cmb2_enqueue_js', true ) ) {
661
			return false;
662
		}
663
664
		self::register_js();
665
		return true;
666
	}
667
668
}
669