summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordevianl2 <devianleong@gmail.com>2022-02-11 11:21:33 +0800
committerGitHub <noreply@github.com>2022-02-11 11:21:33 +0800
commit9a3d5c62ac377bfcb9e31f98275636548c4d65ea (patch)
treef5f792b1775c82e103f492a25cce3c1234f8b4e7
parentca8137f985224373c22fa195288fb093d548a75c (diff)
parentf2faa8f007834444a129db4fb8f4ece901b21fed (diff)
Merge pull request #7 from KhaledLela/main
Improve SCORM disk storage handler
-rw-r--r--README.md34
-rw-r--r--config/scorm.php18
-rw-r--r--database/migrations/create_scorm_tables.php.stub14
-rw-r--r--src/Entity/Scorm.php35
-rw-r--r--src/Library/ScormLib.php30
-rw-r--r--src/Manager/ScormDisk.php74
-rw-r--r--src/Manager/ScormManager.php246
-rw-r--r--src/Model/ScormScoModel.php11
8 files changed, 296 insertions, 166 deletions
diff --git a/README.md b/README.md
index 4aaec66..b0dd5a2 100644
--- a/README.md
+++ b/README.md
@@ -32,8 +32,42 @@ php artisan vendor:publish --provider="Peopleaps\Scorm\ScormServiceProvider"
```
## Step 3:
+Run config cache for update cached configuration
+```sh
+php artisan config:cache
+```
+
+## Step 4:
Migrate file to database
```sh
php artisan migrate
```
+## Step 5 (Optional):
+update SCORM config under config/scorm
+- update scorm table names.
+- update SCORM disk and configure disk @see config/filesystems.php
+```
+ 'disk' => 'scorm-local',
+ 'disk' => 'scorm-s3',
+
+ // @see config/filesystems.php
+ 'disks' => [
+ .....
+ 'scorm-local' => [
+ 'driver' => 'local',
+ 'root' => env('SCORM_ROOT_DIR'), // set root dir
+ 'visibility' => 'public',
+ ],
+
+ 's3-scorm' => [
+ 'driver' => 's3',
+ 'root' => env('SCORM_ROOT_DIR'), // set root dir
+ 'key' => env('AWS_ACCESS_KEY_ID'),
+ 'secret' => env('AWS_SECRET_ACCESS_KEY'),
+ 'region' => env('AWS_DEFAULT_REGION'),
+ 'bucket' => env('AWS_SCORM_BUCKET'),
+ ],
+ .....
+ ]
+```
diff --git a/config/scorm.php b/config/scorm.php
index e5efb3f..0330bc5 100644
--- a/config/scorm.php
+++ b/config/scorm.php
@@ -3,11 +3,25 @@
return [
'table_names' => [
- 'user_table' => 'users',
+ 'user_table' => 'users', // user table name on main LMS app.
'scorm_table' => 'scorm',
'scorm_sco_table' => 'scorm_sco',
'scorm_sco_tracking_table' => 'scorm_sco_tracking',
],
- // Scorm directory. You may create a custom path in file system
+ /**
+ * Scorm directory. You may create a custom path in file system
+ * Define Scorm disk under @see config/filesystems.php
+ * 'disk' => 'local',
+ * 'disk' => 's3-scorm',
+ * ex.
+ * 's3-scorm' => [
+ * 'driver' => 's3',
+ * 'root' => env('SCORM_ROOT_DIR'), // define root dir
+ * 'key' => env('AWS_ACCESS_KEY_ID'),
+ * 'secret' => env('AWS_SECRET_ACCESS_KEY'),
+ * 'region' => env('AWS_DEFAULT_REGION'),
+ * 'bucket' => env('AWS_SCORM_BUCKET'),
+ * ],
+ */
'disk' => 'local',
];
diff --git a/database/migrations/create_scorm_tables.php.stub b/database/migrations/create_scorm_tables.php.stub
index 506e9cb..6a136b3 100644
--- a/database/migrations/create_scorm_tables.php.stub
+++ b/database/migrations/create_scorm_tables.php.stub
@@ -97,19 +97,7 @@ class CreateScormTables extends Migration
*/
public function down()
{
- $tableNames = config('scorm_sco_tracking_table');
-
- if (empty($tableNames)) {
- throw new \Exception('Error: Table not found.');
- }
-
- $tableNames = config('scorm_sco_table');
-
- if (empty($tableNames)) {
- throw new \Exception('Error: Table not found.');
- }
-
- $tableNames = config('scorm_table');
+ $tableNames = config('scorm.table_names');
if (empty($tableNames)) {
throw new \Exception('Error: Table not found.');
diff --git a/src/Entity/Scorm.php b/src/Entity/Scorm.php
index 4095986..48fd711 100644
--- a/src/Entity/Scorm.php
+++ b/src/Entity/Scorm.php
@@ -3,9 +3,6 @@
namespace Peopleaps\Scorm\Entity;
-
-use Doctrine\Common\Collections\ArrayCollection;
-
class Scorm
{
const SCORM_12 = 'scorm_12';
@@ -13,8 +10,9 @@ class Scorm
public $uuid;
public $id;
+ public $title;
public $version;
- public $hashName;
+ public $entryUrl;
public $ratio = 56.25;
public $scos;
public $scoSerializer;
@@ -70,17 +68,33 @@ class Scorm
/**
* @return string
*/
- public function getHashName()
+ public function getTitle()
+ {
+ return $this->title;
+ }
+
+ /**
+ * @param string $title
+ */
+ public function setTitle($title)
+ {
+ $this->title = $title;
+ }
+
+ /**
+ * @return string
+ */
+ public function getEntryUrl()
{
- return $this->hashName;
+ return $this->entryUrl;
}
/**
- * @param string $hashName
+ * @param string $title
*/
- public function setHashName($hashName)
+ public function setEntryUrl($entryUrl)
{
- $this->hashName = $hashName;
+ $this->entryUrl = $entryUrl;
}
/**
@@ -131,7 +145,8 @@ class Scorm
return [
'id' => $scorm->getUuid(),
'version' => $scorm->getVersion(),
- 'hashName' => $scorm->getHashName(),
+ 'title' => $scorm->getTitle(),
+ 'entryUrl' => $scorm->getEntryUrl(),
'ratio' => $scorm->getRatio(),
'scos' => $this->serializeScos($scorm),
];
diff --git a/src/Library/ScormLib.php b/src/Library/ScormLib.php
index 0ca8a6b..731601a 100644
--- a/src/Library/ScormLib.php
+++ b/src/Library/ScormLib.php
@@ -7,7 +7,7 @@ namespace Peopleaps\Scorm\Library;
use DOMDocument;
use Peopleaps\Scorm\Entity\Sco;
use Peopleaps\Scorm\Exception\InvalidScormArchiveException;
-use Ramsey\Uuid\Uuid;
+use Illuminate\Support\Str;
class ScormLib
{
@@ -28,16 +28,20 @@ class ScormLib
$organizations = $organizationsList->item(0);
$organization = $organizations->firstChild;
- if (!is_null($organizations->attributes)
- && !is_null($organizations->attributes->getNamedItem('default'))) {
+ if (
+ !is_null($organizations->attributes)
+ && !is_null($organizations->attributes->getNamedItem('default'))
+ ) {
$defaultOrganization = $organizations->attributes->getNamedItem('default')->nodeValue;
} else {
$defaultOrganization = null;
}
// No default organization is defined
if (is_null($defaultOrganization)) {
- while (!is_null($organization)
- && 'organization' !== $organization->nodeName) {
+ while (
+ !is_null($organization)
+ && 'organization' !== $organization->nodeName
+ ) {
$organization = $organization->nextSibling;
}
@@ -48,10 +52,12 @@ class ScormLib
// A default organization is defined
// Look for it
else {
- while (!is_null($organization)
+ while (
+ !is_null($organization)
&& ('organization' !== $organization->nodeName
|| is_null($organization->attributes->getNamedItem('identifier'))
- || $organization->attributes->getNamedItem('identifier')->nodeValue !== $defaultOrganization)) {
+ || $organization->attributes->getNamedItem('identifier')->nodeValue !== $defaultOrganization)
+ ) {
$organization = $organization->nextSibling;
}
@@ -82,7 +88,7 @@ class ScormLib
if ('item' === $item->nodeName) {
$sco = new Sco();
$scos[] = $sco;
- $sco->setUuid(Uuid::uuid4());
+ $sco->setUuid(Str::uuid());
$sco->setScoParent($parentSco);
$this->findAttrParams($sco, $item, $resources);
$this->findNodeParams($sco, $item->firstChild);
@@ -119,7 +125,7 @@ class ScormLib
throw new InvalidScormArchiveException('sco_resource_without_href_message');
}
$sco = new Sco();
- $sco->setUuid(Uuid::uuid4());
+ $sco->setUuid(Str::uuid());
$sco->setBlock(false);
$sco->setVisible(true);
$sco->setIdentifier($identifier->nodeValue);
@@ -195,10 +201,12 @@ class ScormLib
case 'adlcp:timeLimitAction':
$action = strtolower($item->nodeValue);
- if ('exit,message' === $action
+ if (
+ 'exit,message' === $action
|| 'exit,no message' === $action
|| 'continue,message' === $action
- || 'continue,no message' === $action) {
+ || 'continue,no message' === $action
+ ) {
$sco->setTimeLimitAction($action);
}
break;
diff --git a/src/Manager/ScormDisk.php b/src/Manager/ScormDisk.php
new file mode 100644
index 0000000..249f027
--- /dev/null
+++ b/src/Manager/ScormDisk.php
@@ -0,0 +1,74 @@
+<?php
+
+namespace Peopleaps\Scorm\Manager;
+
+use Illuminate\Filesystem\FilesystemAdapter;
+use Illuminate\Support\Facades\Storage;
+use Peopleaps\Scorm\Exception\StorageNotFoundException;
+use ZipArchive;
+
+class ScormDisk
+{
+ /**
+ * Extract zip file into destination directory.
+ *
+ * @param string $path Destination directory
+ * @param string $zipFilePath The path to the zip file.
+ *
+ * @return bool True on success, false on failure.
+ */
+ public function unzip($file, $path)
+ {
+ $path = $this->cleanPath($path);
+
+ $zipArchive = new ZipArchive();
+ if ($zipArchive->open($file) !== true) {
+ return false;
+ }
+
+ /** @var FilesystemAdapter $disk */
+ $disk = $this->getDisk();
+
+ for ($i = 0; $i < $zipArchive->numFiles; ++$i) {
+ $zipEntryName = $zipArchive->getNameIndex($i);
+ $destination = $path . DIRECTORY_SEPARATOR . $this->cleanPath($zipEntryName);
+ if ($this->isDirectory($zipEntryName)) {
+ $disk->createDir($destination);
+ continue;
+ }
+ $disk->putStream($destination, $zipArchive->getStream($zipEntryName));
+ }
+
+ return true;
+ }
+
+ /**
+ * @param string $directory
+ * @return bool
+ */
+ public function deleteScormFolder($folderHashedName)
+ {
+ return $this->getDisk()->deleteDirectory($folderHashedName);
+ }
+
+ private function isDirectory($zipEntryName)
+ {
+ return substr($zipEntryName, -1) === '/';
+ }
+
+ private function cleanPath($path)
+ {
+ return str_replace('/', DIRECTORY_SEPARATOR, $path);
+ }
+
+ /**
+ * @return FilesystemAdapter $disk
+ */
+ private function getDisk()
+ {
+ if (!config()->has('filesystems.disks.' . config('scorm.disk'))) {
+ throw new StorageNotFoundException('scorm_disk_not_define');
+ }
+ return Storage::disk(config('scorm.disk'));
+ }
+}
diff --git a/src/Manager/ScormManager.php b/src/Manager/ScormManager.php
index 2a54706..56c723c 100644
--- a/src/Manager/ScormManager.php
+++ b/src/Manager/ScormManager.php
@@ -3,31 +3,28 @@
namespace Peopleaps\Scorm\Manager;
-use App\Models\User;
use Carbon\Carbon;
use DOMDocument;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\UploadedFile;
-use Illuminate\Support\Facades\File;
-use Illuminate\Support\Facades\Storage;
-use League\Flysystem\FileNotFoundException;
-use Peopleaps\Scorm\Entity\Sco;
use Peopleaps\Scorm\Entity\Scorm;
use Peopleaps\Scorm\Entity\ScoTracking;
use Peopleaps\Scorm\Exception\InvalidScormArchiveException;
-use Peopleaps\Scorm\Exception\StorageNotFoundException;
use Peopleaps\Scorm\Library\ScormLib;
use Peopleaps\Scorm\Model\ScormModel;
use Peopleaps\Scorm\Model\ScormScoModel;
use Peopleaps\Scorm\Model\ScormScoTrackingModel;
-use Ramsey\Uuid\Uuid;
+use Illuminate\Support\Str;
+use Peopleaps\Scorm\Entity\Sco;
use ZipArchive;
class ScormManager
{
/** @var ScormLib */
private $scormLib;
+ /** @var ScormDisk */
+ private $scormDisk;
/**
* Constructor.
@@ -35,18 +32,18 @@ class ScormManager
* @param string $filesDir
* @param string $uploadDir
*/
- public function __construct(
- ) {
+ public function __construct()
+ {
$this->scormLib = new ScormLib();
+ $this->scormDisk = new ScormDisk();
}
- public function uploadScormArchive(UploadedFile $file, Model $model)
+ public function uploadScormArchive(UploadedFile $file)
{
// Checks if it is a valid scorm archive
$scormData = null;
$zip = new ZipArchive();
$openValue = $zip->open($file);
- $oldModel = null;
$isScormArchive = (true === $openValue) && $zip->getStream('imsmanifest.xml');
@@ -58,57 +55,71 @@ class ScormManager
$scormData = $this->generateScorm($file);
}
- $oldModel = $model->scorm()->first(); // get old scorm data for deletion (If success to store new)
-
// save to db
- if ($scormData && is_array($scormData)) {
-
- $scorm = new ScormModel();
- $scorm->version = $scormData['version'];
- $scorm->hash_name = $scormData['hashName'];
- $scorm->origin_file = $scormData['name'];
- $scorm->origin_file_mime = $scormData['type'];
- $scorm->uuid = $scormData['hashName'];
-
- $scorm = $model->scorm()->save($scorm);
+ if (is_null($scormData) || !is_array($scormData)) {
+ throw new InvalidScormArchiveException('invalid_scorm_data');
+ }
- if (!empty($scormData['scos']) && is_array($scormData['scos'])) {
- foreach ($scormData['scos'] as $scoData) {
+ $scorm = ScormModel::whereOriginFile($scormData['identifier']);
+ // Check if scom package already exists to drop old one.
+ if (!$scorm->exists()) {
+ $scorm = new ScormModel();
+ } else {
+ $scorm = $scorm->first();
+ $this->deleteScormData($scorm);
+ }
- $scoParent = null;
- if (!empty($scoData->scoParent)) {
- $scoParent = ScormScoModel::where('uuid', $scoData->scoParent->uuid)->first();
+ $scorm->uuid = $scormData['uuid'];
+ $scorm->title = $scormData['title'];
+ $scorm->version = $scormData['version'];
+ $scorm->entry_url = $scormData['entryUrl'];
+ $scorm->origin_file = $scormData['identifier'];
+ $scorm->save();
+
+ if (!empty($scormData['scos']) && is_array($scormData['scos'])) {
+ /** @var Sco $scoData */
+ foreach ($scormData['scos'] as $scoData) {
+ $sco = $this->saveScormScos($scorm->id, $scoData);
+ if ($scoData->scoChildren) {
+ foreach ($scoData->scoChildren as $scoChild) {
+ $this->saveScormScos($scorm->id, $scoChild, $sco->id);
}
-
- $sco = new ScormScoModel();
- $sco->scorm_id = $scorm->id;
- $sco->uuid = $scoData->uuid;
- $sco->sco_parent_id = $scoParent ? $scoParent->id : null;
- $sco->entry_url = $scoData->entryUrl;
- $sco->identifier = $scoData->identifier;
- $sco->title = $scoData->title;
- $sco->visible = $scoData->visible;
- $sco->sco_parameters = $scoData->parameters;
- $sco->launch_data = $scoData->launchData;
- $sco->max_time_allowed = $scoData->maxTimeAllowed;
- $sco->time_limit_action = $scoData->timeLimitAction;
- $sco->block = $scoData->block;
- $sco->score_int = $scoData->scoreToPassInt;
- $sco->score_decimal = $scoData->scoreToPassDecimal;
- $sco->completion_threshold = $scoData->completionThreshold;
- $sco->prerequisites = $scoData->prerequisites;
- $sco->save();
}
}
-
- if ($oldModel != null) {
- $this->deleteScormData($oldModel);
- }
}
return $scormData;
}
+ /**
+ * Save Scorm sco and it's nested children
+ * @param int $scorm_id scorm id.
+ * @param Sco $scoData Sco data to be store.
+ * @param int $sco_parent_id sco parent id for children
+ */
+ private function saveScormScos($scorm_id, $scoData, $sco_parent_id = null)
+ {
+ $sco = new ScormScoModel();
+ $sco->scorm_id = $scorm_id;
+ $sco->uuid = $scoData->uuid;
+ $sco->sco_parent_id = $sco_parent_id;
+ $sco->entry_url = $scoData->entryUrl;
+ $sco->identifier = $scoData->identifier;
+ $sco->title = $scoData->title;
+ $sco->visible = $scoData->visible;
+ $sco->sco_parameters = $scoData->parameters;
+ $sco->launch_data = $scoData->launchData;
+ $sco->max_time_allowed = $scoData->maxTimeAllowed;
+ $sco->time_limit_action = $scoData->timeLimitAction;
+ $sco->block = $scoData->block;
+ $sco->score_int = $scoData->scoreToPassInt;
+ $sco->score_decimal = $scoData->scoreToPassDecimal;
+ $sco->completion_threshold = $scoData->completionThreshold;
+ $sco->prerequisites = $scoData->prerequisites;
+ $sco->save();
+ return $sco;
+ }
+
private function parseScormArchive(UploadedFile $file)
{
$data = [];
@@ -129,8 +140,18 @@ class ScormManager
throw new InvalidScormArchiveException('cannot_load_imsmanifest_message');
}
- $scormVersionElements = $dom->getElementsByTagName('schemaversion');
+ $manifest = $dom->getElementsByTagName('manifest')->item(0);
+ if (!is_null($manifest->attributes->getNamedItem('identifier'))) {
+ $data['identifier'] = $manifest->attributes->getNamedItem('identifier')->nodeValue;
+ } else {
+ throw new InvalidScormArchiveException('invalid_scorm_manifest_identifier');
+ }
+ $titles = $dom->getElementsByTagName('title');
+ if ($titles->length > 0) {
+ $data['title'] = Str::of($titles->item(0)->textContent)->trim('/n')->trim();
+ }
+ $scormVersionElements = $dom->getElementsByTagName('schemaversion');
if ($scormVersionElements->length > 0) {
switch ($scormVersionElements->item(0)->textContent) {
case '1.2':
@@ -152,69 +173,43 @@ class ScormManager
if (0 >= count($scos)) {
throw new InvalidScormArchiveException('no_sco_in_scorm_archive_message');
}
+
+ $data['entryUrl'] = $scos[0]->entryUrl ?? $scos[0]->scoChildren[0]->entryUrl;
$data['scos'] = $scos;
return $data;
}
- public function deleteScormData($model) {
+ public function deleteScorm($model)
+ {
// Delete after the previous item is stored
if ($model) {
-
- $oldScos = $model->scos()->get();
-
- // Delete all tracking associate with sco
- foreach ($oldScos as $oldSco) {
- $oldSco->scoTrackings()->delete();
- }
-
- $model->scos()->delete(); // delete scos
+ $this->deleteScormData($model);
$model->delete(); // delete scorm
-
- // Delete folder from server
- $this->deleteScormFolder($model->hash_name);
}
}
- /**
- * @param $folderHashedName
- * @return bool
- */
- protected function deleteScormFolder($folderHashedName) {
- $response = Storage::disk('scorm')->deleteDirectory($folderHashedName);
+ private function deleteScormData($model)
+ {
+ // Delete after the previous item is stored
+ $oldScos = $model->scos()->get();
- return $response;
+ // Delete all tracking associate with sco
+ foreach ($oldScos as $oldSco) {
+ $oldSco->scoTrackings()->delete();
+ }
+ $model->scos()->delete(); // delete scos
+ // Delete folder from server
+ $this->deleteScormFolder($model->uuid);
}
/**
- * Unzip a given ZIP file into the web resources directory.
- *
- * @param string $hashName name of the destination directory
+ * @param $folderHashedName
+ * @return bool
*/
- private function unzipScormArchive(UploadedFile $file, $hashName)
+ protected function deleteScormFolder($folderHashedName)
{
- $zip = new \ZipArchive();
- $zip->open($file);
-
- if (!config()->has('filesystems.disks.'.config('scorm.disk').'.root')) {
- throw new StorageNotFoundException();
- }
-
- $rootFolder = config('filesystems.disks.'.config('scorm.disk').'.root');
-
- if (substr($rootFolder, -1) != '/') {
- // If end with xxx/
- $rootFolder = config('filesystems.disks.'.config('scorm.disk').'.root').'/';
- }
-
- $destinationDir = $rootFolder.$hashName; // file path
-
- if (!File::isDirectory($destinationDir)) {
- File::makeDirectory($destinationDir, 0755, true, true);
- }
-
- $zip->extractTo($destinationDir);
- $zip->close();
+ return $this->scormDisk->deleteScormFolder($folderHashedName);
}
/**
@@ -224,32 +219,21 @@ class ScormManager
*/
private function generateScorm(UploadedFile $file)
{
- $hashName = Uuid::uuid4();
- $hashFileName = $hashName.'.zip';
+ $uuid = Str::uuid();
$scormData = $this->parseScormArchive($file);
- $this->unzipScormArchive($file, $hashName);
-
- if (!config()->has('filesystems.disks.'.config('scorm.disk').'.root')) {
- throw new StorageNotFoundException();
- }
-
- $rootFolder = config('filesystems.disks.'.config('scorm.disk').'.root');
-
- if (substr($rootFolder, -1) != '/') {
- // If end with xxx/
- $rootFolder = config('filesystems.disks.'.config('scorm.disk').'.root').'/';
- }
-
- $destinationDir = $rootFolder.$hashName; // file path
-
- // Move Scorm archive in the files directory
- $finalFile = $file->move($destinationDir, $hashName.'.zip');
+ /**
+ * Unzip a given ZIP file into the web resources directory.
+ *
+ * @param string $hashName name of the destination directory
+ */
+ $this->scormDisk->unzip($file, $uuid);
return [
- 'name' => $hashFileName, // to follow standard file data format
- 'hashName' => $hashName,
- 'type' => $finalFile->getMimeType(),
+ 'identifier' => $scormData['identifier'],
+ 'uuid' => $uuid,
+ 'title' => $scormData['title'], // to follow standard file data format
'version' => $scormData['version'],
+ 'entryUrl' => $scormData['entryUrl'],
'scos' => $scormData['scos'],
];
}
@@ -259,7 +243,8 @@ class ScormManager
* @param $scormId
* @return \Illuminate\Database\Eloquent\Builder[]|\Illuminate\Database\Eloquent\Collection
*/
- public function getScos($scormId) {
+ public function getScos($scormId)
+ {
$scos = ScormScoModel::with([
'scorm'
])->where('scorm_id', $scormId)
@@ -273,7 +258,8 @@ class ScormManager
* @param $scoUuid
* @return null|\Illuminate\Database\Eloquent\Builder|Model
*/
- public function getScoByUuid($scoUuid) {
+ public function getScoByUuid($scoUuid)
+ {
$sco = ScormScoModel::with([
'scorm'
])->where('uuid', $scoUuid)
@@ -282,7 +268,8 @@ class ScormManager
return $sco;
}
- public function getUserResult($scoId, $userId) {
+ public function getUserResult($scoId, $userId)
+ {
return ScormScoTrackingModel::where('sco_id', $scoId)->where('user_id', $userId)->first();
}
@@ -327,7 +314,7 @@ class ScormManager
'user_id' => $userId,
'sco_id' => $sco->id
], [
- 'uuid' => Uuid::uuid4(),
+ 'uuid' => Str::uuid(),
'progression' => $scoTracking->getProgression(),
'score_raw' => $scoTracking->getScoreRaw(),
'score_min' => $scoTracking->getScoreMin(),
@@ -375,7 +362,8 @@ class ScormManager
return $scoTracking;
}
- public function findScoTrackingId($scoUuid, $scoTrackingUuid) {
+ public function findScoTrackingId($scoUuid, $scoTrackingUuid)
+ {
return ScormScoTrackingModel::with([
'sco'
])->whereHas('sco', function (Builder $query) use ($scoUuid) {
@@ -384,7 +372,8 @@ class ScormManager
->firstOrFail();
}
- public function checkUserIsCompletedScorm($scormId, $userId) {
+ public function checkUserIsCompletedScorm($scormId, $userId)
+ {
$completedSco = [];
$scos = ScormScoModel::where('scorm_id', $scormId)->get();
@@ -538,7 +527,8 @@ class ScormManager
$bestStatus = $lessonStatus;
}
- if (empty($tracking->getCompletionStatus())
+ if (
+ empty($tracking->getCompletionStatus())
|| ($completionStatus !== $tracking->getCompletionStatus() && $statusPriority[$completionStatus] > $statusPriority[$tracking->getCompletionStatus()])
) {
// This is no longer needed as completionStatus and successStatus are merged together
@@ -630,7 +620,7 @@ class ScormManager
$remainingTime %= 3600;
$nbMinutes = (int) ($remainingTime / 60);
$nbSeconds = $remainingTime % 60;
- $result .= 'P'.$nbDays.'DT'.$nbHours.'H'.$nbMinutes.'M'.$nbSeconds.'S';
+ $result .= 'P' . $nbDays . 'DT' . $nbHours . 'H' . $nbMinutes . 'M' . $nbSeconds . 'S';
}
return $result;
diff --git a/src/Model/ScormScoModel.php b/src/Model/ScormScoModel.php
index de50741..d606d32 100644
--- a/src/Model/ScormScoModel.php
+++ b/src/Model/ScormScoModel.php
@@ -13,11 +13,18 @@ class ScormScoModel extends Model
return config('scorm.table_names.scorm_sco_table', parent::getTable());
}
- public function scorm() {
+ public function scorm()
+ {
return $this->belongsTo(ScormModel::class, 'scorm_id', 'id');
}
- public function scoTrackings() {
+ public function scoTrackings()
+ {
return $this->hasMany(ScormScoTrackingModel::class, 'sco_id', 'id');
}
+
+ public function children()
+ {
+ return $this->hasMany(ScormScoModel::class, 'sco_parent_id', 'id');
+ }
}