| 240 | | * Validate an object and its properties. |
| 241 | | * |
| 242 | | * @param object $object The value to validate as an object. |
| 243 | | * @param string $prop The name of the property, used in error reporting. |
| 244 | | * @param array $schema The schema for the property, used for validation. |
| 245 | | * |
| 246 | | * @return bool |
| 247 | | */ |
| 248 | | protected function validate_object( $object, $prop, $schema ) { |
| 249 | | if ( ! is_object( $object ) ) { |
| 250 | | $this->messages->add( |
| 251 | | 'error', |
| 252 | | sprintf( |
| 253 | | __( 'The %s property must contain an object value.', 'wporg-plugins' ), |
| 254 | | '<code>' . $prop . '</code>' |
| 255 | | ) |
| 256 | | ); |
| 257 | | $this->append_error_data( $prop, 'error' ); |
| 258 | | |
| 259 | | return false; |
| 260 | | } |
| 261 | | |
| 262 | | $results = array(); |
| 263 | | |
| 264 | | if ( isset( $schema['required'] ) ) { |
| 265 | | foreach ( $schema['required'] as $required_prop ) { |
| 266 | | if ( ! property_exists( $object, $required_prop ) ) { |
| 267 | | $this->messages->add( |
| 268 | | 'error', |
| 269 | | sprintf( |
| 270 | | __( 'The %1$s property is required in the %2$s object.', 'wporg-plugins' ), |
| 271 | | '<code>' . $required_prop . '</code>', |
| 272 | | '<code>' . $prop . '</code>' |
| 273 | | ) |
| 274 | | ); |
| 275 | | $this->append_error_data( "$prop:$required_prop", 'error' ); |
| 276 | | $results[] = false; |
| 277 | | } |
| 278 | | } |
| 279 | | } |
| 280 | | |
| 281 | | if ( isset( $schema['properties'] ) ) { |
| 282 | | foreach ( $schema['properties'] as $subprop => $subschema ) { |
| 283 | | if ( ! isset( $object->$subprop ) ) { |
| 284 | | continue; |
| 285 | | } |
| 286 | | |
| 287 | | if ( isset( $subschema['type'] ) ) { |
| 288 | | $results[] = $this->route_validation_for_type( |
| 289 | | $subschema['type'], |
| 290 | | $object->$subprop, |
| 291 | | "$prop:$subprop", |
| 292 | | $subschema |
| 293 | | ); |
| 294 | | } |
| 295 | | } |
| 296 | | } |
| 297 | | |
| 298 | | if ( isset( $schema['additionalProperties'] ) ) { |
| 299 | | if ( false === $schema['additionalProperties'] ) { |
| 300 | | foreach ( array_keys( get_object_vars( $object ) ) as $key ) { |
| 301 | | if ( ! isset( $schema['properties'][ $key ] ) ) { |
| 302 | | $this->messages->add( |
| 303 | | 'warning', |
| 304 | | sprintf( |
| 305 | | __( '%1$s is not a valid property in the %2$s object.', 'wporg-plugins' ), |
| 306 | | '<code>' . $key . '</code>', |
| 307 | | '<code>' . $prop . '</code>' |
| 308 | | ) |
| 309 | | ); |
| 310 | | $this->append_error_data( "$prop:$key", 'warning' ); |
| 311 | | $results[] = false; |
| 312 | | continue; |
| 313 | | } |
| 314 | | } |
| 315 | | } elseif ( isset( $schema['additionalProperties']['type'] ) ) { |
| 316 | | foreach ( $object as $subprop => $subvalue ) { |
| 317 | | $results[] = $this->route_validation_for_type( |
| 318 | | $schema['additionalProperties']['type'], |
| 319 | | $subvalue, |
| 320 | | "$prop:$subprop", |
| 321 | | $schema['additionalProperties'] |
| 322 | | ); |
| 323 | | } |
| 324 | | } |
| 325 | | } |
| 326 | | |
| 327 | | return ! in_array( false, $results, true ); |
| 328 | | } |
| 329 | | |
| 330 | | /** |
| 331 | | * Validate an array and its items. |
| 332 | | * |
| 333 | | * @param array $array The value to validate as an array. |
| 334 | | * @param string $prop The name of the property, used in error reporting. |
| 335 | | * @param array $schema The schema for the property, used for validation. |
| 336 | | * |
| 337 | | * @return bool |
| 338 | | */ |
| 339 | | protected function validate_array( $array, $prop, $schema ) { |
| 340 | | if ( ! is_array( $array ) ) { |
| 341 | | $this->messages->add( |
| 342 | | 'error', |
| 343 | | sprintf( |
| 344 | | __( 'The %s property must contain an array value.', 'wporg-plugins' ), |
| 345 | | '<code>' . $prop . '</code>' |
| 346 | | ) |
| 347 | | ); |
| 348 | | $this->append_error_data( $prop, 'error' ); |
| 349 | | |
| 350 | | return false; |
| 351 | | } |
| 352 | | |
| 353 | | if ( isset( $schema['items']['type'] ) ) { |
| 354 | | $results = array(); |
| 355 | | $index = 0; |
| 356 | | |
| 357 | | foreach ( $array as $item ) { |
| 358 | | $results[] = $this->route_validation_for_type( |
| 359 | | $schema['items']['type'], |
| 360 | | $item, |
| 361 | | $prop . "[$index]", |
| 362 | | $schema['items'] |
| 363 | | ); |
| 364 | | $index ++; |
| 365 | | } |
| 366 | | |
| 367 | | return ! in_array( false, $results, true ); |
| 368 | | } |
| 369 | | |
| 370 | | return true; |
| 371 | | } |
| 372 | | |
| 373 | | /** |
| 374 | | * Validate a string. |
| 375 | | * |
| 376 | | * @param string $string The value to validate as a string. |
| 377 | | * @param string $prop The name of the property, used in error reporting. |
| 378 | | * @param array $schema The schema for the property, used for validation. |
| 379 | | * |
| 380 | | * @return bool |
| 381 | | */ |
| 382 | | protected function validate_string( $string, $prop, $schema ) { |
| 383 | | if ( ! is_string( $string ) ) { |
| 384 | | $this->messages->add( |
| 385 | | 'error', |
| 386 | | sprintf( |
| 387 | | __( 'The %s property must contain a string value.', 'wporg-plugins' ), |
| 388 | | '<code>' . $prop . '</code>' |
| 389 | | ) |
| 390 | | ); |
| 391 | | $this->append_error_data( $prop, 'error' ); |
| 392 | | |
| 393 | | return false; |
| 394 | | } |
| 395 | | |
| 396 | | if ( isset( $schema['enum'] ) ) { |
| 397 | | if ( ! in_array( $string, $schema['enum'], true ) ) { |
| 398 | | $this->messages->add( |
| 399 | | 'warning', |
| 400 | | sprintf( |
| 401 | | __( '"%1$s" is not a valid value for the %2$s property.', 'wporg-plugins' ), |
| 402 | | esc_html( $string ), |
| 403 | | '<code>' . $prop . '</code>' |
| 404 | | ) |
| 405 | | ); |
| 406 | | $this->append_error_data( $prop, 'warning' ); |
| 407 | | } |
| 408 | | } |
| 409 | | |
| 410 | | if ( isset( $schema['pattern'] ) ) { |
| 411 | | if ( ! preg_match( '#' . $schema['pattern'] . '#', $string ) ) { |
| 412 | | $pattern_description = $this->get_human_readable_pattern_description( $schema['pattern'] ); |
| 413 | | if ( $pattern_description ) { |
| 414 | | $message = sprintf( |
| 415 | | $pattern_description, |
| 416 | | '<code>' . $prop . '</code>' |
| 417 | | ); |
| 418 | | } else { |
| 419 | | $message = sprintf( |
| 420 | | __( 'The value of %s does not match the required pattern.', 'wporg-plugins' ), |
| 421 | | '<code>' . $prop . '</code>' |
| 422 | | ); |
| 423 | | } |
| 424 | | |
| 425 | | $this->messages->add( 'warning', $message ); |
| 426 | | $this->append_error_data( $prop, 'warning' ); |
| 427 | | } |
| 428 | | } |
| 429 | | |
| 430 | | return true; |
| 431 | | } |
| 432 | | |
| 433 | | /** |
| 434 | | * Validate a boolean. |
| 435 | | * |
| 436 | | * @param bool $boolean The value to validate as a boolean. |
| 437 | | * @param string $prop The name of the property, used in error reporting. |
| 438 | | * |
| 439 | | * @return bool |
| 440 | | */ |
| 441 | | protected function validate_boolean( $boolean, $prop ) { |
| 442 | | if ( ! is_bool( $boolean ) ) { |
| 443 | | $this->messages->add( |
| 444 | | 'error', |
| 445 | | sprintf( |
| 446 | | __( 'The %s property must contain a boolean value.', 'wporg-plugins' ), |
| 447 | | '<code>' . $prop . '</code>' |
| 448 | | ) |
| 449 | | ); |
| 450 | | $this->append_error_data( $prop, 'error' ); |
| 451 | | |
| 452 | | return false; |
| 453 | | } |
| 454 | | |
| 455 | | return true; |
| 456 | | } |
| 457 | | |
| 458 | | /** |
| 459 | | * Send a property value to the correct validator depending on which type(s) it can be. |
| 460 | | * |
| 461 | | * @param string|array $valid_types |
| 462 | | * @param mixed $value |
| 463 | | * @param string $prop |
| 464 | | * @param array $schema |
| 465 | | * |
| 466 | | * @return bool |
| 467 | | */ |
| 468 | | protected function route_validation_for_type( $valid_types, $value, $prop, $schema ) { |
| 469 | | // There is a single valid type. |
| 470 | | if ( is_string( $valid_types ) ) { |
| 471 | | $method = "validate_$valid_types"; |
| 472 | | return $this->$method( $value, $prop, $schema ); |
| 473 | | } |
| 474 | | |
| 475 | | // There are multiple valid types in an array. |
| 476 | | foreach ( $valid_types as $type ) { |
| 477 | | switch ( $type ) { |
| 478 | | case 'boolean': |
| 479 | | $check = 'is_bool'; |
| 480 | | break; |
| 481 | | default: |
| 482 | | $check = "is_$type"; |
| 483 | | break; |
| 484 | | } |
| 485 | | |
| 486 | | if ( $check( $value ) ) { |
| 487 | | $method = "validate_$type"; |
| 488 | | return $this->$method( $value, $prop, $schema ); |
| 489 | | } |
| 490 | | } |
| 491 | | |
| 492 | | // Made it this far, it's none of the valid types. |
| 493 | | $this->messages->add( |
| 494 | | 'error', |
| 495 | | sprintf( |
| 496 | | __( 'The %1$s property must contain a value that is one of these types: %2$s', 'wporg-plugins' ), |
| 497 | | '<code>' . $prop . '</code>', |
| 498 | | // translators: used between list items, there is a space after the comma. |
| 499 | | '<code>' . implode( '</code>' . __( ', ', 'wporg-plugins' ) . '<code>', $valid_types ) . '</code>' |
| 500 | | ) |
| 501 | | ); |
| 502 | | $this->append_error_data( $prop, 'error' ); |
| 503 | | |
| 504 | | return false; |
| 505 | | } |
| 506 | | |
| 507 | | /** |