Spaces:
Paused
Paused
File size: 4,076 Bytes
d9f0d1f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | <?php
/**
* Courses endpoints.
*
* GET /bemalearn/v1/courses public list
* GET /bemalearn/v1/courses/{id} public detail
*
* The authority on what these should return is docs/API-CONTRACT.md.
*/
if (!defined('ABSPATH')) {
exit;
}
class BL_Courses_Controller {
const PREVIEW_TTL = 300;
public function register_routes() {
register_rest_route('bemalearn/v1', '/courses', [
'methods' => 'GET',
'callback' => [$this, 'get_courses'],
'permission_callback' => '__return_true',
]);
register_rest_route('bemalearn/v1', '/courses/(?P<id>\d+)', [
'methods' => 'GET',
'callback' => [$this, 'get_course'],
'permission_callback' => '__return_true',
'args' => [
'id' => [
'required' => true,
'validate_callback' => function ($v) {
return is_numeric($v) && (int) $v > 0;
},
],
],
]);
}
public function get_courses($request) {
global $wpdb;
$table = $wpdb->prefix . 'bl_courses';
$users = $wpdb->users;
$rows = $wpdb->get_results(
"SELECT c.id, c.title, c.price_minor, c.currency,
c.enrolment_count, c.average_rating,
c.is_published, c.published_at,
u.display_name AS instructor_name
FROM {$table} c
LEFT JOIN {$users} u ON u.ID = c.instructor_id
WHERE c.is_published = 1
ORDER BY c.published_at DESC, c.id DESC"
);
$courses = [];
foreach ((array) $rows as $row) {
$courses[] = $this->shape($row);
}
return new WP_REST_Response([
'courses' => $courses,
'previewExpiresInSeconds' => self::PREVIEW_TTL,
], 200);
}
public function get_course($request) {
global $wpdb;
$id = (int) $request->get_param('id');
$table = $wpdb->prefix . 'bl_courses';
$users = $wpdb->users;
$row = $wpdb->get_row($wpdb->prepare(
"SELECT c.*, u.display_name AS instructor_name
FROM {$table} c
LEFT JOIN {$users} u ON u.ID = c.instructor_id
WHERE c.id = %d",
$id
));
// An unpublished course is treated as absent. A 403 here would confirm
// that the course exists, which is itself a disclosure.
if (!$row || !(int) $row->is_published) {
return new WP_Error('not_found', 'Course not found.', ['status' => 404]);
}
$shaped = $this->shape($row);
$shaped['description'] = $row->description;
$shaped['lessonCount'] = (int) $row->lesson_count;
return new WP_REST_Response($shaped, 200);
}
/**
* Turn a database row into the contract shape.
*
* enrolmentCount and averageRating are nullable in the contract: null means
* "not yet counted", which is not the same as a measured zero. They are
* cast only when they carry a value.
*/
private function shape($row) {
return [
'id' => (int) $row->id,
'title' => $row->title,
'instructorName' => $row->instructor_name,
'priceMinor' => (int) $row->price_minor,
'currency' => $row->currency,
'enrolmentCount' => $row->enrolment_count === null ? null : (int) $row->enrolment_count,
'averageRating' => $row->average_rating === null ? null : (float) $row->average_rating,
'publishedAt' => $row->published_at
? gmdate('c', strtotime($row->published_at))
: null,
'isPublished' => (bool) $row->is_published,
];
}
}
|