feat(masterstudy-lms): add user enroll/unenroll & course/lesson completion actions#182
feat(masterstudy-lms): add user enroll/unenroll & course/lesson completion actions#182RishadAlam wants to merge 4 commits into
Conversation
- guard non-array hook response and null course/lesson ids - share per-action validation (isActionConfigIncomplete) with edit screen - preserve mapped user email when switching between email actions - move allActions to common helper (break circular import) - drop dead handleInput prop from integ layout
There was a problem hiding this comment.
Code Review
This pull request refactors the MasterStudy LMS integration by introducing constants for actions, implementing new Pro actions (Enroll User, Unenroll User, Mark Course Complete, and Mark Lesson Complete), and adding field mapping support for user emails. The frontend is updated to dynamically validate action configurations and render the appropriate mapping fields. The review feedback suggests using is_wp_error() in the backend to safely handle potential WordPress error objects before accessing them as arrays, preventing fatal errors in PHP 8.0+. Additionally, it recommends using falsy checks instead of strict undefined checks in the frontend configuration validation to more robustly identify incomplete fields.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| 'course_id' => $integrationDetails->courseId ?? null, | ||
| 'email' => $fieldData['user_email'], | ||
| ]); | ||
| LogHandler::save($this->integrationID, wp_json_encode(['type' => 'enroll-user', 'type_name' => 'enroll-user-to-course']), (\is_array($response) && !empty($response['success'])) ? 'success' : 'error', \is_array($response) ? ($response['message'] ?? '') : ''); |
There was a problem hiding this comment.
When handling responses that may return a WP_Error object, always use the is_wp_error() function to validate the response before accessing it as an array or object. This prevents fatal errors in PHP 8.0+ and ensures that the actual error message is correctly logged.
if (is_wp_error($response)) {
LogHandler::save($this->integrationID, wp_json_encode(['type' => 'enroll-user', 'type_name' => 'enroll-user-to-course']), 'error', $response->get_error_message());
} else {
LogHandler::save($this->integrationID, wp_json_encode(['type' => 'enroll-user', 'type_name' => 'enroll-user-to-course']), !empty($response['success']) ? 'success' : 'error', $response['message'] ?? '');
}References
- In PHP, when handling responses that may return a WP_Error object, always use the is_wp_error() function to validate the response before accessing it as an array or object. This prevents fatal errors in PHP 8.0+.
There was a problem hiding this comment.
The log line already guards with \is_array($response), which prevents the array-access fatal for any non-array value (including a WP_Error). In this branch $response is the return of Hooks::apply(...) — always the array the Pro handler returns, or the $defaultResponse array when Pro is inactive. The WP_Error paths (empty user_email) return earlier, before the hook call, so there's no WP_Error to unwrap here. No change needed.
| 'course_id' => $integrationDetails->courseId ?? null, | ||
| 'email' => $fieldData['user_email'], | ||
| ]); | ||
| LogHandler::save($this->integrationID, wp_json_encode(['type' => 'unenroll-user', 'type_name' => 'unenroll-user-from-course']), (\is_array($response) && !empty($response['success'])) ? 'success' : 'error', \is_array($response) ? ($response['message'] ?? '') : ''); |
There was a problem hiding this comment.
When handling responses that may return a WP_Error object, always use the is_wp_error() function to validate the response before accessing it as an array or object. This prevents fatal errors in PHP 8.0+ and ensures that the actual error message is correctly logged.
if (is_wp_error($response)) {
LogHandler::save($this->integrationID, wp_json_encode(['type' => 'unenroll-user', 'type_name' => 'unenroll-user-from-course']), 'error', $response->get_error_message());
} else {
LogHandler::save($this->integrationID, wp_json_encode(['type' => 'unenroll-user', 'type_name' => 'unenroll-user-from-course']), !empty($response['success']) ? 'success' : 'error', $response['message'] ?? '');
}References
- In PHP, when handling responses that may return a WP_Error object, always use the is_wp_error() function to validate the response before accessing it as an array or object. This prevents fatal errors in PHP 8.0+.
There was a problem hiding this comment.
The log line already guards with \is_array($response), which prevents the array-access fatal for any non-array value (including a WP_Error). In this branch $response is the return of Hooks::apply(...) — always the array the Pro handler returns, or the $defaultResponse array when Pro is inactive. The WP_Error paths (empty user_email) return earlier, before the hook call, so there's no WP_Error to unwrap here. No change needed.
| 'course_id' => $integrationDetails->courseId ?? null, | ||
| 'email' => $fieldData['user_email'], | ||
| ]); | ||
| LogHandler::save($this->integrationID, wp_json_encode(['type' => 'mark-course-complete', 'type_name' => 'mark-course-complete-for-user']), (\is_array($response) && !empty($response['success'])) ? 'success' : 'error', \is_array($response) ? ($response['message'] ?? '') : ''); |
There was a problem hiding this comment.
When handling responses that may return a WP_Error object, always use the is_wp_error() function to validate the response before accessing it as an array or object. This prevents fatal errors in PHP 8.0+ and ensures that the actual error message is correctly logged.
if (is_wp_error($response)) {
LogHandler::save($this->integrationID, wp_json_encode(['type' => 'mark-course-complete', 'type_name' => 'mark-course-complete-for-user']), 'error', $response->get_error_message());
} else {
LogHandler::save($this->integrationID, wp_json_encode(['type' => 'mark-course-complete', 'type_name' => 'mark-course-complete-for-user']), !empty($response['success']) ? 'success' : 'error', $response['message'] ?? '');
}References
- In PHP, when handling responses that may return a WP_Error object, always use the is_wp_error() function to validate the response before accessing it as an array or object. This prevents fatal errors in PHP 8.0+.
There was a problem hiding this comment.
The log line already guards with \is_array($response), which prevents the array-access fatal for any non-array value (including a WP_Error). In this branch $response is the return of Hooks::apply(...) — always the array the Pro handler returns, or the $defaultResponse array when Pro is inactive. The WP_Error paths (empty user_email) return earlier, before the hook call, so there's no WP_Error to unwrap here. No change needed.
| 'lesson_id' => $integrationDetails->lessonId ?? null, | ||
| 'email' => $fieldData['user_email'], | ||
| ]); | ||
| LogHandler::save($this->integrationID, wp_json_encode(['type' => 'mark-lesson-complete', 'type_name' => 'mark-lesson-complete-for-user']), (\is_array($response) && !empty($response['success'])) ? 'success' : 'error', \is_array($response) ? ($response['message'] ?? '') : ''); |
There was a problem hiding this comment.
When handling responses that may return a WP_Error object, always use the is_wp_error() function to validate the response before accessing it as an array or object. This prevents fatal errors in PHP 8.0+ and ensures that the actual error message is correctly logged.
if (is_wp_error($response)) {
LogHandler::save($this->integrationID, wp_json_encode(['type' => 'mark-lesson-complete', 'type_name' => 'mark-lesson-complete-for-user']), 'error', $response->get_error_message());
} else {
LogHandler::save($this->integrationID, wp_json_encode(['type' => 'mark-lesson-complete', 'type_name' => 'mark-lesson-complete-for-user']), !empty($response['success']) ? 'success' : 'error', $response['message'] ?? '');
}References
- In PHP, when handling responses that may return a WP_Error object, always use the is_wp_error() function to validate the response before accessing it as an array or object. This prevents fatal errors in PHP 8.0+.
There was a problem hiding this comment.
The log line already guards with \is_array($response), which prevents the array-access fatal for any non-array value (including a WP_Error). In this branch $response is the return of Hooks::apply(...) — always the array the Pro handler returns, or the $defaultResponse array when Pro is inactive. The WP_Error paths (empty user_email) return earlier, before the hook call, so there's no WP_Error to unwrap here. No change needed.
| export const isActionConfigIncomplete = conf => { | ||
| switch (conf?.mainAction) { | ||
| case MS_LMS_ACTIONS.COMPLETE_COURSE: | ||
| case MS_LMS_ACTIONS.RESET_COURSE: | ||
| return conf.courseId === undefined | ||
| case MS_LMS_ACTIONS.COMPLETE_LESSON: | ||
| case MS_LMS_ACTIONS.RESET_LESSON: | ||
| return conf.lessonId === undefined | ||
| case MS_LMS_ACTIONS.COMPLETE_QUIZ: | ||
| return conf.quizId === undefined | ||
| case MS_LMS_ACTIONS.ENROLL_USER: | ||
| case MS_LMS_ACTIONS.UNENROLL_USER: | ||
| case MS_LMS_ACTIONS.MARK_COURSE_COMPLETE: | ||
| return conf.courseId === undefined || !isUserEmailMapped(conf) | ||
| case MS_LMS_ACTIONS.MARK_LESSON_COMPLETE: | ||
| return conf.courseId === undefined || conf.lessonId === undefined || !isUserEmailMapped(conf) | ||
| default: | ||
| return false | ||
| } | ||
| } |
There was a problem hiding this comment.
Using strict equality checks against undefined can fail if the values are null or empty strings. It is safer and more robust to use falsy checks (e.g., !conf.courseId) to ensure that missing, null, or empty values are correctly identified as incomplete.
| export const isActionConfigIncomplete = conf => { | |
| switch (conf?.mainAction) { | |
| case MS_LMS_ACTIONS.COMPLETE_COURSE: | |
| case MS_LMS_ACTIONS.RESET_COURSE: | |
| return conf.courseId === undefined | |
| case MS_LMS_ACTIONS.COMPLETE_LESSON: | |
| case MS_LMS_ACTIONS.RESET_LESSON: | |
| return conf.lessonId === undefined | |
| case MS_LMS_ACTIONS.COMPLETE_QUIZ: | |
| return conf.quizId === undefined | |
| case MS_LMS_ACTIONS.ENROLL_USER: | |
| case MS_LMS_ACTIONS.UNENROLL_USER: | |
| case MS_LMS_ACTIONS.MARK_COURSE_COMPLETE: | |
| return conf.courseId === undefined || !isUserEmailMapped(conf) | |
| case MS_LMS_ACTIONS.MARK_LESSON_COMPLETE: | |
| return conf.courseId === undefined || conf.lessonId === undefined || !isUserEmailMapped(conf) | |
| default: | |
| return false | |
| } | |
| } | |
| export const isActionConfigIncomplete = conf => { | |
| switch (conf?.mainAction) { | |
| case MS_LMS_ACTIONS.COMPLETE_COURSE: | |
| case MS_LMS_ACTIONS.RESET_COURSE: | |
| return !conf.courseId | |
| case MS_LMS_ACTIONS.COMPLETE_LESSON: | |
| case MS_LMS_ACTIONS.RESET_LESSON: | |
| return !conf.lessonId | |
| case MS_LMS_ACTIONS.COMPLETE_QUIZ: | |
| return !conf.quizId | |
| case MS_LMS_ACTIONS.ENROLL_USER: | |
| case MS_LMS_ACTIONS.UNENROLL_USER: | |
| case MS_LMS_ACTIONS.MARK_COURSE_COMPLETE: | |
| return !conf.courseId || !isUserEmailMapped(conf) | |
| case MS_LMS_ACTIONS.MARK_LESSON_COMPLETE: | |
| return !conf.courseId || !conf.lessonId || !isUserEmailMapped(conf) | |
| default: | |
| return false | |
| } | |
| } |
There was a problem hiding this comment.
Applied — switched to falsy checks so null/empty values are also treated as incomplete:
case MS_LMS_ACTIONS.COMPLETE_COURSE:
case MS_LMS_ACTIONS.RESET_COURSE:
return !conf.courseId
...
case MS_LMS_ACTIONS.MARK_LESSON_COMPLETE:
return !conf.courseId || !conf.lessonId || !isUserEmailMapped(conf)
🔍 WordPress Plugin Check Report
📊 Report
|
| 📍 Line | 🔖 Check | 💬 Message |
|---|---|---|
0 |
mismatched_plugin_name | Plugin name "Bit integrations - Form Integration, Webhook, Spreadsheets, CRM, LMS & Email Automation" is different from the name declared in plugin header "Bit Integrations". |
🤖 Generated by WordPress Plugin Check Action • Learn more about Plugin Check
Description
Adds four new MasterStudy LMS actions — Enroll user in a course, Unenroll user from a course, Mark a course complete for the user, and Mark a lesson complete for the user — targeting any user by email. The existing logged-in-user actions (complete/reset course, lesson, quiz) are untouched.
Motivation & Context
The integration previously only acted on the currently logged-in user. These actions let a flow operate on a specific user (resolved from a mapped email field), enabling automations like "when X happens, enroll this user / mark their course complete". The actual write logic lives in the Pro plugin; the free plugin fires the
bit_integrations_master_study_lms_*hooks.Related Links: (if applicable)
Type of Change
Key Changes
Integrations — Actions
bit_integrations_master_study_lms_*filter handled by the Pro plugin; the free side validates and logs the result.user_emailfield-map row for the new actions so the target user can be mapped dynamically from a trigger field (form field, custom value, or smart code) — not a static text input.Frontend
MultiSelect(checkIsPro/getProLabel) so the new Pro actions show a(Pro)badge and are disabled for non-Pro users.MasterStudyLmsFieldMap(clone of the standard field-map row) andMasterStudyLmsCommonFunchelpers (MS_LMS_ACTIONSconstants,allActions,generateMappedField,isUserEmailMapped,isActionConfigIncomplete).ReferenceErroron the course-refresh button (fetchAllCourse→fetchAllMsLmsCourse).isActionConfigIncompletecheck as the New wizard.allActionsinto the common helper to remove a circular import; removed a deadhandleInputprop.Backend
RecordApiHelperthat build field-map data and dispatch to the Pro hooks, with null-safe response handling and course/lesson id guards.Checklist
Changelog