]> _ Git - fluidbook-toolbox.git/commitdiff
wip #6180 @2
authorVincent Vanwaelscappel <vincent@cubedesigners.com>
Thu, 27 Jul 2023 12:48:29 +0000 (14:48 +0200)
committerVincent Vanwaelscappel <vincent@cubedesigners.com>
Thu, 27 Jul 2023 12:48:29 +0000 (14:48 +0200)
app/Elearning/QuizCompiler.php
app/Http/Controllers/Admin/Operations/FluidbookPreviewOperation.php
app/Http/Controllers/Admin/Operations/Quiz/PreviewOperation.php
app/Jobs/Maintenance/CheckPublicationsHashAndCid.php
app/Models/FluidbookPublication.php
app/Models/Quiz.php
app/Models/QuizTheme.php
resources/views/vendor/backpack/crud/buttons/quiz/preview.blade.php

index 1eef0bdc94d254cc85f0cebd0f59f0e3354a5aac..b7b80bc234640b14d05179a4de8b74c3082110c1 100644 (file)
@@ -35,7 +35,7 @@ class QuizCompiler extends Base
     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;
@@ -69,20 +69,7 @@ class QuizCompiler extends Base
     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
index 62d27f677b1e75847de7410d325e23ac9062cde2..b799f88eae50c047132514685e7f713822d3bf69 100644 (file)
@@ -38,13 +38,33 @@ trait FluidbookPreviewOperation
             $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;}';
@@ -52,9 +72,9 @@ trait FluidbookPreviewOperation
             $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>';
index 995a879e29dacaf97422b2c14c5dd96b91d812e2..f59bed7a3bcbd317e2dddb47c486677914481de0 100644 (file)
@@ -2,17 +2,38 @@
 
 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()
@@ -20,16 +41,91 @@ trait PreviewOperation
         $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();
+        }
+    }
 }
index 2bfe24931dc3afdbc6646618a9747a7d3ffadbb9..a3ed706cde174acbcf290cb34b0dcbb562616f6a 100644 (file)
@@ -4,6 +4,7 @@ namespace App\Jobs\Maintenance;
 
 use App\Jobs\Base;
 use App\Models\FluidbookPublication;
+use App\Models\Quiz;
 use Illuminate\Support\Facades\Log;
 
 class CheckPublicationsHashAndCid extends Base
@@ -17,5 +18,13 @@ 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();
+        }
     }
 }
index 9261817dcd0ba9b12f74e473fe7775364658a41d..9a44e36141a3419918099703f093f75aa566dadc 100644 (file)
@@ -906,7 +906,7 @@ class FluidbookPublication extends ToolboxSettingsModel
     {
         $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);
         }
index 40939d4e66d856e5784f615f7e9549f6d3b5bde7..ef5953e5066ff1a0e2d5b654c7f37105695e7cc4 100644 (file)
@@ -11,6 +11,7 @@ use App\Http\Controllers\Admin\Operations\Quiz\LogOperation;
 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;
@@ -18,19 +19,17 @@ use Cubist\Backpack\Magic\Fields\FormBigSection;
 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';
 
@@ -100,6 +99,8 @@ class Quiz extends ToolboxModel
     {
         parent::setFields();
 
+        $this->addField('hash', Hidden::class);
+
         $this->addField(['name' => 'client',
             'label' => __('Nom du client'),
             'type' => 'Text',
@@ -398,4 +399,28 @@ class Quiz extends ToolboxModel
         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);
+    }
 }
index 22cd18b3000390903d7fbec422eeaa783b2f621e..77ca71d616946506aaa4cd79c662c2e8dff2776f 100644 (file)
@@ -146,4 +146,22 @@ class QuizTheme extends ToolboxModel
         $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;
+    }
 }
index 904aaa337157a5fbacb32d3f019ea8115d2ede93..3b4c4a65d41859f49edfa38ceb0b039e2fbc0f16 100644 (file)
@@ -1,4 +1,4 @@
 {{-- __('!! 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>