public function __construct($quiz, $dest, $forceScorm = false)
{
$this->quiz = Quiz::withoutGlobalScopes()->find($quiz);
- $this->theme = QuizTheme::withoutGlobalScopes()->find($this->quiz->theme ?: 3);
+ $this->theme =$this->quiz->getTheme();
$this->compilePath = protected_path('quiz/compile/' . $this->quiz->id);
$this->dest = $dest;
$this->forceScorm = $forceScorm;
protected function writeStyles()
{
// Main texts color
- $this->sassVariables['texts-color'] = $this->theme->textsColor;
- if ($this->theme->textsColor == 'auto' || !$this->theme->textsColor) {
- // IF auto mode, check between black and white which have the biggest distance (i.e. most contrast) with neutralColor
- $neutral = new Color($this->theme->neutralColor);
- $black = new Color('#000000');
- $white = new Color('#ffffff');
- $distBlack = $neutral->distance($black);
- $distWhite = $neutral->distance($white);
- if ($distWhite > $distBlack) {
- $this->sassVariables['texts-color'] = '#ffffff';
- } else {
- $this->sassVariables['texts-color'] = '#000000';
- }
- }
+ $this->sassVariables['texts-color'] = $this->theme->getTextsColor();
// Font
$this->sassVariables['font'] = $this->_font($this->theme->font);
// Colors
$tcolor = $scolor;
}
+ return $this->_compileScreen(
+ $title, $url,
+ __('Compilation du fluidbook en cours'),
+ __('Cette étape ne sera pas nécessaire lorsque le fluidbook sera installé sur son emplacement définitif'),
+ $bgcolor->toCSS(),
+ $tcolor->toCSS(),
+ $lcolor->toCSS(),
+ $scolor->toCSS()
+ );
+
+ }
+
+ protected function _compileScreen($title, $url, $messageTitle, $messageText, $backgroundColor, $messageColor, $loaderBackgroundColor = null, $loaderLineColor = null)
+ {
+ if (null === $loaderBackgroundColor) {
+ $loaderBackgroundColor = $messageColor;
+ }
+ if (null === $loaderLineColor) {
+ $loaderLineColor = $backgroundColor;
+ }
$res = '<!DOCTYPE html><html>';
$res .= '<head>';
$res .= '<script type="text/javascript">function load(){var url=\'' . $url . '\'+window.location.hash;window.location=url;}</script>';
if (!request('shortLoading', false)) {
$res .= '<link href="https://fonts.googleapis.com/css2?family=Open+Sans&display=swap" rel="stylesheet">';
- $res .= '<style>@keyframes loader-spin{0%{transform: rotate(0deg);}100%{transform: rotate(360deg);}}*{margin:0;padding:0;}html,body{height:100%;cursor: wait;font-family: "Open Sans", Arial;background-color:' . $bgcolor->toCSS() . ';}';
- $res .= 'h2,h3{text-align:center;color:' . $tcolor->toCSS() . ';font-weight:400;position:relative;top:55%;}';
+ $res .= '<style>@keyframes loader-spin{0%{transform: rotate(0deg);}100%{transform: rotate(360deg);}}*{margin:0;padding:0;}html,body{height:100%;cursor: wait;font-family: "Open Sans", Arial;background-color:' . $backgroundColor . ';}';
+ $res .= 'h2,h3{text-align:center;color:' . $messageColor . ';font-weight:400;position:relative;top:55%;}';
$res .= 'h2{font-size:16px;}';
$res .= 'h3{font-size:10px;}';
$res .= 'svg{position:absolute;top:calc(50% - 24px);left:calc(50% - 24px);animation-name: loader-spin;animation-duration: 1s;animation-iteration-count: infinite;animation-timing-function: linear;}';
$res .= '<title>' . $title . '</title>';
$res .= '</head>';
$res .= '<body onload="load();">';
- $res .= '<svg width="48" height="48" id="interface-loader" viewBox="0 0 48 48"><circle cx="24" cy="24" r="23" fill="' . $lcolor->toCSS() . '"></circle><circle class="animate" cx="24" cy="24" fill="none" stroke="' . $scolor->toCSS() . '" stroke-width="3" r="16" stroke-dasharray="80 80" transform="rotate(0 24 24)"></circle></svg>';
- $res .= '<h2>' . __('Compilation du fluidbook en cours') . '...</h2>';
- $res .= '<h3>' . __('Cette étape ne sera pas nécessaire lorsque le fluidbook sera installé sur son emplacement définitif') . '</h3>';
+ $res .= '<svg width="48" height="48" id="interface-loader" viewBox="0 0 48 48"><circle cx="24" cy="24" r="23" fill="' . $loaderBackgroundColor . '"></circle><circle class="animate" cx="24" cy="24" fill="none" stroke="' . $loaderLineColor . '" stroke-width="3" r="16" stroke-dasharray="80 80" transform="rotate(0 24 24)"></circle></svg>';
+ $res .= '<h2>' . $messageTitle . '...</h2>';
+ $res .= '<h3>' . $messageText . '</h3>';
} else {
$res .= '<script>load();</script>';
$res .= '</head>';
namespace App\Http\Controllers\Admin\Operations\Quiz;
+use App\Http\Controllers\Admin\Operations\FluidbookPreviewOperation;
+use App\Http\Middleware\CheckIfAdmin;
+use App\Models\FluidbookTheme;
+use App\Models\Quiz;
+use App\Models\QuizTheme;
use Cubist\Util\Files\Files;
+use Cubist\Util\PHP;
use Illuminate\Support\Facades\Route;
// __('!! e-Learning')
trait PreviewOperation
{
+ use FluidbookPreviewOperation;
+
protected function setupPreviewRoutes($segment, $routeName, $controller)
{
- Route::match(['get'], $segment . '/{id}/preview/{path?}', $controller . '@preview')
- ->where(['id' => '[0-9]+', 'path' => '.*']);
+ Route::match(['get'], $segment . '/preview/{id}_{hash}', function ($id, $hash) use ($segment) {
+ return $this->_preview($segment, $id, $hash, null, 'index.html');
+ })->where('id', '([0-9]+)(-[0-9]+)?')
+ ->where('hash', '[0-9a-f]{32}')
+ ->withoutMiddleware([CheckIfAdmin::class])
+ ->name('quiz_preview');
+
+ Route::match(['get'], $segment . '/preview/{id}_{hash}_{time}/{path?}', function ($id, $hash, $time, $path = 'index.html') use ($segment, $controller) {
+ return $this->_preview($segment, $id, $hash, $time, $path);
+ })->where('id', '([0-9]+)(-[0-9]+)?')
+ ->where('hash', '[0-9a-f]{32}')
+ ->where('path', '.*')
+ ->whereAlpha('version')
+ ->withoutMiddleware([CheckIfAdmin::class])
+ ->name('quiz_preview_with_time');
}
protected function setupPreviewDefaults()
$this->crud->addButtonFromView('line', 'open_preview', 'quiz.preview', 'begining');
}
- protected function preview($id, $path = 'index.html')
+
+ protected function _preview($segment, $id, $hash, $time = null, $path = null)
{
- $dest = protected_path('quiz/final/' . $id);
+ PHP::neverStop(true);
+ $q = request()->getQueryString();
+ if ($q) {
+ $q = '?' . $q;
+ }
+ $nointerface = !!request('nointerface', false);
+ $shortLoading = !!request('shortLoading', false);
- if ($path === 'index.html') {
- $entry = $this->crud->getEntry($id);
- $entry->compile($dest);
+ self::_getQuizAndTheme($id, $hash, $quiz, $theme);
+
+ if (null === $time || ((null === $path || $path === 'index.html') && $time > 0 && $time < (time() - 60) && !$nointerface && !$shortLoading)) {
+ $url = backpack_url($segment . '/preview/' . $id . '_' . $hash . '_' . time()) . '/' . $q;
+ return $this->loadingCompile($url, $id, $hash);
}
+ return $this->preview($quiz, $theme, $path);
+ }
+
+
+ /**
+ * @param Quiz $quiz
+ * @param QuizTheme $theme
+ * @param string $path
+ * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Foundation\Application|\Illuminate\Http\Response
+ */
+ protected function preview($quiz, $theme, $path = 'index.html')
+ {
+ $dest = protected_path('quiz/final/' . $quiz->id);
+ if ($path === 'index.html') {
+ $quiz->compile($dest);
+ }
$p = $dest . '/' . $path;
return response(null)->header('Content-Type', Files::_getMimeType($p))->header('X-Sendfile', $p);
}
+
+ /**
+ * @param $url string
+ * @param $id string
+ * @param $hash string
+ * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response
+ */
+ public function loadingCompile($url, $id, $hash)
+ {
+ PHP::neverStop(false);
+
+ self::_getQuizAndTheme($id, $hash, $quiz, $theme);
+ if (null === $theme || null === $quiz) {
+ abort(404);
+ }
+ $res = $this->_compileScreen(
+ $quiz->title, $url, __('Compilation du quiz en cours'),
+ __('Cette étape ne sera pas nécessaire lorsque le fluidbook sera installé sur son emplacement définitif'),
+ $theme->neutralColor,
+ $theme->getTextsColor(),
+ );
+ return response($res);
+ }
+
+ /**
+ * @param $id string
+ * @param $hash string
+ * @param $quiz Quiz
+ * @param $theme QuizTheme
+ * @return void
+ */
+ protected static function _getQuizAndTheme($id, $hash, &$quiz, &$theme)
+ {
+ $ee = explode('-', $id);
+ $forceThemeData = request('theme', false);
+ if ($forceThemeData) {
+ $theme = QuizTheme::fromArray(json_decode($forceThemeData, true), (count($ee) == 2 ? $ee[1] : null));
+ } else if (count($ee) == 2) {
+ $theme = QuizTheme::withoutGlobalScopes()->find($ee[1]);
+ $id = $ee[0];
+ }
+
+ $quiz = Quiz::withoutGlobalScopes()->where('id', $id)->where('hash', $hash)->first();
+ if (null === $quiz) {
+ abort(404, __('Lien de prévisualisation invalide'));
+ }
+ if (!isset($theme)) {
+ $theme = $quiz->getTheme();
+ }
+ }
}
use App\Jobs\Base;
use App\Models\FluidbookPublication;
+use App\Models\Quiz;
use Illuminate\Support\Facades\Log;
class CheckPublicationsHashAndCid extends Base
Log::warning('Fluidbook #' . $fluidbook->id . ' had empty hash or cid (hash: hash , cid: :cid)', ['hash' => $fluidbook->hash, 'cid' => $fluidbook->cid]);
$fluidbook->save();
}
+
+ /** @var Quiz $fluidbook */
+ foreach (Quiz::withoutGlobalScopes()->where('created_ok', '1')->where(function ($query) {
+ $query->whereNull('hash')->orWhere('hash', '');
+ })->get() as $quiz) {
+ Log::warning('Quiz #' . $quiz->id . ' had empty hash or cid (hash: hash , cid: :cid)', ['hash' => $quiz->hash]);
+ $quiz->save();
+ }
}
}
{
$routeName = 'fluidbook_preview';
if (isset($attrs['time']) || isset($attrs['path'])) {
- $routeName = 'fluidbook_preview_with_time';
+ $routeName .= '_with_time';
$default = ['path' => '', 'time' => time()];
$attrs = array_merge($default, $attrs);
}
use App\Http\Controllers\Admin\Operations\Quiz\PreviewOperation;
use App\Http\Controllers\Admin\Operations\Quiz\ReportOperation;
use App\Models\Base\ToolboxModel;
+use App\Models\Traits\CheckHash;
use App\Models\Traits\SCORMVersionTrait;
use Cubist\Backpack\Magic\Fields\Checkbox;
use Cubist\Backpack\Magic\Fields\Code;
use Cubist\Backpack\Magic\Fields\FormSection;
use Cubist\Backpack\Magic\Fields\FormSeparator;
use Cubist\Backpack\Magic\Fields\FormSuperSection;
+use Cubist\Backpack\Magic\Fields\Hidden;
use Cubist\Backpack\Magic\Fields\SelectFromArray;
use Cubist\Backpack\Magic\Fields\Text;
use Cubist\Backpack\Magic\Fields\Textarea;
-use Cubist\Scorm\Manifest;
-use Cubist\Util\CommandLine\Npx;
-use Cubist\Util\Files\VirtualDirectory;
use Spatie\Image\Manipulations;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
-use Cubist\Scorm\Version;
// __('!! e-Learning')
class Quiz extends ToolboxModel
{
+ use CheckHash;
protected $table = 'quiz';
{
parent::setFields();
+ $this->addField('hash', Hidden::class);
+
$this->addField(['name' => 'client',
'label' => __('Nom du client'),
'type' => 'Text',
return protected_path('quiz/final/' . $this->id);
}
+ public function onSaving(): bool
+ {
+ $this->checkHash();
+ return parent::onSaving();
+ }
+
+ public function getPreviewURL($attrs = [])
+ {
+ $routeName = 'quiz_preview';
+ if (isset($attrs['time']) || isset($attrs['path'])) {
+ $routeName .= '_with_time';
+ $default = ['path' => '', 'time' => time()];
+ $attrs = array_merge($default, $attrs);
+ }
+ return route($routeName, array_merge(['id' => $this->id, 'hash' => $this->hash], $attrs));
+ }
+
+ /**
+ * @return QuizTheme
+ */
+ public function getTheme()
+ {
+ return QuizTheme::withoutGlobalScopes()->find($this->theme ?: 3);
+ }
}
$this->addField('outroSuccessAnimation', FluidbookThemeImage::class, __('Animation de réussite'), ['hint' => __('390 x390px')]);
$this->addField('outroFailAnimation', FluidbookThemeImage::class, __('Animation d\'échec'), ['hint' => __('390 x390px')]);
}
+
+ public function getTextsColor()
+ {
+ if ($this->textsColor == 'auto' || !$this->textsColor) {
+ // If auto mode, check between black and white which have the biggest distance (i.e. most contrast) with neutralColor
+ $neutral = new \Cubist\Util\Graphics\Color($this->neutralColor);
+ $black = new \Cubist\Util\Graphics\Color('#000000');
+ $white = new \Cubist\Util\Graphics\Color('#ffffff');
+ $distBlack = $neutral->distance($black);
+ $distWhite = $neutral->distance($white);
+ if ($distWhite > $distBlack) {
+ return '#ffffff';
+ } else {
+ return '#000000';
+ }
+ }
+ return $this->textsColor;
+ }
}
{{-- __('!! e-Learning') --}}
<a class="btn btn-sm btn-link iframe" data-featherlight="iframe" data-featherlight-iframe-width="800"
- data-featherlight-iframe-height="600" href="{{$crud->route}}/{{$entry->getKey()}}/preview/index.html"
+ data-featherlight-iframe-height="600" href="{{$entry->getPreviewURL()}}"
data-toggle="tooltip" title="{{__('Prévisualiser le quiz')}}"><i class="la la-eye"></i> {{__('Prévisualiser')}}</a>