Security and data handling
What an integration must do to avoid leaking course content or student data.
Check access before rendering anything
The rule is absolute: no course or lesson content leaves your code without a call to the access helpers.
if ( ! function_exists( 'pom_lms_user_can_access_lesson' ) ) {
return '';
}
if ( ! pom_lms_user_can_access_lesson( $lesson_id, $course_id ) ) {
return '';
}
This covers more surfaces than it first appears: shortcodes, blocks, page-builder modules, REST responses, feeds, sitemaps that include excerpts, search results that show content, exports, and anything that puts a video URL into an email.
Do not reimplement the decision. See The access control contract.
Fail closed
When POM LMS is missing, when the theme's permission support is missing, when a lookup fails — the safe answer is no access.
Never write a fallback that renders content because a check could not be performed.
Video URLs are content
A lesson's video URL is the thing students paid for. Putting it into an accessible page, a data attribute, an API response or a log entry publishes it to anyone who can read that surface.
Two truths to design around:
- Unlisted is not private. Anyone with the URL can watch a YouTube or Vimeo video, whether or not they bought the course.
- A player rendered for a blocked lesson leaks the URL, even if the page also shows a no-access message.
Check access before rendering, not after.
Progress is personal data
A student's completion record says what they have studied. Treat it as belonging to them:
- Read it with
pom_lms_get_course_completion()in the student's own request. - Do not display one student's progress to another.
- Do not put it in a URL, a shared link or an unauthenticated response.
- Include it when you fulfill an export or erasure request under your privacy policy.
There is no supported way to read another user's progress, and that is a design decision rather than an omission.
Escape output
Everything you render from stored fields must be escaped for its context — esc_html() for text, esc_url() for links and video URLs, esc_attr() for attributes.
Fields configured by editors are trusted no more than any other stored content: an editor account can be compromised, and a rich-text field can carry markup.
Verify capabilities on your own actions
Any endpoint, form or administrative action your integration adds is yours to protect. Check that the current user is allowed to perform it, and verify the request came from your own interface.
POM LMS's access helpers answer "may this person see this course?". They do not answer "may this person change something?" — that is a capability check, and it is separate.
Do not widen who can configure access
Anyone who can edit a course can change who gets access to it, and anyone who can edit a lesson can publish it to the world by marking it a preview.
If your integration adjusts roles or capabilities, keep that in view. Granting course editing to a wider group is an access decision, not a content decision.
Personal data an integration might touch
| Data | Where it lives |
|---|---|
| Which courses a student can access | Computed from roles, rules and orders — not stored |
| Which lessons they have completed | Stored per student, per course |
| What they bought | WooCommerce orders |
| Who they are | The WordPress user account |
POM LMS keeps no enrollment record, no timestamps and no activity log. If your integration creates any of those, it becomes your responsibility under whatever privacy regime applies to the site — including deletion when a user is erased.
Test as a real student
Every access-related change needs testing from a non-administrator account in a private window. An administrator's roles and orders qualify for things students' do not, and testing while signed in as one is the most common way a leak reaches production.