Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.
 
 
 
 
 

1389 rader
43 KiB

  1. <?php
  2. /**
  3. * REST API: WP_REST_Users_Controller class
  4. *
  5. * @package WordPress
  6. * @subpackage REST_API
  7. * @since 4.7.0
  8. */
  9. /**
  10. * Core class used to manage users via the REST API.
  11. *
  12. * @since 4.7.0
  13. *
  14. * @see WP_REST_Controller
  15. */
  16. class WP_REST_Users_Controller extends WP_REST_Controller {
  17. /**
  18. * Instance of a user meta fields object.
  19. *
  20. * @since 4.7.0
  21. * @access protected
  22. * @var WP_REST_User_Meta_Fields
  23. */
  24. protected $meta;
  25. /**
  26. * Constructor.
  27. *
  28. * @since 4.7.0
  29. * @access public
  30. */
  31. public function __construct() {
  32. $this->namespace = 'wp/v2';
  33. $this->rest_base = 'users';
  34. $this->meta = new WP_REST_User_Meta_Fields();
  35. }
  36. /**
  37. * Registers the routes for the objects of the controller.
  38. *
  39. * @since 4.7.0
  40. * @access public
  41. *
  42. * @see register_rest_route()
  43. */
  44. public function register_routes() {
  45. register_rest_route( $this->namespace, '/' . $this->rest_base, array(
  46. array(
  47. 'methods' => WP_REST_Server::READABLE,
  48. 'callback' => array( $this, 'get_items' ),
  49. 'permission_callback' => array( $this, 'get_items_permissions_check' ),
  50. 'args' => $this->get_collection_params(),
  51. ),
  52. array(
  53. 'methods' => WP_REST_Server::CREATABLE,
  54. 'callback' => array( $this, 'create_item' ),
  55. 'permission_callback' => array( $this, 'create_item_permissions_check' ),
  56. 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
  57. ),
  58. 'schema' => array( $this, 'get_public_item_schema' ),
  59. ) );
  60. register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
  61. 'args' => array(
  62. 'id' => array(
  63. 'description' => __( 'Unique identifier for the user.' ),
  64. 'type' => 'integer',
  65. ),
  66. ),
  67. array(
  68. 'methods' => WP_REST_Server::READABLE,
  69. 'callback' => array( $this, 'get_item' ),
  70. 'permission_callback' => array( $this, 'get_item_permissions_check' ),
  71. 'args' => array(
  72. 'context' => $this->get_context_param( array( 'default' => 'view' ) ),
  73. ),
  74. ),
  75. array(
  76. 'methods' => WP_REST_Server::EDITABLE,
  77. 'callback' => array( $this, 'update_item' ),
  78. 'permission_callback' => array( $this, 'update_item_permissions_check' ),
  79. 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
  80. ),
  81. array(
  82. 'methods' => WP_REST_Server::DELETABLE,
  83. 'callback' => array( $this, 'delete_item' ),
  84. 'permission_callback' => array( $this, 'delete_item_permissions_check' ),
  85. 'args' => array(
  86. 'force' => array(
  87. 'type' => 'boolean',
  88. 'default' => false,
  89. 'description' => __( 'Required to be true, as users do not support trashing.' ),
  90. ),
  91. 'reassign' => array(
  92. 'type' => 'integer',
  93. 'description' => __( 'Reassign the deleted user\'s posts and links to this user ID.' ),
  94. 'required' => true,
  95. 'sanitize_callback' => array( $this, 'check_reassign' ),
  96. ),
  97. ),
  98. ),
  99. 'schema' => array( $this, 'get_public_item_schema' ),
  100. ) );
  101. register_rest_route( $this->namespace, '/' . $this->rest_base . '/me', array(
  102. array(
  103. 'methods' => WP_REST_Server::READABLE,
  104. 'callback' => array( $this, 'get_current_item' ),
  105. 'args' => array(
  106. 'context' => $this->get_context_param( array( 'default' => 'view' ) ),
  107. ),
  108. ),
  109. array(
  110. 'methods' => WP_REST_Server::EDITABLE,
  111. 'callback' => array( $this, 'update_current_item' ),
  112. 'permission_callback' => array( $this, 'update_current_item_permissions_check' ),
  113. 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
  114. ),
  115. array(
  116. 'methods' => WP_REST_Server::DELETABLE,
  117. 'callback' => array( $this, 'delete_current_item' ),
  118. 'permission_callback' => array( $this, 'delete_current_item_permissions_check' ),
  119. 'args' => array(
  120. 'force' => array(
  121. 'type' => 'boolean',
  122. 'default' => false,
  123. 'description' => __( 'Required to be true, as users do not support trashing.' ),
  124. ),
  125. 'reassign' => array(
  126. 'type' => 'integer',
  127. 'description' => __( 'Reassign the deleted user\'s posts and links to this user ID.' ),
  128. 'required' => true,
  129. 'sanitize_callback' => array( $this, 'check_reassign' ),
  130. ),
  131. ),
  132. ),
  133. 'schema' => array( $this, 'get_public_item_schema' ),
  134. ));
  135. }
  136. /**
  137. * Checks for a valid value for the reassign parameter when deleting users.
  138. *
  139. * The value can be an integer, 'false', false, or ''.
  140. *
  141. * @since 4.7.0
  142. *
  143. * @param int|bool $value The value passed to the reassign parameter.
  144. * @param WP_REST_Request $request Full details about the request.
  145. * @param string $param The parameter that is being sanitized.
  146. *
  147. * @return int|bool|WP_Error
  148. */
  149. public function check_reassign( $value, $request, $param ) {
  150. if ( is_numeric( $value ) ) {
  151. return $value;
  152. }
  153. if ( empty( $value ) || false === $value || 'false' === $value ) {
  154. return false;
  155. }
  156. return new WP_Error( 'rest_invalid_param', __( 'Invalid user parameter(s).' ), array( 'status' => 400 ) );
  157. }
  158. /**
  159. * Permissions check for getting all users.
  160. *
  161. * @since 4.7.0
  162. * @access public
  163. *
  164. * @param WP_REST_Request $request Full details about the request.
  165. * @return true|WP_Error True if the request has read access, otherwise WP_Error object.
  166. */
  167. public function get_items_permissions_check( $request ) {
  168. // Check if roles is specified in GET request and if user can list users.
  169. if ( ! empty( $request['roles'] ) && ! current_user_can( 'list_users' ) ) {
  170. return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you are not allowed to filter users by role.' ), array( 'status' => rest_authorization_required_code() ) );
  171. }
  172. if ( 'edit' === $request['context'] && ! current_user_can( 'list_users' ) ) {
  173. return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to list users.' ), array( 'status' => rest_authorization_required_code() ) );
  174. }
  175. if ( in_array( $request['orderby'], array( 'email', 'registered_date' ), true ) && ! current_user_can( 'list_users' ) ) {
  176. return new WP_Error( 'rest_forbidden_orderby', __( 'Sorry, you are not allowed to order users by this parameter.' ), array( 'status' => rest_authorization_required_code() ) );
  177. }
  178. return true;
  179. }
  180. /**
  181. * Retrieves all users.
  182. *
  183. * @since 4.7.0
  184. * @access public
  185. *
  186. * @param WP_REST_Request $request Full details about the request.
  187. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
  188. */
  189. public function get_items( $request ) {
  190. // Retrieve the list of registered collection query parameters.
  191. $registered = $this->get_collection_params();
  192. /*
  193. * This array defines mappings between public API query parameters whose
  194. * values are accepted as-passed, and their internal WP_Query parameter
  195. * name equivalents (some are the same). Only values which are also
  196. * present in $registered will be set.
  197. */
  198. $parameter_mappings = array(
  199. 'exclude' => 'exclude',
  200. 'include' => 'include',
  201. 'order' => 'order',
  202. 'per_page' => 'number',
  203. 'search' => 'search',
  204. 'roles' => 'role__in',
  205. );
  206. $prepared_args = array();
  207. /*
  208. * For each known parameter which is both registered and present in the request,
  209. * set the parameter's value on the query $prepared_args.
  210. */
  211. foreach ( $parameter_mappings as $api_param => $wp_param ) {
  212. if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) {
  213. $prepared_args[ $wp_param ] = $request[ $api_param ];
  214. }
  215. }
  216. if ( isset( $registered['offset'] ) && ! empty( $request['offset'] ) ) {
  217. $prepared_args['offset'] = $request['offset'];
  218. } else {
  219. $prepared_args['offset'] = ( $request['page'] - 1 ) * $prepared_args['number'];
  220. }
  221. if ( isset( $registered['orderby'] ) ) {
  222. $orderby_possibles = array(
  223. 'id' => 'ID',
  224. 'include' => 'include',
  225. 'name' => 'display_name',
  226. 'registered_date' => 'registered',
  227. 'slug' => 'user_nicename',
  228. 'email' => 'user_email',
  229. 'url' => 'user_url',
  230. );
  231. $prepared_args['orderby'] = $orderby_possibles[ $request['orderby'] ];
  232. }
  233. if ( ! current_user_can( 'list_users' ) ) {
  234. $prepared_args['has_published_posts'] = get_post_types( array( 'show_in_rest' => true ), 'names' );
  235. }
  236. if ( ! empty( $prepared_args['search'] ) ) {
  237. $prepared_args['search'] = '*' . $prepared_args['search'] . '*';
  238. }
  239. if ( isset( $registered['slug'] ) && ! empty( $request['slug'] ) ) {
  240. $prepared_args['search'] = $request['slug'];
  241. $prepared_args['search_columns'] = array( 'user_nicename' );
  242. }
  243. /**
  244. * Filters WP_User_Query arguments when querying users via the REST API.
  245. *
  246. * @link https://developer.wordpress.org/reference/classes/wp_user_query/
  247. *
  248. * @since 4.7.0
  249. *
  250. * @param array $prepared_args Array of arguments for WP_User_Query.
  251. * @param WP_REST_Request $request The current request.
  252. */
  253. $prepared_args = apply_filters( 'rest_user_query', $prepared_args, $request );
  254. $query = new WP_User_Query( $prepared_args );
  255. $users = array();
  256. foreach ( $query->results as $user ) {
  257. $data = $this->prepare_item_for_response( $user, $request );
  258. $users[] = $this->prepare_response_for_collection( $data );
  259. }
  260. $response = rest_ensure_response( $users );
  261. // Store pagination values for headers then unset for count query.
  262. $per_page = (int) $prepared_args['number'];
  263. $page = ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 );
  264. $prepared_args['fields'] = 'ID';
  265. $total_users = $query->get_total();
  266. if ( $total_users < 1 ) {
  267. // Out-of-bounds, run the query again without LIMIT for total count.
  268. unset( $prepared_args['number'], $prepared_args['offset'] );
  269. $count_query = new WP_User_Query( $prepared_args );
  270. $total_users = $count_query->get_total();
  271. }
  272. $response->header( 'X-WP-Total', (int) $total_users );
  273. $max_pages = ceil( $total_users / $per_page );
  274. $response->header( 'X-WP-TotalPages', (int) $max_pages );
  275. $base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ) );
  276. if ( $page > 1 ) {
  277. $prev_page = $page - 1;
  278. if ( $prev_page > $max_pages ) {
  279. $prev_page = $max_pages;
  280. }
  281. $prev_link = add_query_arg( 'page', $prev_page, $base );
  282. $response->link_header( 'prev', $prev_link );
  283. }
  284. if ( $max_pages > $page ) {
  285. $next_page = $page + 1;
  286. $next_link = add_query_arg( 'page', $next_page, $base );
  287. $response->link_header( 'next', $next_link );
  288. }
  289. return $response;
  290. }
  291. /**
  292. * Get the user, if the ID is valid.
  293. *
  294. * @since 4.7.2
  295. *
  296. * @param int $id Supplied ID.
  297. * @return WP_User|WP_Error True if ID is valid, WP_Error otherwise.
  298. */
  299. protected function get_user( $id ) {
  300. $error = new WP_Error( 'rest_user_invalid_id', __( 'Invalid user ID.' ), array( 'status' => 404 ) );
  301. if ( (int) $id <= 0 ) {
  302. return $error;
  303. }
  304. $user = get_userdata( (int) $id );
  305. if ( empty( $user ) || ! $user->exists() ) {
  306. return $error;
  307. }
  308. if ( is_multisite() && ! is_user_member_of_blog( $user->ID ) ) {
  309. return $error;
  310. }
  311. return $user;
  312. }
  313. /**
  314. * Checks if a given request has access to read a user.
  315. *
  316. * @since 4.7.0
  317. * @access public
  318. *
  319. * @param WP_REST_Request $request Full details about the request.
  320. * @return true|WP_Error True if the request has read access for the item, otherwise WP_Error object.
  321. */
  322. public function get_item_permissions_check( $request ) {
  323. $user = $this->get_user( $request['id'] );
  324. if ( is_wp_error( $user ) ) {
  325. return $user;
  326. }
  327. $types = get_post_types( array( 'show_in_rest' => true ), 'names' );
  328. if ( get_current_user_id() === $user->ID ) {
  329. return true;
  330. }
  331. if ( 'edit' === $request['context'] && ! current_user_can( 'list_users' ) ) {
  332. return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you are not allowed to list users.' ), array( 'status' => rest_authorization_required_code() ) );
  333. } elseif ( ! count_user_posts( $user->ID, $types ) && ! current_user_can( 'edit_user', $user->ID ) && ! current_user_can( 'list_users' ) ) {
  334. return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you are not allowed to list users.' ), array( 'status' => rest_authorization_required_code() ) );
  335. }
  336. return true;
  337. }
  338. /**
  339. * Retrieves a single user.
  340. *
  341. * @since 4.7.0
  342. * @access public
  343. *
  344. * @param WP_REST_Request $request Full details about the request.
  345. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
  346. */
  347. public function get_item( $request ) {
  348. $user = $this->get_user( $request['id'] );
  349. if ( is_wp_error( $user ) ) {
  350. return $user;
  351. }
  352. $user = $this->prepare_item_for_response( $user, $request );
  353. $response = rest_ensure_response( $user );
  354. return $response;
  355. }
  356. /**
  357. * Retrieves the current user.
  358. *
  359. * @since 4.7.0
  360. * @access public
  361. *
  362. * @param WP_REST_Request $request Full details about the request.
  363. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
  364. */
  365. public function get_current_item( $request ) {
  366. $current_user_id = get_current_user_id();
  367. if ( empty( $current_user_id ) ) {
  368. return new WP_Error( 'rest_not_logged_in', __( 'You are not currently logged in.' ), array( 'status' => 401 ) );
  369. }
  370. $user = wp_get_current_user();
  371. $response = $this->prepare_item_for_response( $user, $request );
  372. $response = rest_ensure_response( $response );
  373. return $response;
  374. }
  375. /**
  376. * Checks if a given request has access create users.
  377. *
  378. * @since 4.7.0
  379. * @access public
  380. *
  381. * @param WP_REST_Request $request Full details about the request.
  382. * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise.
  383. */
  384. public function create_item_permissions_check( $request ) {
  385. if ( ! current_user_can( 'create_users' ) ) {
  386. return new WP_Error( 'rest_cannot_create_user', __( 'Sorry, you are not allowed to create new users.' ), array( 'status' => rest_authorization_required_code() ) );
  387. }
  388. return true;
  389. }
  390. /**
  391. * Creates a single user.
  392. *
  393. * @since 4.7.0
  394. * @access public
  395. *
  396. * @param WP_REST_Request $request Full details about the request.
  397. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
  398. */
  399. public function create_item( $request ) {
  400. if ( ! empty( $request['id'] ) ) {
  401. return new WP_Error( 'rest_user_exists', __( 'Cannot create existing user.' ), array( 'status' => 400 ) );
  402. }
  403. $schema = $this->get_item_schema();
  404. if ( ! empty( $request['roles'] ) && ! empty( $schema['properties']['roles'] ) ) {
  405. $check_permission = $this->check_role_update( $request['id'], $request['roles'] );
  406. if ( is_wp_error( $check_permission ) ) {
  407. return $check_permission;
  408. }
  409. }
  410. $user = $this->prepare_item_for_database( $request );
  411. if ( is_multisite() ) {
  412. $ret = wpmu_validate_user_signup( $user->user_login, $user->user_email );
  413. if ( is_wp_error( $ret['errors'] ) && ! empty( $ret['errors']->errors ) ) {
  414. $error = new WP_Error( 'rest_invalid_param', __( 'Invalid user parameter(s).' ), array( 'status' => 400 ) );
  415. foreach ( $ret['errors']->errors as $code => $messages ) {
  416. foreach ( $messages as $message ) {
  417. $error->add( $code, $message );
  418. }
  419. if ( $error_data = $error->get_error_data( $code ) ) {
  420. $error->add_data( $error_data, $code );
  421. }
  422. }
  423. return $error;
  424. }
  425. }
  426. if ( is_multisite() ) {
  427. $user_id = wpmu_create_user( $user->user_login, $user->user_pass, $user->user_email );
  428. if ( ! $user_id ) {
  429. return new WP_Error( 'rest_user_create', __( 'Error creating new user.' ), array( 'status' => 500 ) );
  430. }
  431. $user->ID = $user_id;
  432. $user_id = wp_update_user( wp_slash( (array) $user ) );
  433. if ( is_wp_error( $user_id ) ) {
  434. return $user_id;
  435. }
  436. add_user_to_blog( get_site()->id, $user_id, '' );
  437. } else {
  438. $user_id = wp_insert_user( wp_slash( (array) $user ) );
  439. if ( is_wp_error( $user_id ) ) {
  440. return $user_id;
  441. }
  442. }
  443. $user = get_user_by( 'id', $user_id );
  444. /**
  445. * Fires immediately after a user is created or updated via the REST API.
  446. *
  447. * @since 4.7.0
  448. *
  449. * @param WP_User $user Inserted or updated user object.
  450. * @param WP_REST_Request $request Request object.
  451. * @param bool $creating True when creating a user, false when updating.
  452. */
  453. do_action( 'rest_insert_user', $user, $request, true );
  454. if ( ! empty( $request['roles'] ) && ! empty( $schema['properties']['roles'] ) ) {
  455. array_map( array( $user, 'add_role' ), $request['roles'] );
  456. }
  457. if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
  458. $meta_update = $this->meta->update_value( $request['meta'], $user_id );
  459. if ( is_wp_error( $meta_update ) ) {
  460. return $meta_update;
  461. }
  462. }
  463. $user = get_user_by( 'id', $user_id );
  464. $fields_update = $this->update_additional_fields_for_object( $user, $request );
  465. if ( is_wp_error( $fields_update ) ) {
  466. return $fields_update;
  467. }
  468. $request->set_param( 'context', 'edit' );
  469. $response = $this->prepare_item_for_response( $user, $request );
  470. $response = rest_ensure_response( $response );
  471. $response->set_status( 201 );
  472. $response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $user_id ) ) );
  473. return $response;
  474. }
  475. /**
  476. * Checks if a given request has access to update a user.
  477. *
  478. * @since 4.7.0
  479. * @access public
  480. *
  481. * @param WP_REST_Request $request Full details about the request.
  482. * @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise.
  483. */
  484. public function update_item_permissions_check( $request ) {
  485. $user = $this->get_user( $request['id'] );
  486. if ( is_wp_error( $user ) ) {
  487. return $user;
  488. }
  489. if ( ! current_user_can( 'edit_user', $user->ID ) ) {
  490. return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to edit this user.' ), array( 'status' => rest_authorization_required_code() ) );
  491. }
  492. if ( ! empty( $request['roles'] ) && ! current_user_can( 'edit_users' ) ) {
  493. return new WP_Error( 'rest_cannot_edit_roles', __( 'Sorry, you are not allowed to edit roles of this user.' ), array( 'status' => rest_authorization_required_code() ) );
  494. }
  495. return true;
  496. }
  497. /**
  498. * Updates a single user.
  499. *
  500. * @since 4.7.0
  501. * @access public
  502. *
  503. * @param WP_REST_Request $request Full details about the request.
  504. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
  505. */
  506. public function update_item( $request ) {
  507. $user = $this->get_user( $request['id'] );
  508. if ( is_wp_error( $user ) ) {
  509. return $user;
  510. }
  511. $id = $user->ID;
  512. if ( ! $user ) {
  513. return new WP_Error( 'rest_user_invalid_id', __( 'Invalid user ID.' ), array( 'status' => 404 ) );
  514. }
  515. if ( email_exists( $request['email'] ) && $request['email'] !== $user->user_email ) {
  516. return new WP_Error( 'rest_user_invalid_email', __( 'Invalid email address.' ), array( 'status' => 400 ) );
  517. }
  518. if ( ! empty( $request['username'] ) && $request['username'] !== $user->user_login ) {
  519. return new WP_Error( 'rest_user_invalid_argument', __( "Username isn't editable." ), array( 'status' => 400 ) );
  520. }
  521. if ( ! empty( $request['slug'] ) && $request['slug'] !== $user->user_nicename && get_user_by( 'slug', $request['slug'] ) ) {
  522. return new WP_Error( 'rest_user_invalid_slug', __( 'Invalid slug.' ), array( 'status' => 400 ) );
  523. }
  524. if ( ! empty( $request['roles'] ) ) {
  525. $check_permission = $this->check_role_update( $id, $request['roles'] );
  526. if ( is_wp_error( $check_permission ) ) {
  527. return $check_permission;
  528. }
  529. }
  530. $user = $this->prepare_item_for_database( $request );
  531. // Ensure we're operating on the same user we already checked.
  532. $user->ID = $id;
  533. $user_id = wp_update_user( wp_slash( (array) $user ) );
  534. if ( is_wp_error( $user_id ) ) {
  535. return $user_id;
  536. }
  537. $user = get_user_by( 'id', $user_id );
  538. /* This action is documented in lib/endpoints/class-wp-rest-users-controller.php */
  539. do_action( 'rest_insert_user', $user, $request, false );
  540. if ( ! empty( $request['roles'] ) ) {
  541. array_map( array( $user, 'add_role' ), $request['roles'] );
  542. }
  543. $schema = $this->get_item_schema();
  544. if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
  545. $meta_update = $this->meta->update_value( $request['meta'], $id );
  546. if ( is_wp_error( $meta_update ) ) {
  547. return $meta_update;
  548. }
  549. }
  550. $user = get_user_by( 'id', $user_id );
  551. $fields_update = $this->update_additional_fields_for_object( $user, $request );
  552. if ( is_wp_error( $fields_update ) ) {
  553. return $fields_update;
  554. }
  555. $request->set_param( 'context', 'edit' );
  556. $response = $this->prepare_item_for_response( $user, $request );
  557. $response = rest_ensure_response( $response );
  558. return $response;
  559. }
  560. /**
  561. * Checks if a given request has access to update the current user.
  562. *
  563. * @since 4.7.0
  564. * @access public
  565. *
  566. * @param WP_REST_Request $request Full details about the request.
  567. * @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise.
  568. */
  569. public function update_current_item_permissions_check( $request ) {
  570. $request['id'] = get_current_user_id();
  571. return $this->update_item_permissions_check( $request );
  572. }
  573. /**
  574. * Updates the current user.
  575. *
  576. * @since 4.7.0
  577. * @access public
  578. *
  579. * @param WP_REST_Request $request Full details about the request.
  580. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
  581. */
  582. function update_current_item( $request ) {
  583. $request['id'] = get_current_user_id();
  584. return $this->update_item( $request );
  585. }
  586. /**
  587. * Checks if a given request has access delete a user.
  588. *
  589. * @since 4.7.0
  590. * @access public
  591. *
  592. * @param WP_REST_Request $request Full details about the request.
  593. * @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise.
  594. */
  595. public function delete_item_permissions_check( $request ) {
  596. $user = $this->get_user( $request['id'] );
  597. if ( is_wp_error( $user ) ) {
  598. return $user;
  599. }
  600. if ( ! current_user_can( 'delete_user', $user->ID ) ) {
  601. return new WP_Error( 'rest_user_cannot_delete', __( 'Sorry, you are not allowed to delete this user.' ), array( 'status' => rest_authorization_required_code() ) );
  602. }
  603. return true;
  604. }
  605. /**
  606. * Deletes a single user.
  607. *
  608. * @since 4.7.0
  609. * @access public
  610. *
  611. * @param WP_REST_Request $request Full details about the request.
  612. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
  613. */
  614. public function delete_item( $request ) {
  615. // We don't support delete requests in multisite.
  616. if ( is_multisite() ) {
  617. return new WP_Error( 'rest_cannot_delete', __( 'The user cannot be deleted.' ), array( 'status' => 501 ) );
  618. }
  619. $user = $this->get_user( $request['id'] );
  620. if ( is_wp_error( $user ) ) {
  621. return $user;
  622. }
  623. $id = $user->ID;
  624. $reassign = false === $request['reassign'] ? null : absint( $request['reassign'] );
  625. $force = isset( $request['force'] ) ? (bool) $request['force'] : false;
  626. // We don't support trashing for users.
  627. if ( ! $force ) {
  628. return new WP_Error( 'rest_trash_not_supported', __( 'Users do not support trashing. Set force=true to delete.' ), array( 'status' => 501 ) );
  629. }
  630. if ( ! empty( $reassign ) ) {
  631. if ( $reassign === $id || ! get_userdata( $reassign ) ) {
  632. return new WP_Error( 'rest_user_invalid_reassign', __( 'Invalid user ID for reassignment.' ), array( 'status' => 400 ) );
  633. }
  634. }
  635. $request->set_param( 'context', 'edit' );
  636. $previous = $this->prepare_item_for_response( $user, $request );
  637. /** Include admin user functions to get access to wp_delete_user() */
  638. require_once ABSPATH . 'wp-admin/includes/user.php';
  639. $result = wp_delete_user( $id, $reassign );
  640. if ( ! $result ) {
  641. return new WP_Error( 'rest_cannot_delete', __( 'The user cannot be deleted.' ), array( 'status' => 500 ) );
  642. }
  643. $response = new WP_REST_Response();
  644. $response->set_data( array( 'deleted' => true, 'previous' => $previous->get_data() ) );
  645. /**
  646. * Fires immediately after a user is deleted via the REST API.
  647. *
  648. * @since 4.7.0
  649. *
  650. * @param WP_User $user The user data.
  651. * @param WP_REST_Response $response The response returned from the API.
  652. * @param WP_REST_Request $request The request sent to the API.
  653. */
  654. do_action( 'rest_delete_user', $user, $response, $request );
  655. return $response;
  656. }
  657. /**
  658. * Checks if a given request has access to delete the current user.
  659. *
  660. * @since 4.7.0
  661. * @access public
  662. *
  663. * @param WP_REST_Request $request Full details about the request.
  664. * @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise.
  665. */
  666. public function delete_current_item_permissions_check( $request ) {
  667. $request['id'] = get_current_user_id();
  668. return $this->delete_item_permissions_check( $request );
  669. }
  670. /**
  671. * Deletes the current user.
  672. *
  673. * @since 4.7.0
  674. * @access public
  675. *
  676. * @param WP_REST_Request $request Full details about the request.
  677. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
  678. */
  679. function delete_current_item( $request ) {
  680. $request['id'] = get_current_user_id();
  681. return $this->delete_item( $request );
  682. }
  683. /**
  684. * Prepares a single user output for response.
  685. *
  686. * @since 4.7.0
  687. * @access public
  688. *
  689. * @param WP_User $user User object.
  690. * @param WP_REST_Request $request Request object.
  691. * @return WP_REST_Response Response object.
  692. */
  693. public function prepare_item_for_response( $user, $request ) {
  694. $data = array();
  695. $schema = $this->get_item_schema();
  696. if ( ! empty( $schema['properties']['id'] ) ) {
  697. $data['id'] = $user->ID;
  698. }
  699. if ( ! empty( $schema['properties']['username'] ) ) {
  700. $data['username'] = $user->user_login;
  701. }
  702. if ( ! empty( $schema['properties']['name'] ) ) {
  703. $data['name'] = $user->display_name;
  704. }
  705. if ( ! empty( $schema['properties']['first_name'] ) ) {
  706. $data['first_name'] = $user->first_name;
  707. }
  708. if ( ! empty( $schema['properties']['last_name'] ) ) {
  709. $data['last_name'] = $user->last_name;
  710. }
  711. if ( ! empty( $schema['properties']['email'] ) ) {
  712. $data['email'] = $user->user_email;
  713. }
  714. if ( ! empty( $schema['properties']['url'] ) ) {
  715. $data['url'] = $user->user_url;
  716. }
  717. if ( ! empty( $schema['properties']['description'] ) ) {
  718. $data['description'] = $user->description;
  719. }
  720. if ( ! empty( $schema['properties']['link'] ) ) {
  721. $data['link'] = get_author_posts_url( $user->ID, $user->user_nicename );
  722. }
  723. if ( ! empty( $schema['properties']['locale'] ) ) {
  724. $data['locale'] = get_user_locale( $user );
  725. }
  726. if ( ! empty( $schema['properties']['nickname'] ) ) {
  727. $data['nickname'] = $user->nickname;
  728. }
  729. if ( ! empty( $schema['properties']['slug'] ) ) {
  730. $data['slug'] = $user->user_nicename;
  731. }
  732. if ( ! empty( $schema['properties']['roles'] ) ) {
  733. // Defensively call array_values() to ensure an array is returned.
  734. $data['roles'] = array_values( $user->roles );
  735. }
  736. if ( ! empty( $schema['properties']['registered_date'] ) ) {
  737. $data['registered_date'] = date( 'c', strtotime( $user->user_registered ) );
  738. }
  739. if ( ! empty( $schema['properties']['capabilities'] ) ) {
  740. $data['capabilities'] = (object) $user->allcaps;
  741. }
  742. if ( ! empty( $schema['properties']['extra_capabilities'] ) ) {
  743. $data['extra_capabilities'] = (object) $user->caps;
  744. }
  745. if ( ! empty( $schema['properties']['avatar_urls'] ) ) {
  746. $data['avatar_urls'] = rest_get_avatar_urls( $user->user_email );
  747. }
  748. if ( ! empty( $schema['properties']['meta'] ) ) {
  749. $data['meta'] = $this->meta->get_value( $user->ID, $request );
  750. }
  751. $context = ! empty( $request['context'] ) ? $request['context'] : 'embed';
  752. $data = $this->add_additional_fields_to_object( $data, $request );
  753. $data = $this->filter_response_by_context( $data, $context );
  754. // Wrap the data in a response object.
  755. $response = rest_ensure_response( $data );
  756. $response->add_links( $this->prepare_links( $user ) );
  757. /**
  758. * Filters user data returned from the REST API.
  759. *
  760. * @since 4.7.0
  761. *
  762. * @param WP_REST_Response $response The response object.
  763. * @param object $user User object used to create response.
  764. * @param WP_REST_Request $request Request object.
  765. */
  766. return apply_filters( 'rest_prepare_user', $response, $user, $request );
  767. }
  768. /**
  769. * Prepares links for the user request.
  770. *
  771. * @since 4.7.0
  772. * @access protected
  773. *
  774. * @param WP_Post $user User object.
  775. * @return array Links for the given user.
  776. */
  777. protected function prepare_links( $user ) {
  778. $links = array(
  779. 'self' => array(
  780. 'href' => rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $user->ID ) ),
  781. ),
  782. 'collection' => array(
  783. 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
  784. ),
  785. );
  786. return $links;
  787. }
  788. /**
  789. * Prepares a single user for creation or update.
  790. *
  791. * @since 4.7.0
  792. * @access protected
  793. *
  794. * @param WP_REST_Request $request Request object.
  795. * @return object $prepared_user User object.
  796. */
  797. protected function prepare_item_for_database( $request ) {
  798. $prepared_user = new stdClass;
  799. $schema = $this->get_item_schema();
  800. // required arguments.
  801. if ( isset( $request['email'] ) && ! empty( $schema['properties']['email'] ) ) {
  802. $prepared_user->user_email = $request['email'];
  803. }
  804. if ( isset( $request['username'] ) && ! empty( $schema['properties']['username'] ) ) {
  805. $prepared_user->user_login = $request['username'];
  806. }
  807. if ( isset( $request['password'] ) && ! empty( $schema['properties']['password'] ) ) {
  808. $prepared_user->user_pass = $request['password'];
  809. }
  810. // optional arguments.
  811. if ( isset( $request['id'] ) ) {
  812. $prepared_user->ID = absint( $request['id'] );
  813. }
  814. if ( isset( $request['name'] ) && ! empty( $schema['properties']['name'] ) ) {
  815. $prepared_user->display_name = $request['name'];
  816. }
  817. if ( isset( $request['first_name'] ) && ! empty( $schema['properties']['first_name'] ) ) {
  818. $prepared_user->first_name = $request['first_name'];
  819. }
  820. if ( isset( $request['last_name'] ) && ! empty( $schema['properties']['last_name'] ) ) {
  821. $prepared_user->last_name = $request['last_name'];
  822. }
  823. if ( isset( $request['nickname'] ) && ! empty( $schema['properties']['nickname'] ) ) {
  824. $prepared_user->nickname = $request['nickname'];
  825. }
  826. if ( isset( $request['slug'] ) && ! empty( $schema['properties']['slug'] ) ) {
  827. $prepared_user->user_nicename = $request['slug'];
  828. }
  829. if ( isset( $request['description'] ) && ! empty( $schema['properties']['description'] ) ) {
  830. $prepared_user->description = $request['description'];
  831. }
  832. if ( isset( $request['url'] ) && ! empty( $schema['properties']['url'] ) ) {
  833. $prepared_user->user_url = $request['url'];
  834. }
  835. if ( isset( $request['locale'] ) && ! empty( $schema['properties']['locale'] ) ) {
  836. $prepared_user->locale = $request['locale'];
  837. }
  838. // setting roles will be handled outside of this function.
  839. if ( isset( $request['roles'] ) ) {
  840. $prepared_user->role = false;
  841. }
  842. /**
  843. * Filters user data before insertion via the REST API.
  844. *
  845. * @since 4.7.0
  846. *
  847. * @param object $prepared_user User object.
  848. * @param WP_REST_Request $request Request object.
  849. */
  850. return apply_filters( 'rest_pre_insert_user', $prepared_user, $request );
  851. }
  852. /**
  853. * Determines if the current user is allowed to make the desired roles change.
  854. *
  855. * @since 4.7.0
  856. * @access protected
  857. *
  858. * @param integer $user_id User ID.
  859. * @param array $roles New user roles.
  860. * @return true|WP_Error True if the current user is allowed to make the role change,
  861. * otherwise a WP_Error object.
  862. */
  863. protected function check_role_update( $user_id, $roles ) {
  864. global $wp_roles;
  865. foreach ( $roles as $role ) {
  866. if ( ! isset( $wp_roles->role_objects[ $role ] ) ) {
  867. /* translators: %s: role key */
  868. return new WP_Error( 'rest_user_invalid_role', sprintf( __( 'The role %s does not exist.' ), $role ), array( 'status' => 400 ) );
  869. }
  870. $potential_role = $wp_roles->role_objects[ $role ];
  871. /*
  872. * Don't let anyone with 'edit_users' (admins) edit their own role to something without it.
  873. * Multisite super admins can freely edit their blog roles -- they possess all caps.
  874. */
  875. if ( ! ( is_multisite()
  876. && current_user_can( 'manage_sites' ) )
  877. && get_current_user_id() === $user_id
  878. && ! $potential_role->has_cap( 'edit_users' )
  879. ) {
  880. return new WP_Error( 'rest_user_invalid_role', __( 'Sorry, you are not allowed to give users that role.' ), array( 'status' => rest_authorization_required_code() ) );
  881. }
  882. /** Include admin functions to get access to get_editable_roles() */
  883. require_once ABSPATH . 'wp-admin/includes/admin.php';
  884. // The new role must be editable by the logged-in user.
  885. $editable_roles = get_editable_roles();
  886. if ( empty( $editable_roles[ $role ] ) ) {
  887. return new WP_Error( 'rest_user_invalid_role', __( 'Sorry, you are not allowed to give users that role.' ), array( 'status' => 403 ) );
  888. }
  889. }
  890. return true;
  891. }
  892. /**
  893. * Check a username for the REST API.
  894. *
  895. * Performs a couple of checks like edit_user() in wp-admin/includes/user.php.
  896. *
  897. * @since 4.7.0
  898. *
  899. * @param mixed $value The username submitted in the request.
  900. * @param WP_REST_Request $request Full details about the request.
  901. * @param string $param The parameter name.
  902. * @return WP_Error|string The sanitized username, if valid, otherwise an error.
  903. */
  904. public function check_username( $value, $request, $param ) {
  905. $username = (string) $value;
  906. if ( ! validate_username( $username ) ) {
  907. return new WP_Error( 'rest_user_invalid_username', __( 'Username contains invalid characters.' ), array( 'status' => 400 ) );
  908. }
  909. /** This filter is documented in wp-includes/user.php */
  910. $illegal_logins = (array) apply_filters( 'illegal_user_logins', array() );
  911. if ( in_array( strtolower( $username ), array_map( 'strtolower', $illegal_logins ) ) ) {
  912. return new WP_Error( 'rest_user_invalid_username', __( 'Sorry, that username is not allowed.' ), array( 'status' => 400 ) );
  913. }
  914. return $username;
  915. }
  916. /**
  917. * Check a user password for the REST API.
  918. *
  919. * Performs a couple of checks like edit_user() in wp-admin/includes/user.php.
  920. *
  921. * @since 4.7.0
  922. *
  923. * @param mixed $value The password submitted in the request.
  924. * @param WP_REST_Request $request Full details about the request.
  925. * @param string $param The parameter name.
  926. * @return WP_Error|string The sanitized password, if valid, otherwise an error.
  927. */
  928. public function check_user_password( $value, $request, $param ) {
  929. $password = (string) $value;
  930. if ( empty( $password ) ) {
  931. return new WP_Error( 'rest_user_invalid_password', __( 'Passwords cannot be empty.' ), array( 'status' => 400 ) );
  932. }
  933. if ( false !== strpos( $password, "\\" ) ) {
  934. return new WP_Error( 'rest_user_invalid_password', __( 'Passwords cannot contain the "\\" character.' ), array( 'status' => 400 ) );
  935. }
  936. return $password;
  937. }
  938. /**
  939. * Retrieves the user's schema, conforming to JSON Schema.
  940. *
  941. * @since 4.7.0
  942. * @access public
  943. *
  944. * @return array Item schema data.
  945. */
  946. public function get_item_schema() {
  947. $schema = array(
  948. '$schema' => 'http://json-schema.org/schema#',
  949. 'title' => 'user',
  950. 'type' => 'object',
  951. 'properties' => array(
  952. 'id' => array(
  953. 'description' => __( 'Unique identifier for the user.' ),
  954. 'type' => 'integer',
  955. 'context' => array( 'embed', 'view', 'edit' ),
  956. 'readonly' => true,
  957. ),
  958. 'username' => array(
  959. 'description' => __( 'Login name for the user.' ),
  960. 'type' => 'string',
  961. 'context' => array( 'edit' ),
  962. 'required' => true,
  963. 'arg_options' => array(
  964. 'sanitize_callback' => array( $this, 'check_username' ),
  965. ),
  966. ),
  967. 'name' => array(
  968. 'description' => __( 'Display name for the user.' ),
  969. 'type' => 'string',
  970. 'context' => array( 'embed', 'view', 'edit' ),
  971. 'arg_options' => array(
  972. 'sanitize_callback' => 'sanitize_text_field',
  973. ),
  974. ),
  975. 'first_name' => array(
  976. 'description' => __( 'First name for the user.' ),
  977. 'type' => 'string',
  978. 'context' => array( 'edit' ),
  979. 'arg_options' => array(
  980. 'sanitize_callback' => 'sanitize_text_field',
  981. ),
  982. ),
  983. 'last_name' => array(
  984. 'description' => __( 'Last name for the user.' ),
  985. 'type' => 'string',
  986. 'context' => array( 'edit' ),
  987. 'arg_options' => array(
  988. 'sanitize_callback' => 'sanitize_text_field',
  989. ),
  990. ),
  991. 'email' => array(
  992. 'description' => __( 'The email address for the user.' ),
  993. 'type' => 'string',
  994. 'format' => 'email',
  995. 'context' => array( 'edit' ),
  996. 'required' => true,
  997. ),
  998. 'url' => array(
  999. 'description' => __( 'URL of the user.' ),
  1000. 'type' => 'string',
  1001. 'format' => 'uri',
  1002. 'context' => array( 'embed', 'view', 'edit' ),
  1003. ),
  1004. 'description' => array(
  1005. 'description' => __( 'Description of the user.' ),
  1006. 'type' => 'string',
  1007. 'context' => array( 'embed', 'view', 'edit' ),
  1008. ),
  1009. 'link' => array(
  1010. 'description' => __( 'Author URL of the user.' ),
  1011. 'type' => 'string',
  1012. 'format' => 'uri',
  1013. 'context' => array( 'embed', 'view', 'edit' ),
  1014. 'readonly' => true,
  1015. ),
  1016. 'locale' => array(
  1017. 'description' => __( 'Locale for the user.' ),
  1018. 'type' => 'string',
  1019. 'enum' => array_merge( array( '', 'en_US' ), get_available_languages() ),
  1020. 'context' => array( 'edit' ),
  1021. ),
  1022. 'nickname' => array(
  1023. 'description' => __( 'The nickname for the user.' ),
  1024. 'type' => 'string',
  1025. 'context' => array( 'edit' ),
  1026. 'arg_options' => array(
  1027. 'sanitize_callback' => 'sanitize_text_field',
  1028. ),
  1029. ),
  1030. 'slug' => array(
  1031. 'description' => __( 'An alphanumeric identifier for the user.' ),
  1032. 'type' => 'string',
  1033. 'context' => array( 'embed', 'view', 'edit' ),
  1034. 'arg_options' => array(
  1035. 'sanitize_callback' => array( $this, 'sanitize_slug' ),
  1036. ),
  1037. ),
  1038. 'registered_date' => array(
  1039. 'description' => __( 'Registration date for the user.' ),
  1040. 'type' => 'string',
  1041. 'format' => 'date-time',
  1042. 'context' => array( 'edit' ),
  1043. 'readonly' => true,
  1044. ),
  1045. 'roles' => array(
  1046. 'description' => __( 'Roles assigned to the user.' ),
  1047. 'type' => 'array',
  1048. 'items' => array(
  1049. 'type' => 'string',
  1050. ),
  1051. 'context' => array( 'edit' ),
  1052. ),
  1053. 'password' => array(
  1054. 'description' => __( 'Password for the user (never included).' ),
  1055. 'type' => 'string',
  1056. 'context' => array(), // Password is never displayed.
  1057. 'required' => true,
  1058. 'arg_options' => array(
  1059. 'sanitize_callback' => array( $this, 'check_user_password' ),
  1060. ),
  1061. ),
  1062. 'capabilities' => array(
  1063. 'description' => __( 'All capabilities assigned to the user.' ),
  1064. 'type' => 'object',
  1065. 'context' => array( 'edit' ),
  1066. 'readonly' => true,
  1067. ),
  1068. 'extra_capabilities' => array(
  1069. 'description' => __( 'Any extra capabilities assigned to the user.' ),
  1070. 'type' => 'object',
  1071. 'context' => array( 'edit' ),
  1072. 'readonly' => true,
  1073. ),
  1074. ),
  1075. );
  1076. if ( get_option( 'show_avatars' ) ) {
  1077. $avatar_properties = array();
  1078. $avatar_sizes = rest_get_avatar_sizes();
  1079. foreach ( $avatar_sizes as $size ) {
  1080. $avatar_properties[ $size ] = array(
  1081. /* translators: %d: avatar image size in pixels */
  1082. 'description' => sprintf( __( 'Avatar URL with image size of %d pixels.' ), $size ),
  1083. 'type' => 'string',
  1084. 'format' => 'uri',
  1085. 'context' => array( 'embed', 'view', 'edit' ),
  1086. );
  1087. }
  1088. $schema['properties']['avatar_urls'] = array(
  1089. 'description' => __( 'Avatar URLs for the user.' ),
  1090. 'type' => 'object',
  1091. 'context' => array( 'embed', 'view', 'edit' ),
  1092. 'readonly' => true,
  1093. 'properties' => $avatar_properties,
  1094. );
  1095. }
  1096. $schema['properties']['meta'] = $this->meta->get_field_schema();
  1097. return $this->add_additional_fields_schema( $schema );
  1098. }
  1099. /**
  1100. * Retrieves the query params for collections.
  1101. *
  1102. * @since 4.7.0
  1103. * @access public
  1104. *
  1105. * @return array Collection parameters.
  1106. */
  1107. public function get_collection_params() {
  1108. $query_params = parent::get_collection_params();
  1109. $query_params['context']['default'] = 'view';
  1110. $query_params['exclude'] = array(
  1111. 'description' => __( 'Ensure result set excludes specific IDs.' ),
  1112. 'type' => 'array',
  1113. 'items' => array(
  1114. 'type' => 'integer',
  1115. ),
  1116. 'default' => array(),
  1117. );
  1118. $query_params['include'] = array(
  1119. 'description' => __( 'Limit result set to specific IDs.' ),
  1120. 'type' => 'array',
  1121. 'items' => array(
  1122. 'type' => 'integer',
  1123. ),
  1124. 'default' => array(),
  1125. );
  1126. $query_params['offset'] = array(
  1127. 'description' => __( 'Offset the result set by a specific number of items.' ),
  1128. 'type' => 'integer',
  1129. );
  1130. $query_params['order'] = array(
  1131. 'default' => 'asc',
  1132. 'description' => __( 'Order sort attribute ascending or descending.' ),
  1133. 'enum' => array( 'asc', 'desc' ),
  1134. 'type' => 'string',
  1135. );
  1136. $query_params['orderby'] = array(
  1137. 'default' => 'name',
  1138. 'description' => __( 'Sort collection by object attribute.' ),
  1139. 'enum' => array(
  1140. 'id',
  1141. 'include',
  1142. 'name',
  1143. 'registered_date',
  1144. 'slug',
  1145. 'email',
  1146. 'url',
  1147. ),
  1148. 'type' => 'string',
  1149. );
  1150. $query_params['slug'] = array(
  1151. 'description' => __( 'Limit result set to users with a specific slug.' ),
  1152. 'type' => 'string',
  1153. );
  1154. $query_params['roles'] = array(
  1155. 'description' => __( 'Limit result set to users matching at least one specific role provided. Accepts csv list or single role.' ),
  1156. 'type' => 'array',
  1157. 'items' => array(
  1158. 'type' => 'string',
  1159. ),
  1160. );
  1161. /**
  1162. * Filter collection parameters for the users controller.
  1163. *
  1164. * This filter registers the collection parameter, but does not map the
  1165. * collection parameter to an internal WP_User_Query parameter. Use the
  1166. * `rest_user_query` filter to set WP_User_Query arguments.
  1167. *
  1168. * @since 4.7.0
  1169. *
  1170. * @param array $query_params JSON Schema-formatted collection parameters.
  1171. */
  1172. return apply_filters( 'rest_user_collection_params', $query_params );
  1173. }
  1174. }