36331388d1
CI / Coding Standards (pull_request) Successful in 55s
CI / PHPStan (pull_request) Successful in 1m0s
CI / Tests (PHP 8.1) (pull_request) Successful in 50s
CI / Tests (PHP 8.2) (pull_request) Successful in 46s
CI / Tests (PHP 8.3) (pull_request) Successful in 50s
CI / No Debug Code (pull_request) Successful in 2s
Implements the offerings catalog (#1): private-lesson types and group classes carrying pricing, billing mode (one_time/full_term), duration, capacity, and term details. Adds the src/Offering/ domain (value object, repository, REST endpoint, admin controller + template), the us_offerings table, and an Offerings admin page. Also lands the capability slice of #9: registers the us_studio_admin role and the new capability strings (manage_instructors, manage_offerings, manage_questions, manage_policies, manage_billing, view_all_payments, view_own_payments, export_payments) so offering management gates correctly. Tests: tests/Unit/Offering/ (value object + repository) and a studio-admin case in RoleManagerTest. composer test, cs, and PHPStan level 6 all pass. Refs #1 #9 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
89 lines
2.9 KiB
PHP
89 lines
2.9 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace Unsupervised\Schedular\Offering;
|
|
|
|
use Unsupervised\Schedular\Auth\RoleManager;
|
|
|
|
class OfferingController {
|
|
|
|
public function __construct( private OfferingRepository $repository ) {}
|
|
|
|
public function renderPage(): void {
|
|
if ( ! current_user_can( RoleManager::CAP_MANAGE_OFFERINGS ) ) {
|
|
wp_die( esc_html__( 'You do not have permission to manage offerings.', 'unsupervised-schedular' ) );
|
|
}
|
|
|
|
$instructorId = get_current_user_id();
|
|
$manageAll = current_user_can( RoleManager::CAP_MANAGE_INSTRUCTORS );
|
|
|
|
if ( isset( $_POST['usc_action'] ) && check_admin_referer( 'usc_offering_action' ) ) {
|
|
$this->handleFormAction( $instructorId, $manageAll );
|
|
}
|
|
|
|
$offerings = $manageAll
|
|
? $this->repository->findAll()
|
|
: $this->repository->findAll( $instructorId );
|
|
|
|
include USC_PLUGIN_DIR . 'templates/admin/offerings.php';
|
|
}
|
|
|
|
private function handleFormAction( int $instructorId, bool $manageAll ): void {
|
|
// Nonce is verified by the caller (renderPage) before this method runs.
|
|
// phpcs:disable WordPress.Security.NonceVerification.Missing
|
|
$action = sanitize_key( wp_unslash( $_POST['usc_action'] ?? '' ) );
|
|
|
|
if ( 'add' === $action ) {
|
|
$this->addOffering( $instructorId );
|
|
}
|
|
|
|
if ( 'delete' === $action ) {
|
|
$offeringId = absint( $_POST['offering_id'] ?? 0 );
|
|
if ( $offeringId > 0 ) {
|
|
$offering = $this->repository->findById( $offeringId );
|
|
if ( $offering && ( $manageAll || $offering->instructorId === $instructorId ) ) {
|
|
$this->repository->delete( $offeringId );
|
|
}
|
|
}
|
|
}
|
|
// phpcs:enable WordPress.Security.NonceVerification.Missing
|
|
}
|
|
|
|
private function addOffering( int $instructorId ): void {
|
|
// phpcs:disable WordPress.Security.NonceVerification.Missing
|
|
$title = sanitize_text_field( wp_unslash( $_POST['title'] ?? '' ) );
|
|
$kind = sanitize_key( wp_unslash( $_POST['kind'] ?? '' ) );
|
|
|
|
if ( '' === $title || ! in_array( $kind, Offering::VALID_KINDS, true ) ) {
|
|
return;
|
|
}
|
|
|
|
$billingMode = sanitize_key( wp_unslash( $_POST['billing_mode'] ?? Offering::BILLING_ONE_TIME ) );
|
|
if ( ! in_array( $billingMode, Offering::VALID_BILLING_MODES, true ) ) {
|
|
$billingMode = Offering::BILLING_ONE_TIME;
|
|
}
|
|
|
|
$duration = absint( $_POST['duration_minutes'] ?? 0 );
|
|
$capacity = absint( $_POST['capacity'] ?? 0 );
|
|
|
|
$this->repository->insert(
|
|
new Offering(
|
|
instructorId: $instructorId,
|
|
kind: $kind,
|
|
title: $title,
|
|
priceCents: absint( $_POST['price_cents'] ?? 0 ),
|
|
billingMode: $billingMode,
|
|
durationMinutes: $duration > 0 ? $duration : null,
|
|
allowWeekly: isset( $_POST['allow_weekly'] ),
|
|
capacity: $capacity > 0 ? $capacity : null,
|
|
scheduleNote: $this->nullableText( sanitize_text_field( wp_unslash( $_POST['schedule_note'] ?? '' ) ) ),
|
|
)
|
|
);
|
|
// phpcs:enable WordPress.Security.NonceVerification.Missing
|
|
}
|
|
|
|
private function nullableText( string $value ): ?string {
|
|
return '' === $value ? null : $value;
|
|
}
|
|
}
|