Нет описания

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

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