=== AccessNow LMS ===
Contributors: accessnow
Tags: lms, courses, e-learning, quiz, education
Requires at least: 6.3
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 1.0.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

A lightweight LMS for running online courses on your own site: units, lessons, quizzes, enrolments, progress tracking and certificates.

== Description ==

AccessNow LMS turns WordPress into a small, practical learning platform. You build a course from units and lessons, add a quiz to any lesson, and students work through it at their own pace while the plugin keeps track of where they are up to.

It is designed to stay simple to run: no page builder, no extra services, and everything is stored in your own WordPress database. Course pages, lessons and quizzes use tidy addresses such as `/courses/your-course/unit-1/first-lesson/`.

**Features**

* Courses made of units, lessons and quizzes, with drag-and-drop unit ordering.
* Text or YouTube video lessons, downloadable attachments, and private notes for each student.
* Quizzes with multiple choice, true/false and fill-in-the-blank questions, a pass mark, retry limits, an optional time limit, randomised order, and explanations with the correct answers when you choose.
* Header images for courses, units and lessons, shown as a banner, a compact strip or not at all. Lessons use their unit's or course's unless they have their own.
* Sequential or free progression, a progress bar, and optional re-completion when a lesson or quiz changes.
* Self-enrolment, admin-assigned enrolment or both, with log-in and registration right on the course page.
* Certificates with a shareable link, points and badges, and a student dashboard.
* Enrolment management with a student search, course and student reports with CSV export, email notifications, course export/import (.zip) and one-click course cloning.

**Blocks and shortcodes**

Add the **Course Catalogue**, **My Courses** and **Student Dashboard** blocks in the block editor (search for "LMS"), or use the shortcodes:

* `[accessnow_slms_course_catalog]`: the course catalogue. Optional `per_page="12"` and `lang="ja"`.
* `[accessnow_slms_my_courses]`: the logged-in student's courses with progress.
* `[accessnow_slms_student_dashboard]`: courses, certificates, points, badges and recent quiz results.

The older `[slms_course_catalog]`, `[slms_my_courses]` and `[slms_student_dashboard]` shortcodes still work.

**Languages**

Each course can be marked English or Japanese. Course pages then show the plugin's own text in that language, and the catalogue can be filtered by language. A Japanese translation is included.

== Installation ==

1. Upload the `accessnow-lms` folder to `/wp-content/plugins/`, or install the ZIP from Plugins → Add New → Upload Plugin.
2. Activate "AccessNow LMS".
3. Go to AccessNow LMS → Settings to choose registration, points and email options.
4. Create a course, then units (choose the parent course), then lessons (choose the parent unit), then quizzes (choose the parent lesson).
5. Add the Course Catalogue block (or `[accessnow_slms_course_catalog]`) to a page, or link to `/courses/`.

**Upgrading from "Simple LMS" (version 4.x, installed by hand)?** Follow the steps under "I'm upgrading from Simple LMS" in the FAQ. Never delete the old plugin from the Plugins screen.

To let visitors create their own student accounts, turn on both "Allow Student Registration" (AccessNow LMS → Settings) and "Anyone can register" (Settings → General).

== Frequently Asked Questions ==

= Where do I get help? =

The help articles are at https://support.accessnow.com.au/help/workspace-accessnow/accessnow-lms, and each AccessNow LMS screen has a Help tab linking to the ones about it. To ask a question, use Get support on the project page: https://accessnow.com.au/projects/accessnow-lms/

= Can students register themselves? =

Yes, when both "Allow Student Registration" in AccessNow LMS → Settings and "Anyone can register" in Settings → General are turned on. The course page then shows a Register tab. New accounts get the Student role. Registrations are protected by a hidden honeypot field, a minimum fill-in time and a limit of three registrations per hour from one IP address.

= Who can see lessons and quizzes? =

Only logged-in students enrolled in the course, plus AccessNow LMS managers (administrators). In sequential courses a lesson opens once the earlier lessons are complete. Lesson content is also hidden from the REST API for anyone who is not enrolled.

= Can my theme change the look? =

Yes. Copy a file from the plugin's `templates/` folder into your theme (for example `single-slms_course.php`, or `partials/course-card.php`) and edit the copy.

= What happens to my data if I delete the plugin? =

Nothing, unless you tick "Delete data on uninstall" in AccessNow LMS → Settings first. With that ticked, deleting the plugin removes its courses, lessons, quizzes, enrolments, progress, certificates, points, badges, settings and the Student role.

= I'm upgrading from Simple LMS (version 4.x). What do I do? =

The plugin was renamed to AccessNow LMS and now lives in a new folder, so WordPress sees it as a separate plugin. Your courses, enrolments and progress carry over, because both versions use the same data.

1. Take a backup.
2. Deactivate "Simple LMS". **Do not delete it from the Plugins screen.**
3. Install and activate AccessNow LMS. On the first page load it moves the old settings to their new names.
4. Check that your courses and students are there.
5. Remove the old `simple-lms` folder from `/wp-content/plugins/` by hand (FTP or your host's file manager).

Why by hand: deleting a 4.x copy from the Plugins screen runs its uninstaller, which always erases every course, enrolment and progress record, and AccessNow LMS shares that data.

== Developers ==

AccessNow LMS has actions and filters for payments, drip content, prerequisites, CRM sync and custom emails. Put your code in a small plugin or your theme's `functions.php`. All IDs are integers.

**Actions**

* `accessnow_slms_enrolled( $user_id, $course_id, $enrollment_id, $enrolled_by )`: a new enrolment. `$enrolled_by` is 0 for self-enrolment, or the administrator's user ID.
* `accessnow_slms_unenrolled( $user_id, $course_id, $enrollment_id, $dropped_by, $old_status )`: an enrolment was dropped.
* `accessnow_slms_lesson_completed( $user_id, $lesson_id, $course_id, $enrollment_id )`: a lesson became complete, including a lesson redone after a content change.
* `accessnow_slms_quiz_submitted( $user_id, $quiz_id, $result, $attempt_number, $enrollment_id, $course_id )`: a quiz attempt was stored. `$result` has `score`, `total`, `percentage`, `passed`, `results` and `timed_out`.
* `accessnow_slms_course_completed( $user_id, $course_id, $enrollment_id, $first_completion, $certificate )`: a course was completed. `$first_completion` is false when an enrolment reopened by a course change is completed again.
* `accessnow_slms_certificate_issued( $certificate, $user_id, $course_id, $enrollment_id )`: a certificate was issued.
* `accessnow_slms_enrollment_reopened( $user_id, $course_id, $enrollment_id )`: a completed enrolment went back to active because a lesson was added or changed.

**Filters**

* `accessnow_slms_can_enroll( $allowed, $user_id, $course_id )`: return `false` or a `WP_Error` (its message is shown to the student) to stop self-enrolment, for example until the course is paid for. Administrator enrolments are not filtered.
* `accessnow_slms_can_access_lesson( $can_access, $user_id, $lesson_id, $course_id, $enrollment_id )`: lock or unlock a lesson, for example for drip content.
* `accessnow_slms_grade_result( $result, $quiz_id, $answers )`: change a quiz result before it is stored.
* `accessnow_slms_show_answers( $show, $quiz_id, $passed, $can_retry )`: whether a quiz result shows the correct answers and explanations.
* `accessnow_slms_page_header( $header, $post_id )`: the header at the top of a course, lesson or quiz page. `$header` has `image_id` (0 for none) and `style` (`banner`, `compact` or `none`).
* `accessnow_slms_catalogue_languages( $codes )`: the languages the catalogue's filter offers; the filter shows when there are two or more. Defaults to the languages of published courses.
* `accessnow_slms_points( $points, $action, $user_id, $reference_id, $enrollment_id )`: change the points for `lesson_complete`, `quiz_pass` or `course_complete`. Return 0 to award none.
* `accessnow_slms_email_{type}( $email, $context )`: change or cancel an email. Types: `enrollment`, `completion`, `quiz_result`, `admin_enrollment`. `$email` has `to`, `subject`, `message` and `headers`. Return `false` to not send it.

**Styling**

The plugin's colours are CSS custom properties, so a theme can match them without overriding rules. Set any of these (each falls back to the plugin's own colour): `--slms-accent`, `--slms-accent-hover`, `--slms-on-accent` (buttons and active tabs), `--slms-link`, `--slms-success`, `--slms-progress`, `--slms-track`, `--slms-surface`, `--slms-surface-alt`, `--slms-border`, `--slms-text`, `--slms-text-muted`, `--slms-option-hover`, `--slms-option-selected`. For example: `body { --slms-accent: var(--wp--preset--color--accent); }`.

Example: only let customers enrol.

`add_filter( 'accessnow_slms_can_enroll', function ( $allowed, $user_id, $course_id ) {
    if ( true === $allowed && ! my_has_paid( $user_id, $course_id ) ) {
        return new WP_Error( 'unpaid', 'Please buy this course to enrol.' );
    }
    return $allowed;
}, 10, 3 );`

== External services ==

This plugin does not send data anywhere by itself. If you add a YouTube address to a lesson or quiz question, WordPress uses its built-in oEmbed feature to show the video: your site asks YouTube for the embed code, and the visitor's browser then loads the video player from YouTube. This only happens on lessons and questions that have a YouTube address.

YouTube is provided by Google: [Terms of Service](https://www.youtube.com/t/terms), [Privacy Policy](https://policies.google.com/privacy).

== Changelog ==

= 1.0.0 =
* Changed: renamed from "Simple LMS" to "AccessNow LMS", ready for wordpress.org. The plugin folder, main file and text domain are now `accessnow-lms`, so WordPress treats it as a new plugin: see the FAQ for the upgrade steps. Courses, enrolments and progress carry over, settings move to new names automatically on the first page load, and theme template overrides copied from 4.x keep working.
* New: "Delete data on uninstall" setting (off by default). Deleting the plugin keeps your data unless it is ticked.
* New: actions and filters for developers (enrolment, lesson and course completion, quiz submission, certificates, enrolment and lesson access, grading, points and emails). See the Developers section. The plugin's own emails and points run on these actions.
* New: the student dashboard lists completed courses, and "My courses" lists them with a Completed badge.
* New: quiz attempts that ran out of time show as "Timed out" in the student's quiz history, on the dashboard and in reports.
* New: themes can restyle the plugin with CSS custom properties (see Developers). Nothing changes until a theme sets them.
* New: Help articles and Get support links on the Plugins screen, a Help tab on every AccessNow LMS screen, and a Settings link beside Deactivate.
* New: header images and styles. Units and lessons can have their own header image (lessons fall back to their unit's, then their course's), and courses, units and lessons choose a header style: banner, compact strip or none. A new setting, "Inherited header images", turns the fallback off. Course export, import and cloning keep them.
* New: course export includes the pictures and files used inside lesson and course text, and the imported course points at its own copies. A file used more than once is imported once.
* New: Course Catalogue, My Courses and Student Dashboard blocks, with a live preview in the editor. The catalogue block can show a set number of courses and a single language.
* New: quiz feedback. Each question can have an explanation, and each quiz chooses when students see the correct answers and explanations: never, after they pass, after their last attempt, or after every attempt. Answers are only sent to the browser when the setting allows.
* New: CSV export of enrolments (with each student's progress) and quiz attempts, for one course or all of them, from the Enrollments and Reports screens. Files open in Excel with Japanese and other names intact.
* New: enrol students by searching for their name, username or email, instead of typing a user ID, and enrol several students in several courses at once. An optional checkbox emails each student that they have been enrolled.
* New: `accessnow_slms_*` shortcodes (the `slms_*` ones still work).
* Security: removed the course seed scripts from the plugin. They loaded WordPress with no access check, so visiting their address could create a course.
* Security: the log-in and registration forms no longer redirect to other websites (open redirect), and student registration also requires the WordPress "Anyone can register" setting.
* Security: stricter checks on every student action: enrol only in published courses, complete lessons only in order and in your own enrolment, submit quizzes and save notes only for courses you are enrolled in.
* Security: course import validates the file type and size, reads the ZIP without extracting it, and accepts only allowed media types and known settings. Lesson content is hidden from the REST API for visitors who are not enrolled.
* Security: every student action (enrol, complete a lesson, submit a quiz, save a note) checks the account's permissions as well as its security token, and quiz answers are cleaned as soon as they arrive.
* Security: the registration form's speed check can no longer be skipped. The form now carries a signed timestamp that is required, so a bot can't leave it out or invent one; a page left open for more than a week asks the visitor to reload it.
* Accessibility: progress bars report their value to screen readers; each quiz question is a group whose label is the question; moving between questions and submitting moves the keyboard focus to the new question or the result; the quiz timer announces 5 minutes, 1 minute and 30 seconds left; error and success messages appear in the page instead of pop-up alerts; colours meet WCAG AA contrast; the registration spam trap can no longer be reached with the keyboard.
* Accessibility: every field on the Settings, Enrollments and quiz question screens has a label (including options and answers added with the buttons), and the registration notice's "Dismiss for 30 days" link meets contrast.
* Fix: importing or cloning a course could corrupt text containing backslashes, such as escaped characters in a block's settings or in quiz questions.
* Fix: course export left out each quiz's "Show answers" setting and the course's re-completion and language settings; import and cloning now keep them too.
* Fix: with a block theme such as Twenty Twenty-Five, course, lesson, quiz and catalogue pages showed WordPress's bare fallback header and footer instead of the theme's (and logged a deprecation notice). They now use the theme's header and footer.
* Fix: the quiz results table on the student dashboard and in a lesson ran past the edge of narrow themes. A wide table now scrolls inside its own box, which keyboard and screen reader users can reach.
* Fix: students who had completed a course could no longer use it: finished lessons showed as not complete, quiz history was blank, and "Mark complete" and quiz submission said "Not enrolled."
* Fix: when a lesson is added to a course, or a lesson or quiz changes so it must be redone, a completed enrolment reopens and completes again once everything is done. The certificate is kept and the completion email is not sent twice.
* Fix: an unanswered multiple-choice or true/false question could be marked correct when the right answer was the first option or "True".
* Fix: quiz time limits are enforced by the server, not just the browser. The clock starts when the quiz page is first opened and survives reloads and new tabs. A quiz submitted more than a minute after time runs out, or left to run out without being submitted, counts as an attempt scoring 0.
* Fix: a unit or lesson without a sort order (created through the REST API, an import, cloning or another plugin) was left out of the lesson order, so "Continue", previous and next links and sequential locking skipped it and a course could not reach 100%.
* Fix: the database tables were defined in a form WordPress's table updater could not compare with existing tables, so later versions could never have added a column or index. Activating the plugin also adds two indexes some 4.x copies were missing, including the one that stops points being awarded twice (duplicate awards are removed first).
* Fix: on a fresh install the settings page showed the enrolment, completion and admin enrolment emails as switched on, although none was sent until the settings were saved. They now show as off, which is what happens.
* Fix: the course completion email's "View your certificate" link now goes to the certificate, not the course page.
* Fix: fill-in-the-blank answers match regardless of case, accents and full-width or half-width letters, digits and spaces, so answers typed on a Japanese keyboard match.
* Fix: reordering units and lessons now needs the same permission as the rest of the plugin's admin screens.
* Fix: the reports overview works out each course's average progress with one query instead of one per student.
* Fix: the registration rate limit and the automatic Student role apply only to people registering themselves, never to users created by administrators, WP-CLI or other plugins.
* Fix: clicking Enroll twice sent the enrolment email twice; unticking "Featured course" now saves; quiz answer order no longer reseeds PHP's random number generator; old /lessons/ and /quizzes/ links redirect on sites in a subfolder too.
* Changed: requires WordPress 6.3 or later (was 6.0), for the blocks in the current editor.
* Changed: the catalogue's language filter only appears when the published courses use two or more languages, and only offers those.
* Changed: the quiz result email (setting renamed "Quiz Passed") is now sent only when the student passes, not after every attempt, and the setting is on by default as the settings page already showed.
* Changed: all output escaped and all input sanitised; Japanese translation updated.

= 4.1.1 =
* Last release of Simple LMS.

== Upgrade Notice ==

= 1.0.0 =
Renamed to AccessNow LMS in a new folder. Back up, deactivate Simple LMS, activate this one (data carries over), then remove the old simple-lms folder by hand. Never delete the old plugin from the Plugins screen: that erases every course and enrolment.
