prefix . 'aimslms_course_progress'; } /** * Upsert a lesson's video-timestamp/completion-percentage progress for the current user. * * @param array $data Progress data: lesson_id, course_id, time, percentage. */ public static function save( $data ) { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table, no WP API alternative. $wpdb->replace( self::table(), array( 'user_id' => get_current_user_id(), 'lesson_id' => $data['lesson_id'], 'course_id' => $data['course_id'], 'video_timestamp' => $data['time'], 'completion_percentage' => $data['percentage'], 'completed' => $data['percentage'] >= 100 ? 1 : 0, 'last_accessed' => current_time( 'mysql' ), ), array( '%d', '%d', '%d', '%d', '%d', '%d', '%s' ) ); } /** * Mark a lesson complete for the current user, updating the existing progress row if one exists. * * @param array $data Progress data: lesson_id, course_id, completed. */ public static function mark_complete( $data ) { global $wpdb; $table = self::table(); $where = array( 'user_id' => get_current_user_id(), 'lesson_id' => (int) $data['lesson_id'], ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table, no WP API alternative. $exists = $wpdb->get_var( $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $table is derived from $wpdb->prefix, not user input. "SELECT id FROM {$table} WHERE user_id = %d AND lesson_id = %d", $where['user_id'], $where['lesson_id'] ) ); $payload = array( 'course_id' => (int) $data['course_id'], 'completed' => (int) $data['completed'], 'completion_percentage' => (int) 100, 'last_accessed' => current_time( 'mysql' ), ); $formats = array( '%d', '%d', '%d', '%s' ); if ( $exists ) { // ✅ UPDATE. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table, no WP API alternative. $wpdb->update( $table, $payload, $where, $formats, array( '%d', '%d', '%d' ) ); } else { // ✅ INSERT. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Custom table, no WP API alternative. $wpdb->insert( $table, array_merge( $where, $payload ), array( '%d', '%d', '%d', '%d', '%d', '%s' ) ); } } /** * Get the current user's saved progress row for a lesson. * * @param int $lesson_id Lesson ID. * @return object|null Progress row, or null if none saved yet. */ public static function get_progress( $lesson_id ) { global $wpdb; // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared -- self::table() returns $wpdb->prefix . 'aimslms_course_progress', not user input. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table, no WP API alternative. return $wpdb->get_row( $wpdb->prepare( 'SELECT * FROM ' . self::table() . ' WHERE user_id=%d AND lesson_id=%d', get_current_user_id(), $lesson_id ) ); // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared } }