+++ /dev/null
-<?php
-
-
-namespace App\Http\Controllers\Admin\Base;
-
-use App\Http\Controllers\Admin\Operations\DownloadOperation;
-use App\Http\Controllers\Admin\Operations\ImportOperation;
-use App\Http\Controllers\Admin\Operations\LogOperation;
-use App\Http\Controllers\Admin\Operations\PreviewOperation;
-use App\Http\Controllers\Admin\Operations\ReportOperation;
-use Cubist\Backpack\Magic\Controllers\CubistMagicController;
-
-class QuizController extends CubistMagicController
-{
- use PreviewOperation;
- use DownloadOperation;
- use ImportOperation;
- use LogOperation;
- use ReportOperation;
-
- function setupListOperation()
- {
- $this->crud->addClause('whereIn', 'owner', auth()->user()->getManagedUsers());
- }
-
-}
class CompanyCrudController extends \Cubist\Backpack\Magic\Controllers\CubistMagicController
{
use \Cubist\Backpack\Magic\Operations\CreateOperation;
+ use \Cubist\Backpack\Http\Controllers\Operations\CloneEditOperation;
use \Cubist\Backpack\Magic\Operations\UpdateOperation;
use \Cubist\Backpack\Http\Controllers\Operations\ReviseOperation;
--- /dev/null
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+class CrudController extends \Cubist\Backpack\Magic\Controllers\CubistMagicController
+{
+ use \Cubist\Backpack\Magic\Operations\CreateOperation;
+ use \Cubist\Backpack\Http\Controllers\Operations\CloneEditOperation;
+ use \Cubist\Backpack\Magic\Operations\UpdateOperation;
+ use \Cubist\Backpack\Http\Controllers\Operations\BulkPublishOperation;
+ use \Backpack\CRUD\app\Http\Controllers\Operations\CloneOperation;
+ use \Backpack\CRUD\app\Http\Controllers\Operations\BulkCloneOperation;
+ use \Backpack\CRUD\app\Http\Controllers\Operations\DeleteOperation;
+ use \Backpack\CRUD\app\Http\Controllers\Operations\BulkDeleteOperation;
+ use \Cubist\Backpack\Http\Controllers\Operations\ReviseOperation;
+
+
+
+ /*
+ */
+
+ protected $_modelNamespace = 'App\Models\Base\ToolboxModel';
+ protected $_routeURL = '';
+ protected $_singular = '';
+ protected $_plural = '';
+ protected $_oneInstance= false;
+}
--- /dev/null
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+class CubemailsCrudController extends \Cubist\Backpack\Magic\Controllers\CubistMagicController
+{
+ use \Cubist\Backpack\Magic\Operations\CreateOperation;
+ use \Cubist\Backpack\Http\Controllers\Operations\CloneEditOperation;
+ use \Cubist\Backpack\Magic\Operations\UpdateOperation;
+ use \Cubist\Backpack\Http\Controllers\Operations\BulkPublishOperation;
+ use \Backpack\CRUD\app\Http\Controllers\Operations\CloneOperation;
+ use \Backpack\CRUD\app\Http\Controllers\Operations\BulkCloneOperation;
+ use \Backpack\CRUD\app\Http\Controllers\Operations\DeleteOperation;
+ use \Backpack\CRUD\app\Http\Controllers\Operations\BulkDeleteOperation;
+ use \Cubist\Backpack\Http\Controllers\Operations\ReviseOperation;
+
+
+
+ /*
+ __('paramètre')
+ __('paramètres')
+ */
+
+ protected $_modelNamespace = 'App\Models\CubedesignersEmails';
+ protected $_routeURL = 'cubemails';
+ protected $_singular = 'paramètre';
+ protected $_plural = 'paramètres';
+ protected $_oneInstance= true;
+}
--- /dev/null
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+class ElearningMediaCrudController extends \Cubist\Backpack\Magic\Controllers\CubistMagicController
+{
+ use \App\Http\Controllers\Admin\Operations\ELearningMedia\ImportOperation;
+ use \App\Http\Controllers\Admin\Operations\ELearningMedia\PreviewOperation;
+ use \App\Http\Controllers\Admin\Operations\ELearningMedia\DownloadOperation;
+ use \Cubist\Backpack\Magic\Operations\CreateOperation;
+ use \Cubist\Backpack\Http\Controllers\Operations\CloneEditOperation;
+ use \Cubist\Backpack\Magic\Operations\UpdateOperation;
+ use \Cubist\Backpack\Http\Controllers\Operations\BulkPublishOperation;
+ use \Backpack\CRUD\app\Http\Controllers\Operations\CloneOperation;
+ use \Backpack\CRUD\app\Http\Controllers\Operations\BulkCloneOperation;
+ use \Backpack\CRUD\app\Http\Controllers\Operations\DeleteOperation;
+ use \Backpack\CRUD\app\Http\Controllers\Operations\BulkDeleteOperation;
+ use \Cubist\Backpack\Http\Controllers\Operations\ReviseOperation;
+
+
+
+ /*
+ __('media')
+ __('media')
+ */
+
+ protected $_modelNamespace = 'App\Models\ELearningMedia';
+ protected $_routeURL = 'elearning-media';
+ protected $_singular = 'media';
+ protected $_plural = 'media';
+ protected $_oneInstance= false;
+}
--- /dev/null
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+class ElearningPackageCrudController extends \Cubist\Backpack\Magic\Controllers\CubistMagicController
+{
+ use \App\Http\Controllers\Admin\Operations\ELearningPackage\ImportOperation;
+ use \App\Http\Controllers\Admin\Operations\ELearningPackage\PreviewOperation;
+ use \App\Http\Controllers\Admin\Operations\ELearningPackage\DownloadOperation;
+ use \Cubist\Backpack\Magic\Operations\CreateOperation;
+ use \Cubist\Backpack\Http\Controllers\Operations\CloneEditOperation;
+ use \Cubist\Backpack\Magic\Operations\UpdateOperation;
+ use \Cubist\Backpack\Http\Controllers\Operations\BulkPublishOperation;
+ use \Backpack\CRUD\app\Http\Controllers\Operations\CloneOperation;
+ use \Backpack\CRUD\app\Http\Controllers\Operations\BulkCloneOperation;
+ use \Backpack\CRUD\app\Http\Controllers\Operations\DeleteOperation;
+ use \Backpack\CRUD\app\Http\Controllers\Operations\BulkDeleteOperation;
+ use \Cubist\Backpack\Http\Controllers\Operations\ReviseOperation;
+
+
+
+ /*
+ __('package')
+ __('package')
+ */
+
+ protected $_modelNamespace = 'App\Models\ELearningPackage';
+ protected $_routeURL = 'elearning-package';
+ protected $_singular = 'package';
+ protected $_plural = 'package';
+ protected $_oneInstance= false;
+}
--- /dev/null
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+class FluidbookCollectionCrudController extends \Cubist\Backpack\Magic\Controllers\CubistMagicController
+{
+ use \App\Http\Controllers\Admin\Operations\FluidbookCollection\DownloadOperation;
+ use \Cubist\Backpack\Magic\Operations\CreateOperation;
+ use \Cubist\Backpack\Http\Controllers\Operations\CloneEditOperation;
+ use \Cubist\Backpack\Magic\Operations\UpdateOperation;
+ use \Cubist\Backpack\Http\Controllers\Operations\BulkPublishOperation;
+ use \Backpack\CRUD\app\Http\Controllers\Operations\CloneOperation;
+ use \Backpack\CRUD\app\Http\Controllers\Operations\BulkCloneOperation;
+ use \Backpack\CRUD\app\Http\Controllers\Operations\DeleteOperation;
+ use \Backpack\CRUD\app\Http\Controllers\Operations\BulkDeleteOperation;
+ use \Cubist\Backpack\Http\Controllers\Operations\ReviseOperation;
+
+
+
+ /*
+ __('collection')
+ __('collections')
+ */
+
+ protected $_modelNamespace = 'App\Models\FluidbookCollection';
+ protected $_routeURL = 'fluidbook-collection';
+ protected $_singular = 'collection';
+ protected $_plural = 'collections';
+ protected $_oneInstance= false;
+}
--- /dev/null
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+class FluidbookIconsetCrudController extends \Cubist\Backpack\Magic\Controllers\CubistMagicController
+{
+ use \Cubist\Backpack\Magic\Operations\CreateOperation;
+ use \Cubist\Backpack\Http\Controllers\Operations\CloneEditOperation;
+ use \Cubist\Backpack\Magic\Operations\UpdateOperation;
+ use \Cubist\Backpack\Http\Controllers\Operations\BulkPublishOperation;
+ use \Backpack\CRUD\app\Http\Controllers\Operations\CloneOperation;
+ use \Backpack\CRUD\app\Http\Controllers\Operations\BulkCloneOperation;
+ use \Backpack\CRUD\app\Http\Controllers\Operations\DeleteOperation;
+ use \Backpack\CRUD\app\Http\Controllers\Operations\BulkDeleteOperation;
+ use \Cubist\Backpack\Http\Controllers\Operations\ReviseOperation;
+
+
+
+ /*
+ __('icon set')
+ __('icon sets')
+ */
+
+ protected $_modelNamespace = 'App\Models\FluidbookIconset';
+ protected $_routeURL = 'fluidbook-iconset';
+ protected $_singular = 'icon set';
+ protected $_plural = 'icon sets';
+ protected $_oneInstance= false;
+}
class FluidbookPublicationCrudController extends \Cubist\Backpack\Magic\Controllers\CubistMagicController
{
use \Cubist\Backpack\Magic\Operations\CreateOperation;
+ use \Cubist\Backpack\Http\Controllers\Operations\CloneEditOperation;
use \Cubist\Backpack\Magic\Operations\UpdateOperation;
use \Backpack\CRUD\app\Http\Controllers\Operations\CloneOperation;
use \Backpack\CRUD\app\Http\Controllers\Operations\DeleteOperation;
--- /dev/null
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Http\Controllers\Controller;
+use App\Models\FluidbookPublication;
+use Backpack\CRUD\app\Library\Widget;
+use Carbon\Carbon;
+use Chartisan\PHP\Chartisan;
+use Cubist\Matomo\Reporting;
+
+class FluidbookStatsController extends Controller
+{
+
+ private function _getReporting($fluidbook_id)
+ {
+ // Get the appropriate server / API token based on the Fluidbook ID
+ // Stats are split across different servers depending on the ID:
+ // ID < 21210 = stats3.fluidbook.com
+ // ID >= 21210 (even numbers) = stats4.fluidbook.com
+ // ID >= 21211 (odd numbers) = stats5.fluidbook.com
+
+ // Each stats server has a different instance of Matamo, so we need to provide different API tokens for each
+ // Normally this information would be stored in the .env but there's no good way to do that with an array, so
+ // it is simpler to keep it here. These are also stored in the shared Bitwarden entry for Matomo.
+ $matomo_tokens = [
+ 'stats3.fluidbook.com' => '9df722a0bd30878ddc4d737352427502',
+ 'stats4.fluidbook.com' => '3ffdbe052ae625f065573df9fa9515df',
+ 'stats5.fluidbook.com' => '85e9cc307b6e5083249949e9472a80b8',
+ ];
+
+ $fluidbook_id = intval($fluidbook_id);
+
+ if ($fluidbook_id < 21210) {
+ $server = 'stats3.fluidbook.com';
+ } elseif ($fluidbook_id >= 21210 && $fluidbook_id % 2 === 0) {
+ $server = 'stats4.fluidbook.com';
+ } else {
+ $server = 'stats5.fluidbook.com';
+ }
+
+ //dump("Server is $server");
+
+ return new Reporting("https://{$server}/", $matomo_tokens[$server]);
+ }
+
+
+ private function _parseDate($date) {
+ // Match possible date strings:
+ // - YYYY
+ // - YYYY-MM
+ // - YYYY-MM-DD
+ // - YYYY-MM-DD,YYYY-MM-DD
+ // https://regex101.com/r/BLrqm0/1
+ $regex = '/^(?<start_date>(?<start_year>2\d{3})-?(?<start_month>0[1-9]|1[012])?-?(?<start_day>0[1-9]|[12][0-9]|3[01])?),?(?<end_date>2\d{3}-(?>0[1-9]|1[012])-(?>0[1-9]|[12][0-9]|3[01]))?/';
+
+ preg_match($regex, $date, $date_matches);
+
+ return $date_matches;
+ }
+
+ protected function summary($fluidbook_id, $date = null)
+ {
+ $dates = $date ? $this->_parseDate($date) : false;
+
+ $fluidbook = FluidbookPublication::findOrFail($fluidbook_id);
+
+
+ // TODO: year(s)? view like the old version: https://workshop.fluidbook.com/stats/10003_ab3cacc39ebbf2478e0931629d114e74
+ // Need to calculate all the available dates, probably based on creation date of the Fluidbook
+
+ // TODO: month view, breakdown of individual day stats: https://workshop.fluidbook.com/stats/10003_ab3cacc39ebbf2478e0931629d114e74/2017/10
+ // These would be linked from the "Year(s)" view above...
+
+ // Matomo API
+ // We need to pass it a date (eg. "2022-01-01") or date range (eg. "2022-03-01,2022-05-15")
+ // We can then specify the granularity of stats by specifying the period:
+ // - range = aggregated summary of stats for the specified date range
+ // - year = summary of stats broken down by year(s)
+ // - month = summary of stats broken down by month(s)
+ // - day = summary of stats broken down by day(s)
+
+ // Which mode are we in?
+ if (isset($dates['start_date']) && isset($dates['end_date'])) { // Custom date range
+ $mode = 'range';
+ $date_range = "{$dates['start_date']},{$dates['end_date']}";
+ $period = 'day'; // Segregate stats by day
+
+ } elseif (isset($dates['start_year']) && isset($dates['start_month'])) { // Month view
+ $mode = 'month';
+ $month = $dates['start_month'];
+ $year = $dates['start_year'];
+ $last_day_of_month = cal_days_in_month(CAL_GREGORIAN, $month, $year);
+ $date_range = "{$year}-{$month}-01,{$year}-{$month}-{$last_day_of_month}";
+ $period = 'day'; // Segregate stats by day
+
+ } elseif (isset($dates['start_year'])) {
+ $mode = 'year';
+ $year = $dates['start_year'];
+ $end_date = $year == date('Y') ? date('Y-m-d') : "{$year}-12-31"; // If it's the current year, don't get future dates
+ $date_range = "{$year}-01-01,{$end_date}"; // Full range of specified year
+ $period = 'month'; // Segregate stats by month
+
+ } else { // No valid dates specified, display the full data set
+ $mode = 'overview';
+ $start_date = $fluidbook->created_at->isoFormat('YYYY-MM-DD');
+ $date_range = $start_date . ',' . date('Y-m-d');
+ $period = 'month'; // Segregate stats by month
+ }
+
+ // TODO: support the ability to specify a date range from a date-picker and also maybe choose the breakdown (by year/month/day/range)
+
+ $report = $this->_getReporting($fluidbook_id);
+
+ echo "Getting stats for date range $date_range, segregated by $period";
+ //dump(collect($report->getVisits($fluidbook_id, $date_range, $period))->sum('nb_visits'));
+ //dump($report->getVisits($fluidbook_id, $date_range, 'range'));
+ // dd($report->getVisits($fluidbook_id, $date_range, $period));
+ $visits = collect($report->getVisits($fluidbook_id, $date_range, $period));
+ $pageviews = collect($report->getPageViews($fluidbook_id, $date_range, $period));
+
+ // Get the search keywords as a range because we don't need to display them by date
+ $searches = collect($report->getSearchKeywords($fluidbook_id, $date_range, 'range'));
+
+ // Format dates for display as labels on the x-axis
+ $labels = $visits->keys()->map(function($label, $index) use ($period) {
+ return match ($period) {
+ 'day' => Carbon::parse($label)->isoFormat('DD'), // Convert YYYY-MM-DD string from API into zero-padded day alone
+ 'month' => Carbon::parse($label)->isoFormat('MMM'), // Convert to abbreviated month name
+ default => $label,
+ };
+ })->toArray();
+
+ // Format dates for display in the tooltip title
+ $formatted_dates = $visits->keys()->map(function($label, $index) use ($period) {
+ return match ($period) {
+ 'day' => Carbon::parse($label)->isoFormat('dddd Do MMMM YYYY'),
+ 'month' => Carbon::parse($label)->isoFormat('MMMM YYYY'),
+ default => $label,
+ };
+ })->toArray();
+
+ $chart = Chartisan::build()
+ ->labels($labels)
+ ->extra(['tooltip_labels' => array_combine($labels, $formatted_dates)])
+ ->dataset('Visits', $visits->pluck('nb_visits')->toArray())
+ ->dataset('Page Views', $pageviews->pluck('nb_pageviews')->toArray())
+ ->toJSON();
+
+ // header('Content-Type: application/json; charset=utf-8');
+ // return $chart;
+
+ return view('fluidbook_stats.summary', compact('fluidbook', 'visits', 'pageviews', 'searches', 'chart', 'mode', 'period', 'dates'));
+
+
+ }
+
+
+}
class FluidbookThemeCrudController extends \Cubist\Backpack\Magic\Controllers\CubistMagicController
{
use \Cubist\Backpack\Magic\Operations\CreateOperation;
+ use \Cubist\Backpack\Http\Controllers\Operations\CloneEditOperation;
use \Cubist\Backpack\Magic\Operations\UpdateOperation;
use \Cubist\Backpack\Http\Controllers\Operations\BulkPublishOperation;
use \Backpack\CRUD\app\Http\Controllers\Operations\CloneOperation;
--- /dev/null
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+class FluidbookTranslateCrudController extends \Cubist\Backpack\Magic\Controllers\CubistMagicController
+{
+ use \App\Http\Controllers\Admin\Operations\FluidbookTranslate\ExcelExportOperation;
+ use \App\Http\Controllers\Admin\Operations\FluidbookTranslate\ExcelImportOperation;
+ use \Cubist\Backpack\Magic\Operations\CreateOperation;
+ use \Cubist\Backpack\Http\Controllers\Operations\CloneEditOperation;
+ use \Cubist\Backpack\Magic\Operations\UpdateOperation;
+ use \Cubist\Backpack\Http\Controllers\Operations\BulkPublishOperation;
+ use \Backpack\CRUD\app\Http\Controllers\Operations\CloneOperation;
+ use \Backpack\CRUD\app\Http\Controllers\Operations\BulkCloneOperation;
+ use \Backpack\CRUD\app\Http\Controllers\Operations\DeleteOperation;
+ use \Backpack\CRUD\app\Http\Controllers\Operations\BulkDeleteOperation;
+
+
+
+ /*
+ __('traduction')
+ __('traductions')
+ */
+
+ protected $_modelNamespace = 'App\Models\FluidbookTranslate';
+ protected $_routeURL = 'fluidbook-translate';
+ protected $_singular = 'traduction';
+ protected $_plural = 'traductions';
+ protected $_oneInstance= true;
+}
class LocaleCrudController extends \Cubist\Backpack\Magic\Controllers\CubistMagicController
{
use \Cubist\Backpack\Magic\Operations\CreateOperation;
+ use \Cubist\Backpack\Http\Controllers\Operations\CloneEditOperation;
use \Cubist\Backpack\Magic\Operations\UpdateOperation;
use \Cubist\Backpack\Http\Controllers\Operations\BulkPublishOperation;
use \Backpack\CRUD\app\Http\Controllers\Operations\CloneOperation;
+++ /dev/null
-<?php
-
-namespace App\Http\Controllers\Admin\Operations;
-
-use App\Models\QuizTranslation;
-use Cubist\Util\Files\Files;
-use Cubist\Util\Zip;
-use Illuminate\Support\Facades\Route;
-use Cubist\Util\Str;
-
-trait DownloadOperation
-{
- protected function setupDownloadRoutes($segment, $routeName, $controller)
- {
- Route::match(['get'], $segment . '/{id}/download', $controller . '@download');
- }
-
- protected function setupDownloadDefaults()
- {
- $this->crud->addButtonFromView('line', 'download', 'quiz.download', 'end');
- }
-
- protected function download($id)
- {
- $compilepath = protected_path('quiz/final/' . $id);
- $entry = $this->crud->getEntry($id);
- $entry->compile($compilepath);
-
- $translation = QuizTranslation::find($entry->getAttribute('translation'));
- $fname = Str::slugCase($entry->getAttribute('client') . ' ' . $entry->getAttribute('project') . ' ' . date_format($entry->getAttribute('updated_at'), 'Ymd') . ' ' . $translation->locale . ' ' . $id) . '.zip';
- $dest = protected_path('quiz/download/' . $fname);
-
- Zip::archive($compilepath, $dest);
-
- return response(null)->header('Content-Type', 'application/zip')
- ->header('Content-Disposition', 'attachment; filename="' . $fname . '"')
- ->header('X-Sendfile', $dest);
- }
-}
+++ /dev/null
-<?php
-
-namespace App\Http\Controllers\Admin\Operations;
-
-
-use App\Models\Quiz;
-use App\Models\QuizTranslation;
-use Cubist\Util\Files\Files;
-use Illuminate\Support\Facades\Route;
-use Prologue\Alerts\Facades\Alert;
-use ZipArchive;
-
-trait ImportOperation
-{
- protected function setupImportRoutes($segment, $routeName, $controller)
- {
- Route::match(['post'], $segment . '/import', $controller . '@import');
- }
-
- protected function setupImportDefaults()
- {
- $this->crud->addButtonFromView('top', 'import', 'quiz.import', 'end');
- }
-
- protected function import()
- {
- $files = $_FILES['file']['tmp_name'];
-
- if (!count($files)) {
- Alert::warning('No file were imported')->flash();
- return;
- }
-
- $default = ['title' => '',
- 'translation' => '1',
- 'scorm' => '1',
- 'review' => 'always',
- 'instantReview' => '1',
- 'threshold' => '0',
- 'owner' => auth()->user()->id];
-
- foreach (Quiz::getColors() as $name => $color) {
- $default[$name] = $color['default'];
- }
-
- foreach (Quiz::getMessages() as $name => $message) {
- $default[$name] = '';
- }
-
-
- $j = 0;
- foreach ($files as $file) {
- $z = new ZipArchive();
- $ok = $z->open($file);
- if ($ok !== true) {
- Alert::warning('Unable to open ' . $file)->flash();
- continue;
- }
-
-
- $data = [];
- $datacontent = $z->getFromName('data.xml');
- if (!$datacontent || stripos($datacontent, '<quiz') === false) {
- Alert::warning($file . ' doesn\'t seem to be a valid quiz')->flash();
- continue;
- }
- $x = simplexml_load_string($datacontent, "SimpleXMLElement", LIBXML_NOERROR | LIBXML_ERR_NONE);
-
- // Discover translation
- $validatetrans = $x->xpath('/quiz/translations/validateAnswer');
- if ($validatetrans) {
- $validatetrans = (string)$validatetrans[0];
- $translation = QuizTranslation::where('validateAnswer', '=', $validatetrans)->first();
- if ($translation) {
- $data['translation'] = $translation->id;
- }
- }
- // Handle message from XML
- foreach (Quiz::getMessages() as $name => $message) {
- $e = $x->xpath('/quiz/' . $name);
- if (!$e) {
- continue;
- }
- $m = (string)$e[0];
- // We only define the message if different from the translation default
- if (isset($translation) && $translation->$name !== $m) {
- $data[$name] = $m;
- }
- }
-
- // Handle other attributes from XML
- $attributes = ['title', 'review', 'thresehold'];
- foreach (Quiz::getColors() as $name => $color) {
- $attributes[] = $name;
- }
- foreach (Quiz::getActions() as $name => $action) {
- $attributes[] = $name;
- }
- foreach ($attributes as $attribute => $xpath) {
- if (is_int($attribute)) {
- $attribute = $xpath;
- $xpath = '/quiz/' . $xpath;
- }
- $e = $x->xpath($xpath);
- if (!$e) {
- continue;
- }
- $data[$attribute] = (string)$e[0];
- }
-
- // Handle Questions
- $xquestions = $x->xpath('/quiz/questions/question');
- $questions = [];
- foreach ($xquestions as $xquestion) {
- $q = [
- 'type' => 'multiple',
- 'count_for_score' => true,
- 'report_label' => '',
- 'placeholder' => '',
- 'min_score' => 0,
- 'question' => (string)$xquestion->label,
- 'explaination' => (string)$xquestion->correction,
- 'multiple' => isset($xquestion['multiple']) ? (bool)$xquestion['multiple'] : false,
- 'answers' => [],
- ];
- foreach ($xquestion->answers[0]->answer as $xanswer) {
- $q['answers'][] = [
- 'answer' => (string)$xanswer,
- 'correct' => isset($xanswer['correct']) ? (bool)$xanswer['correct'] : false,
- ];
- }
- $questions[] = $q;
-
- }
- $data['questions'] = $questions;
- $data = array_merge($default, $data);
-
- /** @var Quiz $q */
- $q = new Quiz();
- $q = $q->create($data);
-
- $temp = Files::tmpdir();
- $assets = ['logo' => 'logo.png', 'banner' => 'banner.jpg'];
- foreach ($assets as $field => $asset) {
- $f = $temp . '/' . $asset;
- $c = $z->getFromName('assets/' . $asset);
- if ($c) {
- file_put_contents($f, $c);
- $q->addMediaToField($field, $f);
- }
- }
- $z->close();
- $j++;
- }
-
-
- if ($j === 0) {
- Alert::warning('No quiz were imported')->flash();
- } else {
- Alert::success('<b>' . $j . ' quiz(zes)</b> were imported')->flash();
- }
- return redirect($this->crud->route);
- }
-}
+++ /dev/null
-<?php
-
-
-namespace App\Http\Controllers\Admin\Operations;
-
-use App\Http\Middleware\Authenticate;
-use App\Http\Middleware\CheckIfAdmin;
-use App\Http\Middleware\VerifyCsrfToken;
-use App\Models\QuizAttempt;
-use DateTime;
-use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Route;
-
-trait LogOperation
-{
- protected function setupLogRoutes($segment, $routeName, $controller)
- {
- Route::match(['post','get'], $segment . '/{id}/log', $controller . '@log')->withoutMiddleware([VerifyCsrfToken::class, Authenticate::class, CheckIfAdmin::class]);
- }
-
- protected function log($id)
- {
- $request = request();
-
- $log = new QuizAttempt();
- $log->quiz = $id;
- $log->score = $request->get('score');
- $log->passed = $request->get('passed') !== 'false' ? '1' : '0';
- $log->answers = json_encode($request->get('questions'));
- $log->save();
-
- return response()->json(['ok' => true])->header('Access-Control-Allow-Origin', '*');
- }
-}
+++ /dev/null
-<?php
-
-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/{path?}', $controller . '@preview')
- ->where(['id' => '[0-9]+', 'path' => '.*']);
- }
-
- protected function setupPreviewDefaults()
- {
- $this->crud->addButtonFromView('line', 'open_preview', 'quiz.preview', 'begining');
- }
-
- 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);
- }
-}
--- /dev/null
+<?php
+
+namespace App\Http\Controllers\Admin\Operations\Quiz;
+
+use App\Jobs\QuizDownload;
+use App\Models\Quiz;
+use Illuminate\Support\Facades\Route;
+use Prologue\Alerts\Facades\Alert;
+
+trait DownloadOperation
+{
+ protected function setupDownloadRoutes($segment, $routeName, $controller)
+ {
+ Route::match(['get'], $segment . '/{id}/download/{action}', $controller . '@download');
+ }
+
+ protected function setupDownloadDefaults()
+ {
+
+ $this->crud->addButtonFromView('line', 'download', 'quiz.download', 'end');
+ }
+
+ protected function download($id, $action)
+ {
+ QuizDownload::dispatch(Quiz::find($id), $action, backpack_user())->onQueue('download');;
+ Alert::add('success', __('La compilation a été placée en file d\'attente. Vous recevrez un email lorsqu\'elle sera terminée.'))->flash();
+ return redirect(backpack_url('quiz'));
+ }
+}
--- /dev/null
+<?php
+
+namespace App\Http\Controllers\Admin\Operations\Quiz;
+
+
+use App\Models\Quiz;
+use App\Models\QuizTranslation;
+use Cubist\Util\Files\Files;
+use Illuminate\Support\Facades\Route;
+use Prologue\Alerts\Facades\Alert;
+use ZipArchive;
+
+trait ImportOperation
+{
+ protected function setupImportRoutes($segment, $routeName, $controller)
+ {
+ Route::match(['post'], $segment . '/import', $controller . '@import');
+ }
+
+ protected function setupImportDefaults()
+ {
+ $this->crud->addButtonFromView('top', 'import', 'quiz.import', 'end');
+ }
+
+ protected function import()
+ {
+ $files = $_FILES['file']['tmp_name'];
+
+ if (!count($files)) {
+ Alert::warning('No file were imported')->flash();
+ return;
+ }
+
+ $default = ['title' => '',
+ 'translation' => '1',
+ 'scorm' => '1',
+ 'review' => 'always',
+ 'instantReview' => '1',
+ 'threshold' => '0',
+ 'owner' => auth()->user()->id];
+
+ foreach (Quiz::getColors() as $name => $color) {
+ $default[$name] = $color['default'];
+ }
+
+ foreach (Quiz::getMessages() as $name => $message) {
+ $default[$name] = '';
+ }
+
+
+ $j = 0;
+ foreach ($files as $file) {
+ $z = new ZipArchive();
+ $ok = $z->open($file);
+ if ($ok !== true) {
+ Alert::warning('Unable to open ' . $file)->flash();
+ continue;
+ }
+
+
+ $data = [];
+ $datacontent = $z->getFromName('data.xml');
+ if (!$datacontent || stripos($datacontent, '<quiz') === false) {
+ Alert::warning($file . ' doesn\'t seem to be a valid quiz')->flash();
+ continue;
+ }
+ $x = simplexml_load_string($datacontent, "SimpleXMLElement", LIBXML_NOERROR | LIBXML_ERR_NONE);
+
+ // Discover translation
+ $validatetrans = $x->xpath('/quiz/translations/validateAnswer');
+ if ($validatetrans) {
+ $validatetrans = (string)$validatetrans[0];
+ $translation = QuizTranslation::where('validateAnswer', '=', $validatetrans)->first();
+ if ($translation) {
+ $data['translation'] = $translation->id;
+ }
+ }
+ // Handle message from XML
+ foreach (Quiz::getMessages() as $name => $message) {
+ $e = $x->xpath('/quiz/' . $name);
+ if (!$e) {
+ continue;
+ }
+ $m = (string)$e[0];
+ // We only define the message if different from the translation default
+ if (isset($translation) && $translation->$name !== $m) {
+ $data[$name] = $m;
+ }
+ }
+
+ // Handle other attributes from XML
+ $attributes = ['title', 'review', 'thresehold'];
+ foreach (Quiz::getColors() as $name => $color) {
+ $attributes[] = $name;
+ }
+ foreach (Quiz::getActions() as $name => $action) {
+ $attributes[] = $name;
+ }
+ foreach ($attributes as $attribute => $xpath) {
+ if (is_int($attribute)) {
+ $attribute = $xpath;
+ $xpath = '/quiz/' . $xpath;
+ }
+ $e = $x->xpath($xpath);
+ if (!$e) {
+ continue;
+ }
+ $data[$attribute] = (string)$e[0];
+ }
+
+ // Handle Questions
+ $xquestions = $x->xpath('/quiz/questions/question');
+ $questions = [];
+ foreach ($xquestions as $xquestion) {
+ $q = [
+ 'type' => 'multiple',
+ 'count_for_score' => true,
+ 'report_label' => '',
+ 'placeholder' => '',
+ 'min_score' => 0,
+ 'question' => (string)$xquestion->label,
+ 'explaination' => (string)$xquestion->correction,
+ 'multiple' => isset($xquestion['multiple']) ? (bool)$xquestion['multiple'] : false,
+ 'answers' => [],
+ ];
+ foreach ($xquestion->answers[0]->answer as $xanswer) {
+ $q['answers'][] = [
+ 'answer' => (string)$xanswer,
+ 'correct' => isset($xanswer['correct']) ? (bool)$xanswer['correct'] : false,
+ ];
+ }
+ $questions[] = $q;
+
+ }
+ $data['questions'] = $questions;
+ $data = array_merge($default, $data);
+
+ /** @var Quiz $q */
+ $q = new Quiz();
+ $q = $q->create($data);
+
+ $temp = Files::tmpdir();
+ $assets = ['logo' => 'logo.png', 'banner' => 'banner.jpg'];
+ foreach ($assets as $field => $asset) {
+ $f = $temp . '/' . $asset;
+ $c = $z->getFromName('assets/' . $asset);
+ if ($c) {
+ file_put_contents($f, $c);
+ $q->addMediaToField($field, $f);
+ }
+ }
+ $z->close();
+ $j++;
+ }
+
+
+ if ($j === 0) {
+ Alert::warning('No quiz were imported')->flash();
+ } else {
+ Alert::success('<b>' . $j . ' quiz(zes)</b> were imported')->flash();
+ }
+ return redirect($this->crud->route);
+ }
+}
--- /dev/null
+<?php
+
+
+namespace App\Http\Controllers\Admin\Operations\Quiz;
+
+use App\Http\Middleware\Authenticate;
+use App\Http\Middleware\CheckIfAdmin;
+use App\Http\Middleware\VerifyCsrfToken;
+use App\Models\QuizAttempt;
+use Illuminate\Support\Facades\Route;
+
+trait LogOperation
+{
+ protected function setupLogRoutes($segment, $routeName, $controller)
+ {
+ Route::match(['post','get'], $segment . '/{id}/log', $controller . '@log')->withoutMiddleware([VerifyCsrfToken::class, Authenticate::class, CheckIfAdmin::class]);
+ }
+
+ protected function log($id)
+ {
+ $request = request();
+
+ $log = new QuizAttempt();
+ $log->quiz = $id;
+ $log->score = $request->get('score');
+ $log->passed = $request->get('passed') !== 'false' ? '1' : '0';
+ $log->answers = json_encode($request->get('questions'));
+ $log->save();
+
+ return response()->json(['ok' => true])->header('Access-Control-Allow-Origin', '*');
+ }
+}
--- /dev/null
+<?php
+
+namespace App\Http\Controllers\Admin\Operations\Quiz;
+
+use Cubist\Util\Files\Files;
+use Illuminate\Support\Facades\Route;
+
+trait PreviewOperation
+{
+ protected function setupPreviewRoutes($segment, $routeName, $controller)
+ {
+ Route::match(['get'], $segment . '/{id}/preview/{path?}', $controller . '@preview')
+ ->where(['id' => '[0-9]+', 'path' => '.*']);
+ }
+
+ protected function setupPreviewDefaults()
+ {
+ $this->crud->addButtonFromView('line', 'open_preview', 'quiz.preview', 'begining');
+ }
+
+ 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);
+ }
+}
--- /dev/null
+<?php
+
+namespace App\Http\Controllers\Admin\Operations\Quiz;
+
+use App\Models\Quiz;
+use App\Models\QuizAttempt;
+use Cubist\Util\Files\Files;
+use Illuminate\Support\Facades\Route;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+
+trait ReportOperation
+{
+ protected function setupReportRoutes($segment, $routeName, $controller)
+ {
+ Route::match(['get'], $segment . '/{id}/report', $controller . '@report');
+ }
+
+ protected function setupReportDefaults()
+ {
+ $this->crud->addButtonFromView('line', 'report', 'quiz.report', 'end');
+ }
+
+ protected function report($id)
+ {
+ $quiz = Quiz::where('id', $id)->first()->getPageData();
+ $first = ['#', 'Date'];
+ if ($quiz->display_score) {
+ $first = array_merge($first, ['Score', 'Passed']);
+ }
+
+ $emailQuestion = false;
+ $countForScore = [];
+
+ foreach ($quiz->questions as $q => $question) {
+ $label = $question['report_label'] ?: $question['question'];
+ $first[] = $label;
+ if ($quiz->display_score && $question['count_for_score']) {
+ $first[] = $label . ' status';
+ $countForScore[] = $q;
+ }
+ if ($question['type'] === 'email') {
+ $emailQuestion = $q;
+ }
+ }
+
+ $attemptsList = [$first];
+ $users = [];
+ /** @var QuizAttempt[] $attempts */
+ $attempts = QuizAttempt::where('quiz', $id)->orderBy('created_at', 'ASC')->get();
+ foreach ($attempts as $attempt) {
+ $email = $attempt->id;
+ $data = $attempt->getPageData();
+ $a = [$data->get('id'), $data->get('created_at')];
+ if ($quiz->display_score) {
+ $a[] = $data->get('score');
+ $a[] = ($data->get('passed') ? '1' : '0');
+ }
+ $answers = $data->get('answers', []);
+
+ if (null === $answers || !is_array($answers)) {
+ continue;
+ }
+ foreach ($answers as $aid => $answer) {
+ if (null === $answer) {
+ continue;
+ }
+ $aa = $answer['anwser'] ?? $answer['answer'] ?? '';
+ if ($emailQuestion !== false) {
+ if ($aid == $emailQuestion + 1) {
+ $email = trim(mb_strtolower($aa));
+ }
+ }
+ $a[] = is_array($aa) ? implode(', ', $aa) : $aa;
+ if ($quiz->display_score && in_array($aid - 1, $countForScore, true)) {
+ $a[] = $answer['score'];
+ }
+ }
+
+ if ($emailQuestion !== false) {
+ if (!isset($users[$email])) {
+ $users[$email] = ['totalAttempts' => 0, 'attemptsBeforePassing' => 0, 'passed' => false, 'worstScore' => 100, 'bestScore' => 0];
+ }
+ $users[$email]['totalAttempts']++;
+ $users[$email]['worstScore'] = min($users[$email]['worstScore'], $data->get('score'));
+ $users[$email]['bestScore'] = max($users[$email]['bestScore'], $data->get('score'));
+ if (!$users[$email]['passed']) {
+ $users[$email]['attemptsBeforePassing']++;
+ }
+ if ($data->get('passed')) {
+ $users[$email]['passed'] = true;
+ }
+ }
+
+ $attemptsList[] = $a;
+ }
+
+
+ $spreadsheet = new Spreadsheet();
+ $sheet = $spreadsheet->getActiveSheet();
+
+ if ($emailQuestion !== false) {
+ $usersList = [['Email', 'Passed', 'Attempts before passed', 'Total attempts', 'Best score', 'Worst score']];
+ foreach ($users as $email => $user) {
+ $usersList[] = [$email, $user['passed'] ? '1' : '0', $user['attemptsBeforePassing'], $user['totalAttempts'], $user['bestScore'], $user['worstScore']];
+ }
+
+ $sheet->setTitle('USERS');
+ $sheet->fromArray($usersList);
+ foreach (range('A', 'Z') as $columnID) {
+ $sheet->getColumnDimension($columnID)->setAutoSize(true);
+ }
+ $sheet = $spreadsheet->createSheet();
+ }
+
+ array_reverse($attemptsList);
+ $sheet->setTitle('DATA');
+ $sheet->fromArray($attemptsList);
+ foreach (range('A', 'Z') as $columnID) {
+ $sheet->getColumnDimension($columnID)->setAutoSize(true);
+ }
+
+ $writer = new Xlsx($spreadsheet);
+ $tmp = Files::tempnam();
+ $writer->save($tmp);
+
+ return response()->download($tmp, 'report_' . $id . '_' . date('YmdHi') . '.xlsx')->deleteFileAfterSend();
+ }
+}
+++ /dev/null
-<?php
-
-namespace App\Http\Controllers\Admin\Operations;
-
-use App\Models\Quiz;
-use App\Models\QuizAttempt;
-use Cubist\Util\Files\Files;
-use Illuminate\Support\Facades\Route;
-use PhpOffice\PhpSpreadsheet\Spreadsheet;
-use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
-
-trait ReportOperation
-{
- protected function setupReportRoutes($segment, $routeName, $controller)
- {
- Route::match(['get'], $segment . '/{id}/report', $controller . '@report');
- }
-
- protected function setupReportDefaults()
- {
- $this->crud->addButtonFromView('line', 'report', 'quiz.report', 'end');
- }
-
- protected function report($id)
- {
- $quiz = Quiz::where('id', $id)->first()->getPageData();
- $first = ['#', 'Date'];
- if ($quiz->display_score) {
- $first = array_merge($first, ['Score', 'Passed']);
- }
-
- $emailQuestion = false;
- $countForScore = [];
-
- foreach ($quiz->questions as $q => $question) {
- $label = $question['report_label'] ?: $question['question'];
- $first[] = $label;
- if ($quiz->display_score && $question['count_for_score']) {
- $first[] = $label . ' status';
- $countForScore[] = $q;
- }
- if ($question['type'] === 'email') {
- $emailQuestion = $q;
- }
- }
-
- $attemptsList = [$first];
- $users = [];
- /** @var QuizAttempt[] $attempts */
- $attempts = QuizAttempt::where('quiz', $id)->orderBy('created_at', 'ASC')->get();
- foreach ($attempts as $attempt) {
- $email = $attempt->id;
- $data = $attempt->getPageData();
- $a = [$data->get('id'), $data->get('created_at')];
- if ($quiz->display_score) {
- $a[] = $data->get('score');
- $a[] = ($data->get('passed') ? '1' : '0');
- }
- $answers = $data->get('answers', []);
-
- if (null === $answers || !is_array($answers)) {
- continue;
- }
- foreach ($answers as $aid => $answer) {
- if (null === $answer) {
- continue;
- }
- $aa = $answer['anwser'] ?? $answer['answer'] ?? '';
- if ($emailQuestion !== false) {
- if ($aid == $emailQuestion + 1) {
- $email = trim(mb_strtolower($aa));
- }
- }
- $a[] = is_array($aa) ? implode(', ', $aa) : $aa;
- if ($quiz->display_score && in_array($aid - 1, $countForScore, true)) {
- $a[] = $answer['score'];
- }
- }
-
- if ($emailQuestion !== false) {
- if (!isset($users[$email])) {
- $users[$email] = ['totalAttempts' => 0, 'attemptsBeforePassing' => 0, 'passed' => false, 'worstScore' => 100, 'bestScore' => 0];
- }
- $users[$email]['totalAttempts']++;
- $users[$email]['worstScore'] = min($users[$email]['worstScore'], $data->get('score'));
- $users[$email]['bestScore'] = max($users[$email]['bestScore'], $data->get('score'));
- if (!$users[$email]['passed']) {
- $users[$email]['attemptsBeforePassing']++;
- }
- if ($data->get('passed')) {
- $users[$email]['passed'] = true;
- }
- }
-
- $attemptsList[] = $a;
- }
-
-
- $spreadsheet = new Spreadsheet();
- $sheet = $spreadsheet->getActiveSheet();
-
- if ($emailQuestion !== false) {
- $usersList = [['Email', 'Passed', 'Attempts before passed', 'Total attempts', 'Best score', 'Worst score']];
- foreach ($users as $email => $user) {
- $usersList[] = [$email, $user['passed'] ? '1' : '0', $user['attemptsBeforePassing'], $user['totalAttempts'], $user['bestScore'], $user['worstScore']];
- }
-
- $sheet->setTitle('USERS');
- $sheet->fromArray($usersList);
- foreach (range('A', 'Z') as $columnID) {
- $sheet->getColumnDimension($columnID)->setAutoSize(true);
- }
- $sheet = $spreadsheet->createSheet();
- }
-
- array_reverse($attemptsList);
- $sheet->setTitle('DATA');
- $sheet->fromArray($attemptsList);
- foreach (range('A', 'Z') as $columnID) {
- $sheet->getColumnDimension($columnID)->setAutoSize(true);
- }
-
- $writer = new Xlsx($spreadsheet);
- $tmp = Files::tempnam();
- $writer->save($tmp);
-
- return response()->download($tmp, 'report_' . $id . '_' . date('YmdHi') . '.xlsx')->deleteFileAfterSend();
- }
-}
--- /dev/null
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+class PageCrudController extends \Cubist\Backpack\Magic\Controllers\CubistMagicNestedController
+{
+ use \Cubist\Backpack\Magic\Operations\CreateOperation;
+ use \Cubist\Backpack\Http\Controllers\Operations\CloneEditOperation;
+ use \Cubist\Backpack\Magic\Operations\UpdateOperation;
+ use \Cubist\Backpack\Http\Controllers\Operations\BulkPublishOperation;
+ use \Backpack\CRUD\app\Http\Controllers\Operations\CloneOperation;
+ use \Backpack\CRUD\app\Http\Controllers\Operations\BulkCloneOperation;
+ use \Backpack\CRUD\app\Http\Controllers\Operations\DeleteOperation;
+ use \Backpack\CRUD\app\Http\Controllers\Operations\BulkDeleteOperation;
+ use \Cubist\Backpack\Http\Controllers\Operations\ReviseOperation;
+
+
+
+ /*
+ __('page')
+ __('pages')
+ */
+
+ protected $_modelNamespace = 'App\Models\Page';
+ protected $_routeURL = 'page';
+ protected $_singular = 'page';
+ protected $_plural = 'pages';
+ protected $_oneInstance= false;
+}
namespace App\Http\Controllers\Admin;
-class QuizCrudController extends \App\Http\Controllers\Admin\Base\QuizController
+class QuizCrudController extends \Cubist\Backpack\Magic\Controllers\CubistMagicController
{
- use \Cubist\Backpack\Magic\Operations\CreateOperation;
+ use \App\Http\Controllers\Admin\Operations\Quiz\PreviewOperation;
+ use \App\Http\Controllers\Admin\Operations\Quiz\DownloadOperation;
+ use \App\Http\Controllers\Admin\Operations\Quiz\LogOperation;
+ use \App\Http\Controllers\Admin\Operations\Quiz\ReportOperation;
+ use \App\Http\Controllers\Admin\Operations\Quiz\ImportOperation;
+ use \Cubist\Backpack\Magic\Operations\CreateOperation;
+ use \Cubist\Backpack\Http\Controllers\Operations\CloneEditOperation;
use \Cubist\Backpack\Magic\Operations\UpdateOperation;
use \Backpack\CRUD\app\Http\Controllers\Operations\CloneOperation;
use \Backpack\CRUD\app\Http\Controllers\Operations\DeleteOperation;
class QuizatttemptCrudController extends \Cubist\Backpack\Magic\Controllers\CubistMagicController
{
use \Cubist\Backpack\Magic\Operations\CreateOperation;
+ use \Cubist\Backpack\Http\Controllers\Operations\CloneEditOperation;
use \Cubist\Backpack\Magic\Operations\UpdateOperation;
use \Cubist\Backpack\Http\Controllers\Operations\BulkPublishOperation;
use \Backpack\CRUD\app\Http\Controllers\Operations\CloneOperation;
class QuiztranslationCrudController extends \Cubist\Backpack\Magic\Controllers\CubistMagicController
{
use \Cubist\Backpack\Magic\Operations\CreateOperation;
+ use \Cubist\Backpack\Http\Controllers\Operations\CloneEditOperation;
use \Cubist\Backpack\Magic\Operations\UpdateOperation;
use \Cubist\Backpack\Http\Controllers\Operations\BulkPublishOperation;
use \Backpack\CRUD\app\Http\Controllers\Operations\CloneOperation;
class SettingsCrudController extends \Cubist\Backpack\Magic\Controllers\CubistMagicController
{
use \Cubist\Backpack\Magic\Operations\CreateOperation;
+ use \Cubist\Backpack\Http\Controllers\Operations\CloneEditOperation;
use \Cubist\Backpack\Magic\Operations\UpdateOperation;
use \Cubist\Backpack\Http\Controllers\Operations\BulkPublishOperation;
use \Backpack\CRUD\app\Http\Controllers\Operations\CloneOperation;
class SignatureCrudController extends \Cubist\Backpack\Magic\Controllers\CubistMagicController
{
use \Cubist\Backpack\Magic\Operations\CreateOperation;
+ use \Cubist\Backpack\Http\Controllers\Operations\CloneEditOperation;
use \Cubist\Backpack\Magic\Operations\UpdateOperation;
use \Backpack\CRUD\app\Http\Controllers\Operations\CloneOperation;
use \Backpack\CRUD\app\Http\Controllers\Operations\DeleteOperation;
class ToolboxTranslateCrudController extends \Cubist\Backpack\Magic\Controllers\CubistMagicController
{
use \Cubist\Backpack\Magic\Operations\CreateOperation;
+ use \Cubist\Backpack\Http\Controllers\Operations\CloneEditOperation;
use \Cubist\Backpack\Magic\Operations\UpdateOperation;
use \Cubist\Backpack\Http\Controllers\Operations\BulkPublishOperation;
use \Backpack\CRUD\app\Http\Controllers\Operations\CloneOperation;
use \Cubedesigners\UserDatabase\Operations\LoginasOperation;
use \Cubedesigners\UserDatabase\Operations\CreateFromCompany;
use \Cubist\Backpack\Magic\Operations\CreateOperation;
+ use \Cubist\Backpack\Http\Controllers\Operations\CloneEditOperation;
use \Cubist\Backpack\Magic\Operations\UpdateOperation;
use \Cubist\Backpack\Http\Controllers\Operations\ReviseOperation;
--- /dev/null
+<?php
+
+namespace App\Jobs;
+
+use App\Services\ScormCloud;
+use Cubist\Util\Files\Files;
+use Cubist\Util\Zip;
+
+class QuizDownload extends DownloadBase
+{
+
+ protected $type = 'quiz';
+
+ public function handle()
+ {
+ try {
+ $compilepath = $this->entry->getFinalPath();
+ $this->entry->compile($compilepath, $this->user);
+
+ $fname = $this->_fname();
+ $dest = Files::mkdir(storage_path('app/public/quiz/download/')) . $fname;
+
+ Zip::archive($compilepath, $dest);
+ if (!file_exists($dest)) {
+ throw new \Exception('An error occured while compiling the quiz');
+ }
+
+ $url = $this->_url($fname);
+
+ $subject = __('Quiz ":title" (#:nb) prêt au téléchargement', ['title' => $this->entry->title, 'nb' => $this->entry->id]);
+ $body = __('Le fichier est disponible à l\'adresse suivante : <a href=":url">:url</a>', ['url' => $url]);
+
+ try {
+ if ($this->action === 'scormcloud') {
+ $scormURL = ScormCloud::send($url, 'toolbox_' . $this->type . '_' . $this->entry->id);
+ $body .= "<br><br>";
+ $body .= __('Le package peut être testé sur SCORM Cloud : <a href=":url">:url</a>', ['url' => $scormURL]);
+ }
+ } catch (\Exception $e) {
+ $body .= "<br><br>";
+ $body .= __('Une erreur s\'est produite lors de l\'envoi sur SCORM Cloud (App ID :appid) : :error', ['error' => $e->getMessage(), 'appid' => env('SCORM_CLOUD_APP_ID')]);
+ }
+
+
+ } catch (\Exception $e) {
+ $subject = __('Erreur lors de la compilation du quiz :nb', ['nb' => $this->entry->id]);
+ $body = __('Détails de l\'erreur :message', ['message' => $e->getMessage() . ' at line ' . $e->getLine() . ' of ' . $e->getFile()]);
+ }
+
+ $this->sendEmail($subject, $body);
+ }
+
+
+}
use Cubist\Backpack\Magic\Fields\Text;
use Cubist\Backpack\Magic\Fields\Textarea;
use Cubist\Scorm\Manifest;
+use Cubist\Scorm\Version;
use Cubist\Util\Files\Files;
use Cubist\Util\Files\VirtualDirectory;
use Cubist\Util\Zip;
$data = ['title' => $this->title, 'description' => $this->description, 'modules' => $modules];
$vdir->file_put_contents('data.js', 'const DATA=' . json_encode($data) . ';');
- $vdir->file_put_contents('imsmanifest.xml', new Manifest($this->title, Manifest::SCORM_2004, $organization, 'PACKAGE_' . $this->id));
+ $vdir->file_put_contents('imsmanifest.xml', new Manifest($this->title, Version::SCORM_2004, $organization, 'PACKAGE_' . $this->id));
$vdir->copyDirectory(resource_path('elearningpackage/dist/css'), 'css');
$vdir->copyDirectory(resource_path('elearningpackage/dist/js'), 'js');
$vdir->copyDirectory(resource_path('elearningpackage/dist/fonts'), 'fonts');
<?php
-
namespace App\Models;
+use App\Http\Controllers\Admin\Operations\Quiz\DownloadOperation;
+use App\Http\Controllers\Admin\Operations\Quiz\ImportOperation;
+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 Cubist\Scorm\Manifest;
+use Cubist\Util\Files\VirtualDirectory;
use App\Fields\SCORMVersion;
use App\Http\Controllers\Admin\Base\QuizController;
use App\Models\Base\ToolboxModel;
use App\Models\Traits\SCORMVersionTrait;
-use Cubist\Scorm\Manifest;
-use Cubist\Util\Files\Files;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
use Spatie\Image\Manipulations;
class Quiz extends ToolboxModel
{
- protected $_baseController = QuizController::class;
protected $table = 'quiz';
public $registerMediaConversionsUsingModelInstance = false;
+ protected $_operations = [PreviewOperation::class, DownloadOperation::class, LogOperation::class, ReportOperation::class, ImportOperation::class];
+
use SCORMVersionTrait;
protected static function _getColors()
/**
* @param $quiz self
+ * @throws \JsonException
*/
- public function compile($dest, $forceScorm = false)
+ public function compile($dest, $forceScorm = false, $user = null)
{
- // Clean existing dir and recreate it
- if (file_exists($dest)) {
- Files::rmdir($dest);
- }
- Files::mkdir($dest);
+ /*
+
+ $defaultModuleContent = ['content_title' => '', 'complete_when_opened' => false, 'mandatory' => true,
+ 'quiz_id' => '', 'fb_id' => '', 'audio_id' => '', 'pdf_id' => '', 'video_id' => ''];
+
+
+
+ $modules = [];
+
+ foreach ($this->contents as $id => $content) {
+ $m = $this->_compileModule($id, array_merge($defaultModuleContent, $content), $vdir, $user);
+ if ($m !== false) {
+ $modules[] = $m;
+ }
+ }
+
+ $data = ['title' => $this->title, 'description' => $this->description, 'modules' => $modules];
+
+ $vdir->file_put_contents('data.js', 'const DATA=' . json_encode($data) . ';');
+ $vdir->file_put_contents('imsmanifest.xml', new Manifest($this->title, Version::SCORM_2004, $organization, 'PACKAGE_' . $this->id));
+ $vdir->copyDirectory(resource_path('elearningpackage/dist/css'), 'css');
+ $vdir->copyDirectory(resource_path('elearningpackage/dist/js'), 'js');
+ $vdir->copyDirectory(resource_path('elearningpackage/dist/fonts'), 'fonts');
+ $vdir->copy(resource_path('elearningpackage/index.html'), 'index.html');
+ $vdir->sync(true);*/
- // Extract template into the final dir
- $from = resource_path('quiz') . '/';
- `rsync -a --exclude '*.less' --exclude '*.less' $from $dest/`;
+ $wdir = resource_path('quiz');
+ $vdir = new VirtualDirectory($dest);
+
+
+ $data = $this->getData();
+ $json = json_encode($data, JSON_THROW_ON_ERROR);
+ $vdir->file_put_contents('data.js', 'var DATA=' . $json . ';');
+ $vdir->copy($wdir . '/index.html', 'index.html');
+ $vdir->copyDirectory($wdir . '/dist/css', 'css');
+ $vdir->copyDirectory($wdir . '/dist/js', 'js');
+ $vdir->copyDirectory($wdir . '/fonts', 'fonts');
+ $vdir->copyDirectory($wdir . '/assets', 'assets');
// Copy assets
$assets = ['banner' => 'banner.jpg', 'logo' => 'logo.png'];
$path = $media->getPath($conversionName);
if (file_exists($path)) {
- copy($path, $dest . '/assets/' . $filename);
+ $vdir->copy($path, 'assets/' . $filename);
}
}
}
}
- $data = $this->getData();
-
- $json = json_encode($data, JSON_THROW_ON_ERROR);
-
- file_put_contents($dest . '/data.js', 'var DATA=' . $json . ';');
if ($forceScorm || $this->getAttribute('scorm')) {
- $scorm_js = '<script src="js/libs/scorm/apiwrapper.js"></script>
-<script src="js/scorm.js"></script>';
$manifest = new Manifest($this->getAttribute('title'), $this->getAttribute('scorm_version'), $this->getAttribute('client') ?: backpack_user()->getCompanyNameAttribute(), $this->getAttribute('project') ?: 'Quiz');
- file_put_contents($dest . '/imsmanifest.xml', $manifest);
- } else {
- $scorm_js = '';
- unlink($dest . '/js/scorm.js');
- unlink($dest . '/js/libs/scorm/apiwrapper.js');
+ $vdir->file_put_contents('imsmanifest.xml', $manifest);
}
-
- file_put_contents($dest . '/index.html', str_replace('$SCORM_JS', $scorm_js, file_get_contents($dest . '/index.html')));
+ $vdir->sync(true);
}
public function getData()
return parent::create($data);
}
+ public function getFinalPath()
+ {
+ return protected_path('quiz/final/' . $this->id);
+ }
+
}
require('./bootstrap');
-import {SCORM} from 'pipwerks-scorm-api-wrapper';
+require('../../scorm/scorm');
window.savedState = {};
window.currentModule = null;
-var SCORM_INITED = false;
-var SCORM_START_TIME = null;
-var SCORM_INTERACTION_TIMESTAMPS = [];
-var SCORM_CORRECT_ANSWERS = [];
-var SCORM_ID_TO_N = {};
-var SCORM_WEIGHTING = 0;
-var SCORM_QUESTIONS = [];
-var SCORM_SUCCESS_STATUS = 'unknown';
-var SCORM_SUCCESS_SCORE = 0;
-var SCORM_EVENTS_INITED = false;
-var SCORM_INTERACTIONS_INITED = false;
-var SCORM_LOCATION_INITED = false;
-var SCORM_OK = false;
-
-var _CMI12 = {
- 'location': 'cmi.core.lesson_location',
- 'status': "cmi.core.lesson_status",
- 'session_time': 'cmi.core.session_time',
- 'success_status': '',
- 'exit': 'cmi.core.exit',
- 'cmi.score.raw': 'cmi.core.score.raw',
- 'cmi.score.min': 'cmi.core.score.min',
- 'cmi.score.max': 'cmi.core.score.max',
- 'cmi.score.scaled': '',
-};
-
-var _CMI2004 = {
- 'location': 'cmi.location',
- 'status': 'cmi.completion_status',
- 'session_time': 'cmi.session_time',
- 'success_status': 'cmi.success_status',
- 'exit': 'cmi.exit',
-};
-
-
$(function () {
$("header #logo").html(getSpriteIcon('logo'));
$("header #tile").html(getSpriteIcon('tile'));
- initScorm();
+ initPackage();
$(window).on('resize', function () {
resize();
});
}
}
-function _cmi(key) {
- var res = null;
- switch (SCORM.version) {
- case "1.2" :
- res = _CMI12[key];
- break;
- case '2004':
- res = _CMI2004[key];
- break;
- }
- if (res == undefined || res == null) {
- res = key;
- }
- return res;
-}
-
-function initScorm() {
+function initPackage() {
if (SCORM_INITED) {
return;
}
- SCORM_INITED = true;
- try {
- if (SCORM.init()) {
- SCORM_OK = true;
- }
- } catch (e) {
-
- }
-
- try {
- if (FORCE_SCORM) {
- SCORM_OK = true;
- }
- } catch (e) {
-
- }
-
- if (SCORM_OK) {
- scormExit();
- startScormTimer();
- initScormEvents();
- }
+ var res=initScorm();
initState();
setContents();
initEvents();
window.API = window.API_1484_11 = new SCORMFacade();
- return SCORM_OK;
+ return res;
}
function initState() {
-
if (SCORM_LOCATION_INITED) {
return;
}
}
});
}
-
-function scormMarkAsComplete() {
- if (!SCORM_OK) {
- return;
- }
- scormExit();
-}
-
-
-function finishScorm() {
- if (!SCORM_OK) {
- return;
- }
- setSessionTime();
- SCORM.save();
- SCORM.quit();
-}
-
-function scormExit() {
- if (!SCORM_OK) {
- return;
- }
- var v = 'suspend';
- setScormValue('exit', v);
-}
-
-function startScormTimer() {
- SCORM_START_TIME = new Date();
-}
-
-function scormCompleteAndClose() {
- scormComplete();
- scormClose();
-}
-
-function scormClose() {
- parent.close();
- top.close();
- window.close();
-}
-
function initEvents() {
$(document).on('click', '[data-id]:not([data-lock="locked"])', function () {
openSubSCO($(this).data('id'));
// Using `this` for web workers & supports Browserify / Webpack.
})(typeof window === 'undefined' ? this : window);
-
-function initScormEvents() {
- if (!SCORM_OK || SCORM_EVENTS_INITED) {
- return;
- }
- SCORM_EVENTS_INITED = true;
-
- $(window).on('unload', function () {
- finishScorm();
- });
-
- setInterval(function () {
- SCORM.save();
- }, 5000);
-}
-
-function setSCORMLocation(location) {
- return setScormValue('cmi.location', JSON.stringify(location));
-}
-
-function setSCORMScore(score, max, min) {
- if (min === undefined) {
- min = 0;
- }
-
- setScormValue('cmi.core.score.raw', score);
- setScormValue('cmi.core.score.min', min);
- setScormValue('cmi.core.score.max', max);
-}
-
-
-function getScormValue(elementName) {
- if (!SCORM_OK) {
- return null;
- }
- var cmi = _cmi(elementName);
- if (cmi == '') {
- return null;
- }
- var result = SCORM.get(cmi);
- return result;
-}
-
-function setScormValue(elementName, value) {
- if (!SCORM_OK) {
- return;
- }
- var cmi = _cmi(elementName);
- if (cmi == '') {
- return false;
- }
- var result = SCORM.set(cmi, value);
- return result;
-}
-
function getSpriteIcon(icon, attrs, dimensions) {
var a = [];
var iconSymbol = $('svg symbol[id="' + icon + '"]');
return '<svg ' + a.join(' ') + ' aria-hidden="true"><use xlink:href="#' + icon + '" /></svg>';
}
-
-function setSessionTime() {
- if (!SCORM_OK) {
- return;
- }
- var currentTime = new Date();
- var sessionTime;
-
- var endTime = currentTime.getTime()
- var calculatedTime = endTime - SCORM_START_TIME;
-
- if (SCORM.version == '1.2') {
- var totalHours = Math.floor(calculatedTime / 1000 / 60 / 60);
- calculatedTime = calculatedTime - totalHours * 1000 * 60 * 60
- if (totalHours < 1000 && totalHours > 99) {
- totalHours = "0" + totalHours;
- } else if (totalHours < 100 && totalHours > 9) {
- totalHours = "00" + totalHours;
- } else if (totalHours < 10) {
- totalHours = "000" + totalHours;
- }
-
- var totalMinutes = Math.floor(calculatedTime / 1000 / 60);
- calculatedTime = calculatedTime - totalMinutes * 1000 * 60;
- if (totalMinutes < 10) {
- totalMinutes = "0" + totalMinutes;
- }
-
- var totalSeconds = Math.floor(calculatedTime / 1000);
- if (totalSeconds < 10) {
- totalSeconds = "0" + totalSeconds;
- }
- sessionTime = totalHours + ":" + totalMinutes + ":" + totalSeconds;
- setScormValue('session_time', sessionTime);
- } else {
- setScormValue('session_time', scormSecondsToTimeInterval(calculatedTime / 1000));
- }
-
-}
-
-function dateToScormTime(date) {
- var res = date.toISOString();
- var e = res.split('.');
- e.pop();
- return e.join('.');
-}
-
-function getScormTimeInterval(start, end) {
- var diff = Math.round((end.getTime() - start.getTime()) / 1000);
- return scormSecondsToTimeInterval(diff);
-}
-
-function scormSecondsToTimeInterval(diff) {
- var diff = Math.round(diff);
- var h = Math.floor(diff / 3600);
- diff = diff % 3600;
- var m = Math.floor(diff / 60);
- var s = diff % 60;
- return 'PT' + h + 'H' + m + 'M' + s + 'S';
-}
-
function SCORMFacade() {
}
<meta name="width" content="1024">
<meta name="height" content="768">
<title></title>
- <link type="text/css" rel="stylesheet" href="style/style.css">
+ <link type="text/css" rel="stylesheet" href="css/style.css">
</head>
<body>
<div id="holder">
</main>
</div>
<script src="data.js"></script>
-<script src="js/polyfill/cssvars.js"></script>
-<script src="js/libs/jquery.min.js"></script>
-<script src="js/libs/jquery.scrollTo.min.js"></script>
-<script src="js/libs/gsap/TweenMax.min.js"></script>
-<script src="js/libs/gsap/jquery.gsap.min.js"></script>
-$SCORM_JS
-<script src="js/main.js"></script>
+<script src="js/app.js"></script>
</body>
</html>
-require('./bootstrap');
+window.$ = window.jQuery = require('jquery');
import {SCORM} from 'pipwerks-scorm-api-wrapper';
import {gsap} from "gsap";
+require('../../scorm/scorm');
+
(function (global) {
$(function () {
var data;
var score;
var questionStatus = {};
+ var showReview, threshold, instantReview, logAttempts, displayScore, countQuestions, passedAction, failedAction,
+ logQuestions;
+
+ function initApp() {
+ if (SCORM_INITED) {
+ return;
+ }
+ var scormok = initScorm();
+ var defaultState = {q: 1};
+ var state;
+ if (!scormok) {
+ state = defaultState;
+ } else {
+ var currentLocation = getScormValue('location');
+ try {
+ state = JSON.parse(currentLocation);
+ } catch (e) {
+ state = defaultState;
+ }
+ if (state['q'] === undefined || state['q'] === null) {
+ state = defaultState;
+ }
+ }
+ init(state);
+ }
$(window).on('resize', resize);
document.documentElement.style.setProperty('--nok-color', DATA.nokColor);
document.documentElement.style.setProperty('--review-background', DATA.reviewBackground);
$('head').append('<style>:root{--main-color:' + DATA.mainColor + ';}</style>');
- cssVars({});
showReview = DATA.review;
threshold = 0;
$(document).on('quizinit', function (event, state) {
init(state);
});
- if (DATA.scorm) {
- initScormEvents();
- } else {
- SCORM = false;
- $(document).trigger('quizinit', {q: 1});
- }
+
+ initApp();
+
resizeContainer();
resize();
}
return false;
});
-
if (state.a) {
$.each($(state.a), function (k, v) {
var q = $('section.question[data-q="' + (k + 1) + '"]');
return {sprintf: y, vsprintf: e}
}))
}();
-
-require('./scorm');
+++ /dev/null
-window._ = require('lodash');
-
-/**
- * We'll load the axios HTTP library which allows us to easily issue requests
- * to our Laravel back-end. This library automatically handles sending the
- * CSRF token as a header based on the value of the "XSRF" token cookie.
- */
-
-window.axios = require('axios');
-window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
-window.$ = window.jQuery = require('jquery');
+++ /dev/null
-SCORM = true;
-
-function initScormEvents() {
- SCORM = doLMSInitialize();
- var defaultState = {q: 1};
- if (!SCORM) {
- // No SCORM, we init at question 1
- $(document).trigger('quizinit', defaultState);
- return false;
- }
- $(window).on('unload', function () {
- doLMSFinish();
- });
-
- var currentStatus = getScormValue('cmi.core.lesson_status');
- if (currentStatus != 'passed' || currentStatus != 'completed') {
- setScormValue('cmi.core.lesson_status', 'incomplete');
- }
-
- var savedState = getScormValue('cmi.core.lesson_location');
- try {
- if (savedState == '') {
- savedState = defaultState;
- } else {
- savedState = JSON.parse(savedState);
- }
- $(document).trigger('quizinit', savedState);
- } catch (err) {
- console.error(err);
- }
-
- return true;
-}
-
-function setSCORMLocation(location) {
- return setScormValue('cmi.core.lesson_location', JSON.stringify(location));
-}
-
-function setSCORMScore(score, max, min) {
- if (min === undefined) {
- min = 0;
- }
-
- setScormValue('cmi.core.score.raw', ((score - min) / (max - min)) * 100);
- setScormValue('cmi.core.score.min', 0);
- setScormValue('cmi.core.score.max', 100);
-}
-
-
-function getScormValue(elementName) {
- if (!SCORM) {
- return null;
- }
- var result = String(doLMSGetValue(elementName));
- return result;
-}
-
-function setScormValue(elementName, value) {
- if (!SCORM) {
- return;
- }
- var result = doLMSSetValue(elementName, value);
- doLMSCommit();
- return result;
-}
@font-face
font-family: 'RobotoCondensed'
- src: url('fonts/RobotoCondensed-Regular-webfont.woff') format('woff')
+ src: url('../fonts/RobotoCondensed-Regular-webfont.woff') format('woff')
font-weight: 400
font-style: normal
@font-face
font-family: 'RobotoCondensed'
- src: url('fonts/RobotoCondensed-Light-webfont.woff') format('woff')
+ src: url('../fonts/RobotoCondensed-Light-webfont.woff') format('woff')
font-weight: 300
font-style: normal
@font-face
font-family: 'RobotoCondensed'
- src: url('fonts/RobotoCondensed-Bold-webfont.woff') format('woff')
+ src: url('../fonts/RobotoCondensed-Bold-webfont.woff') format('woff')
font-weight: 700
font-style: normal
--- /dev/null
+import {SCORM} from "pipwerks-scorm-api-wrapper";
+
+window.SCORM_INITED = false;
+window.SCORM_START_TIME = null;
+window.SCORM_INTERACTION_TIMESTAMPS = [];
+window.SCORM_CORRECT_ANSWERS = [];
+window.SCORM_ID_TO_N = {};
+window.SCORM_WEIGHTING = 0;
+window.SCORM_QUESTIONS = [];
+window.SCORM_SUCCESS_STATUS = 'unknown';
+window.SCORM_SUCCESS_SCORE = 0;
+window.SCORM_EVENTS_INITED = false;
+window.SCORM_INTERACTIONS_INITED = false;
+window.SCORM_LOCATION_INITED = false;
+window.SCORM_OK = false;
+
+window._CMI12 = {
+ 'location': 'cmi.core.lesson_location',
+ 'status': "cmi.core.lesson_status",
+ 'session_time': 'cmi.core.session_time',
+ 'success_status': '',
+ 'exit': 'cmi.core.exit',
+ 'cmi.score.raw': 'cmi.core.score.raw',
+ 'cmi.score.min': 'cmi.core.score.min',
+ 'cmi.score.max': 'cmi.core.score.max',
+ 'cmi.score.scaled': '',
+};
+
+window._CMI2004 = {
+ 'location': 'cmi.location',
+ 'status': 'cmi.completion_status',
+ 'session_time': 'cmi.session_time',
+ 'success_status': 'cmi.success_status',
+ 'exit': 'cmi.exit',
+};
+
+window.initScorm=function()
+{
+ if (SCORM_INITED) {
+ return;
+ }
+
+ SCORM_INITED = true;
+ try {
+ if (SCORM.init()) {
+ SCORM_OK = true;
+ }
+ } catch (e) {
+
+ }
+
+ try {
+ if (FORCE_SCORM) {
+ SCORM_OK = true;
+ }
+ } catch (e) {
+
+ }
+
+ if (SCORM_OK) {
+ scormExit();
+ startScormTimer();
+ initScormEvents();
+ }
+ return SCORM_OK;
+}
+
+window._cmi = function (key) {
+ var res = null;
+ switch (SCORM.version) {
+ case "1.2" :
+ res = _CMI12[key];
+ break;
+ case '2004':
+ res = _CMI2004[key];
+ break;
+ }
+ if (res == undefined || res == null) {
+ res = key;
+ }
+ return res;
+};
+
+window.initScormEvents = function () {
+ if (!SCORM_OK || SCORM_EVENTS_INITED) {
+ return;
+ }
+ SCORM_EVENTS_INITED = true;
+
+ $(window).on('unload', function () {
+ finishScorm();
+ });
+
+ setInterval(function () {
+ SCORM.save();
+ }, 5000);
+};
+
+window.setSCORMLocation = function (location) {
+ return setScormValue('cmi.location', JSON.stringify(location));
+};
+
+window.setSCORMScore = function (score, max, min) {
+ if (min === undefined) {
+ min = 0;
+ }
+
+ setScormValue('cmi.core.score.raw', score);
+ setScormValue('cmi.core.score.min', min);
+ setScormValue('cmi.core.score.max', max);
+};
+window.getScormValue = function (elementName) {
+ if (!SCORM_OK) {
+ return null;
+ }
+ var cmi = _cmi(elementName);
+ if (cmi == '') {
+ return null;
+ }
+ return result = SCORM.get(cmi);
+};
+
+window.setScormValue=function(elementName, value) {
+ if (!SCORM_OK) {
+ return;
+ }
+ var cmi = _cmi(elementName);
+ if (cmi == '') {
+ return false;
+ }
+ var result = SCORM.set(cmi, value);
+ return result;
+};
+
+
+window.scormMarkAsComplete=function() {
+ if (!SCORM_OK) {
+ return;
+ }
+ scormExit();
+};
+
+
+window.finishScorm=function() {
+ if (!SCORM_OK) {
+ return;
+ }
+ setSessionTime();
+ SCORM.save();
+ SCORM.quit();
+};
+
+window.scormExit = function () {
+ if (!SCORM_OK) {
+ return;
+ }
+ var v = 'suspend';
+ setScormValue('exit', v);
+}
+
+window.startScormTimer = function () {
+ SCORM_START_TIME = new Date();
+}
+
+window.scormCompleteAndClose = function () {
+ scormComplete();
+ scormClose();
+}
+
+window.scormClose = function () {
+ parent.close();
+ top.close();
+ window.close();
+};
+
+
+window.setSessionTime = function () {
+ if (!SCORM_OK) {
+ return;
+ }
+ var currentTime = new Date();
+ var sessionTime;
+
+ var endTime = currentTime.getTime()
+ var calculatedTime = endTime - SCORM_START_TIME;
+
+ if (SCORM.version == '1.2') {
+ var totalHours = Math.floor(calculatedTime / 1000 / 60 / 60);
+ calculatedTime = calculatedTime - totalHours * 1000 * 60 * 60
+ if (totalHours < 1000 && totalHours > 99) {
+ totalHours = "0" + totalHours;
+ } else if (totalHours < 100 && totalHours > 9) {
+ totalHours = "00" + totalHours;
+ } else if (totalHours < 10) {
+ totalHours = "000" + totalHours;
+ }
+
+ var totalMinutes = Math.floor(calculatedTime / 1000 / 60);
+ calculatedTime = calculatedTime - totalMinutes * 1000 * 60;
+ if (totalMinutes < 10) {
+ totalMinutes = "0" + totalMinutes;
+ }
+
+ var totalSeconds = Math.floor(calculatedTime / 1000);
+ if (totalSeconds < 10) {
+ totalSeconds = "0" + totalSeconds;
+ }
+ sessionTime = totalHours + ":" + totalMinutes + ":" + totalSeconds;
+ setScormValue('session_time', sessionTime);
+ } else {
+ setScormValue('session_time', scormSecondsToTimeInterval(calculatedTime / 1000));
+ }
+
+};
+
+window.dateToScormTime = function (date) {
+ var res = date.toISOString();
+ var e = res.split('.');
+ e.pop();
+ return e.join('.');
+};
+
+window.getScormTimeInterval = function (start, end) {
+ var diff = Math.round((end.getTime() - start.getTime()) / 1000);
+ return scormSecondsToTimeInterval(diff);
+};
+
+window.scormSecondsToTimeInterval = function (diff) {
+ var diff = Math.round(diff);
+ var h = Math.floor(diff / 3600);
+ diff = diff % 3600;
+ var m = Math.floor(diff / 60);
+ var s = diff % 60;
+ return 'PT' + h + 'H' + m + 'M' + s + 'S';
+};
-<a class="btn btn-sm btn-link" href="{{$crud->route}}/{{$entry->getKey()}}/download" data-toggle="tooltip"
- title="{{__('Télécharger le quiz')}}"><i class="la la-arrow-circle-down"></i> {{__('Télécharger')}}</a>
+@once
+ @php
+ $showjs=false;
+ if($crud->getValue('seenExportJS')===null){
+ $showjs =true;
+ $crud->setValue('seenExportJS',true);
+ }
+ @endphp
+ @if($showjs)
+ <style>
+ a.exportquiz {
+ position: relative;
+ }
+
+ a.exportquiz select {
+ opacity: 0;
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ top: 0;
+ left: 0;
+ cursor: pointer;
+ }
+ </style>
+ <script>
+ jQuery(document).ready(function ($) {
+ $('a.exportquiz').on('click', function () {
+ return false;
+ });
+ $('a.exportquiz select').on('change', function () {
+ var val = $(this).val();
+ if (val <= 0 || val == null || val == 'null') {
+ return;
+ }
+ var id = $(this).data('id');
+ window.location = $(this).data('route') + "/" + id + "/download/" + val;
+ });
+ });
+ </script>
+ @endif
+@endonce
+
+<a class="btn btn-sm btn-link exportquiz" href="#"
+ data-toggle="tooltip"
+ title="{{__('Exporter le quiz')}}"><i class="la la-arrow-circle-down"></i> {{__('Exporter')}}
+ <select data-route="{{$crud->route}}" data-id="{{$entry->getKey()}}">
+ <option value="" selected style="display: none">--</option>
+ <option value="download">{{__('Télécharger')}}</option>
+ <option value="scormcloud">{{__('Tester sur Scorm Cloud')}}</option>
+ </select>
+</a>