From 8db5f39aec45a25817dce885ecf0ffbe09c31cca Mon Sep 17 00:00:00 2001 From: Khaled Lela Date: Thu, 3 Feb 2022 00:05:57 +0200 Subject: fix scorm update archive with updateOrCreated, drop old scorm package when already exists. --- src/Manager/ScormManager.php | 117 ++++++++++++++++++++++--------------------- 1 file changed, 61 insertions(+), 56 deletions(-) diff --git a/src/Manager/ScormManager.php b/src/Manager/ScormManager.php index 2a54706..20f25ff 100644 --- a/src/Manager/ScormManager.php +++ b/src/Manager/ScormManager.php @@ -35,18 +35,17 @@ class ScormManager * @param string $filesDir * @param string $uploadDir */ - public function __construct( - ) { + public function __construct() + { $this->scormLib = new ScormLib(); } - 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,19 +57,19 @@ 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']; + if ($scormData && is_array($scormData)) + // Check if scom package already exists to drop old one. + if (ScormModel::whereOriginFile($scormData['name'])->exists()) { + $this->deleteScormData(ScormModel::whereOriginFile($scormData['name'])->first()); + } - $scorm = $model->scorm()->save($scorm); + $scorm = ScormModel::updateOrCreate(['uuid' => $scormData['hashName']], [ + 'version' => $scormData['version'], + 'hash_name' => $scormData['hashName'], + 'origin_file' => $scormData['name'], + 'origin_file_mime' => $scormData['type'] + ]); if (!empty($scormData['scos']) && is_array($scormData['scos'])) { foreach ($scormData['scos'] as $scoData) { @@ -80,30 +79,28 @@ class ScormManager $scoParent = ScormScoModel::where('uuid', $scoData->scoParent->uuid)->first(); } - $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(); + // Check if scom package already exists update or create when not exists. + $sco = ScormScoModel::updateOrCreate([ + 'scorm_id' => $scorm->id, + 'uuid' => $scoData->uuid + ], [ + 'sco_parent_id' => $scoParent ? $scoParent->id : null, + 'entry_url' => $scoData->entryUrl, + 'identifier' => $scoData->identifier, + 'title' => $scoData->title, + 'visible' => $scoData->visible, + 'sco_parameters' => $scoData->parameters, + 'launch_data' => $scoData->launchData, + 'max_time_allowed' => $scoData->maxTimeAllowed, + 'time_limit_action' => $scoData->timeLimitAction, + 'block' => $scoData->block, + 'score_int' => $scoData->scoreToPassInt, + 'score_decimal' => $scoData->scoreToPassDecimal, + 'completion_threshold' => $scoData->completionThreshold, + 'prerequisites' => $scoData->prerequisites, + ]); } } - - if ($oldModel != null) { - $this->deleteScormData($oldModel); - } } return $scormData; @@ -157,7 +154,8 @@ class ScormManager return $data; } - public function deleteScormData($model) { + public function deleteScormData($model) + { // Delete after the previous item is stored if ($model) { @@ -179,7 +177,8 @@ class ScormManager * @param $folderHashedName * @return bool */ - protected function deleteScormFolder($folderHashedName) { + protected function deleteScormFolder($folderHashedName) + { $response = Storage::disk('scorm')->deleteDirectory($folderHashedName); @@ -196,18 +195,18 @@ class ScormManager $zip = new \ZipArchive(); $zip->open($file); - if (!config()->has('filesystems.disks.'.config('scorm.disk').'.root')) { + if (!config()->has('filesystems.disks.' . config('scorm.disk') . '.root')) { throw new StorageNotFoundException(); } - $rootFolder = config('filesystems.disks.'.config('scorm.disk').'.root'); + $rootFolder = config('filesystems.disks.' . config('scorm.disk') . '.root'); if (substr($rootFolder, -1) != '/') { // If end with xxx/ - $rootFolder = config('filesystems.disks.'.config('scorm.disk').'.root').'/'; + $rootFolder = config('filesystems.disks.' . config('scorm.disk') . '.root') . '/'; } - $destinationDir = $rootFolder.$hashName; // file path + $destinationDir = $rootFolder . $hashName; // file path if (!File::isDirectory($destinationDir)) { File::makeDirectory($destinationDir, 0755, true, true); @@ -225,25 +224,25 @@ class ScormManager private function generateScorm(UploadedFile $file) { $hashName = Uuid::uuid4(); - $hashFileName = $hashName.'.zip'; + $hashFileName = $hashName . '.zip'; $scormData = $this->parseScormArchive($file); $this->unzipScormArchive($file, $hashName); - if (!config()->has('filesystems.disks.'.config('scorm.disk').'.root')) { + if (!config()->has('filesystems.disks.' . config('scorm.disk') . '.root')) { throw new StorageNotFoundException(); } - $rootFolder = config('filesystems.disks.'.config('scorm.disk').'.root'); + $rootFolder = config('filesystems.disks.' . config('scorm.disk') . '.root'); if (substr($rootFolder, -1) != '/') { // If end with xxx/ - $rootFolder = config('filesystems.disks.'.config('scorm.disk').'.root').'/'; + $rootFolder = config('filesystems.disks.' . config('scorm.disk') . '.root') . '/'; } - $destinationDir = $rootFolder.$hashName; // file path + $destinationDir = $rootFolder . $hashName; // file path // Move Scorm archive in the files directory - $finalFile = $file->move($destinationDir, $hashName.'.zip'); + $finalFile = $file->move($destinationDir, $hashName . '.zip'); return [ 'name' => $hashFileName, // to follow standard file data format @@ -259,7 +258,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 +273,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 +283,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(); } @@ -375,7 +377,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 +387,8 @@ class ScormManager ->firstOrFail(); } - public function checkUserIsCompletedScorm($scormId, $userId) { + public function checkUserIsCompletedScorm($scormId, $userId) + { $completedSco = []; $scos = ScormScoModel::where('scorm_id', $scormId)->get(); @@ -538,7 +542,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 +635,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; -- cgit v1.2.3 From 4de0d3953834e367a91255088861ae264f7d561a Mon Sep 17 00:00:00 2001 From: Khaled Lela Date: Thu, 3 Feb 2022 12:17:00 +0200 Subject: fix typo remove extra bracket --- src/Manager/ScormManager.php | 71 ++++++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/src/Manager/ScormManager.php b/src/Manager/ScormManager.php index 20f25ff..3b8c683 100644 --- a/src/Manager/ScormManager.php +++ b/src/Manager/ScormManager.php @@ -58,48 +58,47 @@ class ScormManager } // save to db - if ($scormData && is_array($scormData)) + if ($scormData && is_array($scormData)) // Check if scom package already exists to drop old one. if (ScormModel::whereOriginFile($scormData['name'])->exists()) { $this->deleteScormData(ScormModel::whereOriginFile($scormData['name'])->first()); } - $scorm = ScormModel::updateOrCreate(['uuid' => $scormData['hashName']], [ - 'version' => $scormData['version'], - 'hash_name' => $scormData['hashName'], - 'origin_file' => $scormData['name'], - 'origin_file_mime' => $scormData['type'] - ]); - - if (!empty($scormData['scos']) && is_array($scormData['scos'])) { - foreach ($scormData['scos'] as $scoData) { - - $scoParent = null; - if (!empty($scoData->scoParent)) { - $scoParent = ScormScoModel::where('uuid', $scoData->scoParent->uuid)->first(); - } - - // Check if scom package already exists update or create when not exists. - $sco = ScormScoModel::updateOrCreate([ - 'scorm_id' => $scorm->id, - 'uuid' => $scoData->uuid - ], [ - 'sco_parent_id' => $scoParent ? $scoParent->id : null, - 'entry_url' => $scoData->entryUrl, - 'identifier' => $scoData->identifier, - 'title' => $scoData->title, - 'visible' => $scoData->visible, - 'sco_parameters' => $scoData->parameters, - 'launch_data' => $scoData->launchData, - 'max_time_allowed' => $scoData->maxTimeAllowed, - 'time_limit_action' => $scoData->timeLimitAction, - 'block' => $scoData->block, - 'score_int' => $scoData->scoreToPassInt, - 'score_decimal' => $scoData->scoreToPassDecimal, - 'completion_threshold' => $scoData->completionThreshold, - 'prerequisites' => $scoData->prerequisites, - ]); + $scorm = ScormModel::updateOrCreate(['uuid' => $scormData['hashName']], [ + 'version' => $scormData['version'], + 'hash_name' => $scormData['hashName'], + 'origin_file' => $scormData['name'], + 'origin_file_mime' => $scormData['type'] + ]); + + if (!empty($scormData['scos']) && is_array($scormData['scos'])) { + foreach ($scormData['scos'] as $scoData) { + + $scoParent = null; + if (!empty($scoData->scoParent)) { + $scoParent = ScormScoModel::where('uuid', $scoData->scoParent->uuid)->first(); } + + // Check if scom package already exists update or create when not exists. + $sco = ScormScoModel::updateOrCreate([ + 'scorm_id' => $scorm->id, + 'uuid' => $scoData->uuid + ], [ + 'sco_parent_id' => $scoParent ? $scoParent->id : null, + 'entry_url' => $scoData->entryUrl, + 'identifier' => $scoData->identifier, + 'title' => $scoData->title, + 'visible' => $scoData->visible, + 'sco_parameters' => $scoData->parameters, + 'launch_data' => $scoData->launchData, + 'max_time_allowed' => $scoData->maxTimeAllowed, + 'time_limit_action' => $scoData->timeLimitAction, + 'block' => $scoData->block, + 'score_int' => $scoData->scoreToPassInt, + 'score_decimal' => $scoData->scoreToPassDecimal, + 'completion_threshold' => $scoData->completionThreshold, + 'prerequisites' => $scoData->prerequisites, + ]); } } -- cgit v1.2.3 From 1e50016f2d8eeea1e8ce72c3f098f30237d207cd Mon Sep 17 00:00:00 2001 From: Khaled Lela Date: Fri, 4 Feb 2022 00:47:51 +0200 Subject: update README with step 3 of update config cache, and add optional step to describe scorm disk handler with example. --- README.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) 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'), + ], + ..... + ] +``` -- cgit v1.2.3 From ea1e10b8754c229a6133da8c807affe58676cf67 Mon Sep 17 00:00:00 2001 From: Khaled Lela Date: Fri, 4 Feb 2022 00:49:20 +0200 Subject: add extra doc on scorm config file including example for explanation --- config/scorm.php | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) 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', ]; -- cgit v1.2.3 From aa3613dd25217bfd40025aad57f31ab8e479b395 Mon Sep 17 00:00:00 2001 From: Khaled Lela Date: Fri, 4 Feb 2022 00:52:21 +0200 Subject: code format and same UUID but under laravel support helper --- src/Library/ScormLib.php | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) 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; -- cgit v1.2.3 From 2e8cf8822305aaea60d12a47d124122ed5e4c145 Mon Sep 17 00:00:00 2001 From: Khaled Lela Date: Fri, 4 Feb 2022 00:56:05 +0200 Subject: add scorm dist to manage scorm storage disk operation --- src/Manager/ScormDisk.php | 74 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 src/Manager/ScormDisk.php diff --git a/src/Manager/ScormDisk.php b/src/Manager/ScormDisk.php new file mode 100644 index 0000000..9a03f59 --- /dev/null +++ b/src/Manager/ScormDisk.php @@ -0,0 +1,74 @@ +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(); + } + return Storage::disk(config('scorm.disk')); + } +} -- cgit v1.2.3 From 9e6f0a90b6b99d7a08fbe07ecd9c304b825f3a16 Mon Sep 17 00:00:00 2001 From: Khaled Lela Date: Fri, 4 Feb 2022 00:59:55 +0200 Subject: refactor(manager): fix manager duplicated storage code, make it depend on ScormDist helper --- src/Manager/ScormManager.php | 184 ++++++++++++++++++------------------------- 1 file changed, 76 insertions(+), 108 deletions(-) diff --git a/src/Manager/ScormManager.php b/src/Manager/ScormManager.php index 3b8c683..4e9e9c5 100644 --- a/src/Manager/ScormManager.php +++ b/src/Manager/ScormManager.php @@ -3,31 +3,27 @@ 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 ZipArchive; class ScormManager { /** @var ScormLib */ private $scormLib; + /** @var ScormDisk */ + private $scormDisk; /** * Constructor. @@ -38,6 +34,7 @@ class ScormManager public function __construct() { $this->scormLib = new ScormLib(); + $this->scormDisk = new ScormDisk(); } public function uploadScormArchive(UploadedFile $file) @@ -58,18 +55,25 @@ class ScormManager } // save to db - if ($scormData && is_array($scormData)) - // Check if scom package already exists to drop old one. - if (ScormModel::whereOriginFile($scormData['name'])->exists()) { - $this->deleteScormData(ScormModel::whereOriginFile($scormData['name'])->first()); - } + if (is_null($scormData) || !is_array($scormData)) { + throw new InvalidScormArchiveException('invalid_scorm_data'); + } - $scorm = ScormModel::updateOrCreate(['uuid' => $scormData['hashName']], [ - 'version' => $scormData['version'], - 'hash_name' => $scormData['hashName'], - 'origin_file' => $scormData['name'], - 'origin_file_mime' => $scormData['type'] - ]); + $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); + } + + $scorm->version = $scormData['version']; + $scorm->hash_name = $scormData['hashName']; + $scorm->origin_file = $scormData['identifier']; + $scorm->origin_file_mime = $scormData['type']; + $scorm->uuid = $scormData['hashName']; + $scorm->save(); if (!empty($scormData['scos']) && is_array($scormData['scos'])) { foreach ($scormData['scos'] as $scoData) { @@ -80,25 +84,24 @@ class ScormManager } // Check if scom package already exists update or create when not exists. - $sco = ScormScoModel::updateOrCreate([ - 'scorm_id' => $scorm->id, - 'uuid' => $scoData->uuid - ], [ - 'sco_parent_id' => $scoParent ? $scoParent->id : null, - 'entry_url' => $scoData->entryUrl, - 'identifier' => $scoData->identifier, - 'title' => $scoData->title, - 'visible' => $scoData->visible, - 'sco_parameters' => $scoData->parameters, - 'launch_data' => $scoData->launchData, - 'max_time_allowed' => $scoData->maxTimeAllowed, - 'time_limit_action' => $scoData->timeLimitAction, - 'block' => $scoData->block, - 'score_int' => $scoData->scoreToPassInt, - 'score_decimal' => $scoData->scoreToPassDecimal, - 'completion_threshold' => $scoData->completionThreshold, - 'prerequisites' => $scoData->prerequisites, - ]); + $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(); } } @@ -125,8 +128,14 @@ 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'); + } + $scormVersionElements = $dom->getElementsByTagName('schemaversion'); if ($scormVersionElements->length > 0) { switch ($scormVersionElements->item(0)->textContent) { case '1.2': @@ -153,66 +162,36 @@ class ScormManager 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->hash_name); } /** - * 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); } /** @@ -222,31 +201,20 @@ 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'], + // 'name' => $hashFileName, // to follow standard file data format + 'hashName' => $uuid, + 'type' => $file->getMimeType(), 'version' => $scormData['version'], 'scos' => $scormData['scos'], ]; @@ -328,7 +296,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(), -- cgit v1.2.3 From f658708e0ba11ced12a395b848cc13771db2628b Mon Sep 17 00:00:00 2001 From: Khaled Lela Date: Thu, 10 Feb 2022 01:17:43 +0200 Subject: update scorm model --- database/migrations/create_scorm_tables.php.stub | 14 +--------- src/Entity/Scorm.php | 35 +++++++++++++++++------- src/Manager/ScormManager.php | 20 +++++++++----- 3 files changed, 39 insertions(+), 30 deletions(-) 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/Manager/ScormManager.php b/src/Manager/ScormManager.php index 4e9e9c5..2b901ab 100644 --- a/src/Manager/ScormManager.php +++ b/src/Manager/ScormManager.php @@ -68,11 +68,11 @@ class ScormManager $this->deleteScormData($scorm); } + $scorm->uuid = $scormData['uuid']; + $scorm->title = $scormData['title']; $scorm->version = $scormData['version']; - $scorm->hash_name = $scormData['hashName']; + $scorm->entryUrl = $scormData['entryUrl']; $scorm->origin_file = $scormData['identifier']; - $scorm->origin_file_mime = $scormData['type']; - $scorm->uuid = $scormData['hashName']; $scorm->save(); if (!empty($scormData['scos']) && is_array($scormData['scos'])) { @@ -134,6 +134,10 @@ class ScormManager } else { throw new InvalidScormArchiveException('invalid_scorm_manifest_identifier'); } + $titles = $dom->getElementsByTagName('title'); + if ($titles->length > 0) { + $data['title'] = $titles->item(0)->textContent; + } $scormVersionElements = $dom->getElementsByTagName('schemaversion'); if ($scormVersionElements->length > 0) { @@ -157,6 +161,8 @@ class ScormManager if (0 >= count($scos)) { throw new InvalidScormArchiveException('no_sco_in_scorm_archive_message'); } + + $data['entryUrl'] = $scos[0]->entryUrl; $data['scos'] = $scos; return $data; @@ -182,7 +188,7 @@ class ScormManager } $model->scos()->delete(); // delete scos // Delete folder from server - $this->deleteScormFolder($model->hash_name); + $this->deleteScormFolder($model->title); } /** @@ -212,10 +218,10 @@ class ScormManager return [ 'identifier' => $scormData['identifier'], - // 'name' => $hashFileName, // to follow standard file data format - 'hashName' => $uuid, - 'type' => $file->getMimeType(), + 'uuid' => $uuid, + 'title' => $scormData['title'], // to follow standard file data format 'version' => $scormData['version'], + 'entryUrl' => $scormData['entryUrl'], 'scos' => $scormData['scos'], ]; } -- cgit v1.2.3 From cbc4af7a11ece4cf13551aa4b098cbe9fea8dadc Mon Sep 17 00:00:00 2001 From: Khaled Lela Date: Thu, 10 Feb 2022 02:22:29 +0200 Subject: fix delete by uuid, and title trim --- src/Manager/ScormManager.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Manager/ScormManager.php b/src/Manager/ScormManager.php index 2b901ab..4cf0c79 100644 --- a/src/Manager/ScormManager.php +++ b/src/Manager/ScormManager.php @@ -71,7 +71,7 @@ class ScormManager $scorm->uuid = $scormData['uuid']; $scorm->title = $scormData['title']; $scorm->version = $scormData['version']; - $scorm->entryUrl = $scormData['entryUrl']; + $scorm->entry_url = $scormData['entryUrl']; $scorm->origin_file = $scormData['identifier']; $scorm->save(); @@ -136,7 +136,7 @@ class ScormManager } $titles = $dom->getElementsByTagName('title'); if ($titles->length > 0) { - $data['title'] = $titles->item(0)->textContent; + $data['title'] = Str::of($titles->item(0)->textContent)->trim('/n')->trim(); } $scormVersionElements = $dom->getElementsByTagName('schemaversion'); @@ -188,7 +188,7 @@ class ScormManager } $model->scos()->delete(); // delete scos // Delete folder from server - $this->deleteScormFolder($model->title); + $this->deleteScormFolder($model->uuid); } /** -- cgit v1.2.3 From 910f5cee7439466d797d9202c955b6c6540d45f1 Mon Sep 17 00:00:00 2001 From: Khaled Lela Date: Thu, 10 Feb 2022 17:39:31 +0200 Subject: fix scorm entry_url fail case --- src/Manager/ScormManager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Manager/ScormManager.php b/src/Manager/ScormManager.php index 4cf0c79..21aaf73 100644 --- a/src/Manager/ScormManager.php +++ b/src/Manager/ScormManager.php @@ -162,7 +162,7 @@ class ScormManager throw new InvalidScormArchiveException('no_sco_in_scorm_archive_message'); } - $data['entryUrl'] = $scos[0]->entryUrl; + $data['entryUrl'] = $scos[0]->entryUrl ?? $scos[0]->scoChildren[0]->entryUrl; $data['scos'] = $scos; return $data; -- cgit v1.2.3 From e18d5c803a5bab898515fd87e1ca5de517b8d88f Mon Sep 17 00:00:00 2001 From: Khaled Lela Date: Thu, 10 Feb 2022 18:59:23 +0200 Subject: fix storing Scorm sco children --- src/Manager/ScormManager.php | 60 ++++++++++++++++++++++++++------------------ src/Model/ScormScoModel.php | 11 ++++++-- 2 files changed, 45 insertions(+), 26 deletions(-) diff --git a/src/Manager/ScormManager.php b/src/Manager/ScormManager.php index 21aaf73..56c723c 100644 --- a/src/Manager/ScormManager.php +++ b/src/Manager/ScormManager.php @@ -16,6 +16,7 @@ use Peopleaps\Scorm\Model\ScormModel; use Peopleaps\Scorm\Model\ScormScoModel; use Peopleaps\Scorm\Model\ScormScoTrackingModel; use Illuminate\Support\Str; +use Peopleaps\Scorm\Entity\Sco; use ZipArchive; class ScormManager @@ -76,38 +77,49 @@ class ScormManager $scorm->save(); if (!empty($scormData['scos']) && is_array($scormData['scos'])) { + /** @var Sco $scoData */ foreach ($scormData['scos'] as $scoData) { - - $scoParent = null; - if (!empty($scoData->scoParent)) { - $scoParent = ScormScoModel::where('uuid', $scoData->scoParent->uuid)->first(); + $sco = $this->saveScormScos($scorm->id, $scoData); + if ($scoData->scoChildren) { + foreach ($scoData->scoChildren as $scoChild) { + $this->saveScormScos($scorm->id, $scoChild, $sco->id); + } } - - // Check if scom package already exists update or create when not exists. - $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(); } } 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 = []; 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'); + } } -- cgit v1.2.3 From f2faa8f007834444a129db4fb8f4ece901b21fed Mon Sep 17 00:00:00 2001 From: Khaled Lela Date: Thu, 10 Feb 2022 21:43:06 +0200 Subject: update scorm disk not define exception msg --- src/Manager/ScormDisk.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Manager/ScormDisk.php b/src/Manager/ScormDisk.php index 9a03f59..249f027 100644 --- a/src/Manager/ScormDisk.php +++ b/src/Manager/ScormDisk.php @@ -67,7 +67,7 @@ class ScormDisk private function getDisk() { if (!config()->has('filesystems.disks.' . config('scorm.disk'))) { - throw new StorageNotFoundException(); + throw new StorageNotFoundException('scorm_disk_not_define'); } return Storage::disk(config('scorm.disk')); } -- cgit v1.2.3