From b6c960b5b6384f02814ef4c45c9cc5c9d83a3eff Mon Sep 17 00:00:00 2001 From: Vincent Vanwaelscappel Date: Wed, 4 Mar 2020 19:06:06 +0100 Subject: [PATCH] done #3458 @2.5 --- .../Admin/Operations/DownloadOperation.php | 6 +- .../Admin/Operations/PreviewOperation.php | 17 +- app/Models/Quiz.php | 147 ++++++++++++++---- app/Models/QuizTranslation.php | 24 +-- composer.json | 3 +- config/backpack/base.php | 5 +- .../featherlight/featherlight.min.css | 8 + .../packages/featherlight/featherlight.min.js | 8 + resources/quiz/banner.jpg | Bin 0 -> 1539135 bytes resources/quiz/template.zip | Bin 0 -> 756616 bytes 10 files changed, 170 insertions(+), 48 deletions(-) create mode 100644 public/packages/featherlight/featherlight.min.css create mode 100644 public/packages/featherlight/featherlight.min.js create mode 100644 resources/quiz/banner.jpg create mode 100644 resources/quiz/template.zip diff --git a/app/Http/Controllers/Admin/Operations/DownloadOperation.php b/app/Http/Controllers/Admin/Operations/DownloadOperation.php index 60f82043b..5854ee6d7 100644 --- a/app/Http/Controllers/Admin/Operations/DownloadOperation.php +++ b/app/Http/Controllers/Admin/Operations/DownloadOperation.php @@ -11,8 +11,10 @@ trait DownloadOperation Route::match(['get'], $segment . '/{id}/download', $controller . '@download'); } - protected function download() + protected function download($id) { - return ';))'; + $id = $this->crud->getCurrentEntryId() ?? $id; + $entry=$this->crud->getEntry($id); + $entry->compile(); } } diff --git a/app/Http/Controllers/Admin/Operations/PreviewOperation.php b/app/Http/Controllers/Admin/Operations/PreviewOperation.php index 8a11e0723..9fa48e812 100644 --- a/app/Http/Controllers/Admin/Operations/PreviewOperation.php +++ b/app/Http/Controllers/Admin/Operations/PreviewOperation.php @@ -2,16 +2,27 @@ namespace App\Http\Controllers\Admin\Operations; +use Cubist\Util\Files\Files; use Illuminate\Support\Facades\Route; trait PreviewOperation { protected function setupPreviewRoutes($segment, $routeName, $controller) { - Route::match(['get'], $segment . '/{id}/preview', $controller . '@preview'); + Route::match(['get'], $segment . '/{id}/preview/{path?}', $controller . '@preview') + ->where(['id' => '[0-9]+', 'path' => '.*']); } - protected function preview(){ - return ':)'; + protected function preview($id, $path = 'index.html') + { + $dest = protected_path('quiz/final/' . $id); + + if ($path === 'index.html') { + $entry = $this->crud->getEntry($id); + $entry->compile($dest); + } + + $p = $dest . '/' . $path; + return response(null)->header('Content-Type', Files::_getMimeType($p))->header('X-Sendfile', $p); } } diff --git a/app/Models/Quiz.php b/app/Models/Quiz.php index ab39b96c2..283390e0b 100644 --- a/app/Models/Quiz.php +++ b/app/Models/Quiz.php @@ -6,6 +6,9 @@ namespace App\Models; use App\Http\Controllers\Admin\Base\QuizController; use Backpack\CRUD\app\Library\CrudPanel\CrudPanel; use Cubist\Backpack\app\Magic\Models\CubistMagicAbstractModel; +use Cubist\Util\Files\Files; +use Cubist\Util\Zip; +use Spatie\MediaLibrary\Models\Media; class Quiz extends CubistMagicAbstractModel { @@ -17,6 +20,27 @@ class Quiz extends CubistMagicAbstractModel 'singular' => 'quiz', 'plural' => 'quizzes']; + protected static $_messages = ['defaultMessage' => 'Message displayed at the end of the quiz', + 'passedMessage' => 'Message displayed when passing the quiz', + 'failedMessage' => 'Message displayed when failing the quiz']; + + protected static $_colors = [ + 'mainColor' => ['label' => 'Main color', 'hint' => 'Buttons and rollovers color', 'value' => '#e7007a'], + 'overlay' => ['label' => 'Overlay', 'hint' => 'Overlay set on the banner', 'value' => 'rgba(0,0,0,0.75)'], + 'reviewBackground' => ['label' => 'Review panel background color', 'value' => '#262626'], + 'okColor' => ['label' => 'OK color', 'hint' => 'Color of the ok badge (usually green)', 'value' => '#66c924'], + 'nokColor' => ['label' => 'NOK color', 'hint' => 'Color of the not ok badge (usually red)', 'value' => '#c61e35'], + ]; + + protected static $_images = [ + 'logo' => 'Logo', + 'banner' => 'Banner', + ]; + + protected static $_actions = [ + 'passedAction' => 'Run code when passing the quiz', + 'failedAction' => 'Run code when failing the quiz']; + /** * @param $crud CrudPanel */ @@ -30,7 +54,7 @@ class Quiz extends CubistMagicAbstractModel public function openPreviewButton($crud = false) { - return ' Preview'; + return ' Preview'; } public function downloadButton($crud = false) @@ -56,11 +80,8 @@ class Quiz extends CubistMagicAbstractModel 'column' => true, 'tab' => 'Contents']); - $messages = ['defaultMessage' => 'Message displayed at the end of the quiz', - 'passedMessage' => 'Message displayed when passing the quiz', - 'failedMessage' => 'Message displayed when failing the quiz']; - foreach ($messages as $name => $label) { + foreach (self::$_messages as $name => $label) { $this->addField(['name' => $name, 'label' => $label, 'hint' => 'Let this field empty to use the default message', @@ -97,41 +118,109 @@ class Quiz extends CubistMagicAbstractModel 'type' => 'Checkbox', 'tab' => 'Settings']); - $this->addField(['name' => 'passedAction', - 'label' => 'Run code when passing the quiz', - 'hint' => 'JavaScript code', - 'type' => 'Textarea', - 'tab' => 'Settings']); - - $this->addField(['name' => 'failedAction', - 'label' => 'Run code when failing the quiz', - 'hint' => 'JavaScript code', - 'type' => 'Textarea', - 'tab' => 'Settings']); - $colors = [ - 'mainColor' => ['label' => 'Main color', 'hint' => 'Buttons and rollovers color', 'value' => '#e7007a'], - 'overlay' => ['label' => 'Overlay', 'hint' => 'Overlay set on the banner', 'value' => 'rgba(0,0,0,0.75)'], - 'reviewBackground' => ['label' => 'Review panel background color', 'value' => '#262626'], - 'okColor' => ['label' => 'OK color', 'hint' => 'Color of the ok badge (usually green)', 'value' => '#66c924'], - 'nokColor' => ['label' => 'NOK color', 'hint' => 'Color of the not ok badge (usually red)', 'value' => '#c61e35'], - ]; + foreach (self::$_actions as $action => $label) { + $this->addField(['name' => $action, + 'label' => $label, + 'hint' => 'JavaScript code', + 'type' => 'Textarea', + 'tab' => 'Settings']); + } $default = ['tab' => 'Theme', 'type' => 'Color']; - foreach ($colors as $name => $color) { + foreach (self::$_colors as $name => $color) { $f = array_merge($default, $color, ['name' => $name]); $this->addField($f); } - $images = [ - 'logo' => 'Logo', - 'banner' => 'Banner', - ]; + $default = ['tab' => 'Theme', 'type' => 'Images', 'maxFiles' => 1]; - foreach ($images as $name => $label) { + foreach (self::$_images as $name => $label) { $f = array_merge($default, ['name' => $name, 'label' => $label]); $this->addField($f); } } + + public function registerMediaConversions(Media $media = null) + { + parent::registerMediaConversions($media); + + $this->addMediaConversion('logo_compile')->width(300)->height(100)->format('png'); + $this->addMediaConversion('banner_compile')->width(1920)->height(600)->format('jpg'); + } + + /** + * @param $quiz self + */ + public function compile($dest) + { + // Clean existing dir and recreate it + if (file_exists($dest)) { + Files::rmdir($dest); + } + mkdir($dest, 0777, true); + + // Extract template into the final dir + $template = resource_path('quiz/template.zip'); + Zip::extract($template, $dest); + + // Copy assets + $assets = ['banner' => 'banner.jpg', 'logo' => 'logo.png']; + foreach ($assets as $asset => $filename) { + $coll = $this->getAttribute($asset); + if ($coll) { + $media = $this->getFirstMedia($coll); + if ($media) { + $path = $media->getPath($asset . '_compile'); + copy($path, $dest . '/assets/' . $filename); + } + } + } + + // Create data.xml + $xml = simplexml_load_string(''); + $xml->addAttribute('id', $this->getAttribute('id')); + $xml->addChild('title', $this->getAttribute('title')); + $xml->addChild('threshold', $this->getAttribute('threshold') ?: '0'); + $xml->addChild('review', $this->getAttribute('review') ? '1' : '0'); + $xt = $xml->addChild('translations'); + /** @var QuizTranslation $translation */ + $translation = QuizTranslation::find($this->getAttribute('translation')); + foreach (QuizTranslation::getTexts() as $text => $default) { + $xt->addChild($text, $translation->getAttribute($text)); + } + foreach (self::$_colors as $color => $c) { + $xml->addChild($color, $this->getAttribute($color)); + } + foreach (self::$_messages as $message => $m) { + $xml->addChild($message, $this->getAttribute($message) ?: $translation->getAttribute($message)); + } + foreach (self::$_actions as $action => $a) { + $xml->addChild($action, $this->getAttribute($action)); + } + $questions = $this->getAttribute('questions'); + $xqs = $xml->addChild('questions'); + foreach ($questions as $question) { + $xq = $xqs->addChild('question'); + if ($question['multiple']) { + $xq->addAttribute('multiple', '1'); + } + $xq->addChild('label', $question['question']); + $xas = $xq->addChild('answers'); + foreach ($question['answers'] as $answer) { + $xa = $xas->addChild('answer', $answer['answer']); + if ($answer['correct']) { + $xa->addAttribute('correct', '1'); + } + } + $xq->addChild('correction', $question['explaination']); + } + file_put_contents($dest . '/data.xml', tidy_repair_string($xml->asXML(), ['input-xml' => 1, 'indent' => 1, 'wrap' => 0])); + } + + public static function getMessages() + { + return self::$_messages; + } } diff --git a/app/Models/QuizTranslation.php b/app/Models/QuizTranslation.php index 062ee8539..bc9be5fed 100644 --- a/app/Models/QuizTranslation.php +++ b/app/Models/QuizTranslation.php @@ -11,36 +11,36 @@ class QuizTranslation extends CubistMagicAbstractModel 'singular' => 'quiz translation', 'plural' => 'quiz translations']; + protected static $_texts= ['validateAnswer' => 'Validate answers', + 'reviewAnswer' => 'Review answers', + 'question' => 'Question %d:', + 'totalQuestions' => 'Total questions:', + 'correctAnswers' => 'Correct answers:']; + public function setFields() { parent::setFields(); - $texts = ['validateAnswer' => 'Validate answers', - 'reviewAnswer' => 'Review answers', - 'question' => 'Question %d:', - 'totalQuestions' => 'Total questions:', - 'correctAnswers' => 'Correct answers:']; - $this->addField(['name' => 'locale', 'label' => 'Language', 'type' => 'Locale', 'column' => true]); - foreach ($texts as $name => $default) { + foreach (self::$_texts as $name => $default) { $this->addField(['name' => $name, 'label' => 'Translation of « ' . $default . ' »', 'type' => 'Text']); } - $messages = ['defaultMessage' => 'Message displayed at the end of the quiz', - 'passedMessage' => 'Message displayed when passing the quiz', - 'failedMessage' => 'Message displayed when failing the quiz']; - - foreach ($messages as $name => $label) { + foreach (Quiz::getMessages() as $name => $label) { $this->addField([ 'name' => $name, 'label' => $label, 'type' => 'Text']); } } + + public static function getTexts(){ + return self::$_texts; + } } diff --git a/composer.json b/composer.json index 352d604bc..b40c28146 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,8 @@ "require": { "php": ">=7.4", "cubist/cms-back": "dev-master", - "league/csv": "^9.5" + "league/csv": "^9.5", + "ext-tidy": "*" }, "require-dev": { "facade/ignition": "^1.4", diff --git a/config/backpack/base.php b/config/backpack/base.php index 682ab435b..6d8b4f00f 100644 --- a/config/backpack/base.php +++ b/config/backpack/base.php @@ -44,6 +44,7 @@ return [ // CSS files that are loaded in all pages, using Laravel's asset() helper 'styles' => [ 'packages/backpack/base/css/bundle.css', + 'packages/featherlight/featherlight.min.css', 'packages/cubist/fluidbooktoolbox/css/style.css', // Here's what's inside the bundle: @@ -124,7 +125,9 @@ return [ 'scripts' => [ // Backstrap includes jQuery, Bootstrap, CoreUI, PNotify, Popper 'packages/backpack/base/js/bundle.js', - 'packages/cubist/fluidbooktoolbox/js/bundle.css', + 'packages/featherlight/featherlight.min.js', + 'packages/cubist/fluidbooktoolbox/js/bundle.js', + // examples (everything inside the bundle, loaded from CDN) // 'https://code.jquery.com/jquery-3.4.1.min.js', diff --git a/public/packages/featherlight/featherlight.min.css b/public/packages/featherlight/featherlight.min.css new file mode 100644 index 000000000..62fdb71d1 --- /dev/null +++ b/public/packages/featherlight/featherlight.min.css @@ -0,0 +1,8 @@ +/** + * Featherlight - ultra slim jQuery lightbox + * Version 1.7.14 - http://noelboss.github.io/featherlight/ + * + * Copyright 2019, Noël Raoul Bossart (http://www.noelboss.com) + * MIT Licensed. +**/ +html.with-featherlight{overflow:hidden}.featherlight{display:none;position:fixed;top:0;right:0;bottom:0;left:0;z-index:2147483647;text-align:center;white-space:nowrap;cursor:pointer;background:#333;background:rgba(0,0,0,0)}.featherlight:last-of-type{background:rgba(0,0,0,.8)}.featherlight:before{content:'';display:inline-block;height:100%;vertical-align:middle}.featherlight .featherlight-content{position:relative;text-align:left;vertical-align:middle;display:inline-block;overflow:auto;padding:25px 25px 0;border-bottom:25px solid transparent;margin-left:5%;margin-right:5%;max-height:95%;background:#fff;cursor:auto;white-space:normal}.featherlight .featherlight-inner{display:block}.featherlight link.featherlight-inner,.featherlight script.featherlight-inner,.featherlight style.featherlight-inner{display:none}.featherlight .featherlight-close-icon{position:absolute;z-index:9999;top:0;right:0;line-height:25px;width:25px;cursor:pointer;text-align:center;font-family:Arial,sans-serif;background:#fff;background:rgba(255,255,255,.3);color:#000;border:0;padding:0}.featherlight .featherlight-close-icon::-moz-focus-inner{border:0;padding:0}.featherlight .featherlight-image{width:100%}.featherlight-iframe .featherlight-content{border-bottom:0;padding:0;-webkit-overflow-scrolling:touch}.featherlight iframe{border:0}.featherlight *{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}@media only screen and (max-width:1024px){.featherlight .featherlight-content{margin-left:0;margin-right:0;max-height:98%;padding:10px 10px 0;border-bottom:10px solid transparent}}@media print{html.with-featherlight>*>:not(.featherlight){display:none}} \ No newline at end of file diff --git a/public/packages/featherlight/featherlight.min.js b/public/packages/featherlight/featherlight.min.js new file mode 100644 index 000000000..3b53d6684 --- /dev/null +++ b/public/packages/featherlight/featherlight.min.js @@ -0,0 +1,8 @@ +/** + * Featherlight - ultra slim jQuery lightbox + * Version 1.7.14 - http://noelboss.github.io/featherlight/ + * + * Copyright 2019, Noël Raoul Bossart (http://www.noelboss.com) + * MIT Licensed. +**/ +!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof module&&module.exports?module.exports=function(b,c){return void 0===c&&(c="undefined"!=typeof window?require("jquery"):require("jquery")(b)),a(c),c}:a(jQuery)}(function(a){"use strict";function b(a,c){if(!(this instanceof b)){var d=new b(a,c);return d.open(),d}this.id=b.id++,this.setup(a,c),this.chainCallbacks(b._callbackChain)}function c(a,b){var c={};for(var d in a)d in b&&(c[d]=a[d],delete a[d]);return c}function d(a,b){var c={},d=new RegExp("^"+b+"([A-Z])(.*)");for(var e in a){var f=e.match(d);if(f){var g=(f[1]+f[2].replace(/([A-Z])/g,"-$1")).toLowerCase();c[g]=a[e]}}return c}if("undefined"==typeof a)return void("console"in window&&window.console.info("Too much lightness, Featherlight needs jQuery."));if(a.fn.jquery.match(/-ajax/))return void("console"in window&&window.console.info("Featherlight needs regular jQuery, not the slim version."));var e=[],f=function(b){return e=a.grep(e,function(a){return a!==b&&a.$instance.closest("body").length>0})},g={allow:1,allowfullscreen:1,frameborder:1,height:1,longdesc:1,marginheight:1,marginwidth:1,mozallowfullscreen:1,name:1,referrerpolicy:1,sandbox:1,scrolling:1,src:1,srcdoc:1,style:1,webkitallowfullscreen:1,width:1},h={keyup:"onKeyUp",resize:"onResize"},i=function(c){a.each(b.opened().reverse(),function(){return c.isDefaultPrevented()||!1!==this[h[c.type]](c)?void 0:(c.preventDefault(),c.stopPropagation(),!1)})},j=function(c){if(c!==b._globalHandlerInstalled){b._globalHandlerInstalled=c;var d=a.map(h,function(a,c){return c+"."+b.prototype.namespace}).join(" ");a(window)[c?"on":"off"](d,i)}};b.prototype={constructor:b,namespace:"featherlight",targetAttr:"data-featherlight",variant:null,resetCss:!1,background:null,openTrigger:"click",closeTrigger:"click",filter:null,root:"body",openSpeed:250,closeSpeed:250,closeOnClick:"background",closeOnEsc:!0,closeIcon:"✕",loading:"",persist:!1,otherClose:null,beforeOpen:a.noop,beforeContent:a.noop,beforeClose:a.noop,afterOpen:a.noop,afterContent:a.noop,afterClose:a.noop,onKeyUp:a.noop,onResize:a.noop,type:null,contentFilters:["jquery","image","html","ajax","iframe","text"],setup:function(b,c){"object"!=typeof b||b instanceof a!=!1||c||(c=b,b=void 0);var d=a.extend(this,c,{target:b}),e=d.resetCss?d.namespace+"-reset":d.namespace,f=a(d.background||['
','
','",'
'+d.loading+"
","
","
"].join("")),g="."+d.namespace+"-close"+(d.otherClose?","+d.otherClose:"");return d.$instance=f.clone().addClass(d.variant),d.$instance.on(d.closeTrigger+"."+d.namespace,function(b){if(!b.isDefaultPrevented()){var c=a(b.target);("background"===d.closeOnClick&&c.is("."+d.namespace)||"anywhere"===d.closeOnClick||c.closest(g).length)&&(d.close(b),b.preventDefault())}}),this},getContent:function(){if(this.persist!==!1&&this.$content)return this.$content;var b=this,c=this.constructor.contentFilters,d=function(a){return b.$currentTarget&&b.$currentTarget.attr(a)},e=d(b.targetAttr),f=b.target||e||"",g=c[b.type];if(!g&&f in c&&(g=c[f],f=b.target&&e),f=f||d("href")||"",!g)for(var h in c)b[h]&&(g=c[h],f=b[h]);if(!g){var i=f;if(f=null,a.each(b.contentFilters,function(){return g=c[this],g.test&&(f=g.test(i)),!f&&g.regex&&i.match&&i.match(g.regex)&&(f=i),!f}),!f)return"console"in window&&window.console.error("Featherlight: no content filter found "+(i?' for "'+i+'"':" (no target specified)")),!1}return g.process.call(b,f)},setContent:function(b){return this.$instance.removeClass(this.namespace+"-loading"),this.$instance.toggleClass(this.namespace+"-iframe",b.is("iframe")),this.$instance.find("."+this.namespace+"-inner").not(b).slice(1).remove().end().replaceWith(a.contains(this.$instance[0],b[0])?"":b),this.$content=b.addClass(this.namespace+"-inner"),this},open:function(b){var c=this;if(c.$instance.hide().appendTo(c.root),!(b&&b.isDefaultPrevented()||c.beforeOpen(b)===!1)){b&&b.preventDefault();var d=c.getContent();if(d)return e.push(c),j(!0),c.$instance.fadeIn(c.openSpeed),c.beforeContent(b),a.when(d).always(function(a){a&&(c.setContent(a),c.afterContent(b))}).then(c.$instance.promise()).done(function(){c.afterOpen(b)})}return c.$instance.detach(),a.Deferred().reject().promise()},close:function(b){var c=this,d=a.Deferred();return c.beforeClose(b)===!1?d.reject():(0===f(c).length&&j(!1),c.$instance.fadeOut(c.closeSpeed,function(){c.$instance.detach(),c.afterClose(b),d.resolve()})),d.promise()},resize:function(a,b){if(a&&b){this.$content.css("width","").css("height","");var c=Math.max(a/(this.$content.parent().width()-1),b/(this.$content.parent().height()-1));c>1&&(c=b/Math.floor(b/c),this.$content.css("width",""+a/c+"px").css("height",""+b/c+"px"))}},chainCallbacks:function(b){for(var c in b)this[c]=a.proxy(b[c],this,a.proxy(this[c],this))}},a.extend(b,{id:0,autoBind:"[data-featherlight]",defaults:b.prototype,contentFilters:{jquery:{regex:/^[#.]\w/,test:function(b){return b instanceof a&&b},process:function(b){return this.persist!==!1?a(b):a(b).clone(!0)}},image:{regex:/\.(png|jpg|jpeg|gif|tiff?|bmp|svg)(\?\S*)?$/i,process:function(b){var c=this,d=a.Deferred(),e=new Image,f=a('');return e.onload=function(){f.naturalWidth=e.width,f.naturalHeight=e.height,d.resolve(f)},e.onerror=function(){d.reject(f)},e.src=b,d.promise()}},html:{regex:/^\s*<[\w!][^<]*>/,process:function(b){return a(b)}},ajax:{regex:/./,process:function(b){var c=a.Deferred(),d=a("
").load(b,function(a,b){"error"!==b&&c.resolve(d.contents()),c.reject()});return c.promise()}},iframe:{process:function(b){var e=new a.Deferred,f=a("