offerings->findAll() : $this->offerings->findAll( $userId ); $selectedOffering = $offeringId > 0 ? $this->offerings->findById( $offeringId ) : null; if ( null !== $selectedOffering && ! $this->canManageOffering( $selectedOffering, $userId, $manageAll ) ) { $selectedOffering = null; } $questions = null; if ( $accountScope ) { if ( isset( $_POST['usc_action'] ) && check_admin_referer( 'usc_question_action' ) ) { $this->handleFormAction( null ); } $questions = $this->questions->findByScope( Question::SCOPE_ACCOUNT ); } elseif ( null !== $selectedOffering ) { if ( isset( $_POST['usc_action'] ) && check_admin_referer( 'usc_question_action' ) ) { $this->handleFormAction( $selectedOffering ); } $questions = $this->questions->findByOffering( (int) $selectedOffering->id ); } include USC_PLUGIN_DIR . 'templates/admin/questions.php'; } /** * Handle an add/delete action for the given context: an offering, or account * scope when $offering is null. */ private function handleFormAction( ?Offering $offering ): void { // Nonce is verified by the caller (renderPage) before this method runs. // phpcs:disable WordPress.Security.NonceVerification.Missing $action = sanitize_key( Val::string( wp_unslash( $_POST['usc_action'] ?? '' ) ) ); if ( 'add' === $action ) { $this->addQuestion( $offering ); } if ( 'delete' === $action ) { $questionId = absint( Val::int( $_POST['question_id'] ?? 0 ) ); if ( $questionId > 0 ) { $question = $this->questions->findById( $questionId ); if ( $question && $this->belongsToContext( $question, $offering ) ) { $this->questions->delete( $questionId ); } } } // phpcs:enable WordPress.Security.NonceVerification.Missing } private function addQuestion( ?Offering $offering ): void { // phpcs:disable WordPress.Security.NonceVerification.Missing $label = sanitize_text_field( Val::string( wp_unslash( $_POST['label'] ?? '' ) ) ); $fieldType = sanitize_key( Val::string( wp_unslash( $_POST['field_type'] ?? Question::FIELD_TEXT ) ) ); if ( '' === $label || ! in_array( $fieldType, Question::VALID_FIELD_TYPES, true ) ) { return; } $this->questions->insert( new Question( offeringId: null === $offering ? null : (int) $offering->id, label: $label, fieldType: $fieldType, options: $this->parseOptions( sanitize_textarea_field( Val::string( wp_unslash( $_POST['options'] ?? '' ) ) ) ), isRequired: isset( $_POST['is_required'] ), sortOrder: absint( Val::int( $_POST['sort_order'] ?? 0 ) ), scope: null === $offering ? Question::SCOPE_ACCOUNT : Question::SCOPE_OFFERING, ) ); // phpcs:enable WordPress.Security.NonceVerification.Missing } /** * Whether a question belongs to the current editing context — the given * offering, or account scope when $offering is null. */ private function belongsToContext( Question $question, ?Offering $offering ): bool { if ( null === $offering ) { return Question::SCOPE_ACCOUNT === $question->scope; } return $question->offeringId === (int) $offering->id; } private function canManageOffering( Offering $offering, int $userId, bool $manageAll ): bool { return $manageAll || $offering->instructorId === $userId; } /** * Parse a newline-separated textarea into a list of option strings. * * @return list|null */ private function parseOptions( string $raw ): ?array { $lines = preg_split( '/\r\n|\r|\n/', $raw ); $options = array_values( array_filter( array_map( static fn( string $line ): string => sanitize_text_field( trim( $line ) ), false === $lines ? [] : $lines ) ) ); return [] === $options ? null : $options; } }