abstract-wc-rest-terms-controller.php 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791
  1. <?php
  2. /**
  3. * Abstract Rest Terms Controller
  4. *
  5. * @package WooCommerce/Abstracts
  6. * @version 3.3.0
  7. */
  8. if ( ! defined( 'ABSPATH' ) ) {
  9. exit;
  10. }
  11. /**
  12. * Terms controller class.
  13. */
  14. abstract class WC_REST_Terms_Controller extends WC_REST_Controller {
  15. /**
  16. * Route base.
  17. *
  18. * @var string
  19. */
  20. protected $rest_base = '';
  21. /**
  22. * Taxonomy.
  23. *
  24. * @var string
  25. */
  26. protected $taxonomy = '';
  27. /**
  28. * Register the routes for terms.
  29. */
  30. public function register_routes() {
  31. register_rest_route(
  32. $this->namespace, '/' . $this->rest_base, array(
  33. array(
  34. 'methods' => WP_REST_Server::READABLE,
  35. 'callback' => array( $this, 'get_items' ),
  36. 'permission_callback' => array( $this, 'get_items_permissions_check' ),
  37. 'args' => $this->get_collection_params(),
  38. ),
  39. array(
  40. 'methods' => WP_REST_Server::CREATABLE,
  41. 'callback' => array( $this, 'create_item' ),
  42. 'permission_callback' => array( $this, 'create_item_permissions_check' ),
  43. 'args' => array_merge(
  44. $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), array(
  45. 'name' => array(
  46. 'type' => 'string',
  47. 'description' => __( 'Name for the resource.', 'woocommerce' ),
  48. 'required' => true,
  49. ),
  50. )
  51. ),
  52. ),
  53. 'schema' => array( $this, 'get_public_item_schema' ),
  54. )
  55. );
  56. register_rest_route(
  57. $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
  58. 'args' => array(
  59. 'id' => array(
  60. 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
  61. 'type' => 'integer',
  62. ),
  63. ),
  64. array(
  65. 'methods' => WP_REST_Server::READABLE,
  66. 'callback' => array( $this, 'get_item' ),
  67. 'permission_callback' => array( $this, 'get_item_permissions_check' ),
  68. 'args' => array(
  69. 'context' => $this->get_context_param( array( 'default' => 'view' ) ),
  70. ),
  71. ),
  72. array(
  73. 'methods' => WP_REST_Server::EDITABLE,
  74. 'callback' => array( $this, 'update_item' ),
  75. 'permission_callback' => array( $this, 'update_item_permissions_check' ),
  76. 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
  77. ),
  78. array(
  79. 'methods' => WP_REST_Server::DELETABLE,
  80. 'callback' => array( $this, 'delete_item' ),
  81. 'permission_callback' => array( $this, 'delete_item_permissions_check' ),
  82. 'args' => array(
  83. 'force' => array(
  84. 'default' => false,
  85. 'type' => 'boolean',
  86. 'description' => __( 'Required to be true, as resource does not support trashing.', 'woocommerce' ),
  87. ),
  88. ),
  89. ),
  90. 'schema' => array( $this, 'get_public_item_schema' ),
  91. )
  92. );
  93. register_rest_route(
  94. $this->namespace, '/' . $this->rest_base . '/batch', array(
  95. array(
  96. 'methods' => WP_REST_Server::EDITABLE,
  97. 'callback' => array( $this, 'batch_items' ),
  98. 'permission_callback' => array( $this, 'batch_items_permissions_check' ),
  99. 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
  100. ),
  101. 'schema' => array( $this, 'get_public_batch_schema' ),
  102. )
  103. );
  104. }
  105. /**
  106. * Check if a given request has access to read the terms.
  107. *
  108. * @param WP_REST_Request $request Full details about the request.
  109. * @return WP_Error|boolean
  110. */
  111. public function get_items_permissions_check( $request ) {
  112. $permissions = $this->check_permissions( $request, 'read' );
  113. if ( is_wp_error( $permissions ) ) {
  114. return $permissions;
  115. }
  116. if ( ! $permissions ) {
  117. return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
  118. }
  119. return true;
  120. }
  121. /**
  122. * Check if a given request has access to create a term.
  123. *
  124. * @param WP_REST_Request $request Full details about the request.
  125. * @return WP_Error|boolean
  126. */
  127. public function create_item_permissions_check( $request ) {
  128. $permissions = $this->check_permissions( $request, 'create' );
  129. if ( is_wp_error( $permissions ) ) {
  130. return $permissions;
  131. }
  132. if ( ! $permissions ) {
  133. return new WP_Error( 'woocommerce_rest_cannot_create', __( 'Sorry, you are not allowed to create resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
  134. }
  135. return true;
  136. }
  137. /**
  138. * Check if a given request has access to read a term.
  139. *
  140. * @param WP_REST_Request $request Full details about the request.
  141. * @return WP_Error|boolean
  142. */
  143. public function get_item_permissions_check( $request ) {
  144. $permissions = $this->check_permissions( $request, 'read' );
  145. if ( is_wp_error( $permissions ) ) {
  146. return $permissions;
  147. }
  148. if ( ! $permissions ) {
  149. return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot view this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
  150. }
  151. return true;
  152. }
  153. /**
  154. * Check if a given request has access to update a term.
  155. *
  156. * @param WP_REST_Request $request Full details about the request.
  157. * @return WP_Error|boolean
  158. */
  159. public function update_item_permissions_check( $request ) {
  160. $permissions = $this->check_permissions( $request, 'edit' );
  161. if ( is_wp_error( $permissions ) ) {
  162. return $permissions;
  163. }
  164. if ( ! $permissions ) {
  165. return new WP_Error( 'woocommerce_rest_cannot_edit', __( 'Sorry, you are not allowed to edit this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
  166. }
  167. return true;
  168. }
  169. /**
  170. * Check if a given request has access to delete a term.
  171. *
  172. * @param WP_REST_Request $request Full details about the request.
  173. * @return WP_Error|boolean
  174. */
  175. public function delete_item_permissions_check( $request ) {
  176. $permissions = $this->check_permissions( $request, 'delete' );
  177. if ( is_wp_error( $permissions ) ) {
  178. return $permissions;
  179. }
  180. if ( ! $permissions ) {
  181. return new WP_Error( 'woocommerce_rest_cannot_delete', __( 'Sorry, you are not allowed to delete this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
  182. }
  183. return true;
  184. }
  185. /**
  186. * Check if a given request has access batch create, update and delete items.
  187. *
  188. * @param WP_REST_Request $request Full details about the request.
  189. * @return boolean|WP_Error
  190. */
  191. public function batch_items_permissions_check( $request ) {
  192. $permissions = $this->check_permissions( $request, 'batch' );
  193. if ( is_wp_error( $permissions ) ) {
  194. return $permissions;
  195. }
  196. if ( ! $permissions ) {
  197. return new WP_Error( 'woocommerce_rest_cannot_batch', __( 'Sorry, you are not allowed to batch manipulate this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
  198. }
  199. return true;
  200. }
  201. /**
  202. * Check permissions.
  203. *
  204. * @param WP_REST_Request $request Full details about the request.
  205. * @param string $context Request context.
  206. * @return bool|WP_Error
  207. */
  208. protected function check_permissions( $request, $context = 'read' ) {
  209. // Get taxonomy.
  210. $taxonomy = $this->get_taxonomy( $request );
  211. if ( ! $taxonomy || ! taxonomy_exists( $taxonomy ) ) {
  212. return new WP_Error( 'woocommerce_rest_taxonomy_invalid', __( 'Taxonomy does not exist.', 'woocommerce' ), array( 'status' => 404 ) );
  213. }
  214. // Check permissions for a single term.
  215. $id = intval( $request['id'] );
  216. if ( $id ) {
  217. $term = get_term( $id, $taxonomy );
  218. if ( is_wp_error( $term ) || ! $term || $term->taxonomy !== $taxonomy ) {
  219. return new WP_Error( 'woocommerce_rest_term_invalid', __( 'Resource does not exist.', 'woocommerce' ), array( 'status' => 404 ) );
  220. }
  221. return wc_rest_check_product_term_permissions( $taxonomy, $context, $term->term_id );
  222. }
  223. return wc_rest_check_product_term_permissions( $taxonomy, $context );
  224. }
  225. /**
  226. * Get terms associated with a taxonomy.
  227. *
  228. * @param WP_REST_Request $request Full details about the request.
  229. * @return WP_REST_Response|WP_Error
  230. */
  231. public function get_items( $request ) {
  232. $taxonomy = $this->get_taxonomy( $request );
  233. $prepared_args = array(
  234. 'exclude' => $request['exclude'],
  235. 'include' => $request['include'],
  236. 'order' => $request['order'],
  237. 'orderby' => $request['orderby'],
  238. 'product' => $request['product'],
  239. 'hide_empty' => $request['hide_empty'],
  240. 'number' => $request['per_page'],
  241. 'search' => $request['search'],
  242. 'slug' => $request['slug'],
  243. );
  244. if ( ! empty( $request['offset'] ) ) {
  245. $prepared_args['offset'] = $request['offset'];
  246. } else {
  247. $prepared_args['offset'] = ( $request['page'] - 1 ) * $prepared_args['number'];
  248. }
  249. $taxonomy_obj = get_taxonomy( $taxonomy );
  250. if ( $taxonomy_obj->hierarchical && isset( $request['parent'] ) ) {
  251. if ( 0 === $request['parent'] ) {
  252. // Only query top-level terms.
  253. $prepared_args['parent'] = 0;
  254. } else {
  255. if ( $request['parent'] ) {
  256. $prepared_args['parent'] = $request['parent'];
  257. }
  258. }
  259. }
  260. /**
  261. * Filter the query arguments, before passing them to `get_terms()`.
  262. *
  263. * Enables adding extra arguments or setting defaults for a terms
  264. * collection request.
  265. *
  266. * @see https://developer.wordpress.org/reference/functions/get_terms/
  267. *
  268. * @param array $prepared_args Array of arguments to be
  269. * passed to get_terms.
  270. * @param WP_REST_Request $request The current request.
  271. */
  272. $prepared_args = apply_filters( "woocommerce_rest_{$taxonomy}_query", $prepared_args, $request );
  273. if ( ! empty( $prepared_args['product'] ) ) {
  274. $query_result = $this->get_terms_for_product( $prepared_args, $request );
  275. $total_terms = $this->total_terms;
  276. } else {
  277. $query_result = get_terms( $taxonomy, $prepared_args );
  278. $count_args = $prepared_args;
  279. unset( $count_args['number'] );
  280. unset( $count_args['offset'] );
  281. $total_terms = wp_count_terms( $taxonomy, $count_args );
  282. // Ensure we don't return results when offset is out of bounds.
  283. // See https://core.trac.wordpress.org/ticket/35935.
  284. if ( $prepared_args['offset'] >= $total_terms ) {
  285. $query_result = array();
  286. }
  287. // wp_count_terms can return a falsy value when the term has no children.
  288. if ( ! $total_terms ) {
  289. $total_terms = 0;
  290. }
  291. }
  292. $response = array();
  293. foreach ( $query_result as $term ) {
  294. $data = $this->prepare_item_for_response( $term, $request );
  295. $response[] = $this->prepare_response_for_collection( $data );
  296. }
  297. $response = rest_ensure_response( $response );
  298. // Store pagination values for headers then unset for count query.
  299. $per_page = (int) $prepared_args['number'];
  300. $page = ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 );
  301. $response->header( 'X-WP-Total', (int) $total_terms );
  302. $max_pages = ceil( $total_terms / $per_page );
  303. $response->header( 'X-WP-TotalPages', (int) $max_pages );
  304. $base = str_replace( '(?P<attribute_id>[\d]+)', $request['attribute_id'], $this->rest_base );
  305. $base = add_query_arg( $request->get_query_params(), rest_url( '/' . $this->namespace . '/' . $base ) );
  306. if ( $page > 1 ) {
  307. $prev_page = $page - 1;
  308. if ( $prev_page > $max_pages ) {
  309. $prev_page = $max_pages;
  310. }
  311. $prev_link = add_query_arg( 'page', $prev_page, $base );
  312. $response->link_header( 'prev', $prev_link );
  313. }
  314. if ( $max_pages > $page ) {
  315. $next_page = $page + 1;
  316. $next_link = add_query_arg( 'page', $next_page, $base );
  317. $response->link_header( 'next', $next_link );
  318. }
  319. return $response;
  320. }
  321. /**
  322. * Create a single term for a taxonomy.
  323. *
  324. * @param WP_REST_Request $request Full details about the request.
  325. * @return WP_REST_Request|WP_Error
  326. */
  327. public function create_item( $request ) {
  328. $taxonomy = $this->get_taxonomy( $request );
  329. $name = $request['name'];
  330. $args = array();
  331. $schema = $this->get_item_schema();
  332. if ( ! empty( $schema['properties']['description'] ) && isset( $request['description'] ) ) {
  333. $args['description'] = $request['description'];
  334. }
  335. if ( isset( $request['slug'] ) ) {
  336. $args['slug'] = $request['slug'];
  337. }
  338. if ( isset( $request['parent'] ) ) {
  339. if ( ! is_taxonomy_hierarchical( $taxonomy ) ) {
  340. return new WP_Error( 'woocommerce_rest_taxonomy_not_hierarchical', __( 'Can not set resource parent, taxonomy is not hierarchical.', 'woocommerce' ), array( 'status' => 400 ) );
  341. }
  342. $args['parent'] = $request['parent'];
  343. }
  344. $term = wp_insert_term( $name, $taxonomy, $args );
  345. if ( is_wp_error( $term ) ) {
  346. $error_data = array( 'status' => 400 );
  347. // If we're going to inform the client that the term exists,
  348. // give them the identifier they can actually use.
  349. $term_id = $term->get_error_data( 'term_exists' );
  350. if ( $term_id ) {
  351. $error_data['resource_id'] = $term_id;
  352. }
  353. return new WP_Error( $term->get_error_code(), $term->get_error_message(), $error_data );
  354. }
  355. $term = get_term( $term['term_id'], $taxonomy );
  356. $this->update_additional_fields_for_object( $term, $request );
  357. // Add term data.
  358. $meta_fields = $this->update_term_meta_fields( $term, $request );
  359. if ( is_wp_error( $meta_fields ) ) {
  360. wp_delete_term( $term->term_id, $taxonomy );
  361. return $meta_fields;
  362. }
  363. /**
  364. * Fires after a single term is created or updated via the REST API.
  365. *
  366. * @param WP_Term $term Inserted Term object.
  367. * @param WP_REST_Request $request Request object.
  368. * @param boolean $creating True when creating term, false when updating.
  369. */
  370. do_action( "woocommerce_rest_insert_{$taxonomy}", $term, $request, true );
  371. $request->set_param( 'context', 'edit' );
  372. $response = $this->prepare_item_for_response( $term, $request );
  373. $response = rest_ensure_response( $response );
  374. $response->set_status( 201 );
  375. $base = '/' . $this->namespace . '/' . $this->rest_base;
  376. if ( ! empty( $request['attribute_id'] ) ) {
  377. $base = str_replace( '(?P<attribute_id>[\d]+)', (int) $request['attribute_id'], $base );
  378. }
  379. $response->header( 'Location', rest_url( $base . '/' . $term->term_id ) );
  380. return $response;
  381. }
  382. /**
  383. * Get a single term from a taxonomy.
  384. *
  385. * @param WP_REST_Request $request Full details about the request.
  386. * @return WP_REST_Request|WP_Error
  387. */
  388. public function get_item( $request ) {
  389. $taxonomy = $this->get_taxonomy( $request );
  390. $term = get_term( (int) $request['id'], $taxonomy );
  391. if ( is_wp_error( $term ) ) {
  392. return $term;
  393. }
  394. $response = $this->prepare_item_for_response( $term, $request );
  395. return rest_ensure_response( $response );
  396. }
  397. /**
  398. * Update a single term from a taxonomy.
  399. *
  400. * @param WP_REST_Request $request Full details about the request.
  401. * @return WP_REST_Request|WP_Error
  402. */
  403. public function update_item( $request ) {
  404. $taxonomy = $this->get_taxonomy( $request );
  405. $term = get_term( (int) $request['id'], $taxonomy );
  406. $schema = $this->get_item_schema();
  407. $prepared_args = array();
  408. if ( isset( $request['name'] ) ) {
  409. $prepared_args['name'] = $request['name'];
  410. }
  411. if ( ! empty( $schema['properties']['description'] ) && isset( $request['description'] ) ) {
  412. $prepared_args['description'] = $request['description'];
  413. }
  414. if ( isset( $request['slug'] ) ) {
  415. $prepared_args['slug'] = $request['slug'];
  416. }
  417. if ( isset( $request['parent'] ) ) {
  418. if ( ! is_taxonomy_hierarchical( $taxonomy ) ) {
  419. return new WP_Error( 'woocommerce_rest_taxonomy_not_hierarchical', __( 'Can not set resource parent, taxonomy is not hierarchical.', 'woocommerce' ), array( 'status' => 400 ) );
  420. }
  421. $prepared_args['parent'] = $request['parent'];
  422. }
  423. // Only update the term if we haz something to update.
  424. if ( ! empty( $prepared_args ) ) {
  425. $update = wp_update_term( $term->term_id, $term->taxonomy, $prepared_args );
  426. if ( is_wp_error( $update ) ) {
  427. return $update;
  428. }
  429. }
  430. $term = get_term( (int) $request['id'], $taxonomy );
  431. $this->update_additional_fields_for_object( $term, $request );
  432. // Update term data.
  433. $meta_fields = $this->update_term_meta_fields( $term, $request );
  434. if ( is_wp_error( $meta_fields ) ) {
  435. return $meta_fields;
  436. }
  437. /**
  438. * Fires after a single term is created or updated via the REST API.
  439. *
  440. * @param WP_Term $term Inserted Term object.
  441. * @param WP_REST_Request $request Request object.
  442. * @param boolean $creating True when creating term, false when updating.
  443. */
  444. do_action( "woocommerce_rest_insert_{$taxonomy}", $term, $request, false );
  445. $request->set_param( 'context', 'edit' );
  446. $response = $this->prepare_item_for_response( $term, $request );
  447. return rest_ensure_response( $response );
  448. }
  449. /**
  450. * Delete a single term from a taxonomy.
  451. *
  452. * @param WP_REST_Request $request Full details about the request.
  453. * @return WP_REST_Response|WP_Error
  454. */
  455. public function delete_item( $request ) {
  456. $taxonomy = $this->get_taxonomy( $request );
  457. $force = isset( $request['force'] ) ? (bool) $request['force'] : false;
  458. // We don't support trashing for this type, error out.
  459. if ( ! $force ) {
  460. return new WP_Error( 'woocommerce_rest_trash_not_supported', __( 'Resource does not support trashing.', 'woocommerce' ), array( 'status' => 501 ) );
  461. }
  462. $term = get_term( (int) $request['id'], $taxonomy );
  463. $request->set_param( 'context', 'edit' );
  464. $response = $this->prepare_item_for_response( $term, $request );
  465. $retval = wp_delete_term( $term->term_id, $term->taxonomy );
  466. if ( ! $retval ) {
  467. return new WP_Error( 'woocommerce_rest_cannot_delete', __( 'The resource cannot be deleted.', 'woocommerce' ), array( 'status' => 500 ) );
  468. }
  469. /**
  470. * Fires after a single term is deleted via the REST API.
  471. *
  472. * @param WP_Term $term The deleted term.
  473. * @param WP_REST_Response $response The response data.
  474. * @param WP_REST_Request $request The request sent to the API.
  475. */
  476. do_action( "woocommerce_rest_delete_{$taxonomy}", $term, $response, $request );
  477. return $response;
  478. }
  479. /**
  480. * Prepare links for the request.
  481. *
  482. * @param object $term Term object.
  483. * @param WP_REST_Request $request Full details about the request.
  484. * @return array Links for the given term.
  485. */
  486. protected function prepare_links( $term, $request ) {
  487. $base = '/' . $this->namespace . '/' . $this->rest_base;
  488. if ( ! empty( $request['attribute_id'] ) ) {
  489. $base = str_replace( '(?P<attribute_id>[\d]+)', (int) $request['attribute_id'], $base );
  490. }
  491. $links = array(
  492. 'self' => array(
  493. 'href' => rest_url( trailingslashit( $base ) . $term->term_id ),
  494. ),
  495. 'collection' => array(
  496. 'href' => rest_url( $base ),
  497. ),
  498. );
  499. if ( $term->parent ) {
  500. $parent_term = get_term( (int) $term->parent, $term->taxonomy );
  501. if ( $parent_term ) {
  502. $links['up'] = array(
  503. 'href' => rest_url( trailingslashit( $base ) . $parent_term->term_id ),
  504. );
  505. }
  506. }
  507. return $links;
  508. }
  509. /**
  510. * Update term meta fields.
  511. *
  512. * @param WP_Term $term Term object.
  513. * @param WP_REST_Request $request Full details about the request.
  514. * @return bool|WP_Error
  515. */
  516. protected function update_term_meta_fields( $term, $request ) {
  517. return true;
  518. }
  519. /**
  520. * Get the terms attached to a product.
  521. *
  522. * This is an alternative to `get_terms()` that uses `get_the_terms()`
  523. * instead, which hits the object cache. There are a few things not
  524. * supported, notably `include`, `exclude`. In `self::get_items()` these
  525. * are instead treated as a full query.
  526. *
  527. * @param array $prepared_args Arguments for `get_terms()`.
  528. * @param WP_REST_Request $request Full details about the request.
  529. * @return array List of term objects. (Total count in `$this->total_terms`).
  530. */
  531. protected function get_terms_for_product( $prepared_args, $request ) {
  532. $taxonomy = $this->get_taxonomy( $request );
  533. $query_result = get_the_terms( $prepared_args['product'], $taxonomy );
  534. if ( empty( $query_result ) ) {
  535. $this->total_terms = 0;
  536. return array();
  537. }
  538. // get_items() verifies that we don't have `include` set, and default.
  539. // ordering is by `name`.
  540. if ( ! in_array( $prepared_args['orderby'], array( 'name', 'none', 'include' ), true ) ) {
  541. switch ( $prepared_args['orderby'] ) {
  542. case 'id':
  543. $this->sort_column = 'term_id';
  544. break;
  545. case 'slug':
  546. case 'term_group':
  547. case 'description':
  548. case 'count':
  549. $this->sort_column = $prepared_args['orderby'];
  550. break;
  551. }
  552. usort( $query_result, array( $this, 'compare_terms' ) );
  553. }
  554. if ( strtolower( $prepared_args['order'] ) !== 'asc' ) {
  555. $query_result = array_reverse( $query_result );
  556. }
  557. // Pagination.
  558. $this->total_terms = count( $query_result );
  559. $query_result = array_slice( $query_result, $prepared_args['offset'], $prepared_args['number'] );
  560. return $query_result;
  561. }
  562. /**
  563. * Comparison function for sorting terms by a column.
  564. *
  565. * Uses `$this->sort_column` to determine field to sort by.
  566. *
  567. * @param stdClass $left Term object.
  568. * @param stdClass $right Term object.
  569. * @return int <0 if left is higher "priority" than right, 0 if equal, >0 if right is higher "priority" than left.
  570. */
  571. protected function compare_terms( $left, $right ) {
  572. $col = $this->sort_column;
  573. $left_val = $left->$col;
  574. $right_val = $right->$col;
  575. if ( is_int( $left_val ) && is_int( $right_val ) ) {
  576. return $left_val - $right_val;
  577. }
  578. return strcmp( $left_val, $right_val );
  579. }
  580. /**
  581. * Get the query params for collections
  582. *
  583. * @return array
  584. */
  585. public function get_collection_params() {
  586. $params = parent::get_collection_params();
  587. if ( '' !== $this->taxonomy && taxonomy_exists( $this->taxonomy ) ) {
  588. $taxonomy = get_taxonomy( $this->taxonomy );
  589. } else {
  590. $taxonomy = new stdClass();
  591. $taxonomy->hierarchical = true;
  592. }
  593. $params['context']['default'] = 'view';
  594. $params['exclude'] = array(
  595. 'description' => __( 'Ensure result set excludes specific ids.', 'woocommerce' ),
  596. 'type' => 'array',
  597. 'items' => array(
  598. 'type' => 'integer',
  599. ),
  600. 'default' => array(),
  601. 'sanitize_callback' => 'wp_parse_id_list',
  602. );
  603. $params['include'] = array(
  604. 'description' => __( 'Limit result set to specific ids.', 'woocommerce' ),
  605. 'type' => 'array',
  606. 'items' => array(
  607. 'type' => 'integer',
  608. ),
  609. 'default' => array(),
  610. 'sanitize_callback' => 'wp_parse_id_list',
  611. );
  612. if ( ! $taxonomy->hierarchical ) {
  613. $params['offset'] = array(
  614. 'description' => __( 'Offset the result set by a specific number of items.', 'woocommerce' ),
  615. 'type' => 'integer',
  616. 'sanitize_callback' => 'absint',
  617. 'validate_callback' => 'rest_validate_request_arg',
  618. );
  619. }
  620. $params['order'] = array(
  621. 'description' => __( 'Order sort attribute ascending or descending.', 'woocommerce' ),
  622. 'type' => 'string',
  623. 'sanitize_callback' => 'sanitize_key',
  624. 'default' => 'asc',
  625. 'enum' => array(
  626. 'asc',
  627. 'desc',
  628. ),
  629. 'validate_callback' => 'rest_validate_request_arg',
  630. );
  631. $params['orderby'] = array(
  632. 'description' => __( 'Sort collection by resource attribute.', 'woocommerce' ),
  633. 'type' => 'string',
  634. 'sanitize_callback' => 'sanitize_key',
  635. 'default' => 'name',
  636. 'enum' => array(
  637. 'id',
  638. 'include',
  639. 'name',
  640. 'slug',
  641. 'term_group',
  642. 'description',
  643. 'count',
  644. ),
  645. 'validate_callback' => 'rest_validate_request_arg',
  646. );
  647. $params['hide_empty'] = array(
  648. 'description' => __( 'Whether to hide resources not assigned to any products.', 'woocommerce' ),
  649. 'type' => 'boolean',
  650. 'default' => false,
  651. 'validate_callback' => 'rest_validate_request_arg',
  652. );
  653. if ( $taxonomy->hierarchical ) {
  654. $params['parent'] = array(
  655. 'description' => __( 'Limit result set to resources assigned to a specific parent.', 'woocommerce' ),
  656. 'type' => 'integer',
  657. 'sanitize_callback' => 'absint',
  658. 'validate_callback' => 'rest_validate_request_arg',
  659. );
  660. }
  661. $params['product'] = array(
  662. 'description' => __( 'Limit result set to resources assigned to a specific product.', 'woocommerce' ),
  663. 'type' => 'integer',
  664. 'default' => null,
  665. 'validate_callback' => 'rest_validate_request_arg',
  666. );
  667. $params['slug'] = array(
  668. 'description' => __( 'Limit result set to resources with a specific slug.', 'woocommerce' ),
  669. 'type' => 'string',
  670. 'validate_callback' => 'rest_validate_request_arg',
  671. );
  672. return $params;
  673. }
  674. /**
  675. * Get taxonomy.
  676. *
  677. * @param WP_REST_Request $request Full details about the request.
  678. * @return int|WP_Error
  679. */
  680. protected function get_taxonomy( $request ) {
  681. // Check if taxonomy is defined.
  682. // Prevents check for attribute taxonomy more than one time for each query.
  683. if ( '' !== $this->taxonomy ) {
  684. return $this->taxonomy;
  685. }
  686. if ( ! empty( $request['attribute_id'] ) ) {
  687. $taxonomy = wc_attribute_taxonomy_name_by_id( (int) $request['attribute_id'] );
  688. $this->taxonomy = $taxonomy;
  689. }
  690. return $this->taxonomy;
  691. }
  692. }