array( 'type' => 'string' ), 'description' => array( 'type' => 'string' ), 'starts_at' => array( 'type' => 'string' ), 'ends_at' => array( 'type' => 'string' ), 'location_type' => array( 'type' => 'string', 'enum' => array( 'online', 'in_person' ), ), 'location' => array( 'type' => 'string' ), 'online_url' => array( 'type' => 'string' ), ); /** * Filters the fields an event form may send, as REST argument * definitions. SpaceBay Pro adds "capacity". * * @since 1.0.0 * * @param array $fields Argument definitions keyed by field. */ $fields = apply_filters( 'spacebay_event_fields', $fields ); register_rest_route( $this->namespace, '/events', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'permission_events' ), 'args' => array_merge( $this->paging_args( 100 ), array( 'space_id' => array( 'type' => 'integer', 'default' => 0, ), 'when' => array( 'type' => 'string', 'enum' => array( 'upcoming', 'past', 'range' ), 'default' => 'upcoming', ), 'from' => array( 'type' => 'string' ), 'to' => array( 'type' => 'string' ), 'rsvp' => array( 'type' => 'string', 'enum' => array( '', 'going' ), 'default' => '', ), ) ), ), array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'create_item' ), 'permission_callback' => array( $this, 'permission_create' ), 'args' => array_merge( $fields, array( 'space_id' => array( 'type' => 'integer', 'required' => true, ), 'notify' => array( 'type' => 'boolean', 'default' => true, ), ) ), ), ) ); register_rest_route( $this->namespace, '/events/(?P\d+)', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), 'permission_callback' => array( $this, 'permission_events' ), ), array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => array( $this, 'update_item' ), 'permission_callback' => array( $this, 'permission_create' ), 'args' => $fields, ), array( 'methods' => WP_REST_Server::DELETABLE, 'callback' => array( $this, 'delete_item' ), 'permission_callback' => array( $this, 'permission_create' ), ), ) ); register_rest_route( $this->namespace, '/events/(?P\d+)/cancel', array( array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'cancel_item' ), 'permission_callback' => array( $this, 'permission_create' ), ), ) ); register_rest_route( $this->namespace, '/events/(?P\d+)/rsvp', array( array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'rsvp' ), 'permission_callback' => array( $this, 'permission_create' ), 'args' => array( 'answer' => array( 'type' => 'string', 'enum' => array( 'going', 'maybe', 'not_going', '' ), 'required' => true, ), ), ), ) ); register_rest_route( $this->namespace, '/events/(?P\d+)/attendees', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'attendees' ), 'permission_callback' => array( $this, 'permission_events' ), 'args' => array_merge( $this->paging_args( 100 ), array( 'answer' => array( 'type' => 'string', 'enum' => array( 'going', 'maybe' ), 'default' => 'going', ), ) ), ), ) ); } /** * Anyone who may open the community, while events are on. * * @return true|WP_Error */ public function permission_events() { if ( ! Events::enabled() ) { return $this->events_off(); } return $this->permission_community(); } /** * Logged-in, active members, while events are on. * * @return true|WP_Error */ public function permission_create() { if ( ! Events::enabled() ) { return $this->events_off(); } return $this->permission_logged_in(); } /** * Lists events. * * @param WP_REST_Request $request Request. * @return \WP_REST_Response|WP_Error */ public function get_items( $request ) { $viewer = get_current_user_id(); $args = array( 'space_id' => (int) $request['space_id'], 'when' => $request['when'], 'rsvp' => $viewer ? $request['rsvp'] : '', 'page' => $request['page'], 'per_page' => $request['per_page'], ); if ( 'range' === $request['when'] ) { $from = strtotime( (string) $request['from'] ); $to = strtotime( (string) $request['to'] ); if ( ! $from || ! $to || $to < $from || $to - $from > 62 * DAY_IN_SECONDS ) { return new WP_Error( 'spacebay_event_range', __( 'Choose a range of up to two months.', 'spacebay' ), array( 'status' => 400 ) ); } $args['from'] = gmdate( 'Y-m-d H:i:s', $from ); $args['to'] = gmdate( 'Y-m-d H:i:s', $to ); } $result = Events::query( $args, $viewer ); return $this->paged_response( EventFormatter::many( $result['items'], $viewer ), $result['total'], $request['per_page'] ); } /** * Returns one event. * * @param WP_REST_Request $request Request. * @return \WP_REST_Response|WP_Error */ public function get_item( $request ) { $found = $this->readable( (int) $request['id'] ); if ( is_wp_error( $found ) ) { return $found; } return rest_ensure_response( $this->full( $found['event'], $found['space'] ) ); } /** * Creates an event. * * @param WP_REST_Request $request Request. * @return \WP_REST_Response|WP_Error */ public function create_item( $request ) { $space = $this->visible_space( (int) $request['space_id'] ); if ( is_wp_error( $space ) ) { return $space; } if ( ! Events::can_create( $space, get_current_user_id() ) ) { return new WP_Error( 'spacebay_event_forbidden', __( 'Only space admins and moderators can add events to this space.', 'spacebay' ), array( 'status' => 403 ) ); } $event = Events::create( $space, $this->input( $request ), get_current_user_id() ); if ( is_wp_error( $event ) ) { return $event; } $response = rest_ensure_response( $this->full( $event, $space ) ); $response->set_status( 201 ); return $response; } /** * Changes an event. * * @param WP_REST_Request $request Request. * @return \WP_REST_Response|WP_Error */ public function update_item( $request ) { $found = $this->editable( (int) $request['id'] ); if ( is_wp_error( $found ) ) { return $found; } $event = Events::update( $found['event'], $this->input( $request ), get_current_user_id() ); return is_wp_error( $event ) ? $event : rest_ensure_response( $this->full( $event, $found['space'] ) ); } /** * Cancels an event. * * @param WP_REST_Request $request Request. * @return \WP_REST_Response|WP_Error */ public function cancel_item( $request ) { $found = $this->editable( (int) $request['id'] ); if ( is_wp_error( $found ) ) { return $found; } $event = Events::cancel( $found['event'], get_current_user_id() ); return $event ? rest_ensure_response( $this->full( $event, $found['space'] ) ) : $this->gone(); } /** * Deletes an event. * * @param WP_REST_Request $request Request. * @return \WP_REST_Response|WP_Error */ public function delete_item( $request ) { $found = $this->editable( (int) $request['id'] ); if ( is_wp_error( $found ) ) { return $found; } Events::delete( $found['event'] ); return rest_ensure_response( array( 'deleted' => true ) ); } /** * Saves the viewer's answer. * * @param WP_REST_Request $request Request. * @return \WP_REST_Response|WP_Error */ public function rsvp( $request ) { $found = $this->readable( (int) $request['id'] ); if ( is_wp_error( $found ) ) { return $found; } $user_id = get_current_user_id(); if ( ! Events::can_rsvp( $found['event'], $found['space'], $user_id ) ) { $message = 'published' !== $found['event']['status'] || strtotime( $found['event']['ends_at'] . ' UTC' ) <= time() ? __( 'This event has ended or was cancelled.', 'spacebay' ) : __( 'Join the space to answer this event.', 'spacebay' ); return new WP_Error( 'spacebay_rsvp_forbidden', $message, array( 'status' => 403 ) ); } $saved = Events::answer( $found['event'], $user_id, (string) $request['answer'] ); if ( is_wp_error( $saved ) ) { return $saved; } $event = Events::find( $found['event']['id'] ); return $event ? rest_ensure_response( $this->full( $event, $found['space'] ) ) : $this->gone(); } /** * Error for an event that was deleted while the request ran. * * @return WP_Error */ private function gone() { return new WP_Error( 'spacebay_event_not_found', __( 'This event no longer exists.', 'spacebay' ), array( 'status' => 404 ) ); } /** * Lists members who answered going or maybe. * * @param WP_REST_Request $request Request. * @return \WP_REST_Response|WP_Error */ public function attendees( $request ) { $found = $this->readable( (int) $request['id'] ); if ( is_wp_error( $found ) ) { return $found; } $result = Events::attendees( $found['event']['id'], $request['answer'], $request['page'], $request['per_page'] ); return $this->paged_response( $this->members( $result['ids'] ), $result['total'], $request['per_page'] ); } /** * An event with its first attendees. * * @param array $event Event row. * @param array $space Space row. * @return array */ private function full( array $event, array $space ) { $viewer = get_current_user_id(); $data = EventFormatter::one( $event, $space, $viewer, Events::answer_of( $event['id'], $viewer ) ); $going = Events::attendees( $event['id'], 'going', 1, 12 ); $data['attendees'] = $this->members( $going['ids'] ); return $data; } /** * Formats members, leaving out hidden profiles for other viewers. * * @param int[] $ids Member IDs. * @return array[] */ private function members( array $ids ) { $hidden = \SpaceBay\Members\Profiles::hidden_ids(); $viewer = get_current_user_id(); $users = array(); cache_users( $ids ); foreach ( $ids as $id ) { $user = get_userdata( $id ); if ( $user && ( ! in_array( $id, $hidden, true ) || $id === $viewer ) ) { $users[] = $user; } } return MemberDirectory::format_many( $users ); } /** * Finds an event the viewer may read. * * @param int $id Event ID. * @return array|WP_Error Event and space. */ private function readable( $id ) { $event = Events::find( $id ); $space = $event ? SpaceRepository::find( $event['space_id'] ) : null; if ( ! $event || ! $space || ! SpaceAccess::can_read( $space, get_current_user_id() ) ) { return new WP_Error( 'spacebay_event_not_found', __( 'Event not found.', 'spacebay' ), array( 'status' => 404 ) ); } return array( 'event' => $event, 'space' => $space, ); } /** * Finds an event the viewer may change. * * @param int $id Event ID. * @return array|WP_Error Event and space. */ private function editable( $id ) { $found = $this->readable( $id ); if ( ! is_wp_error( $found ) && ! Events::can_edit( $found['event'], $found['space'], get_current_user_id() ) ) { return $this->forbidden(); } return $found; } /** * Event fields sent with a request. * * @param WP_REST_Request $request Request. * @return array */ private function input( $request ) { $input = array(); $keys = array( 'title', 'description', 'starts_at', 'ends_at', 'location_type', 'location', 'online_url', 'notify' ); /** This filter is documented in includes/Rest/EventsController.php */ $keys = array_merge( $keys, array_keys( (array) apply_filters( 'spacebay_event_fields', array() ) ) ); foreach ( array_unique( $keys ) as $key ) { if ( null !== $request[ $key ] ) { $input[ $key ] = $request[ $key ]; } } return $input; } /** * Error used while events are switched off. * * @return WP_Error */ private function events_off() { return new WP_Error( 'spacebay_events_off', __( 'Events are switched off in this community.', 'spacebay' ), array( 'status' => 404 ) ); } }