authorise( 'read', $request ); } /** * @param WP_REST_Request $request The request. */ public function can_edit( WP_REST_Request $request ): bool|WP_Error { return $this->authorise( 'edit', $request ); } /** * Authorise against the record the note hangs off. * * @param string $action 'read' or 'edit'. * @param WP_REST_Request $request The request. */ private function authorise( string $action, WP_REST_Request $request ): bool|WP_Error { $parent = $this->parent_of( $request ); if ( $parent instanceof WP_Error ) { return $parent; } [ $entity_type, $record ] = $parent; return Capabilities::user_can( $action, $entity_type, $record ) ? true : $this->forbidden(); } /** * The entity slug and record a request is really about. * * @param WP_REST_Request $request The request. * * @return array{0: string, 1: array}|WP_Error */ private function parent_of( WP_REST_Request $request ): array|WP_Error { $note_id = (int) ( $request['id'] ?? 0 ); if ( $note_id > 0 ) { $note = $this->service->find( $note_id ); if ( null === $note ) { return $this->fail( 'plugixa_crm_not_found', __( 'That note does not exist.', 'plugixa-crm' ), 404 ); } $entity_type = (string) $note['entity_type']; $entity_id = (int) $note['entity_id']; } else { $entity_type = (string) ( $request['entity_type'] ?? '' ); $entity_id = (int) ( $request['entity_id'] ?? 0 ); } if ( ! $this->service->knows( $entity_type ) ) { return $this->fail( 'plugixa_crm_unknown_entity', __( 'There is no such thing to note.', 'plugixa-crm' ), 404 ); } $record = $this->records->load( $entity_type, $entity_id ); if ( null === $record ) { return $this->fail( 'plugixa_crm_not_found', __( 'That record does not exist.', 'plugixa-crm' ), 404 ); } return array( $entity_type, $record ); } public function register_routes(): void { register_rest_route( $this->namespace, // The slug is matched loosely and validated against the registry, rather than pinned // to a list here that would go stale the moment a module adds an entity. '/notes/(?P[a-z_]+)/(?P\d+)', array( array( 'methods' => 'GET', 'callback' => array( $this, 'index' ), 'permission_callback' => array( $this, 'can_read' ), ), array( 'methods' => 'POST', 'callback' => array( $this, 'store' ), 'permission_callback' => array( $this, 'can_edit' ), ), ) ); register_rest_route( $this->namespace, '/notes/(?P\d+)/restore', array( array( 'methods' => 'POST', 'callback' => array( $this, 'restore' ), 'permission_callback' => array( $this, 'can_edit' ), ), ) ); register_rest_route( $this->namespace, '/notes/(?P\d+)/purge', array( array( 'methods' => 'DELETE', 'callback' => array( $this, 'purge' ), 'permission_callback' => array( $this, 'can_edit' ), ), ) ); register_rest_route( $this->namespace, '/notes/(?P\d+)', array( array( 'methods' => 'PUT, PATCH', 'callback' => array( $this, 'update' ), 'permission_callback' => array( $this, 'can_edit' ), ), array( 'methods' => 'DELETE', 'callback' => array( $this, 'destroy' ), 'permission_callback' => array( $this, 'can_edit' ), ), ) ); } /** * Bring a note back out of the trash. * * Authorised against the PARENT record, like everything else here: restoring a note onto a * contact you may not edit is writing on that contact. * * @param WP_REST_Request $request The request. * * @return WP_REST_Response|WP_Error */ public function restore( WP_REST_Request $request ): WP_REST_Response|WP_Error { $result = $this->service->restore( (int) $request['id'] ); return $result instanceof WP_Error ? $result : $this->ok( array( 'restored' => true ) ); } /** * Destroy a note for good. * * @param WP_REST_Request $request The request. * * @return WP_REST_Response|WP_Error */ public function purge( WP_REST_Request $request ): WP_REST_Response|WP_Error { $result = $this->service->purge( (int) $request['id'] ); return $result instanceof WP_Error ? $result : $this->ok( array( 'purged' => true ) ); } /** * @param WP_REST_Request $request The request. */ public function index( WP_REST_Request $request ): WP_REST_Response { // `view=trash`, the same spelling every other list route in the CRM already uses. A second // name for one idea (`?trashed=1`) would be a second convention to remember and to get wrong. $trashed = 'trash' === $request->get_param( 'view' ); return $this->ok( $this->service->for_record( (string) $request['entity_type'], (int) $request['entity_id'], $trashed ) ); } /** * @param WP_REST_Request $request The request. * * @return WP_REST_Response|WP_Error */ public function store( WP_REST_Request $request ): WP_REST_Response|WP_Error { $note = $this->service->create( (string) $request['entity_type'], (int) $request['entity_id'], (array) $request->get_json_params() ); return $note instanceof WP_Error ? $note : $this->ok( $note, array(), 201 ); } /** * @param WP_REST_Request $request The request. * * @return WP_REST_Response|WP_Error */ public function update( WP_REST_Request $request ): WP_REST_Response|WP_Error { $note = $this->service->update( (int) $request['id'], (array) $request->get_json_params() ); return $note instanceof WP_Error ? $note : $this->ok( $note ); } /** * @param WP_REST_Request $request The request. * * @return WP_REST_Response|WP_Error */ public function destroy( WP_REST_Request $request ): WP_REST_Response|WP_Error { $result = $this->service->delete( (int) $request['id'] ); return $result instanceof WP_Error ? $result : $this->ok( array( 'deleted' => true ) ); } }