class-acf-field-page_link.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716
  1. <?php
  2. if ( ! class_exists( 'acf_field_page_link' ) ) :
  3. class acf_field_page_link extends acf_field {
  4. /*
  5. * __construct
  6. *
  7. * This function will setup the field type data
  8. *
  9. * @type function
  10. * @date 5/03/2014
  11. * @since 5.0.0
  12. *
  13. * @param n/a
  14. * @return n/a
  15. */
  16. function initialize() {
  17. // vars
  18. $this->name = 'page_link';
  19. $this->label = __( 'Page Link', 'acf' );
  20. $this->category = 'relational';
  21. $this->defaults = array(
  22. 'post_type' => array(),
  23. 'taxonomy' => array(),
  24. 'allow_null' => 0,
  25. 'multiple' => 0,
  26. 'allow_archives' => 1,
  27. );
  28. // extra
  29. add_action( 'wp_ajax_acf/fields/page_link/query', array( $this, 'ajax_query' ) );
  30. add_action( 'wp_ajax_nopriv_acf/fields/page_link/query', array( $this, 'ajax_query' ) );
  31. }
  32. /*
  33. * ajax_query
  34. *
  35. * description
  36. *
  37. * @type function
  38. * @date 24/10/13
  39. * @since 5.0.0
  40. *
  41. * @param $post_id (int)
  42. * @return $post_id (int)
  43. */
  44. function ajax_query() {
  45. // validate
  46. if ( ! acf_verify_ajax() ) {
  47. die();
  48. }
  49. // defaults
  50. $options = acf_parse_args(
  51. $_POST,
  52. array(
  53. 'post_id' => 0,
  54. 's' => '',
  55. 'field_key' => '',
  56. 'paged' => 1,
  57. )
  58. );
  59. // vars
  60. $results = array();
  61. $args = array();
  62. $s = false;
  63. $is_search = false;
  64. // paged
  65. $args['posts_per_page'] = 20;
  66. $args['paged'] = $options['paged'];
  67. // search
  68. if ( $options['s'] !== '' ) {
  69. // strip slashes (search may be integer)
  70. $s = wp_unslash( strval( $options['s'] ) );
  71. // update vars
  72. $args['s'] = $s;
  73. $is_search = true;
  74. }
  75. // load field
  76. $field = acf_get_field( $options['field_key'] );
  77. if ( ! $field ) {
  78. die();
  79. }
  80. // update $args
  81. if ( ! empty( $field['post_type'] ) ) {
  82. $args['post_type'] = acf_get_array( $field['post_type'] );
  83. } else {
  84. $args['post_type'] = acf_get_post_types();
  85. }
  86. // create tax queries
  87. if ( ! empty( $field['taxonomy'] ) ) {
  88. // append to $args
  89. $args['tax_query'] = array();
  90. // decode terms
  91. $taxonomies = acf_decode_taxonomy_terms( $field['taxonomy'] );
  92. // now create the tax queries
  93. foreach ( $taxonomies as $taxonomy => $terms ) {
  94. $args['tax_query'][] = array(
  95. 'taxonomy' => $taxonomy,
  96. 'field' => 'slug',
  97. 'terms' => $terms,
  98. );
  99. }
  100. }
  101. // filters
  102. $args = apply_filters( 'acf/fields/page_link/query', $args, $field, $options['post_id'] );
  103. $args = apply_filters( 'acf/fields/page_link/query/name=' . $field['name'], $args, $field, $options['post_id'] );
  104. $args = apply_filters( 'acf/fields/page_link/query/key=' . $field['key'], $args, $field, $options['post_id'] );
  105. // add archives to $results
  106. if ( $field['allow_archives'] && $args['paged'] == 1 ) {
  107. // Generate unique list of URLs.
  108. $links = array();
  109. $links[] = home_url();
  110. foreach ( $args['post_type'] as $post_type ) {
  111. $links[] = get_post_type_archive_link( $post_type );
  112. }
  113. $links = array_filter( $links );
  114. $links = array_unique( $links );
  115. // Convert list into choices.
  116. $children = array();
  117. foreach ( $links as $link ) {
  118. // Ignore if search does not match.
  119. if ( $is_search && stripos( $link, $s ) === false ) {
  120. continue;
  121. }
  122. $children[] = array(
  123. 'id' => $link,
  124. 'text' => $link,
  125. );
  126. }
  127. if ( $children ) {
  128. $results[] = array(
  129. 'text' => __( 'Archives', 'acf' ),
  130. 'children' => $children,
  131. );
  132. }
  133. }
  134. // get posts grouped by post type
  135. $groups = acf_get_grouped_posts( $args );
  136. // loop
  137. if ( ! empty( $groups ) ) {
  138. foreach ( array_keys( $groups ) as $group_title ) {
  139. // vars
  140. $posts = acf_extract_var( $groups, $group_title );
  141. // data
  142. $data = array(
  143. 'text' => $group_title,
  144. 'children' => array(),
  145. );
  146. // convert post objects to post titles
  147. foreach ( array_keys( $posts ) as $post_id ) {
  148. $posts[ $post_id ] = $this->get_post_title( $posts[ $post_id ], $field, $options['post_id'], $is_search );
  149. }
  150. // order posts by search
  151. if ( $is_search && empty( $args['orderby'] ) && isset( $args['s'] ) ) {
  152. $posts = acf_order_by_search( $posts, $args['s'] );
  153. }
  154. // append to $data
  155. foreach ( array_keys( $posts ) as $post_id ) {
  156. $data['children'][] = $this->get_post_result( $post_id, $posts[ $post_id ] );
  157. }
  158. // append to $results
  159. $results[] = $data;
  160. }
  161. }
  162. // return
  163. acf_send_ajax_results(
  164. array(
  165. 'results' => $results,
  166. 'limit' => $args['posts_per_page'],
  167. )
  168. );
  169. }
  170. /*
  171. * get_post_result
  172. *
  173. * This function will return an array containing id, text and maybe description data
  174. *
  175. * @type function
  176. * @date 7/07/2016
  177. * @since 5.4.0
  178. *
  179. * @param $id (mixed)
  180. * @param $text (string)
  181. * @return (array)
  182. */
  183. function get_post_result( $id, $text ) {
  184. // vars
  185. $result = array(
  186. 'id' => $id,
  187. 'text' => $text,
  188. );
  189. // look for parent
  190. $search = '| ' . __( 'Parent', 'acf' ) . ':';
  191. $pos = strpos( $text, $search );
  192. if ( $pos !== false ) {
  193. $result['description'] = substr( $text, $pos + 2 );
  194. $result['text'] = substr( $text, 0, $pos );
  195. }
  196. // return
  197. return $result;
  198. }
  199. /*
  200. * get_post_title
  201. *
  202. * This function returns the HTML for a result
  203. *
  204. * @type function
  205. * @date 1/11/2013
  206. * @since 5.0.0
  207. *
  208. * @param $post (object)
  209. * @param $field (array)
  210. * @param $post_id (int) the post_id to which this value is saved to
  211. * @return (string)
  212. */
  213. function get_post_title( $post, $field, $post_id = 0, $is_search = 0 ) {
  214. // get post_id
  215. if ( ! $post_id ) {
  216. $post_id = acf_get_form_data( 'post_id' );
  217. }
  218. // vars
  219. $title = acf_get_post_title( $post, $is_search );
  220. // filters
  221. $title = apply_filters( 'acf/fields/page_link/result', $title, $post, $field, $post_id );
  222. $title = apply_filters( 'acf/fields/page_link/result/name=' . $field['_name'], $title, $post, $field, $post_id );
  223. $title = apply_filters( 'acf/fields/page_link/result/key=' . $field['key'], $title, $post, $field, $post_id );
  224. // return
  225. return $title;
  226. }
  227. /*
  228. * get_posts
  229. *
  230. * This function will return an array of posts for a given field value
  231. *
  232. * @type function
  233. * @date 13/06/2014
  234. * @since 5.0.0
  235. *
  236. * @param $value (array)
  237. * @return $value
  238. */
  239. function get_posts( $value, $field ) {
  240. // force value to array
  241. $value = acf_get_array( $value );
  242. // get selected post ID's
  243. $post__in = array();
  244. foreach ( $value as $k => $v ) {
  245. if ( is_numeric( $v ) ) {
  246. // append to $post__in
  247. $post__in[] = (int) $v;
  248. }
  249. }
  250. // bail early if no posts
  251. if ( empty( $post__in ) ) {
  252. return $value;
  253. }
  254. // get posts
  255. $posts = acf_get_posts(
  256. array(
  257. 'post__in' => $post__in,
  258. 'post_type' => $field['post_type'],
  259. )
  260. );
  261. // override value with post
  262. $return = array();
  263. // append to $return
  264. foreach ( $value as $k => $v ) {
  265. if ( is_numeric( $v ) ) {
  266. // extract first post
  267. $post = array_shift( $posts );
  268. // append
  269. if ( $post ) {
  270. $return[] = $post;
  271. }
  272. } else {
  273. $return[] = $v;
  274. }
  275. }
  276. // return
  277. return $return;
  278. }
  279. /*
  280. * render_field()
  281. *
  282. * Create the HTML interface for your field
  283. *
  284. * @param $field - an array holding all the field's data
  285. *
  286. * @type action
  287. * @since 3.6
  288. * @date 23/01/13
  289. */
  290. function render_field( $field ) {
  291. // Change Field into a select
  292. $field['type'] = 'select';
  293. $field['ui'] = 1;
  294. $field['ajax'] = 1;
  295. $field['choices'] = array();
  296. // populate choices if value exists
  297. if ( ! empty( $field['value'] ) ) {
  298. // get posts
  299. $posts = $this->get_posts( $field['value'], $field );
  300. // set choices
  301. if ( ! empty( $posts ) ) {
  302. foreach ( array_keys( $posts ) as $i ) {
  303. // vars
  304. $post = acf_extract_var( $posts, $i );
  305. if ( is_object( $post ) ) {
  306. // append to choices
  307. $field['choices'][ $post->ID ] = $this->get_post_title( $post, $field );
  308. } else {
  309. // append to choices
  310. $field['choices'][ $post ] = $post;
  311. }
  312. }
  313. }
  314. }
  315. // render
  316. acf_render_field( $field );
  317. }
  318. /*
  319. * render_field_settings()
  320. *
  321. * Create extra options for your field. This is rendered when editing a field.
  322. * The value of $field['name'] can be used (like bellow) to save extra data to the $field
  323. *
  324. * @type action
  325. * @since 3.6
  326. * @date 23/01/13
  327. *
  328. * @param $field - an array holding all the field's data
  329. */
  330. function render_field_settings( $field ) {
  331. acf_render_field_setting(
  332. $field,
  333. array(
  334. 'label' => __( 'Filter by Post Type', 'acf' ),
  335. 'instructions' => '',
  336. 'type' => 'select',
  337. 'name' => 'post_type',
  338. 'choices' => acf_get_pretty_post_types(),
  339. 'multiple' => 1,
  340. 'ui' => 1,
  341. 'allow_null' => 1,
  342. 'placeholder' => __( 'All post types', 'acf' ),
  343. )
  344. );
  345. acf_render_field_setting(
  346. $field,
  347. array(
  348. 'label' => __( 'Filter by Taxonomy', 'acf' ),
  349. 'instructions' => '',
  350. 'type' => 'select',
  351. 'name' => 'taxonomy',
  352. 'choices' => acf_get_taxonomy_terms(),
  353. 'multiple' => 1,
  354. 'ui' => 1,
  355. 'allow_null' => 1,
  356. 'placeholder' => __( 'All taxonomies', 'acf' ),
  357. )
  358. );
  359. acf_render_field_setting(
  360. $field,
  361. array(
  362. 'label' => __( 'Allow Archives URLs', 'acf' ),
  363. 'instructions' => '',
  364. 'name' => 'allow_archives',
  365. 'type' => 'true_false',
  366. 'ui' => 1,
  367. )
  368. );
  369. acf_render_field_setting(
  370. $field,
  371. array(
  372. 'label' => __( 'Select multiple values?', 'acf' ),
  373. 'instructions' => '',
  374. 'name' => 'multiple',
  375. 'type' => 'true_false',
  376. 'ui' => 1,
  377. )
  378. );
  379. }
  380. /**
  381. * Renders the field settings used in the "Validation" tab.
  382. *
  383. * @since 6.0
  384. *
  385. * @param array $field The field settings array.
  386. * @return void
  387. */
  388. function render_field_validation_settings( $field ) {
  389. acf_render_field_setting(
  390. $field,
  391. array(
  392. 'label' => __( 'Allow Null?', 'acf' ),
  393. 'instructions' => '',
  394. 'name' => 'allow_null',
  395. 'type' => 'true_false',
  396. 'ui' => 1,
  397. )
  398. );
  399. }
  400. /*
  401. * format_value()
  402. *
  403. * This filter is appied to the $value after it is loaded from the db and before it is returned to the template
  404. *
  405. * @type filter
  406. * @since 3.6
  407. * @date 23/01/13
  408. *
  409. * @param $value (mixed) the value which was loaded from the database
  410. * @param $post_id (mixed) the $post_id from which the value was loaded
  411. * @param $field (array) the field array holding all the field options
  412. *
  413. * @return $value (mixed) the modified value
  414. */
  415. function format_value( $value, $post_id, $field ) {
  416. // ACF4 null
  417. if ( $value === 'null' ) {
  418. return false;
  419. }
  420. // bail early if no value
  421. if ( empty( $value ) ) {
  422. return $value;
  423. }
  424. // get posts
  425. $value = $this->get_posts( $value, $field );
  426. // set choices
  427. foreach ( array_keys( $value ) as $i ) {
  428. // vars
  429. $post = acf_extract_var( $value, $i );
  430. // convert $post to permalink
  431. if ( is_object( $post ) ) {
  432. $post = get_permalink( $post );
  433. }
  434. // append back to $value
  435. $value[ $i ] = $post;
  436. }
  437. // convert back from array if neccessary
  438. if ( ! $field['multiple'] ) {
  439. $value = array_shift( $value );
  440. }
  441. // return value
  442. return $value;
  443. }
  444. /*
  445. * update_value()
  446. *
  447. * This filter is appied to the $value before it is updated in the db
  448. *
  449. * @type filter
  450. * @since 3.6
  451. * @date 23/01/13
  452. *
  453. * @param $value - the value which will be saved in the database
  454. * @param $post_id - the $post_id of which the value will be saved
  455. * @param $field - the field array holding all the field options
  456. *
  457. * @return $value - the modified value
  458. */
  459. function update_value( $value, $post_id, $field ) {
  460. // Bail early if no value.
  461. if ( empty( $value ) ) {
  462. return $value;
  463. }
  464. // Format array of values.
  465. // - ensure each value is an id.
  466. // - Parse each id as string for SQL LIKE queries.
  467. if ( acf_is_sequential_array( $value ) ) {
  468. $value = array_map( 'acf_maybe_idval', $value );
  469. $value = array_map( 'strval', $value );
  470. // Parse single value for id.
  471. } else {
  472. $value = acf_maybe_idval( $value );
  473. }
  474. // Return value.
  475. return $value;
  476. }
  477. /**
  478. * Validates page link fields updated via the REST API.
  479. *
  480. * @param bool $valid
  481. * @param int $value
  482. * @param array $field
  483. *
  484. * @return bool|WP_Error
  485. */
  486. public function validate_rest_value( $valid, $value, $field ) {
  487. return acf_get_field_type( 'post_object' )->validate_rest_value( $valid, $value, $field );
  488. }
  489. /**
  490. * Return the schema array for the REST API.
  491. *
  492. * @param array $field
  493. * @return array
  494. */
  495. public function get_rest_schema( array $field ) {
  496. $schema = array(
  497. 'type' => array( 'integer', 'array', 'null' ),
  498. 'required' => ! empty( $field['required'] ),
  499. 'items' => array(
  500. 'type' => array( 'integer' ),
  501. ),
  502. );
  503. if ( empty( $field['allow_null'] ) ) {
  504. $schema['minItems'] = 1;
  505. }
  506. if ( ! empty( $field['allow_archives'] ) ) {
  507. $schema['type'][] = 'string';
  508. $schema['items']['type'][] = 'string';
  509. }
  510. if ( empty( $field['multiple'] ) ) {
  511. $schema['maxItems'] = 1;
  512. }
  513. return $schema;
  514. }
  515. /**
  516. * @see \acf_field::get_rest_links()
  517. * @param mixed $value The raw (unformatted) field value.
  518. * @param int|string $post_id
  519. * @param array $field
  520. * @return array
  521. */
  522. public function get_rest_links( $value, $post_id, array $field ) {
  523. $links = array();
  524. if ( empty( $value ) ) {
  525. return $links;
  526. }
  527. foreach ( (array) $value as $object_id ) {
  528. if ( ! $post_type = get_post_type( $object_id ) or ! $post_type = get_post_type_object( $post_type ) ) {
  529. continue;
  530. }
  531. $rest_base = acf_get_object_type_rest_base( $post_type );
  532. $links[] = array(
  533. 'rel' => $post_type->name === 'attachment' ? 'acf:attachment' : 'acf:post',
  534. 'href' => rest_url( sprintf( '/wp/v2/%s/%s', $rest_base, $object_id ) ),
  535. 'embeddable' => true,
  536. );
  537. }
  538. return $links;
  539. }
  540. /**
  541. * Apply basic formatting to prepare the value for default REST output.
  542. *
  543. * @param mixed $value
  544. * @param string|int $post_id
  545. * @param array $field
  546. * @return mixed
  547. */
  548. public function format_value_for_rest( $value, $post_id, array $field ) {
  549. return acf_format_numerics( $value );
  550. }
  551. }
  552. // initialize
  553. acf_register_field_type( 'acf_field_page_link' );
  554. endif; // class_exists check