WebDevStudios /
CMB2
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 1 | <?php |
||
| 2 | /** |
||
| 3 | * Handles hooking CMB2 objects/fields into the WordPres REST API |
||
| 4 | * which can allow fields to be read and/or updated. |
||
| 5 | * |
||
| 6 | * @since 2.2.4 |
||
| 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_REST extends CMB2_Hookup_Base { |
||
|
0 ignored issues
–
show
|
|||
| 15 | |||
| 16 | /** |
||
| 17 | * The current CMB2 REST endpoint version |
||
| 18 | * @var string |
||
| 19 | * @since 2.2.4 |
||
| 20 | */ |
||
| 21 | const VERSION = '1'; |
||
| 22 | |||
| 23 | /** |
||
| 24 | * The CMB2 REST base namespace (v should always be followed by $version) |
||
| 25 | * @var string |
||
| 26 | * @since 2.2.4 |
||
| 27 | */ |
||
| 28 | const NAME_SPACE = 'cmb2/v1'; |
||
| 29 | |||
| 30 | /** |
||
| 31 | * @var CMB2 object |
||
| 32 | * @since 2.2.4 |
||
| 33 | */ |
||
| 34 | public $cmb; |
||
| 35 | |||
| 36 | /** |
||
| 37 | * @var CMB2_REST[] objects |
||
| 38 | * @since 2.2.4 |
||
| 39 | */ |
||
| 40 | public static $boxes; |
||
| 41 | |||
| 42 | /** |
||
| 43 | * Array of readable field objects. |
||
| 44 | * @var CMB2_Field[] |
||
| 45 | * @since 2.2.4 |
||
| 46 | */ |
||
| 47 | protected $read_fields = array(); |
||
| 48 | |||
| 49 | /** |
||
| 50 | * Array of editable field objects. |
||
| 51 | * @var CMB2_Field[] |
||
| 52 | * @since 2.2.4 |
||
| 53 | */ |
||
| 54 | protected $edit_fields = array(); |
||
| 55 | |||
| 56 | /** |
||
| 57 | * whether CMB2 object is readable via the rest api. |
||
| 58 | * @var boolean |
||
| 59 | */ |
||
| 60 | protected $rest_read = false; |
||
| 61 | |||
| 62 | /** |
||
| 63 | * whether CMB2 object is editable via the rest api. |
||
| 64 | * @var boolean |
||
| 65 | */ |
||
| 66 | protected $rest_edit = false; |
||
| 67 | |||
| 68 | /** |
||
| 69 | * Constructor |
||
| 70 | * |
||
| 71 | * @since 2.2.4 |
||
| 72 | * |
||
| 73 | * @param CMB2 $cmb The CMB2 object to be registered for the API. |
||
| 74 | */ |
||
| 75 | public function __construct( CMB2 $cmb ) { |
||
| 76 | $this->cmb = $cmb; |
||
| 77 | self::$boxes[ $cmb->cmb_id ] = $this; |
||
| 78 | |||
| 79 | $show_value = $this->cmb->prop( 'show_in_rest' ); |
||
| 80 | |||
| 81 | $this->rest_read = self::is_readable( $show_value ); |
||
| 82 | $this->rest_edit = self::is_editable( $show_value ); |
||
| 83 | } |
||
| 84 | |||
| 85 | public function universal_hooks() { |
||
| 86 | // hook up the CMB rest endpoint classes |
||
| 87 | $this->once( 'rest_api_init', array( $this, 'init_routes' ), 0 ); |
||
| 88 | |||
| 89 | if ( function_exists( 'register_rest_field' ) ) { |
||
| 90 | $this->once( 'rest_api_init', array( __CLASS__, 'register_appended_fields' ), 50 ); |
||
| 91 | } |
||
| 92 | |||
| 93 | $this->declare_read_edit_fields(); |
||
| 94 | |||
| 95 | add_filter( 'is_protected_meta', array( $this, 'is_protected_meta' ), 10, 3 ); |
||
| 96 | } |
||
| 97 | |||
| 98 | public function init_routes() { |
||
| 99 | $wp_rest_server = rest_get_server(); |
||
| 100 | |||
| 101 | $boxes_controller = new CMB2_REST_Controller_Boxes( $wp_rest_server ); |
||
| 102 | $boxes_controller->register_routes(); |
||
| 103 | |||
| 104 | $fields_controller = new CMB2_REST_Controller_Fields( $wp_rest_server ); |
||
| 105 | $fields_controller->register_routes(); |
||
| 106 | } |
||
| 107 | |||
| 108 | public static function register_appended_fields() { |
||
| 109 | |||
| 110 | $types = array(); |
||
| 111 | foreach ( self::$boxes as $cmb_id => $cmb_rest ) { |
||
| 112 | $types = array_merge( $types, $cmb_rest->cmb->prop( 'object_types' ) ); |
||
| 113 | } |
||
| 114 | $types = array_unique( $types ); |
||
| 115 | |||
| 116 | register_rest_field( |
||
| 117 | $types, |
||
| 118 | 'cmb2', |
||
| 119 | array( |
||
| 120 | 'get_callback' => array( __CLASS__, 'get_restable_field_values' ), |
||
| 121 | 'update_callback' => array( __CLASS__, 'update_restable_field_values' ), |
||
| 122 | 'schema' => null, |
||
| 123 | ) |
||
| 124 | ); |
||
| 125 | } |
||
| 126 | |||
| 127 | protected function declare_read_edit_fields() { |
||
| 128 | foreach ( $this->cmb->prop( 'fields' ) as $field ) { |
||
| 129 | $show_in_rest = isset( $field['show_in_rest'] ) ? $field['show_in_rest'] : null; |
||
| 130 | |||
| 131 | if ( false === $show_in_rest ) { |
||
| 132 | continue; |
||
| 133 | } |
||
| 134 | |||
| 135 | if ( $this->can_read( $show_in_rest ) ) { |
||
| 136 | $this->read_fields[] = $field['id']; |
||
| 137 | } |
||
| 138 | |||
| 139 | if ( $this->can_edit( $show_in_rest ) ) { |
||
| 140 | $this->edit_fields[] = $field['id']; |
||
| 141 | } |
||
| 142 | |||
| 143 | } |
||
| 144 | } |
||
| 145 | |||
| 146 | protected function can_read( $show_in_rest ) { |
||
| 147 | // if 'null', then use default box value. |
||
| 148 | if ( null === $show_in_rest ) { |
||
| 149 | return $this->rest_read; |
||
| 150 | } |
||
| 151 | |||
| 152 | // Else check if the value represents readable. |
||
| 153 | return self::is_readable( $show_in_rest ); |
||
| 154 | } |
||
| 155 | |||
| 156 | protected function can_edit( $show_in_rest ) { |
||
| 157 | // if 'null', then use default box value. |
||
| 158 | if ( null === $show_in_rest ) { |
||
| 159 | return $this->rest_edit; |
||
| 160 | } |
||
| 161 | |||
| 162 | // Else check if the value represents editable. |
||
| 163 | return self::is_editable( $show_in_rest ); |
||
| 164 | } |
||
| 165 | |||
| 166 | /** |
||
| 167 | * Handler for getting custom field data. |
||
| 168 | * |
||
| 169 | * @since 2.2.4 |
||
| 170 | * |
||
| 171 | * @param array $data The data from the response |
||
| 172 | * @param string $field_name Name of field |
||
| 173 | * @param WP_REST_Request $request Current request |
||
| 174 | * |
||
| 175 | * @return mixed |
||
| 176 | */ |
||
| 177 | public static function get_restable_field_values( $data, $field_name, $request ) { |
||
|
0 ignored issues
–
show
|
|||
| 178 | $values = array(); |
||
| 179 | if ( ! isset( $data['id'] ) ) { |
||
| 180 | return; |
||
| 181 | } |
||
| 182 | |||
| 183 | foreach ( self::$boxes as $cmb_id => $rest_box ) { |
||
| 184 | foreach ( $rest_box->read_fields as $field_id ) { |
||
| 185 | $field = $rest_box->cmb->get_field( $field_id ); |
||
| 186 | $field->object_id( $data['id'] ); |
||
| 187 | |||
| 188 | // TODO: test other object types (users, comments, etc) |
||
| 189 | // if ( isset( $data['type'] ) ) { |
||
|
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
62% of this comment could be valid code. Did you maybe forget this after debugging?
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it. The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production. This check looks for comments that seem to be mostly valid code and reports them. Loading history...
|
|||
| 190 | // $field->object_type( $data['type'] ); |
||
|
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
70% of this comment could be valid code. Did you maybe forget this after debugging?
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it. The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production. This check looks for comments that seem to be mostly valid code and reports them. Loading history...
|
|||
| 191 | // } |
||
| 192 | |||
| 193 | $values[ $cmb_id ][ $field->id( true ) ] = $field->get_data(); |
||
| 194 | } |
||
| 195 | } |
||
| 196 | |||
| 197 | return $values; |
||
| 198 | } |
||
| 199 | |||
| 200 | /** |
||
| 201 | * Handler for updating custom field data. |
||
| 202 | * |
||
| 203 | * @since 2.2.4 |
||
| 204 | * |
||
| 205 | * @param mixed $value The value of the field |
||
|
0 ignored issues
–
show
There is no parameter named
$value. Did you maybe mean $values?
This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit. Consider the following example. The parameter /**
* @param array $germany
* @param array $ireland
*/
function finale($germany, $island) {
return "2:1";
}
The most likely cause is that the parameter was changed, but the annotation was not. Loading history...
|
|||
| 206 | * @param object $object The object from the response |
||
| 207 | * @param string $field_name Name of field |
||
| 208 | * |
||
| 209 | * @return bool|int |
||
| 210 | */ |
||
| 211 | public static function update_restable_field_values( $values, $object, $field_name ) { |
||
| 212 | if ( empty( $values ) || ! is_array( $values ) || 'cmb2' !== $field_name ) { |
||
| 213 | return; |
||
| 214 | } |
||
| 215 | |||
| 216 | $data = self::get_object_data( $object ); |
||
| 217 | if ( ! $data ) { |
||
| 218 | return; |
||
| 219 | } |
||
| 220 | |||
| 221 | $updated = array(); |
||
| 222 | |||
| 223 | foreach ( self::$boxes as $cmb_id => $rest_box ) { |
||
| 224 | if ( ! array_key_exists( $cmb_id, $values ) ) { |
||
| 225 | continue; |
||
| 226 | } |
||
| 227 | |||
| 228 | $rest_box->cmb->object_id( $data['object_id'] ); |
||
| 229 | $rest_box->cmb->object_type( $data['object_type'] ); |
||
| 230 | |||
| 231 | // TODO: Test since refactor. |
||
| 232 | $updated[ $cmb_id ] = $rest_box->sanitize_box_values( $values ); |
||
| 233 | } |
||
| 234 | |||
| 235 | return $updated; |
||
| 236 | } |
||
| 237 | |||
| 238 | /** |
||
| 239 | * Loop through box fields and sanitize the values. |
||
| 240 | * |
||
| 241 | * @since 2.2.o |
||
| 242 | * |
||
| 243 | * @param array $values Array of values being provided. |
||
| 244 | * @return array Array of updated/sanitized values. |
||
| 245 | */ |
||
| 246 | public function sanitize_box_values( array $values ) { |
||
| 247 | $updated = array(); |
||
| 248 | |||
| 249 | $this->cmb->pre_process(); |
||
| 250 | |||
| 251 | foreach ( $this->edit_fields as $field_id ) { |
||
| 252 | $updated[ $field_id ] = $this->sanitize_field_value( $values, $field_id ); |
||
|
0 ignored issues
–
show
$field_id is of type object<CMB2_Field>, but the function expects a string.
It seems like the type of the argument is not accepted by the function/method which you are calling. In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug. We suggest to add an explicit type cast like in the following example: function acceptsInteger($int) { }
$x = '123'; // string "123"
// Instead of
acceptsInteger($x);
// we recommend to use
acceptsInteger((integer) $x);
Loading history...
|
|||
| 253 | } |
||
| 254 | |||
| 255 | $this->cmb->after_save(); |
||
| 256 | |||
| 257 | return $updated; |
||
| 258 | } |
||
| 259 | |||
| 260 | /** |
||
| 261 | * Handles returning a sanitized field value. |
||
| 262 | * |
||
| 263 | * @since 2.2.4 |
||
| 264 | * |
||
| 265 | * @param array $values Array of values being provided. |
||
| 266 | * @param string $field_id The id of the field to update. |
||
| 267 | * |
||
| 268 | * @return mixed The results of saving/sanitizing a field value. |
||
| 269 | */ |
||
| 270 | protected function sanitize_field_value( array $values, $field_id ) { |
||
| 271 | if ( ! array_key_exists( $field_id, $values[ $this->cmb->cmb_id ] ) ) { |
||
| 272 | return; |
||
| 273 | } |
||
| 274 | |||
| 275 | $field = $this->cmb->get_field( $field_id ); |
||
| 276 | |||
| 277 | if ( 'title' == $field->type() ) { |
||
| 278 | return; |
||
| 279 | } |
||
| 280 | |||
| 281 | $field->object_id( $this->cmb->object_id() ); |
||
| 282 | $field->object_type( $this->cmb->object_type() ); |
||
| 283 | |||
| 284 | if ( 'group' == $field->type() ) { |
||
| 285 | return $this->sanitize_group_value( $values, $field ); |
||
|
0 ignored issues
–
show
It seems like
$field defined by $this->cmb->get_field($field_id) on line 275 can also be of type false; however, CMB2_REST::sanitize_group_value() does only seem to accept object<CMB2_Field>, did you maybe forget to handle an error condition?
This check looks for type mismatches where the missing type is Consider the follow example <?php
function getDate($date)
{
if ($date !== null) {
return new DateTime($date);
}
return false;
}
This function either returns a new Loading history...
|
|||
| 286 | } |
||
| 287 | |||
| 288 | return $field->save_field( $values[ $this->cmb->cmb_id ][ $field_id ] ); |
||
| 289 | } |
||
| 290 | |||
| 291 | /** |
||
| 292 | * Handles returning a sanitized group field value. |
||
| 293 | * |
||
| 294 | * @since 2.2.4 |
||
| 295 | * |
||
| 296 | * @param array $values Array of values being provided. |
||
| 297 | * @param CMB2_Field $field CMB2_Field object. |
||
| 298 | * |
||
| 299 | * @return mixed The results of saving/sanitizing the group field value. |
||
| 300 | */ |
||
| 301 | protected function sanitize_group_value( array $values, CMB2_Field $field ) { |
||
| 302 | $fields = $field->fields(); |
||
| 303 | if ( empty( $fields ) ) { |
||
| 304 | return; |
||
| 305 | } |
||
| 306 | |||
| 307 | $this->cmb->data_to_save[ $field->_id() ] = $values[ $this->cmb->cmb_id ][ $field->_id() ]; |
||
| 308 | |||
| 309 | return $this->cmb->save_group_field( $field ); |
||
|
0 ignored issues
–
show
$field is of type object<CMB2_Field>, but the function expects a array.
It seems like the type of the argument is not accepted by the function/method which you are calling. In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug. We suggest to add an explicit type cast like in the following example: function acceptsInteger($int) { }
$x = '123'; // string "123"
// Instead of
acceptsInteger($x);
// we recommend to use
acceptsInteger((integer) $x);
Loading history...
|
|||
| 310 | } |
||
| 311 | |||
| 312 | /** |
||
| 313 | * Filter whether a meta key is protected. |
||
| 314 | * |
||
| 315 | * @since 2.2.4 |
||
| 316 | * |
||
| 317 | * @param bool $protected Whether the key is protected. Default false. |
||
| 318 | * @param string $meta_key Meta key. |
||
| 319 | * @param string $meta_type Meta type. |
||
| 320 | */ |
||
| 321 | public function is_protected_meta( $protected, $meta_key, $meta_type ) { |
||
|
0 ignored issues
–
show
|
|||
| 322 | if ( $this->field_can_edit( $meta_key ) ) { |
||
| 323 | return false; |
||
| 324 | } |
||
| 325 | |||
| 326 | return $protected; |
||
| 327 | } |
||
| 328 | |||
| 329 | protected static function get_object_data( $object ) { |
||
| 330 | $object_id = 0; |
||
| 331 | if ( isset( $object->ID ) ) { |
||
| 332 | $object_id = intval( $object->ID ); |
||
| 333 | $object_type = isset( $object->user_login ) ? 'user' : 'post'; |
||
| 334 | } elseif ( isset( $object->comment_ID ) ) { |
||
| 335 | $object_id = intval( $object->comment_ID ); |
||
| 336 | $object_type = 'comment'; |
||
| 337 | } elseif ( is_array( $object ) && isset( $object['term_id'] ) ) { |
||
| 338 | $object_id = intval( $object['term_id'] ); |
||
| 339 | $object_type = 'term'; |
||
| 340 | } elseif ( isset( $object->term_id ) ) { |
||
| 341 | $object_id = intval( $object->term_id ); |
||
| 342 | $object_type = 'term'; |
||
| 343 | } |
||
| 344 | |||
| 345 | if ( empty( $object_id ) ) { |
||
| 346 | return false; |
||
| 347 | } |
||
| 348 | |||
| 349 | return compact( 'object_id', 'object_type' ); |
||
| 350 | } |
||
| 351 | |||
| 352 | public function field_can_read( $field_id, $return_object = false ) { |
||
| 353 | return $this->field_can( 'read_fields', $field_id, $return_object ); |
||
| 354 | } |
||
| 355 | |||
| 356 | public function field_can_edit( $field_id, $return_object = false ) { |
||
| 357 | return $this->field_can( 'edit_fields', $field_id, $return_object ); |
||
| 358 | } |
||
| 359 | |||
| 360 | protected function field_can( $type = 'read_fields', $field_id, $return_object = false ) { |
||
| 361 | if ( ! in_array( $field_id instanceof CMB2_Field ? $field_id->id() : $field_id, $this->{$type}, true ) ) { |
||
| 362 | return false; |
||
| 363 | } |
||
| 364 | |||
| 365 | return $return_object ? $this->cmb->get_field( $field_id ) : true; |
||
| 366 | } |
||
| 367 | |||
| 368 | /** |
||
| 369 | * Get an instance of this class by a CMB2 id |
||
| 370 | * |
||
| 371 | * @since 2.2.4 |
||
| 372 | * |
||
| 373 | * @param string $cmb_id CMB2 config id |
||
| 374 | * |
||
| 375 | * @return CMB2_REST|false The CMB2_REST object or false. |
||
| 376 | */ |
||
| 377 | public static function get_rest_box( $cmb_id ) { |
||
| 378 | return isset( self::$boxes[ $cmb_id ] ) ? self::$boxes[ $cmb_id ] : false; |
||
| 379 | } |
||
| 380 | |||
| 381 | /** |
||
| 382 | * Checks if given value is readable. |
||
| 383 | * |
||
| 384 | * Value is considered readable if it is not empty and if it does not match the editable blacklist. |
||
| 385 | * |
||
| 386 | * @since 2.2.4 |
||
| 387 | * |
||
| 388 | * @param mixed $value Value to check. |
||
| 389 | * |
||
| 390 | * @return boolean Whether value is considered readable. |
||
| 391 | */ |
||
| 392 | public static function is_readable( $value ) { |
||
| 393 | return ! empty( $value ) && ! in_array( $value, array( |
||
| 394 | WP_REST_Server::CREATABLE, |
||
| 395 | WP_REST_Server::EDITABLE, |
||
| 396 | WP_REST_Server::DELETABLE, |
||
| 397 | ), true ); |
||
| 398 | } |
||
| 399 | |||
| 400 | /** |
||
| 401 | * Checks if given value is editable. |
||
| 402 | * |
||
| 403 | * Value is considered editable if matches the editable whitelist. |
||
| 404 | * |
||
| 405 | * @since 2.2.4 |
||
| 406 | * |
||
| 407 | * @param mixed $value Value to check. |
||
| 408 | * |
||
| 409 | * @return boolean Whether value is considered editable. |
||
| 410 | */ |
||
| 411 | public static function is_editable( $value ) { |
||
| 412 | return in_array( $value, array( |
||
| 413 | WP_REST_Server::EDITABLE, |
||
| 414 | WP_REST_Server::ALLMETHODS, |
||
| 415 | ), true ); |
||
| 416 | } |
||
| 417 | |||
| 418 | /** |
||
| 419 | * Magic getter for our object. |
||
| 420 | * |
||
| 421 | * @param string $field |
||
| 422 | * @throws Exception Throws an exception if the field is invalid. |
||
| 423 | * |
||
| 424 | * @return mixed |
||
| 425 | */ |
||
| 426 | public function __get( $field ) { |
||
| 427 | switch ( $field ) { |
||
| 428 | case 'read_fields': |
||
| 429 | case 'edit_fields': |
||
| 430 | case 'rest_read': |
||
| 431 | case 'rest_edit': |
||
| 432 | return $this->{$field}; |
||
| 433 | default: |
||
| 434 | throw new Exception( 'Invalid ' . __CLASS__ . ' property: ' . $field ); |
||
| 435 | } |
||
| 436 | } |
||
| 437 | |||
| 438 | } |
||
| 439 |
You can fix this by adding a namespace to your class:
When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.