summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--composer.json3
-rw-r--r--composer.lock6
-rw-r--r--config/scorm-player.php5
-rw-r--r--database/migrations/scorm_player_improvements.php.stub53
-rw-r--r--resources/views/player.blade.php13
-rw-r--r--routes/web.php29
-rw-r--r--src/Http/Controllers/ScormPlayerController.php103
-rw-r--r--src/Models/Scorm.php12
-rw-r--r--src/Models/ScormSco.php12
-rw-r--r--src/Models/ScormScoTracking.php115
-rw-r--r--src/ScormPlayerServiceProvider.php27
11 files changed, 371 insertions, 7 deletions
diff --git a/composer.json b/composer.json
index b01c72d..484426e 100644
--- a/composer.json
+++ b/composer.json
@@ -22,6 +22,9 @@
"laravel": {
"providers": [
"Lightscale\\ScormPlayer\\ScormPlayerServiceProvider"
+ ],
+ "dont-discover": [
+ "devianl2/laravel-scorm"
]
}
}
diff --git a/composer.lock b/composer.lock
index 57467d7..ee15c34 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "828ec0e609f83cd705bc45738334f2ed",
+ "content-hash": "4a4f1c1d08af50453de8e49ac552c9d0",
"packages": [
{
"name": "devianl2/laravel-scorm",
@@ -842,7 +842,9 @@
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
- "platform": [],
+ "platform": {
+ "php": "^8.0"
+ },
"platform-dev": [],
"plugin-api-version": "2.3.0"
}
diff --git a/config/scorm-player.php b/config/scorm-player.php
new file mode 100644
index 0000000..74f89fa
--- /dev/null
+++ b/config/scorm-player.php
@@ -0,0 +1,5 @@
+<?php
+
+return [
+ 'middleware' => '',
+];
diff --git a/database/migrations/scorm_player_improvements.php.stub b/database/migrations/scorm_player_improvements.php.stub
new file mode 100644
index 0000000..4ee5d03
--- /dev/null
+++ b/database/migrations/scorm_player_improvements.php.stub
@@ -0,0 +1,53 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+ /**
+ * Run the migrations.
+ *
+ * @return void
+ */
+ public function up()
+ {
+ Schema::table('scorm_sco_tracking', function (Blueprint $table) {
+ $table->renameColumn('exit_mode', 'exit');
+ $table->renameColumn('lesson_location', 'location');
+ $table->renameColumn('lesson_mode', 'mode');
+ $table->string('session_time')->change();
+ });
+
+ Schema::table('scorm', function (Blueprint $table) {
+ $table->unique('uuid');
+ });
+
+ Schema::table('scorm_sco', function (Blueprint $table) {
+ $table->unique('uuid');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('scorm_sco_tracking', function (Blueprint $table) {
+ $table->renameColumn('exit', 'exit_mode');
+ $table->renameColumn('location', 'lesson_location');
+ $table->renameColumn('mode', 'lesson_mode');
+ });
+
+ Schema::table('scorm', function (Blueprint $table) {
+ $table->dropUnique('scorm_uuid_unique');
+ });
+
+ Schema::table('scorm_sco', function (Blueprint $table) {
+ $table->dropUnique('scorm_sco_uuid_unique');
+ });
+ }
+};
diff --git a/resources/views/player.blade.php b/resources/views/player.blade.php
new file mode 100644
index 0000000..a2baee5
--- /dev/null
+++ b/resources/views/player.blade.php
@@ -0,0 +1,13 @@
+<html>
+ <head>
+ <link rel="stylesheet" type="text/css" href="{{ manifest('css/scorm_player.css') }}" />
+ </head>
+
+ <body>
+ <iframe class="scorm-player"></iframe>
+ <script>
+ window.scorm_api_data = {{ Js::from($scorm_api_data) }};
+ </script>
+ <script src="{{ manifest('js/scorm_player.js') }}"></script>
+ </body>
+</html>
diff --git a/routes/web.php b/routes/web.php
new file mode 100644
index 0000000..b86d710
--- /dev/null
+++ b/routes/web.php
@@ -0,0 +1,29 @@
+<?php
+
+use Illuminate\Support\Facades\Route;
+
+//use Illuminate\Routing\Middleware\SubstituteBindings;
+
+use Lightscale\ScormPlayer\Http\Controllers\ScormPlayerController;
+
+Route::name('scorm-player.')->prefix('elearning')->middleware([
+ 'web',
+])->group(function() {
+
+ $group = function() {
+ Route::get('/scorm/{sco}', 'scormLoad')->name('scorm.load');
+ Route::post('/scorm/{tracking}', 'scormCommit')->name('scorm.commit');
+
+ Route::get('{module:uuid}', 'player')->name('player');
+ Route::get('files/{uuid}/{path}', 'serveModule')->name('serve')->where('path', '.*');
+ };
+
+ $route = Route::controller(ScormPlayerController::class);
+ $middleware = config('scorm.middleware');
+
+ if(!empty($middleware)) {
+ $route->middleware($middleware);
+ }
+
+ $route->group($group);
+});
diff --git a/src/Http/Controllers/ScormPlayerController.php b/src/Http/Controllers/ScormPlayerController.php
new file mode 100644
index 0000000..711be60
--- /dev/null
+++ b/src/Http/Controllers/ScormPlayerController.php
@@ -0,0 +1,103 @@
+<?php
+
+namespace Lightscale\ScormPlayer\Http\Controllers;
+
+use Lightscale\ScormPlayer\Models\{
+ Scorm,
+ ScormSco,
+ ScormScoTracking,
+};
+
+use Illuminate\Routing\Controller;
+use Illuminate\Support\Facades\Storage;
+use Illuminate\Support\Facades\File;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Http\Request;
+use Illuminate\Http\Testing\MimeType;
+
+use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
+
+
+class ScormPlayerController extends Controller
+{
+
+ public function player(Request $request, Scorm $module)
+ {
+ $sco = $request->query('sco');
+ $sco = $module->scos()->findOrFail($sco);
+
+ $route_data = ['sco' => $sco];
+ $scorm_api_data = [
+ 'routes' => [
+ 'load' => route('scorm-player.scorm.load', $route_data),
+ ],
+ ];
+
+ return view('scorm-player::player', compact(
+ 'scorm_api_data'
+ ));
+ }
+
+ public function serveModule(string $uuid, string $path)
+ {
+ $path = Storage::disk('scorm-local')->path("{$uuid}/{$path}");
+ $mime = MimeType::from($path);
+
+ try {
+ return response()->file($path, [
+ 'content-type' => $mime,
+ ]);
+ }
+ catch(FileNotFoundException $e) {
+ abort(404);
+ }
+ }
+
+ public function scormLoad(ScormSco $sco)
+ {
+ $user = Auth::user();
+
+ $tracking = ScormScoTracking::where([
+ 'sco_id' => $sco->id,
+ 'user_id' => $user->id,
+ ])->first();
+
+ if(!$tracking) {
+ $tracking = new ScormScoTracking([
+ 'uuid' => Str::uuid(),
+ 'progression' => 0,
+ ]);
+ $tracking->user()->associate($user);
+ $tracking->sco_id = $sco->id;
+ $tracking->save();
+ }
+
+ $commit_url = route('scorm-player.scorm.commit', [
+ 'tracking' => $tracking->id,
+ ]);
+
+ $scorm_entry = route('scorm-player.serve', [
+ 'uuid' => $sco->scorm->uuid,
+ 'path' => $sco->entry_url,
+ ]);
+
+ return [
+ 'tracking_id' => $tracking->id,
+ 'tracking' => $tracking->getCMIData(),
+ 'entry_url' => $scorm_entry,
+ 'commit_url' => $commit_url,
+ ];
+ }
+
+ public function scormCommit(Request $request, ScormScoTracking $tracking)
+ {
+ $data = $request->all();
+
+ $tracking->setCMIData($data);
+
+ return [
+ 'result' => true,
+ ];
+ }
+
+}
diff --git a/src/Models/Scorm.php b/src/Models/Scorm.php
new file mode 100644
index 0000000..7218ff6
--- /dev/null
+++ b/src/Models/Scorm.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Lightscale\ScormPlayer\Models;
+
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+
+use Peopleaps\Scorm\Model\ScormModel;
+
+class Scorm extends ScormModel
+{
+ use HasFactory;
+}
diff --git a/src/Models/ScormSco.php b/src/Models/ScormSco.php
new file mode 100644
index 0000000..b237f70
--- /dev/null
+++ b/src/Models/ScormSco.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Lightscale\ScormPlayer\Models;
+
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+
+use Peopleaps\Scorm\Model\ScormScoModel;
+
+class ScormSco extends ScormScoModel
+{
+ use HasFactory;
+}
diff --git a/src/Models/ScormScoTracking.php b/src/Models/ScormScoTracking.php
new file mode 100644
index 0000000..517015e
--- /dev/null
+++ b/src/Models/ScormScoTracking.php
@@ -0,0 +1,115 @@
+<?php
+
+namespace Lightscale\ScormPlayer\Models;
+
+use Illuminate\Database\Eloquent\Casts\Attribute;
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+
+use Peopleaps\Scorm\Model\ScormScoTrackingModel;
+
+class ScormScoTracking extends ScormScoTrackingModel
+{
+ use HasFactory;
+
+ protected $fillable = [
+ 'uuid',
+ 'progression',
+ 'score',
+ 'score_raw',
+ 'score_min',
+ 'score_max',
+ 'score_scaled',
+ 'lesson_status',
+ 'completion_status',
+ 'session_time',
+ 'total_time_int',
+ 'total_time_string',
+ 'entry',
+ 'suspend_data',
+ 'credit',
+ 'exit',
+ 'location',
+ 'mode',
+ 'is_locked',
+ 'details',
+ 'latest_date',
+ ];
+
+ public function user()
+ {
+ return $this->belongsTo(config('scorm.user_class'));
+ }
+
+ public function commentsFromLearner() : Attribute
+ {
+ return Attribute::make(
+ get: fn() => []
+ );
+ }
+
+ protected function commentsFromLms() : Attribute
+ {
+ return Attribute::make(
+ get: fn() => []
+ );
+ }
+
+ protected function score() : Attribute
+ {
+ return Attribute::make(
+ get: fn() => [
+ 'min' => $this->score_min,
+ 'max' => $this->score_max,
+ 'raw' => $this->score_raw,
+ 'scaled' => $this->score_scaled,
+ ],
+ set: fn(array $data) => [
+ 'score_min' => $data['min'] ?? null,
+ 'score_max' => $data['max'] ?? null,
+ 'score_raw' => $data['raw'] ?? null,
+ 'score_scaled' => $data['scaled'] ?? null,
+ ]
+ );
+ }
+
+ protected function learnerId() : Attribute
+ {
+ return Attribute::make(
+ get: fn() => $this->user_id
+ );
+ }
+
+ protected function learnerName() : Attribute
+ {
+ return Attribute::make(
+ get: fn() => $this->user->name
+ );
+ }
+
+ public function getCMIData() : array
+ {
+ $data = collect([
+ 'score',
+ 'entry',
+ 'credit',
+ 'exit',
+ 'mode',
+ 'location',
+ 'suspend_data',
+ 'completion_status',
+ //'comments_from_lms',
+ //'comments_from_learner',
+ 'learner_id',
+ 'learner_name',
+ ])->mapWithKeys(fn($k) => [$k => $this->{$k}])->all();
+
+ return ['cmi' => $data];
+ }
+
+ public function setCMIData(array $data)
+ {
+ $cmi = $data['cmi'] ?? [];
+ $this->fill($cmi);
+ $this->save();
+ }
+}
diff --git a/src/ScormPlayerServiceProvider.php b/src/ScormPlayerServiceProvider.php
index dc269be..c1f3369 100644
--- a/src/ScormPlayerServiceProvider.php
+++ b/src/ScormPlayerServiceProvider.php
@@ -2,23 +2,40 @@
namespace Lightscale\ScormPlayer;
-use Illuminate\Support\ServiceProvider;
+use Peopleaps\Scorm\ScormServiceProvider;
-class ScormPlayerServiceProvider extends ServiceProvider
+class ScormPlayerServiceProvider extends ScormServiceProvider
{
public function register()
{
- \Log::debug('player regist');
+ parent::register();
+
+ $this->mergeConfigFrom(
+ __DIR__ . '/../config/scorm-player.php', 'scorm'
+ );
+ }
+
+ protected function offerPublishing()
+ {
+ parent::offerPublishing();
+
+ $this->publishes([
+ __DIR__ . '/../database/migrations/scorm_player_improvements.php.stub' =>
+ $this->getMigrationFileName('scorm_player_improvements.php'),
+ ], 'migrations');
+
}
public function boot()
{
+ parent::boot();
+
// Load routes
- //$this->loadRoutesFrom(__DIR__.'/../routes/web.php');
+ $this->loadRoutesFrom(__DIR__ . '/../routes/web.php');
// Load views
- //$this->loadViewsFrom(__DIR__.'/../resources/views', 'courier');
+ $this->loadViewsFrom(__DIR__.'/../resources/views', 'scorm-player');
}
}