\WP_REST_Server::READABLE, 'callback' => [ $this, 'index' ], 'permission_callback' => [ $this, 'isLoggedIn' ], ], [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ $this, 'enroll' ], 'permission_callback' => [ $this, 'canBook' ], 'args' => [ 'offering_id' => [ 'type' => 'integer', 'required' => true, 'sanitize_callback' => 'absint', ], 'answers' => [ 'type' => 'object', 'default' => [], ], 'accepted_policy_version_ids' => [ 'type' => 'array', 'default' => [], ], ], ], ] ); } public function index( \WP_REST_Request $request ): \WP_REST_Response { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found $userId = get_current_user_id(); if ( current_user_can( RoleManager::CAP_VIEW_ALL_LESSONS ) ) { $enrollments = $this->enrollments->findAllActive(); } elseif ( current_user_can( RoleManager::CAP_MANAGE_AVAILABILITY ) ) { $enrollments = $this->enrollments->findByInstructor( $userId ); } else { $enrollments = $this->enrollments->findByStudent( $userId ); } return new \WP_REST_Response( array_map( fn( Enrollment $e ) => $e->toArray(), $enrollments ), 200 ); } public function enroll( \WP_REST_Request $request ): \WP_REST_Response|\WP_Error { $offeringId = absint( $request->get_param( 'offering_id' ) ); $offering = $this->offerings->findById( $offeringId ); if ( null === $offering || Offering::KIND_GROUP_CLASS !== $offering->kind ) { return new \WP_Error( 'invalid_offering', __( 'Group class not found.', 'unsupervised-schedular' ), [ 'status' => 404 ] ); } $studentId = get_current_user_id(); if ( $this->enrollments->hasActiveEnrollment( $offeringId, $studentId ) ) { return new \WP_Error( 'already_enrolled', __( 'You are already enrolled in this class.', 'unsupervised-schedular' ), [ 'status' => 409 ] ); } if ( null !== $offering->capacity && $this->enrollments->countActiveForOffering( $offeringId ) >= $offering->capacity ) { return new \WP_Error( 'class_full', __( 'This class is full.', 'unsupervised-schedular' ), [ 'status' => 409 ] ); } $answers = $this->answers( $request ); $acceptedVersionIds = array_map( 'absint', (array) $request->get_param( 'accepted_policy_version_ids' ) ); $gateError = $this->gate->validate( $offeringId, $answers, $acceptedVersionIds ); if ( $gateError instanceof \WP_Error ) { return $gateError; } $id = $this->enrollments->insert( new Enrollment( offeringId: $offeringId, studentId: $studentId, instructorId: $offering->instructorId, ) ); $this->gate->record( PolicyAcceptance::REG_ENROLLMENT, $id, $studentId, $offeringId, $answers, $acceptedVersionIds, $this->clientIp() ); if ( $offering->price > 0.0 ) { $this->payments->createForRegistration( Payment::REG_ENROLLMENT, $id, $studentId, $offering->instructorId, $offering->price, $offering->currency, $offering->etransferEmail ); } return new \WP_REST_Response( [ 'id' => $id, 'status' => Enrollment::STATUS_ACTIVE, ], 201 ); } public function isLoggedIn(): bool { return is_user_logged_in(); } public function canBook(): bool { return is_user_logged_in() && current_user_can( RoleManager::CAP_BOOK_LESSON ); } /** * Extract a question_id => value map from the request. * * @return array */ private function answers( \WP_REST_Request $request ): array { $out = []; foreach ( (array) $request->get_param( 'answers' ) as $questionId => $value ) { $out[ (int) $questionId ] = sanitize_text_field( (string) $value ); } return $out; } private function clientIp(): ?string { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- IP stored verbatim for audit. $ip = sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ?? '' ) ); return '' !== $ip ? $ip : null; } }