From b4434b85913b9611798ba9e1f1c79706740015c2 Mon Sep 17 00:00:00 2001 From: Edmund Farrow Date: Thu, 7 Nov 2024 16:53:27 +0000 Subject: [PATCH] quiz-data - First pass at whole course export --- classes/cli_helper.php | 26 ++- classes/create_repo.php | 2 +- classes/export_repo.php | 2 +- classes/import_repo.php | 8 +- ...plerepos.php => createwholecourserepo.php} | 17 +- cli/exportrepofrommoodle.php | 8 + cli/exportwholecoursefrommoodle.php | 161 ++++++++++++++++++ 7 files changed, 215 insertions(+), 9 deletions(-) rename cli/{createmultiplerepos.php => createwholecourserepo.php} (91%) create mode 100644 cli/exportwholecoursefrommoodle.php diff --git a/classes/cli_helper.php b/classes/cli_helper.php index e047e9b..29c82d9 100644 --- a/classes/cli_helper.php +++ b/classes/cli_helper.php @@ -63,6 +63,10 @@ class cli_helper { * QUIZ_FILE - File name ending for quiz structure file. */ public const QUIZ_FILE = '_quiz.json'; + /** + * QUIZPATH_FILE - File name for quiz location file. + */ + public const QUIZPATH_FILE = 'quizlocation.json'; /** * TEMP_MANIFEST_FILE - File name ending for temporary manifest file. * Appended to name of moodle instance. @@ -445,13 +449,15 @@ public static function get_quiz_directory(string $basedirectory, string $quiznam * @param int|null $subcategoryid * @param string|null $subdirectory * @param bool $showupdated + * @param object $activity * @return object */ public static function create_manifest_file(object $manifestcontents, string $tempfilepath, string $manifestpath, string $moodleurl, ?int $subcategoryid=null, ?string $subdirectory=null, - bool $showupdated=true):object { + bool $showupdated=true, + object $activity=null):object { // Read in temp file a question at a time, process and add to manifest. // No actual processing at the moment so could simplify to write straight // to manifest in the first place if no processing materialises. @@ -506,6 +512,20 @@ public static function create_manifest_file(object $manifestcontents, string $te } } } + // If there are no questions, we'll need to get context. + if ($manifestcontents->context === null) { + $context = $activity->cli_helper->check_context($activity, false, true); + $manifestcontents->context = new \stdClass(); + $manifestcontents->context->contextlevel = $questioninfo->contextlevel; + $manifestcontents->context->coursename = $questioninfo->coursename; + $manifestcontents->context->modulename = $questioninfo->modulename; + $manifestcontents->context->coursecategory = $questioninfo->coursecategory; + $manifestcontents->context->instanceid = $questioninfo->instanceid; + $manifestcontents->context->defaultsubcategoryid = $subcategoryid; + $manifestcontents->context->defaultsubdirectory = $subdirectory; + $manifestcontents->context->defaultignorecat = $questioninfo->ignorecat; + $manifestcontents->context->moodleurl = $moodleurl; + } echo "\nAdded {$addedcount} question" . (($addedcount !== 1) ? 's' : '') . ".\n"; if ($showupdated) { echo "Updated {$updatedcount} question" . (($updatedcount !== 1) ? 's' : '') . ".\n"; @@ -628,7 +648,9 @@ public function create_gitignore(string $manifestpath):void { $manifestdirname = dirname($manifestpath); if (!is_file($manifestdirname . '/.gitignore')) { $ignore = fopen($manifestdirname . '/.gitignore', 'a'); - $contents = "**/*_question_manifest.json\n**/*_manifest_update.tmp\n"; + + $contents = "**/*" . self::MANIFEST_FILE . "\n**/*" . + self::TEMP_MANIFEST_FILE . "\n**/" . self::QUIZPATH_FILE . "\n"; fwrite($ignore, $contents); fclose($ignore); } diff --git a/classes/create_repo.php b/classes/create_repo.php index cf1d317..4f07cbc 100644 --- a/classes/create_repo.php +++ b/classes/create_repo.php @@ -212,7 +212,7 @@ public function process():void { $this->export_to_repo(); cli_helper::create_manifest_file($this->manifestcontents, $this->tempfilepath, $this->manifestpath, $this->moodleurl, - $this->qcategoryid, $this->subdirectory, false); + $this->qcategoryid, $this->subdirectory, false, $this); unlink($this->tempfilepath); } diff --git a/classes/export_repo.php b/classes/export_repo.php index 5b5e028..130061f 100644 --- a/classes/export_repo.php +++ b/classes/export_repo.php @@ -193,7 +193,7 @@ public function process():void { $this->export_to_repo(); cli_helper::create_manifest_file($this->manifestcontents, $this->tempfilepath, $this->manifestpath, $this->moodleurl, - null, null, false); + null, null, false, $this); unlink($this->tempfilepath); // Remove questions from manifest that are no longer in Moodle. // Will be restored from repo on next import if file is still there. diff --git a/classes/import_repo.php b/classes/import_repo.php index d94dfa3..c2f0921 100644 --- a/classes/import_repo.php +++ b/classes/import_repo.php @@ -358,7 +358,9 @@ public function process():void { $this->manifestpath, $this->moodleurl, $instanceinfo->contextinfo->qcategoryid, - $this->subdirectory); + $this->subdirectory, + true, + $this); unlink($this->tempfilepath); $this->delete_no_file_questions(false); $this->delete_no_record_questions(false); @@ -600,7 +602,9 @@ public function recovery():void { $this->manifestpath, $this->moodleurl, $instanceinfo->contextinfo->qcategoryid, - $this->subdirectory); + $this->subdirectory, + true, + $this); unlink($this->tempfilepath); echo 'Recovery successful. Continuing...'; } diff --git a/cli/createmultiplerepos.php b/cli/createwholecourserepo.php similarity index 91% rename from cli/createmultiplerepos.php rename to cli/createwholecourserepo.php index af9cbd4..9ba3aab 100755 --- a/cli/createmultiplerepos.php +++ b/cli/createwholecourserepo.php @@ -25,6 +25,8 @@ */ namespace qbank_gitsync; + +use stdClass; define('CLI_SCRIPT', true); require_once('./config.php'); require_once('../classes/curl_request.php'); @@ -140,7 +142,7 @@ exit; } -// Create course repo. +// Create course directory and populate. $scriptdirectory = dirname(__FILE__); $clihelper = new cli_helper($options); $arguments = $clihelper->get_arguments(); @@ -153,7 +155,7 @@ $createrepo->process(); $clihelper->commit_hash_setup($createrepo); -// Create quiz repos. +// Create quiz directories and populate. $contextinfo = $clihelper->check_context($createrepo, false, true); if ($arguments['directory']) { $basedirectory = $arguments['rootdirectory'] . '/' . $arguments['directory']; @@ -167,9 +169,12 @@ $token = $arguments['token'][$moodleinstance]; $ignorecat = $arguments['ignorecat']; $ignorecat = ($ignorecat) ? ' -x "' . $ignorecat . '"' : ''; +$quizfilepath = $basedirectory . '/' . $clihelper::QUIZPATH_FILE; +$quiz_locations = []; foreach ($contextinfo->quizzes as $quiz) { $instanceid = "{$quiz->instanceid}"; - $rootdirectory = $clihelper->create_directory(cli_helper::get_quiz_directory($basedirectory, $quiz->name)); + $quizdirectory = cli_helper::get_quiz_directory($basedirectory, $quiz->name); + $rootdirectory = $clihelper->create_directory($quizdirectory); echo "\nExporting quiz: {$quiz->name} to {$rootdirectory}\n"; chdir($scriptdirectory); $output = shell_exec('php createrepo.php -r "' . $rootdirectory . '" -i "' . $moodleinstance . '" -l "module" -n ' . (int) $instanceid . ' -t ' . $token . ' -z' . $ignorecat); @@ -178,6 +183,12 @@ $contextinfo->contextinfo->coursename, $quiz->name, $rootdirectory); chdir($scriptdirectory); $output = shell_exec('php exportquizstructurefrommoodle.php -z -r "" -i "' . $moodleinstance . '" -n ' . (int) $instanceid . ' -t ' . $token. ' -p "' . $coursemanifestname. '" -f "' . $quizmanifestname . '"'); + $quiz_locations[$instanceid] = basename($rootdirectory); + $success = file_put_contents($quizfilepath, json_encode($quiz_locations)); + if ($success === false) { + echo "\nUnable to update quizpath file: {$quizfilepath}\n Aborting.\n"; + exit(); + } echo $output; } // Commit the final quiz file. diff --git a/cli/exportrepofrommoodle.php b/cli/exportrepofrommoodle.php index a7491f0..399362b 100644 --- a/cli/exportrepofrommoodle.php +++ b/cli/exportrepofrommoodle.php @@ -105,6 +105,14 @@ 'variable' => 'ignorecat', 'valuerequired' => true, ], + [ + 'longopt' => 'quiet', + 'shortopt' => 'z', + 'description' => 'Do not display context info or option to abort.', + 'default' => false, + 'variable' => 'quiet', + 'valuerequired' => false, + ], ]; if (!function_exists('simplexml_load_file')) { diff --git a/cli/exportwholecoursefrommoodle.php b/cli/exportwholecoursefrommoodle.php new file mode 100644 index 0000000..87655db --- /dev/null +++ b/cli/exportwholecoursefrommoodle.php @@ -0,0 +1,161 @@ +. + +/** + * Export from Moodle into a git repo containing questions. + * + * @package qbank_gitsync + * @copyright 2023 University of Edinburgh + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace qbank_gitsync; +define('CLI_SCRIPT', true); +require_once('./config.php'); +require_once('../classes/curl_request.php'); +require_once('../classes/cli_helper.php'); +require_once('../classes/export_trait.php'); +require_once('../classes/tidy_trait.php'); +require_once('../classes/export_repo.php'); + +$options = [ + [ + 'longopt' => 'moodleinstance', + 'shortopt' => 'i', + 'description' => 'Key of Moodle instance in $moodleinstances to use. ' . + 'Should match end of instance URL.', + 'default' => $instance, + 'variable' => 'moodleinstance', + 'valuerequired' => true, + ], + [ + 'longopt' => 'rootdirectory', + 'shortopt' => 'r', + 'description' => "Directory on user's computer containing repos.", + 'default' => $rootdirectory, + 'variable' => 'rootdirectory', + 'valuerequired' => true, + ], + [ + 'longopt' => 'manifestpath', + 'shortopt' => 'f', + 'description' => 'Filepath of manifest file relative to root directory.', + 'default' => $manifestpath, + 'variable' => 'manifestpath', + 'valuerequired' => true, + ], + [ + 'longopt' => 'subcategory', + 'shortopt' => 's', + 'description' => 'Relative subcategory of question to actually export.', + 'default' => null, + 'variable' => 'subcategory', + 'valuerequired' => true, + ], + [ + 'longopt' => 'questioncategoryid', + 'shortopt' => 'q', + 'description' => 'Numerical id of subcategory to actually export.', + 'default' => null, + 'variable' => 'qcategoryid', + 'valuerequired' => true, + ], + [ + 'longopt' => 'token', + 'shortopt' => 't', + 'description' => 'Security token for webservice.', + 'default' => $token, + 'variable' => 'token', + 'valuerequired' => true, + ], + [ + 'longopt' => 'help', + 'shortopt' => 'h', + 'description' => '', + 'default' => false, + 'variable' => 'help', + 'valuerequired' => false, + ], + [ + 'longopt' => 'usegit', + 'shortopt' => 'u', + 'description' => 'Is the repo controlled using Git?', + 'default' => $usegit, + 'variable' => 'usegit', + 'valuerequired' => false, + ], + [ + 'longopt' => 'ignorecat', + 'shortopt' => 'x', + 'description' => 'Regex of categories to ignore - add an extra leading / for Windows.', + 'default' => $ignorecat, + 'variable' => 'ignorecat', + 'valuerequired' => true, + ], +]; + +if (!function_exists('simplexml_load_file')) { + echo 'Please install the PHP library SimpleXML.' . "\n"; + exit; +} +$scriptdirectory = dirname(__FILE__); +$clihelper = new cli_helper($options); +$arguments = $clihelper->get_arguments(); +echo "Exporting a course. Associated quiz contexts will also be exported.\n"; +$exportrepo = new export_repo($clihelper, $moodleinstances); +$clihelper->check_for_changes($exportrepo->manifestpath); +$clihelper->backup_manifest($exportrepo->manifestpath); +// $exportrepo->process(); + +// Create/update quiz directories and populate. +$contextinfo = $clihelper->check_context($exportrepo, false, true); +$basedirectory = dirname($exportrepo->manifestpath); +$moodleinstance = $arguments['moodleinstance']; +$coursemanifestname = cli_helper::get_manifest_path($moodleinstance, 'course', null, + $contextinfo->contextinfo->coursename, null, $basedirectory); +$token = $arguments['token'][$moodleinstance]; +$ignorecat = $arguments['ignorecat']; +$ignorecat = ($ignorecat) ? ' -x "' . $ignorecat . '"' : ''; +$quizfilepath = $basedirectory . '/' . $clihelper::QUIZPATH_FILE; +$quizlocations = json_decode(file_get_contents($quizfilepath)); +foreach ($contextinfo->quizzes as $quiz) { + $instanceid = (int) $quiz->instanceid; + if (!isset($quizlocations->$instanceid)) { + $rootdirectory = $clihelper->create_directory(cli_helper::get_quiz_directory($basedirectory, $quiz->name)); + $quiz_locations[$instanceid] = basename($rootdirectory); + $success = file_put_contents($quizfilepath, json_encode($quiz_locations)); + if ($success === false) { + echo "\nUnable to update quizpath file: {$quizfilepath}\n Aborting.\n"; + exit(); + } + echo "\nExporting quiz: {$quiz->name} to {$rootdirectory}\n"; + chdir($scriptdirectory); + $output = shell_exec('php createrepo.php -r "' . $rootdirectory . '" -i "' . $moodleinstance . '" -l "module" -n ' . (int) $instanceid . ' -t ' . $token . ' -z' . $ignorecat); + } else { + $rootdirectory = dirname($basedirectory) . '/' . $quizlocations->$instanceid; + echo "\nExporting quiz: {$quiz->name} to {$rootdirectory}\n"; + chdir($scriptdirectory); + $quizmanifestname = cli_helper::get_manifest_path($moodleinstance, 'module', null, + $contextinfo->contextinfo->coursename, $quiz->name, ''); + $output = shell_exec('php exportrepofrommoodle.php -r "' . $rootdirectory . '" -i "' . $moodleinstance . '" -f "' . $quizmanifestname . '" -t ' . $token); + } + echo $output; + $quizmanifestname = cli_helper::get_manifest_path($moodleinstance, 'module', null, + $contextinfo->contextinfo->coursename, $quiz->name, $rootdirectory); + chdir($scriptdirectory); + $output = shell_exec('php exportquizstructurefrommoodle.php -z -r "" -i "' . $moodleinstance . '" -n ' . (int) $instanceid . ' -t ' . $token. ' -p "' . $coursemanifestname. '" -f "' . $quizmanifestname . '"'); + echo $output; +}