class-acf-field-user.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622
  1. <?php
  2. if ( ! class_exists( 'ACF_Field_User' ) ) :
  3. class ACF_Field_User extends ACF_Field {
  4. /**
  5. * Initializes the field type.
  6. *
  7. * @date 5/03/2014
  8. * @since 5.0.0
  9. *
  10. * @param void
  11. * @return void
  12. */
  13. function initialize() {
  14. // Props.
  15. $this->name = 'user';
  16. $this->label = __( 'User', 'acf' );
  17. $this->category = 'relational';
  18. $this->defaults = array(
  19. 'role' => '',
  20. 'multiple' => 0,
  21. 'allow_null' => 0,
  22. 'return_format' => 'array',
  23. );
  24. // Register filter variations.
  25. acf_add_filter_variations( 'acf/fields/user/query', array( 'name', 'key' ), 1 );
  26. acf_add_filter_variations( 'acf/fields/user/result', array( 'name', 'key' ), 2 );
  27. acf_add_filter_variations( 'acf/fields/user/search_columns', array( 'name', 'key' ), 3 );
  28. // Add AJAX query.
  29. add_action( 'wp_ajax_acf/fields/user/query', array( $this, 'ajax_query' ) );
  30. add_action( 'wp_ajax_nopriv_acf/fields/user/query', array( $this, 'ajax_query' ) );
  31. }
  32. /**
  33. * Renders the field settings HTML.
  34. *
  35. * @date 23/01/13
  36. * @since 3.6.0
  37. *
  38. * @param array $field The ACF field.
  39. * @return void
  40. */
  41. function render_field_settings( $field ) {
  42. acf_render_field_setting(
  43. $field,
  44. array(
  45. 'label' => __( 'Filter by role', 'acf' ),
  46. 'instructions' => '',
  47. 'type' => 'select',
  48. 'name' => 'role',
  49. 'choices' => acf_get_user_role_labels(),
  50. 'multiple' => 1,
  51. 'ui' => 1,
  52. 'allow_null' => 1,
  53. 'placeholder' => __( 'All user roles', 'acf' ),
  54. )
  55. );
  56. acf_render_field_setting(
  57. $field,
  58. array(
  59. 'label' => __( 'Return Format', 'acf' ),
  60. 'instructions' => '',
  61. 'type' => 'radio',
  62. 'name' => 'return_format',
  63. 'choices' => array(
  64. 'array' => __( 'User Array', 'acf' ),
  65. 'object' => __( 'User Object', 'acf' ),
  66. 'id' => __( 'User ID', 'acf' ),
  67. ),
  68. 'layout' => 'horizontal',
  69. )
  70. );
  71. acf_render_field_setting(
  72. $field,
  73. array(
  74. 'label' => __( 'Select multiple values?', 'acf' ),
  75. 'instructions' => '',
  76. 'name' => 'multiple',
  77. 'type' => 'true_false',
  78. 'ui' => 1,
  79. )
  80. );
  81. }
  82. /**
  83. * Renders the field settings used in the "Validation" tab.
  84. *
  85. * @since 6.0
  86. *
  87. * @param array $field The field settings array.
  88. * @return void
  89. */
  90. function render_field_validation_settings( $field ) {
  91. acf_render_field_setting(
  92. $field,
  93. array(
  94. 'label' => __( 'Allow Null?', 'acf' ),
  95. 'instructions' => '',
  96. 'name' => 'allow_null',
  97. 'type' => 'true_false',
  98. 'ui' => 1,
  99. )
  100. );
  101. }
  102. /**
  103. * Renders the field input HTML.
  104. *
  105. * @date 23/01/13
  106. * @since 3.6.0
  107. *
  108. * @param array $field The ACF field.
  109. * @return void
  110. */
  111. function render_field( $field ) {
  112. // Change Field into a select.
  113. $field['type'] = 'select';
  114. $field['ui'] = 1;
  115. $field['ajax'] = 1;
  116. $field['choices'] = array();
  117. $field['query_nonce'] = wp_create_nonce( 'acf/fields/user/query' . $field['key'] );
  118. // Populate choices.
  119. if ( $field['value'] ) {
  120. // Clean value into an array of IDs.
  121. $user_ids = array_map( 'intval', acf_array( $field['value'] ) );
  122. // Find users in database (ensures all results are real).
  123. $users = acf_get_users(
  124. array(
  125. 'include' => $user_ids,
  126. )
  127. );
  128. // Append.
  129. if ( $users ) {
  130. foreach ( $users as $user ) {
  131. $field['choices'][ $user->ID ] = $this->get_result( $user, $field );
  132. }
  133. }
  134. }
  135. // Render.
  136. acf_render_field( $field );
  137. }
  138. /**
  139. * Returns the result text for a fiven WP_User object.
  140. *
  141. * @date 1/11/2013
  142. * @since 5.0.0
  143. *
  144. * @param WP_User $user The WP_User object.
  145. * @param array $field The ACF field related to this query.
  146. * @param (int|string) $post_id The post_id being edited.
  147. * @return string
  148. */
  149. function get_result( $user, $field, $post_id = 0 ) {
  150. // Get user result item.
  151. $item = acf_get_user_result( $user );
  152. // Default $post_id to current post being edited.
  153. $post_id = $post_id ? $post_id : acf_get_form_data( 'post_id' );
  154. /**
  155. * Filters the result text.
  156. *
  157. * @date 21/5/19
  158. * @since 5.8.1
  159. *
  160. * @param array $args The query args.
  161. * @param array $field The ACF field related to this query.
  162. * @param (int|string) $post_id The post_id being edited.
  163. */
  164. return apply_filters( 'acf/fields/user/result', $item['text'], $user, $field, $post_id );
  165. }
  166. /**
  167. * Filters the field value after it is loaded from the database.
  168. *
  169. * @date 23/01/13
  170. * @since 3.6.0
  171. *
  172. * @param mixed $value The field value.
  173. * @param mixed $post_id The post ID where the value is saved.
  174. * @param array $field The field array containing all settings.
  175. * @return mixed
  176. */
  177. function load_value( $value, $post_id, $field ) {
  178. // Add compatibility for version 4.
  179. if ( $value === 'null' ) {
  180. return false;
  181. }
  182. return $value;
  183. }
  184. /**
  185. * Filters the field value after it is loaded from the database but before it is returned to the front-end API.
  186. *
  187. * @date 23/01/13
  188. * @since 3.6.0
  189. *
  190. * @param mixed $value The field value.
  191. * @param mixed $post_id The post ID where the value is saved.
  192. * @param array $field The field array containing all settings.
  193. * @return mixed
  194. */
  195. function format_value( $value, $post_id, $field ) {
  196. // Bail early if no value.
  197. if ( ! $value ) {
  198. return false;
  199. }
  200. // Clean value into an array of IDs.
  201. $user_ids = array_map( 'intval', acf_array( $value ) );
  202. // Find users in database (ensures all results are real).
  203. $users = acf_get_users(
  204. array(
  205. 'include' => $user_ids,
  206. )
  207. );
  208. // Bail early if no users found.
  209. if ( ! $users ) {
  210. return false;
  211. }
  212. // Format values using field settings.
  213. $value = array();
  214. foreach ( $users as $user ) {
  215. // Return object.
  216. if ( $field['return_format'] == 'object' ) {
  217. $item = $user;
  218. // Return array.
  219. } elseif ( $field['return_format'] == 'array' ) {
  220. $item = array(
  221. 'ID' => $user->ID,
  222. 'user_firstname' => $user->user_firstname,
  223. 'user_lastname' => $user->user_lastname,
  224. 'nickname' => $user->nickname,
  225. 'user_nicename' => $user->user_nicename,
  226. 'display_name' => $user->display_name,
  227. 'user_email' => $user->user_email,
  228. 'user_url' => $user->user_url,
  229. 'user_registered' => $user->user_registered,
  230. 'user_description' => $user->user_description,
  231. 'user_avatar' => get_avatar( $user->ID ),
  232. );
  233. // Return ID.
  234. } else {
  235. $item = $user->ID;
  236. }
  237. // Append item
  238. $value[] = $item;
  239. }
  240. // Convert to single.
  241. if ( ! $field['multiple'] ) {
  242. $value = array_shift( $value );
  243. }
  244. // Return.
  245. return $value;
  246. }
  247. /**
  248. * Filters the field value before it is saved into the database.
  249. *
  250. * @date 23/01/13
  251. * @since 3.6.0
  252. *
  253. * @param mixed $value The field value.
  254. * @param mixed $post_id The post ID where the value is saved.
  255. * @param array $field The field array containing all settings.
  256. * @return mixed
  257. */
  258. function update_value( $value, $post_id, $field ) {
  259. // Bail early if no value.
  260. if ( empty( $value ) ) {
  261. return $value;
  262. }
  263. // Format array of values.
  264. // - ensure each value is an id.
  265. // - Parse each id as string for SQL LIKE queries.
  266. if ( acf_is_sequential_array( $value ) ) {
  267. $value = array_map( 'acf_idval', $value );
  268. $value = array_map( 'strval', $value );
  269. // Parse single value for id.
  270. } else {
  271. $value = acf_idval( $value );
  272. }
  273. // Return value.
  274. return $value;
  275. }
  276. /**
  277. * Callback for the AJAX query request.
  278. *
  279. * @date 24/10/13
  280. * @since 5.0.0
  281. *
  282. * @param void
  283. * @return void
  284. */
  285. function ajax_query() {
  286. // phpcs:disable WordPress.Security.NonceVerification.Recommended
  287. // Modify Request args.
  288. if ( isset( $_REQUEST['s'] ) ) {
  289. $_REQUEST['search'] = sanitize_text_field( $_REQUEST['s'] );
  290. }
  291. if ( isset( $_REQUEST['paged'] ) ) {
  292. $_REQUEST['page'] = absint( $_REQUEST['paged'] );
  293. }
  294. // phpcs:enable WordPress.Security.NonceVerification.Recommended
  295. // Add query hooks.
  296. add_action( 'acf/ajax/query_users/init', array( $this, 'ajax_query_init' ), 10, 2 );
  297. add_filter( 'acf/ajax/query_users/args', array( $this, 'ajax_query_args' ), 10, 3 );
  298. add_filter( 'acf/ajax/query_users/result', array( $this, 'ajax_query_result' ), 10, 3 );
  299. add_filter( 'acf/ajax/query_users/search_columns', array( $this, 'ajax_query_search_columns' ), 10, 4 );
  300. // Simulate AJAX request.
  301. acf_get_instance( 'ACF_Ajax_Query_Users' )->request();
  302. }
  303. /**
  304. * Runs during the AJAX query initialization.
  305. *
  306. * @date 9/3/20
  307. * @since 5.8.8
  308. *
  309. * @param array $request The query request.
  310. * @param ACF_Ajax_Query $query The query object.
  311. * @return void
  312. */
  313. function ajax_query_init( $request, $query ) {
  314. // Require field and make sure it's a user field.
  315. if ( ! $query->field || $query->field['type'] !== $this->name ) {
  316. $query->send( new WP_Error( 'acf_missing_field', __( 'Error loading field.', 'acf' ), array( 'status' => 404 ) ) );
  317. }
  318. // Verify that this is a legitimate request using a separate nonce from the main AJAX nonce.
  319. if ( ! isset( $_REQUEST['user_query_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( $_REQUEST['user_query_nonce'] ), 'acf/fields/user/query' . $query->field['key']) ) {
  320. $query->send( new WP_Error( 'acf_invalid_request', __( 'Invalid request.', 'acf' ), array( 'status' => 404 ) ) );
  321. }
  322. }
  323. /**
  324. * Filters the AJAX query args.
  325. *
  326. * @date 9/3/20
  327. * @since 5.8.8
  328. *
  329. * @param array $args The query args.
  330. * @param array $request The query request.
  331. * @param ACF_Ajax_Query $query The query object.
  332. * @return array
  333. */
  334. function ajax_query_args( $args, $request, $query ) {
  335. // Add specific roles.
  336. if ( $query->field['role'] ) {
  337. $args['role__in'] = acf_array( $query->field['role'] );
  338. }
  339. /**
  340. * Filters the query args.
  341. *
  342. * @date 21/5/19
  343. * @since 5.8.1
  344. *
  345. * @param array $args The query args.
  346. * @param array $field The ACF field related to this query.
  347. * @param (int|string) $post_id The post_id being edited.
  348. */
  349. return apply_filters( 'acf/fields/user/query', $args, $query->field, $query->post_id );
  350. }
  351. /**
  352. * Filters the WP_User_Query search columns.
  353. *
  354. * @date 9/3/20
  355. * @since 5.8.8
  356. *
  357. * @param array $columns An array of column names to be searched.
  358. * @param string $search The search term.
  359. * @param WP_User_Query $WP_User_Query The WP_User_Query instance.
  360. * @return array
  361. */
  362. function ajax_query_search_columns( $columns, $search, $WP_User_Query, $query ) {
  363. /**
  364. * Filters the column names to be searched.
  365. *
  366. * @date 21/5/19
  367. * @since 5.8.1
  368. *
  369. * @param array $columns An array of column names to be searched.
  370. * @param string $search The search term.
  371. * @param WP_User_Query $WP_User_Query The WP_User_Query instance.
  372. * @param array $field The ACF field related to this query.
  373. */
  374. return apply_filters( 'acf/fields/user/search_columns', $columns, $search, $WP_User_Query, $query->field );
  375. }
  376. /**
  377. * Filters the AJAX Query result.
  378. *
  379. * @date 9/3/20
  380. * @since 5.8.8
  381. *
  382. * @param array $item The choice id and text.
  383. * @param WP_User $user The user object.
  384. * @param ACF_Ajax_Query $query The query object.
  385. * @return array
  386. */
  387. function ajax_query_result( $item, $user, $query ) {
  388. /**
  389. * Filters the result text.
  390. *
  391. * @date 21/5/19
  392. * @since 5.8.1
  393. *
  394. * @param string The result text.
  395. * @param WP_User $user The user object.
  396. * @param array $field The ACF field related to this query.
  397. * @param (int|string) $post_id The post_id being edited.
  398. */
  399. $item['text'] = apply_filters( 'acf/fields/user/result', $item['text'], $user, $query->field, $query->post_id );
  400. return $item;
  401. }
  402. /**
  403. * Return an array of data formatted for use in a select2 AJAX response.
  404. *
  405. * @date 15/10/2014
  406. * @since 5.0.9
  407. * @deprecated 5.8.9
  408. *
  409. * @param array $args An array of query args.
  410. * @return array
  411. */
  412. function get_ajax_query( $options = array() ) {
  413. _deprecated_function( __FUNCTION__, '5.8.9' );
  414. return array();
  415. }
  416. /**
  417. * Filters the WP_User_Query search columns.
  418. *
  419. * @date 15/10/2014
  420. * @since 5.0.9
  421. * @deprecated 5.8.9
  422. *
  423. * @param array $columns An array of column names to be searched.
  424. * @param string $search The search term.
  425. * @param WP_User_Query $WP_User_Query The WP_User_Query instance.
  426. * @return array
  427. */
  428. function user_search_columns( $columns, $search, $WP_User_Query ) {
  429. _deprecated_function( __FUNCTION__, '5.8.9' );
  430. return $columns;
  431. }
  432. /**
  433. * Validates user fields updated via the REST API.
  434. *
  435. * @param bool $valid
  436. * @param int $value
  437. * @param array $field
  438. *
  439. * @return bool|WP_Error
  440. */
  441. public function validate_rest_value( $valid, $value, $field ) {
  442. if ( is_null( $value ) ) {
  443. return $valid;
  444. }
  445. $param = sprintf( '%s[%s]', $field['prefix'], $field['name'] );
  446. $data = array( 'param' => $param );
  447. $value = is_array( $value ) ? $value : array( $value );
  448. $invalid_users = array();
  449. $insufficient_roles = array();
  450. foreach ( $value as $user_id ) {
  451. $user_data = get_userdata( $user_id );
  452. if ( ! $user_data ) {
  453. $invalid_users[] = $user_id;
  454. continue;
  455. }
  456. if ( empty( $field['role'] ) ) {
  457. continue;
  458. }
  459. $has_roles = count( array_intersect( $field['role'], $user_data->roles ) );
  460. if ( ! $has_roles ) {
  461. $insufficient_roles[] = $user_id;
  462. }
  463. }
  464. if ( count( $invalid_users ) ) {
  465. $error = sprintf(
  466. __( '%1$s must have a valid user ID.', 'acf' ),
  467. $param
  468. );
  469. $data['value'] = $invalid_users;
  470. return new WP_Error( 'rest_invalid_param', $error, $data );
  471. }
  472. if ( count( $insufficient_roles ) ) {
  473. $error = sprintf(
  474. _n(
  475. '%1$s must have a user with the %2$s role.',
  476. '%1$s must have a user with one of the following roles: %2$s',
  477. count( $field['role'] ),
  478. 'acf'
  479. ),
  480. $param,
  481. count( $field['role'] ) > 1 ? implode( ', ', $field['role'] ) : $field['role'][0]
  482. );
  483. $data['value'] = $insufficient_roles;
  484. return new WP_Error( 'rest_invalid_param', $error, $data );
  485. }
  486. return $valid;
  487. }
  488. /**
  489. * Return the schema array for the REST API.
  490. *
  491. * @param array $field
  492. * @return array
  493. */
  494. public function get_rest_schema( array $field ) {
  495. $schema = array(
  496. 'type' => array( 'integer', 'array', 'null' ),
  497. 'required' => ! empty( $field['required'] ),
  498. 'items' => array(
  499. 'type' => 'integer',
  500. ),
  501. );
  502. if ( empty( $field['allow_null'] ) ) {
  503. $schema['minItems'] = 1;
  504. }
  505. if ( empty( $field['multiple'] ) ) {
  506. $schema['maxItems'] = 1;
  507. }
  508. return $schema;
  509. }
  510. /**
  511. * @see \acf_field::get_rest_links()
  512. * @param mixed $value The raw (unformatted) field value.
  513. * @param int|string $post_id
  514. * @param array $field
  515. * @return array
  516. */
  517. public function get_rest_links( $value, $post_id, array $field ) {
  518. $links = array();
  519. if ( empty( $value ) ) {
  520. return $links;
  521. }
  522. foreach ( (array) $value as $object_id ) {
  523. $links[] = array(
  524. 'rel' => 'acf:user',
  525. 'href' => rest_url( '/wp/v2/users/' . $object_id ),
  526. 'embeddable' => true,
  527. );
  528. }
  529. return $links;
  530. }
  531. /**
  532. * Apply basic formatting to prepare the value for default REST output.
  533. *
  534. * @param mixed $value
  535. * @param string|int $post_id
  536. * @param array $field
  537. * @return mixed
  538. */
  539. public function format_value_for_rest( $value, $post_id, array $field ) {
  540. return acf_format_numerics( $value );
  541. }
  542. }
  543. // initialize
  544. acf_register_field_type( 'ACF_Field_User' );
  545. endif; // class_exists check