Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 127 additions & 0 deletions backend/Actions/SenseiLMS/RecordApiHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<?php

/**
* Sensei LMS Record Api
*/

namespace BitApps\Integrations\Actions\SenseiLMS;

use BitApps\Integrations\Config;
use BitApps\Integrations\Core\Util\Common;
use BitApps\Integrations\Core\Util\Hooks;
use BitApps\Integrations\Log\LogHandler;

/**
* Provide functionality for Record insert, update
*/
class RecordApiHelper
{
private $_integrationID;

private $_integrationDetails;

public function __construct($integrationDetails, $integId)
{
$this->_integrationDetails = $integrationDetails;
$this->_integrationID = $integId;
}

public function execute($fieldValues, $fieldMap, $utilities)
{
if (!class_exists('Sensei_Main')) {
return [
'success' => false,
'message' => __('Sensei LMS is not installed or activated', 'bit-integrations')
];
}

$fieldData = static::generateReqDataFromFieldMap($fieldMap, $fieldValues);

$mainAction = $this->_integrationDetails->mainAction ?? null;

$defaultResponse = [
'success' => false,
// translators: %s: Plugin name
'message' => wp_sprintf(__('%s plugin is not installed or activated', 'bit-integrations'), 'Bit Integrations Pro')
];

switch ($mainAction) {
case 'enroll_user_in_course':
$response = Hooks::apply(Config::withPrefix('sensei_lms_enroll_user_in_course'), $defaultResponse, $fieldData, $this->_integrationDetails);

break;
case 'withdraw_user_from_course':
$response = Hooks::apply(Config::withPrefix('sensei_lms_withdraw_user_from_course'), $defaultResponse, $fieldData, $this->_integrationDetails);

break;
case 'start_course_for_user':
$response = Hooks::apply(Config::withPrefix('sensei_lms_start_course_for_user'), $defaultResponse, $fieldData, $this->_integrationDetails);

break;
case 'complete_course_for_user':
$response = Hooks::apply(Config::withPrefix('sensei_lms_complete_course_for_user'), $defaultResponse, $fieldData, $this->_integrationDetails);

break;
case 'reset_course_for_user':
$response = Hooks::apply(Config::withPrefix('sensei_lms_reset_course_for_user'), $defaultResponse, $fieldData, $this->_integrationDetails);

break;
case 'start_lesson_for_user':
$response = Hooks::apply(Config::withPrefix('sensei_lms_start_lesson_for_user'), $defaultResponse, $fieldData, $this->_integrationDetails);

break;
case 'update_lesson_status':
$response = Hooks::apply(Config::withPrefix('sensei_lms_update_lesson_status'), $defaultResponse, $fieldData, $this->_integrationDetails);

break;
case 'reset_lesson_for_user':
$response = Hooks::apply(Config::withPrefix('sensei_lms_reset_lesson_for_user'), $defaultResponse, $fieldData, $this->_integrationDetails);

break;
case 'grade_quiz':
$response = Hooks::apply(Config::withPrefix('sensei_lms_grade_quiz'), $defaultResponse, $fieldData, $this->_integrationDetails);

break;
case 'create_course':
$response = Hooks::apply(Config::withPrefix('sensei_lms_create_course'), $defaultResponse, $fieldData, $this->_integrationDetails);

break;
case 'create_lesson':
$response = Hooks::apply(Config::withPrefix('sensei_lms_create_lesson'), $defaultResponse, $fieldData, $this->_integrationDetails);

break;
case 'create_certificate':
$response = Hooks::apply(Config::withPrefix('sensei_lms_create_certificate'), $defaultResponse, $fieldData, $this->_integrationDetails);

break;
default:
$response = ['success' => false, 'message' => __('Invalid action', 'bit-integrations')];

break;
}

$responseType = isset($response['success']) && $response['success'] ? 'success' : 'error';
LogHandler::save($this->_integrationID, ['type' => 'SenseiLMS', 'type_name' => $mainAction], $responseType, $response);

return $response;
}

private static function generateReqDataFromFieldMap($fieldMap, $fieldValues)
{
$dataFinal = [];
foreach ($fieldMap as $item) {
if (empty($item->senseiLMSField)) {
continue;
}

$triggerValue = $item->formField;
$actionValue = $item->senseiLMSField;

$dataFinal[$actionValue] = $triggerValue === 'custom' && isset($item->customValue)
? Common::replaceFieldWithValue($item->customValue, $fieldValues)
: $fieldValues[$triggerValue] ?? '';
}

return $dataFinal;
}
}
12 changes: 12 additions & 0 deletions backend/Actions/SenseiLMS/Routes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

if (!defined('ABSPATH')) {
exit;
}

use BitApps\Integrations\Actions\SenseiLMS\SenseiLMSController;
use BitApps\Integrations\Core\Util\Route;

Route::post('refresh_sensei_lms_courses', [SenseiLMSController::class, 'refreshCourses']);
Route::post('refresh_sensei_lms_lessons', [SenseiLMSController::class, 'refreshLessons']);
Route::post('refresh_sensei_lms_quizzes', [SenseiLMSController::class, 'refreshQuizzes']);
87 changes: 87 additions & 0 deletions backend/Actions/SenseiLMS/SenseiLMSController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php

/**
* Sensei LMS Integration
*/

namespace BitApps\Integrations\Actions\SenseiLMS;

use WP_Error;

/**
* Provide functionality for Sensei LMS integration
*/
class SenseiLMSController
{
public static function isExists()
{
if (!class_exists('Sensei_Main')) {
wp_send_json_error(
__('Sensei LMS is not activated or not installed', 'bit-integrations'),
400
);
}
}

public static function refreshCourses()
{
self::isExists();

$response['courses'] = self::postOptions('course');
wp_send_json_success($response, 200);
}

public static function refreshLessons()
{
self::isExists();

$response['lessons'] = self::postOptions('lesson');
wp_send_json_success($response, 200);
}

public static function refreshQuizzes()
{
self::isExists();

$response['quizzes'] = self::postOptions('quiz');
wp_send_json_success($response, 200);
}

public function execute($integrationData, $fieldValues)
{
$integrationDetails = $integrationData->flow_details;
$integId = $integrationData->id;
$fieldMap = $integrationDetails->field_map;
$utilities = isset($integrationDetails->utilities) ? $integrationDetails->utilities : [];

if (empty($fieldMap)) {
return new WP_Error('field_map_empty', __('Field map is empty', 'bit-integrations'));
}

$recordApiHelper = new RecordApiHelper($integrationDetails, $integId);

return $recordApiHelper->execute($fieldValues, $fieldMap, $utilities);
}

private static function postOptions($postType)
{
$options = [];

$posts = get_posts(
[
'post_type' => $postType,
'post_status' => 'publish',
'numberposts' => -1,
]
);

foreach ($posts as $post) {
$options[] = (object) [
'value' => $post->ID,
'label' => $post->post_title,
];
}

return $options;
}
}
3 changes: 3 additions & 0 deletions frontend/src/components/AllIntegrations/EditInteg.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ const EditCreatorLms = lazy(() => import('./CreatorLms/EditCreatorLms'))
const EditUltimateAffiliatePro = lazy(() => import('./UltimateAffiliatePro/EditUltimateAffiliatePro'))
const EditBookly = lazy(() => import('./Bookly/EditBookly'))
const EditFluentCart = lazy(() => import('./FluentCart/EditFluentCart'))
const EditSenseiLMS = lazy(() => import('./SenseiLMS/EditSenseiLMS'))
const EditFluentPlayer = lazy(() => import('./FluentPlayer/EditFluentPlayer'))
const EditBitCrm = lazy(() => import('./BitCrm/EditBitCrm'))
const EditWsms = lazy(() => import('./Wsms/EditWsms'))
Expand Down Expand Up @@ -631,6 +632,8 @@ const IntegType = memo(({ allIntegURL, flow }) => {
return <EditBookly allIntegURL={allIntegURL} />
case 'FluentCart':
return <EditFluentCart allIntegURL={allIntegURL} />
case 'SenseiLMS':
return <EditSenseiLMS allIntegURL={allIntegURL} />
case 'FluentPlayer':
return <EditFluentPlayer allIntegURL={allIntegURL} />
case 'BitCrm':
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/components/AllIntegrations/IntegInfo.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ const UltimateAffiliateProAuthorization = lazy(
)
const BooklyAuthorization = lazy(() => import('./Bookly/BooklyAuthorization'))
const FluentCartAuthorization = lazy(() => import('./FluentCart/FluentCartAuthorization'))
const SenseiLMSAuthorization = lazy(() => import('./SenseiLMS/SenseiLMSAuthorization'))
const FluentPlayerAuthorization = lazy(() => import('./FluentPlayer/FluentPlayerAuthorization'))
const BitCrmAuthorization = lazy(() => import('./BitCrm/BitCrmAuthorization'))
const WsmsAuthorization = lazy(() => import('./Wsms/WsmsAuthorization'))
Expand Down Expand Up @@ -671,6 +672,8 @@ const IntegrationInfo = memo(({ integrationConf, location, editUrl }) => {
return <BooklyAuthorization booklyConf={integrationConf} step={1} isInfo />
case 'FluentCart':
return <FluentCartAuthorization fluentCartConf={integrationConf} step={1} isInfo />
case 'SenseiLMS':
return <SenseiLMSAuthorization senseiLMSConf={integrationConf} step={1} isInfo />
case 'FluentPlayer':
return <FluentPlayerAuthorization fluentPlayerConf={integrationConf} step={1} isInfo />
case 'BitCrm':
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/components/AllIntegrations/NewInteg.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ const CreatorLms = lazy(() => import('./CreatorLms/CreatorLms'))
const UltimateAffiliatePro = lazy(() => import('./UltimateAffiliatePro/UltimateAffiliatePro'))
const Bookly = lazy(() => import('./Bookly/Bookly'))
const FluentCart = lazy(() => import('./FluentCart/FluentCart'))
const SenseiLMS = lazy(() => import('./SenseiLMS/SenseiLMS'))
const FluentPlayer = lazy(() => import('./FluentPlayer/FluentPlayer'))
const BitCrm = lazy(() => import('./BitCrm/BitCrm'))
const Wsms = lazy(() => import('./Wsms/Wsms'))
Expand Down Expand Up @@ -1750,6 +1751,15 @@ const NewIntegs = memo(({ integUrlName, allIntegURL, flow, setFlow }) => {
setFlow={setFlow}
/>
)
case 'SenseiLMS':
return (
<SenseiLMS
allIntegURL={allIntegURL}
formFields={flow?.triggerData?.fields}
flow={flow}
setFlow={setFlow}
/>
)
case 'FluentPlayer':
return (
<FluentPlayer
Expand Down
108 changes: 108 additions & 0 deletions frontend/src/components/AllIntegrations/SenseiLMS/EditSenseiLMS.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { create } from 'mutative'
import { useEffect, useState } from 'react'
import { useNavigate, useParams } from 'react-router'
import { useRecoilState, useRecoilValue } from 'recoil'
import { $actionConf, $formFields, $newFlow } from '../../../GlobalStates'
import { __ } from '../../../Utils/i18nwrap'
import SnackMsg from '../../Utilities/SnackMsg'
import { saveActionConf } from '../IntegrationHelpers/IntegrationHelpers'
import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree'
import SetEditIntegComponents from '../IntegrationHelpers/SetEditIntegComponents'
import {
checkMappedFields,
handleInput,
refreshCourses,
refreshLessons,
refreshQuizzes
} from './SenseiLMSCommonFunc'
import SenseiLMSIntegLayout from './SenseiLMSIntegLayout'
import { courseActions, lessonActions, quizActions, senseiLMSStaticData } from './staticData'

export default function EditSenseiLMS({ allIntegURL }) {
const navigate = useNavigate()
const { id, formID } = useParams()

const [senseiLMSConf, setSenseiLMSConf] = useRecoilState($actionConf)
const [flow, setFlow] = useRecoilState($newFlow)
const formFields = useRecoilValue($formFields)
const [isLoading, setIsLoading] = useState(false)
const [snack, setSnackbar] = useState({ show: false })
Comment thread
RishadAlam marked this conversation as resolved.

// On edit, rebuild the non-persisted field definitions from the saved action and
// repopulate the resource dropdown options. The saved field_map mapping is untouched.
useEffect(() => {
const action = senseiLMSConf?.mainAction
if (!action) {
return
}

setSenseiLMSConf(prevConf =>
create(prevConf, draftConf => {
draftConf.senseiLMSFields = senseiLMSStaticData[action] || []
})
)

if (courseActions.includes(action)) {
refreshCourses(setSenseiLMSConf, setIsLoading)
} else if (lessonActions.includes(action)) {
refreshLessons(setSenseiLMSConf, setIsLoading)
} else if (quizActions.includes(action)) {
refreshQuizzes(setSenseiLMSConf, setIsLoading)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

return (
<div style={{ width: 900 }}>
<SnackMsg snack={snack} setSnackbar={setSnackbar} />

<div className="flx mt-3">
<b className="wdt-200 d-in-b">{__('Integration Name:', 'bit-integrations')}</b>
<input
className="btcd-paper-inp w-5"
onChange={e => handleInput(e, senseiLMSConf, setSenseiLMSConf)}
name="name"
value={senseiLMSConf.name}
type="text"
placeholder={__('Integration Name...', 'bit-integrations')}
/>
</div>
<br />

<SetEditIntegComponents entity={flow.triggered_entity} setSnackbar={setSnackbar} />

<SenseiLMSIntegLayout
formID={formID}
formFields={formFields}
senseiLMSConf={senseiLMSConf}
setSenseiLMSConf={setSenseiLMSConf}
setSnackbar={setSnackbar}
setIsLoading={setIsLoading}
isLoading={isLoading}
/>

<IntegrationStepThree
edit
saveConfig={() =>
saveActionConf({
flow,
setFlow,
allIntegURL,
conf: senseiLMSConf,
navigate,
id,
edit: 1,
setIsLoading,
setSnackbar
})
}
disabled={!checkMappedFields(senseiLMSConf)}
isLoading={isLoading}
dataConf={senseiLMSConf}
setDataConf={setSenseiLMSConf}
formFields={formFields}
/>
<br />
</div>
)
}
Loading