'GET', 'callback' => array( $this, 'get_state' ), 'permission_callback' => $permission, ) ); register_rest_route( self::API_NAMESPACE, '/lots', array( 'methods' => 'POST', 'callback' => array( $this, 'create_lot' ), 'permission_callback' => $permission, ) ); register_rest_route( self::API_NAMESPACE, '/lots/(?P\d+)', array( array( 'methods' => 'PUT', 'callback' => array( $this, 'update_lot' ), 'permission_callback' => $permission, ), array( 'methods' => 'DELETE', 'callback' => array( $this, 'delete_lot' ), 'permission_callback' => $permission, ), ) ); register_rest_route( self::API_NAMESPACE, '/settings', array( 'methods' => 'PUT', 'callback' => array( $this, 'save_settings' ), 'permission_callback' => $permission, ) ); register_rest_route( self::API_NAMESPACE, '/products', array( 'methods' => 'GET', 'callback' => array( $this, 'search_products' ), 'permission_callback' => $permission, 'args' => array( 'q' => array( 'type' => 'string', 'default' => '', 'sanitize_callback' => 'sanitize_text_field', ), 'lot' => array( 'type' => 'integer', 'default' => 0, 'sanitize_callback' => 'absint', ), ), ) ); register_rest_route( self::API_NAMESPACE, '/write', array( 'methods' => 'POST', 'callback' => array( $this, 'write_costs' ), 'permission_callback' => $permission, ) ); } /** * Turn a Validator/store result into a response. * * @param array $result Result array. * @return \WP_REST_Response */ private function respond( array $result ): \WP_REST_Response { if ( $result['ok'] ) { return new \WP_REST_Response( $result['value'], 200 ); } return new \WP_REST_Response( array( 'code' => 'invalid', 'errors' => $result['errors'], ), $result['status'] ?? 422 ); } /** * The ids of the pieces of some lots. * * @param array[] $lots Lots. * @return string[] */ private static function piece_ids( array $lots ): array { $ids = array(); foreach ( $lots as $lot ) { foreach ( $lot['pieces'] as $piece ) { $ids[] = (string) $piece['variantId']; } } return $ids; } /** * Everything the page needs on load. * * @return \WP_REST_Response */ public function get_state(): \WP_REST_Response { $lots = ( new Lots() )->all(); $state = array( 'settings' => ( new Settings() )->get(), 'lots' => $lots, // Every piece as WooCommerce describes it today (price, cost, stock), by variant id. 'catalog' => (object) ( new Catalog() )->describe_variants( self::piece_ids( $lots ) ), 'cogs' => array( 'supported' => Cogs::supported(), 'enabled' => Cogs::enabled(), 'settingsUrl' => Cogs::settings_url(), ), ); /** * Lets an add-on add to the state the page loads. * * @param array $state The state. */ $state = apply_filters( 'lotcost_state', $state ); return new \WP_REST_Response( $state, 200 ); } /** * A saved lot with the description of its pieces, so the page can show it at once. * * @param array $result Result of Lots::save(). * @return \WP_REST_Response */ private function lot_response( array $result ): \WP_REST_Response { if ( ! $result['ok'] ) { return $this->respond( $result ); } return new \WP_REST_Response( array( 'lot' => $result['value'], 'catalog' => (object) ( new Catalog() )->describe_variants( self::piece_ids( array( $result['value'] ) ) ), ), 200 ); } /** * Create a lot. * * @param \WP_REST_Request $request Request. */ public function create_lot( \WP_REST_Request $request ): \WP_REST_Response { return $this->lot_response( ( new Lots() )->save( $request->get_json_params() ) ); } /** * Replace a lot. * * @param \WP_REST_Request $request Request. */ public function update_lot( \WP_REST_Request $request ): \WP_REST_Response { return $this->lot_response( ( new Lots() )->save( $request->get_json_params(), (int) $request['id'] ) ); } /** * Delete a lot (the costs already written to WooCommerce stay). * * @param \WP_REST_Request $request Request. */ public function delete_lot( \WP_REST_Request $request ): \WP_REST_Response { $deleted = ( new Lots() )->delete( (int) $request['id'] ); return new \WP_REST_Response( array( 'deleted' => $deleted ), $deleted ? 200 : 404 ); } /** * Save the settings. * * @param \WP_REST_Request $request Request. */ public function save_settings( \WP_REST_Request $request ): \WP_REST_Response { return $this->respond( ( new Settings() )->save( $request->get_json_params() ) ); } /** * Search products for the "add pieces" picker. Each piece says which other lot holds it. * * @param \WP_REST_Request $request Request. */ public function search_products( \WP_REST_Request $request ): \WP_REST_Response { $hits = ( new Catalog() )->search( (string) $request['q'] ); $holders = ( new Lots() )->holders( (int) $request['lot'] ); foreach ( $hits as $index => $hit ) { foreach ( $hit['variants'] as $position => $variant ) { $hits[ $index ]['variants'][ $position ]['inLot'] = isset( $holders[ $variant['id'] ] ) ? $holders[ $variant['id'] ]['name'] : null; } } return new \WP_REST_Response( $hits, 200 ); } /** * Write costs to WooCommerce. The answer carries the pieces as WooCommerce holds them afterwards. * * @param \WP_REST_Request $request Request. */ public function write_costs( \WP_REST_Request $request ): \WP_REST_Response { $body = $request->get_json_params(); $items = is_array( $body ) && isset( $body['items'] ) && is_array( $body['items'] ) ? $body['items'] : null; if ( null === $items || count( $items ) > 5000 ) { return new \WP_REST_Response( array( 'code' => 'invalid', 'errors' => array( 'form' => __( 'Nothing to write, or too many items at once.', 'lotcost' ) ), ), 422 ); } $results = ( new Cogs() )->write( $items ); $written = array(); foreach ( $results as $result ) { if ( $result['ok'] ) { $written[] = $result['variantId']; } } return new \WP_REST_Response( array( 'results' => $results, 'catalog' => (object) ( new Catalog() )->describe_variants( $written ), ), 200 ); } }