* XAPI: Single import process - refs BT#16742 * XAPI: Set cascade operation for tool entity - refs BT#16742 * XAPI: Rename files - refs BT#16742 * XAPI: Fix LRS config when import/edit package - refs BT#16742 * XAPI: Fix breadcrumb and back button - refs BT#16742 * XAPI: Fix width in table column - refs BT#16742pull/3732/head
parent
8c95efcb3e
commit
6ffe842b2e
@ -1,119 +0,0 @@ |
||||
<?php |
||||
|
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
use Chamilo\PluginBundle\XApi\Importer\Cmi5Importer; |
||||
use Chamilo\PluginBundle\XApi\Parser\Cmi5Parser; |
||||
|
||||
require_once __DIR__.'/../../../main/inc/global.inc.php'; |
||||
|
||||
api_protect_course_script(true); |
||||
api_protect_teacher_script(); |
||||
|
||||
$course = api_get_course_entity(); |
||||
$session = api_get_session_entity(); |
||||
|
||||
$plugin = XApiPlugin::create(); |
||||
|
||||
$frmActivity = new FormValidator('frm_cmi5', 'post', api_get_self().'?'.api_get_cidreq()); |
||||
$frmActivity->addFile('file', $plugin->get_lang('Cmi5Package')); |
||||
$frmActivity->addButtonAdvancedSettings('lrs_params', $plugin->get_lang('LrsConfiguration')); |
||||
$frmActivity->addHtml('<div id="lrs_params_options" style="display:none">'); |
||||
$frmActivity->addText( |
||||
'lrs_url', |
||||
[ |
||||
$plugin->get_lang('lrs_url'), |
||||
$plugin->get_lang('lrs_url_help'), |
||||
], |
||||
false |
||||
); |
||||
$frmActivity->addText( |
||||
'lrs_auth', |
||||
[ |
||||
$plugin->get_lang('lrs_auth_username'), |
||||
$plugin->get_lang('lrs_auth_username_help'), |
||||
], |
||||
false |
||||
); |
||||
$frmActivity->addText( |
||||
'lrs_auth', |
||||
[ |
||||
$plugin->get_lang('lrs_auth_password'), |
||||
$plugin->get_lang('lrs_auth_password_help'), |
||||
], |
||||
false |
||||
); |
||||
$frmActivity->addHtml('</div>'); |
||||
$frmActivity->addButtonImport(get_lang('Import')); |
||||
$frmActivity->addRule('file', get_lang('ThisFileIsRequired'), 'required'); |
||||
$frmActivity->addRule( |
||||
'file', |
||||
$plugin->get_lang('OnlyZipAllowed'), |
||||
'filetype', |
||||
['zip'] |
||||
); |
||||
$frmActivity->applyFilter('title', 'trim'); |
||||
$frmActivity->applyFilter('description', 'trim'); |
||||
$frmActivity->applyFilter('lrs_url', 'trim'); |
||||
$frmActivity->applyFilter('lrs_auth', 'trim'); |
||||
|
||||
if ($frmActivity->validate()) { |
||||
$values = $frmActivity->exportValues(); |
||||
$zipFileInfo = $_FILES['file']; |
||||
|
||||
try { |
||||
$packageFile = Cmi5Importer::create($zipFileInfo, $course)->import(); |
||||
|
||||
$parser = Cmi5Parser::create($packageFile, $course, $session); |
||||
|
||||
$toolLaunch = $parser->parse(); |
||||
} catch (Exception $e) { |
||||
Display::addFlash( |
||||
Display::return_message($e->getMessage(), 'error') |
||||
); |
||||
|
||||
exit; |
||||
} |
||||
|
||||
if (!empty($values['lrs_url']) |
||||
&& !empty($values['lrs_auth_username']) |
||||
&& !empty($values['lrs_auth_password']) |
||||
) { |
||||
$toolLaunch |
||||
->setLrsUrl($values['lrs_url']) |
||||
->setLrsAuthUsername($values['lrs_auth_username']) |
||||
->setLrsAuthUsername($values['lrs_auth_password']); |
||||
} |
||||
|
||||
$em = Database::getManager(); |
||||
$em->persist($toolLaunch); |
||||
|
||||
foreach ($parser->getToc() as $cmi5Item) { |
||||
$cmi5Item->setTool($toolLaunch); |
||||
|
||||
$em->persist($cmi5Item); |
||||
} |
||||
|
||||
$em->flush(); |
||||
|
||||
Display::addFlash( |
||||
Display::return_message($plugin->get_lang('ActivityImported'), 'success') |
||||
); |
||||
|
||||
header('Location: '.api_get_course_url()); |
||||
exit; |
||||
} |
||||
|
||||
$frmActivity->setDefaults(['allow_multiple_attempts' => true]); |
||||
|
||||
$pageTitle = $plugin->get_title(); |
||||
$pageContent = $frmActivity->returnForm(); |
||||
|
||||
$interbreadcrumb[] = ['url' => '../tincan/index.php', 'name' => $plugin->get_lang('ToolTinCan')]; |
||||
|
||||
$langAddActivity = $plugin->get_lang('AddActivity'); |
||||
|
||||
$view = new Template($langAddActivity); |
||||
$view->assign('header', $langAddActivity); |
||||
$view->assign('content', $pageContent); |
||||
$view->display_one_col_template(); |
||||
@ -1,129 +0,0 @@ |
||||
<?php |
||||
|
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
namespace Chamilo\PluginBundle\XApi\Importer; |
||||
|
||||
use Chamilo\CoreBundle\Entity\Course; |
||||
use DocumentManager; |
||||
use Exception; |
||||
use PclZip; |
||||
use Symfony\Component\Filesystem\Filesystem; |
||||
|
||||
/** |
||||
* Class AbstractImporter. |
||||
* |
||||
* @package Chamilo\PluginBundle\XApi\Importer |
||||
*/ |
||||
abstract class AbstractImporter |
||||
{ |
||||
/** |
||||
* @var \Chamilo\CoreBundle\Entity\Course |
||||
*/ |
||||
protected $course; |
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $courseDirectoryPath; |
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $toolDirectory; |
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $packageDirectoryPath; |
||||
/** |
||||
* @var \PclZip |
||||
*/ |
||||
protected $zipFile; |
||||
|
||||
/** |
||||
* AbstractImporter constructor. |
||||
*/ |
||||
protected function __construct(array $fileInfo, string $toolDirectory, Course $course) |
||||
{ |
||||
$this->course = $course; |
||||
|
||||
$pathInfo = pathinfo($fileInfo['name']); |
||||
|
||||
$this->courseDirectoryPath = api_get_path(SYS_COURSE_PATH).$this->course->getDirectory(); |
||||
$this->toolDirectory = $toolDirectory; |
||||
$this->packageDirectoryPath = implode( |
||||
'/', |
||||
[ |
||||
$this->courseDirectoryPath, |
||||
$this->toolDirectory, |
||||
api_replace_dangerous_char($pathInfo['filename']), |
||||
] |
||||
); |
||||
|
||||
$this->zipFile = new PclZip($fileInfo['tmp_name']); |
||||
} |
||||
|
||||
/** |
||||
* @return \Chamilo\PluginBundle\XApi\Importer\AbstractImporter |
||||
*/ |
||||
abstract public static function create(array $fileInfo, Course $course); |
||||
|
||||
/** |
||||
* @throws \Exception |
||||
*/ |
||||
public function import() |
||||
{ |
||||
$this->validPackage(); |
||||
|
||||
if (!$this->isEnoughSpace()) { |
||||
throw new Exception('Not enough space to storage package.'); |
||||
} |
||||
|
||||
$fs = new Filesystem(); |
||||
$fs->mkdir( |
||||
$this->packageDirectoryPath, |
||||
api_get_permissions_for_new_directories() |
||||
); |
||||
|
||||
$this->zipFile->extract($this->packageDirectoryPath); |
||||
|
||||
return "{$this->packageDirectoryPath}/{$this->toolDirectory}.xml"; |
||||
} |
||||
|
||||
/** |
||||
* @throws \Exception |
||||
*/ |
||||
protected function validPackage() |
||||
{ |
||||
$zipContent = $this->zipFile->listContent(); |
||||
|
||||
if (empty($zipContent)) { |
||||
throw new Exception('Package file is empty'); |
||||
} |
||||
|
||||
foreach ($zipContent as $zipEntry) { |
||||
if (preg_match('~.(php.*|phtml)$~i', $zipEntry['filename'])) { |
||||
throw new Exception("File \"{$zipEntry['filename']}\" contains a PHP script"); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @return bool |
||||
*/ |
||||
private function isEnoughSpace() |
||||
{ |
||||
$zipRealSize = array_reduce( |
||||
$this->zipFile->listContent(), |
||||
function ($accumulator, $zipEntry) { |
||||
return $accumulator + $zipEntry['size']; |
||||
} |
||||
); |
||||
|
||||
$courseSpaceQuota = DocumentManager::get_course_quota($this->course->getCode()); |
||||
|
||||
if (!enough_size($zipRealSize, $this->courseDirectoryPath, $courseSpaceQuota)) { |
||||
return false; |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
} |
||||
@ -1,45 +0,0 @@ |
||||
<?php |
||||
|
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
namespace Chamilo\PluginBundle\XApi\Importer; |
||||
|
||||
use Chamilo\CoreBundle\Entity\Course; |
||||
|
||||
/** |
||||
* Class Cmi5Importer. |
||||
* |
||||
* @package Chamilo\PluginBundle\XApi\Importer |
||||
*/ |
||||
class Cmi5Importer extends AbstractImporter |
||||
{ |
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public static function create(array $fileInfo, Course $course) |
||||
{ |
||||
return new self($fileInfo, 'cmi5', $course); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
protected function validPackage() |
||||
{ |
||||
parent::validPackage(); |
||||
|
||||
$zipContent = $this->zipFile->listContent(); |
||||
|
||||
$isValid = false; |
||||
|
||||
foreach ($zipContent as $zipEntry) { |
||||
if ('cmi5.xml' === $zipEntry['filename']) { |
||||
$isValid = true; |
||||
} |
||||
} |
||||
|
||||
if (!$isValid) { |
||||
throw new \Exception('Incorrect package. Missing "cmi5.xml" file'); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,76 @@ |
||||
<?php |
||||
|
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
namespace Chamilo\PluginBundle\XApi\Importer; |
||||
|
||||
use Chamilo\CoreBundle\Entity\Course; |
||||
|
||||
/** |
||||
* Class AbstractImporter. |
||||
* |
||||
* @package Chamilo\PluginBundle\XApi\Importer |
||||
*/ |
||||
abstract class PackageImporter |
||||
{ |
||||
/** |
||||
* @var \Chamilo\CoreBundle\Entity\Course |
||||
*/ |
||||
protected $course; |
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $courseDirectoryPath; |
||||
/** |
||||
* @var array |
||||
*/ |
||||
protected $packageFileInfo; |
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $packageType; |
||||
|
||||
/** |
||||
* AbstractImporter constructor. |
||||
* |
||||
* @param array $fileInfo |
||||
* @param \Chamilo\CoreBundle\Entity\Course $course |
||||
*/ |
||||
protected function __construct(array $fileInfo, Course $course) |
||||
{ |
||||
$this->packageFileInfo = $fileInfo; |
||||
$this->course = $course; |
||||
|
||||
$this->courseDirectoryPath = api_get_path(SYS_COURSE_PATH).$this->course->getDirectory(); |
||||
} |
||||
|
||||
/** |
||||
* @param array $fileInfo |
||||
* @param \Chamilo\CoreBundle\Entity\Course $course |
||||
* |
||||
* @return \Chamilo\PluginBundle\XApi\Importer\XmlPackageImporter|\Chamilo\PluginBundle\XApi\Importer\ZipPackageImporter |
||||
*/ |
||||
public static function create(array $fileInfo, Course $course) |
||||
{ |
||||
if ('text/xml' === $fileInfo['type']) { |
||||
return new XmlPackageImporter($fileInfo, $course); |
||||
} |
||||
|
||||
return new ZipPackageImporter($fileInfo, $course); |
||||
} |
||||
|
||||
/** |
||||
* @throws \Exception |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
abstract public function import(): string; |
||||
|
||||
/** |
||||
* @return string |
||||
*/ |
||||
public function getPackageType(): string |
||||
{ |
||||
return $this->packageType; |
||||
} |
||||
} |
||||
@ -1,46 +0,0 @@ |
||||
<?php |
||||
|
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
namespace Chamilo\PluginBundle\XApi\Importer; |
||||
|
||||
use Chamilo\CoreBundle\Entity\Course; |
||||
use Exception; |
||||
|
||||
/** |
||||
* Class TinCanImporter. |
||||
* |
||||
* @package Chamilo\PluginBundle\XApi\Importer |
||||
*/ |
||||
class TinCanImporter extends AbstractImporter |
||||
{ |
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public static function create(array $fileInfo, Course $course) |
||||
{ |
||||
return new self($fileInfo, 'tincan', $course); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
protected function validPackage() |
||||
{ |
||||
parent::validPackage(); |
||||
|
||||
$zipContent = $this->zipFile->listContent(); |
||||
|
||||
$isValid = false; |
||||
|
||||
foreach ($zipContent as $zipEntry) { |
||||
if ('tincan.xml' === $zipEntry['filename']) { |
||||
$isValid = true; |
||||
} |
||||
} |
||||
|
||||
if (!$isValid) { |
||||
throw new Exception('Incorrect package. Missing "tincan.xml" file'); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,29 @@ |
||||
<?php |
||||
|
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
namespace Chamilo\PluginBundle\XApi\Importer; |
||||
|
||||
use Exception; |
||||
|
||||
/** |
||||
* Class XmlImporter. |
||||
* |
||||
* @package Chamilo\PluginBundle\XApi\Importer |
||||
*/ |
||||
class XmlPackageImporter extends PackageImporter |
||||
{ |
||||
/** |
||||
* @inheritDoc |
||||
*/ |
||||
public function import(): string |
||||
{ |
||||
if (!in_array($this->packageFileInfo['name'], ['tincan.xml', 'cmi5.xml'])) { |
||||
throw new Exception('Invalid package'); |
||||
} |
||||
|
||||
$this->packageType = explode('.', $this->packageFileInfo['name'], 2)[0]; |
||||
|
||||
return $this->packageFileInfo['tmp_name']; |
||||
} |
||||
} |
||||
@ -0,0 +1,95 @@ |
||||
<?php |
||||
|
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
namespace Chamilo\PluginBundle\XApi\Importer; |
||||
|
||||
use DocumentManager; |
||||
use Exception; |
||||
use PclZip; |
||||
use Symfony\Component\Filesystem\Filesystem; |
||||
|
||||
/** |
||||
* Class ZipImporter |
||||
* |
||||
* @package Chamilo\PluginBundle\XApi\Importer |
||||
*/ |
||||
class ZipPackageImporter extends PackageImporter |
||||
{ |
||||
/** |
||||
* @inheritDoc |
||||
*/ |
||||
public function import(): string |
||||
{ |
||||
$zipFile = new PclZip($this->packageFileInfo['tmp_name']); |
||||
$zipContent = $zipFile->listContent(); |
||||
|
||||
$packageSize = array_reduce( |
||||
$zipContent, |
||||
function ($accumulator, $zipEntry) { |
||||
if (preg_match('~.(php.*|phtml)$~i', $zipEntry['filename'])) { |
||||
throw new Exception("File \"{$zipEntry['filename']}\" contains a PHP script"); |
||||
} |
||||
|
||||
if (in_array($zipEntry['filename'], ['tincan.xml', 'cmi5.xml'])) { |
||||
$this->packageType = explode('.', $zipEntry['filename'], 2)[0]; |
||||
} |
||||
|
||||
return $accumulator + $zipEntry['size']; |
||||
} |
||||
); |
||||
|
||||
if (empty($this->packageType)) { |
||||
throw new Exception('Invalid package'); |
||||
} |
||||
|
||||
$this->validateEnoughSpace($packageSize); |
||||
|
||||
$pathInfo = pathinfo($this->packageFileInfo['name']); |
||||
|
||||
$packageDirectoryPath = $this->generatePackageDirectory($pathInfo['filename']); |
||||
|
||||
$zipFile->extract($packageDirectoryPath); |
||||
|
||||
return "$packageDirectoryPath/{$this->packageType}.xml"; |
||||
} |
||||
|
||||
/** |
||||
* @param int $packageSize |
||||
* |
||||
* @throws \Exception |
||||
*/ |
||||
protected function validateEnoughSpace(int $packageSize) |
||||
{ |
||||
$courseSpaceQuota = DocumentManager::get_course_quota($this->course->getCode()); |
||||
|
||||
if (!enough_size($packageSize, $this->courseDirectoryPath, $courseSpaceQuota)) { |
||||
throw new Exception('Not enough space to storage package.'); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @param string $name |
||||
* |
||||
* @return string |
||||
*/ |
||||
private function generatePackageDirectory(string $name): string |
||||
{ |
||||
$directoryPath = implode( |
||||
'/', |
||||
[ |
||||
$this->courseDirectoryPath, |
||||
$this->packageType, |
||||
api_replace_dangerous_char($name), |
||||
] |
||||
); |
||||
|
||||
$fs = new Filesystem(); |
||||
$fs->mkdir( |
||||
$directoryPath, |
||||
api_get_permissions_for_new_directories() |
||||
); |
||||
|
||||
return $directoryPath; |
||||
} |
||||
} |
||||
@ -1,53 +0,0 @@ |
||||
<?php |
||||
|
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
namespace Chamilo\PluginBundle\XApi\Parser; |
||||
|
||||
use Chamilo\CoreBundle\Entity\Course; |
||||
use Chamilo\CoreBundle\Entity\Session; |
||||
|
||||
/** |
||||
* Class AbstractParser. |
||||
* |
||||
* @package Chamilo\PluginBundle\XApi\Parser |
||||
*/ |
||||
abstract class AbstractParser |
||||
{ |
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $filePath; |
||||
/** |
||||
* @var Course |
||||
*/ |
||||
protected $course; |
||||
/** |
||||
* @var Session|null |
||||
*/ |
||||
protected $session; |
||||
|
||||
/** |
||||
* AbstractParser constructor. |
||||
* |
||||
* @param $filePath |
||||
*/ |
||||
protected function __construct($filePath, Course $course, Session $session = null) |
||||
{ |
||||
$this->filePath = $filePath; |
||||
$this->course = $course; |
||||
$this->session = $session; |
||||
} |
||||
|
||||
/** |
||||
* @param string $filePath |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
abstract public static function create($filePath, Course $course, Session $session = null); |
||||
|
||||
/** |
||||
* @return \Chamilo\PluginBundle\Entity\XApi\ToolLaunch |
||||
*/ |
||||
abstract public function parse(); |
||||
} |
||||
@ -0,0 +1,70 @@ |
||||
<?php |
||||
|
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
namespace Chamilo\PluginBundle\XApi\Parser; |
||||
|
||||
use Chamilo\CoreBundle\Entity\Course; |
||||
use Chamilo\CoreBundle\Entity\Session; |
||||
|
||||
/** |
||||
* Class PackageParser. |
||||
* |
||||
* @package Chamilo\PluginBundle\XApi\Parser |
||||
*/ |
||||
abstract class PackageParser |
||||
{ |
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $filePath; |
||||
/** |
||||
* @var Course |
||||
*/ |
||||
protected $course; |
||||
/** |
||||
* @var Session|null |
||||
*/ |
||||
protected $session; |
||||
|
||||
/** |
||||
* AbstractParser constructor. |
||||
* |
||||
* @param $filePath |
||||
* @param \Chamilo\CoreBundle\Entity\Course $course |
||||
* @param \Chamilo\CoreBundle\Entity\Session|null $session |
||||
*/ |
||||
protected function __construct($filePath, Course $course, Session $session = null) |
||||
{ |
||||
$this->filePath = $filePath; |
||||
$this->course = $course; |
||||
$this->session = $session; |
||||
} |
||||
|
||||
/** |
||||
* @param string $packageType |
||||
* @param string $filePath |
||||
* @param \Chamilo\CoreBundle\Entity\Course $course |
||||
* @param \Chamilo\CoreBundle\Entity\Session|null $session |
||||
* |
||||
* @throws \Exception |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public static function create(string $packageType, string $filePath, Course $course, Session $session = null) |
||||
{ |
||||
switch ($packageType) { |
||||
case 'tincan': |
||||
return new TinCanParser($filePath, $course, $session); |
||||
case 'cmi5': |
||||
return new Cmi5Parser($filePath, $course, $session); |
||||
default: |
||||
throw new \Exception('Invalid package.'); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @return \Chamilo\PluginBundle\Entity\XApi\ToolLaunch |
||||
*/ |
||||
abstract public function parse(): \Chamilo\PluginBundle\Entity\XApi\ToolLaunch; |
||||
} |
||||
Loading…
Reference in new issue