Skip to content

Commit

Permalink
quiz-data - Basic export CLI
Browse files Browse the repository at this point in the history
  • Loading branch information
EJMFarrow committed Oct 17, 2024
1 parent 132a509 commit 2ebe5cd
Show file tree
Hide file tree
Showing 3 changed files with 323 additions and 1 deletion.
21 changes: 20 additions & 1 deletion classes/cli_helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ class cli_helper {
* Appended to name of moodle instance.
*/
public const MANIFEST_FILE = '_question_manifest.json';
/**
* QUIZ_FILE - File name ending for quiz structure file.
*/
public const QUIZ_FILE = '_quiz.json';
/**
* TEMP_MANIFEST_FILE - File name ending for temporary manifest file.
* Appended to name of moodle instance.
Expand Down Expand Up @@ -270,7 +274,7 @@ public function validate_and_clean_args(): void {
break;
}
}
if (!isset($cliargs['manifestpath']) && !isset($cliargs['contextlevel'])) {
if (!(isset($cliargs['manifestpath']) || isset($cliargs['quizmanifestpath']) || isset($cliargs['nonquizmanifestpath'])) && !isset($cliargs['contextlevel'])) {
echo "\nYou have not specified context. " .
"You must specify context level (--contextlevel) unless " .
"using a function where this information can be read from a manifest file, in which case " .
Expand Down Expand Up @@ -399,6 +403,21 @@ public static function get_manifest_path(string $moodleinstance, string $context
return $filename;
}

/**
* Create quiz structure path.
*
* @param string|null $modulename
* @param string $directory
* @return string
*/
public static function get_quiz_structure_path(string $modulename, string $directory):string {
$filename = substr($modulename, 0, 100);
$filename = $directory . '/' .
preg_replace(self::BAD_CHARACTERS, '-', strtolower($filename)) .
self::QUIZ_FILE;
return $filename;
}

/**
* Create manifest file from temporary file.
*
Expand Down
181 changes: 181 additions & 0 deletions classes/export_quiz.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
<?php
// This file is part of Stack - http://stack.maths.ed.ac.uk/
//
// Stack is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Stack is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Stack. If not, see <http://www.gnu.org/licenses/>.

/**
* Wrapper class for processing performed by command line interface for exporting quiz data from Moodle.
*
* Utilised in cli\exportquizdatafrommoodle.php
*
* Allows mocking and unit testing via PHPUnit.
* Used outside Moodle.
*
* @package qbank_gitsync
* @copyright 2024 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace qbank_gitsync;

/**
* Export a Git repo.
*/
class export_quiz {
/**
* Settings for POST request
*
* These are the parameters for the webservice call.
*
* @var array
*/
public array $postsettings;
/**
* cURL request handle for file upload
*
* @var curl_request
*/
public curl_request $curlrequest;
/**
* Full path to manifest file
*
* @var string
*/
public string $quizmanifestpath;
/**
* Parsed content of JSON manifest file
*
* @var \stdClass|null
*/
public ?\stdClass $quizmanifestcontents;
/**
* URL of Moodle instance
*
* @var string
*/
public string $moodleurl;
/**
* Full path to output file
*
* @var string
*/
public string $filepath;

/**
* Constructor
*
* @param cli_helper $clihelper
* @param array $moodleinstances pairs of names and URLs
*/
public function __construct(cli_helper $clihelper, array $moodleinstances) {
// Convert command line options into variables.
$arguments = $clihelper->get_arguments();
$moodleinstance = $arguments['moodleinstance'];
// TODO Use additional manifest file as well.
$this->quizmanifestpath = $arguments['rootdirectory'] . '/' . $arguments['quizmanifestpath'];
if (is_array($arguments['token'])) {
$token = $arguments['token'][$moodleinstance];
} else {
$token = $arguments['token'];
}
$this->quizmanifestcontents = json_decode(file_get_contents($this->quizmanifestpath));
if (!$this->quizmanifestcontents) {
echo "\nUnable to access or parse manifest file: {$this->quizmanifestpath}\nAborting.\n";
$this->call_exit();
}

$this->tempfilepath = str_replace(cli_helper::MANIFEST_FILE,
'_export' . cli_helper::TEMP_MANIFEST_FILE,
$this->quizmanifestpath);
$this->moodleurl = $moodleinstances[$moodleinstance];
$wsurl = $this->moodleurl . '/webservice/rest/server.php';

$this->curlrequest = $this->get_curl_request($wsurl);
$this->postsettings = [
'wstoken' => $token,
'wsfunction' => 'qbank_gitsync_export_quiz_data',
'moodlewsrestformat' => 'json',
'quizname' => $arguments['moodlename'],
'moduleid' => $arguments['instanceid'],
];
$this->curlrequest->set_option(CURLOPT_RETURNTRANSFER, true);
$this->curlrequest->set_option(CURLOPT_POST, 1);
$this->curlrequest->set_option(CURLOPT_POSTFIELDS, $this->postsettings);
}

/**
* Get quiz data from webservice, convert question ids to file locations
* and then write to file.
*
* @return void
*/
public function process():void {
$this->export_quiz_data();
}

/**
* Wrapper for cURL request to allow mocking.
*
* @param string $wsurl webservice URL
* @return curl_request
*/
public function get_curl_request($wsurl):curl_request {
return new \qbank_gitsync\curl_request($wsurl);
}

/**
* Loop through questions in manifest file, export each from Moodle and update local copy
*
* @return void
*/
public function export_quiz_data() {
$response = $this->curlrequest->execute();
$responsejson = json_decode($response);
if (!$responsejson) {
echo "Broken JSON returned from Moodle:\n";
echo $response . "\n";
echo "{$this->filepath} not updated.\n";
$this->call_exit();
} else if (property_exists($responsejson, 'exception')) {
echo "{$responsejson->message}\n";
if (property_exists($responsejson, 'debuginfo')) {
echo "{$responsejson->debuginfo}\n";
}
echo "{$this->filepath} not updated.\n";
$this->call_exit();
}
$this->filepath = cli_helper::get_quiz_structure_path($responsejson->quiz->name, dirname($this->quizmanifestpath));
$manifestentries = array_column($this->quizmanifestcontents->questions, null, 'questionbankentryid');
foreach ($responsejson->questions as $question) {
$manifestentry = $manifestentries["{$question->questionbankentryid}"] ?? false;
if ($manifestentry) {
$question->quizfilepath = $manifestentry->filepath;
unset($question->questionbankentryid);
} else {
// TODO - what happens here?
}
}
file_put_contents($this->filepath, json_encode($responsejson));
}

/**
* Mockable function that just exits code.
*
* Required to stop PHPUnit displaying output after exit.
*
* @return void
*/
public function call_exit():void {
exit;
}
}
122 changes: 122 additions & 0 deletions cli/exportquizstructurefrommoodle.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
<?php
// This file is part of Stack - http://stack.maths.ed.ac.uk/
//
// Stack is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Stack is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Stack. If not, see <http://www.gnu.org/licenses/>.

/**
* Export structure (not questions) of a quiz.
*
* @package qbank_gitsync
* @copyright 2024 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_quiz.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' => 'nonquizmanifestpath',
'shortopt' => 'p',
'description' => 'Filepath of non-quiz manifest file relative to root directory.',
'default' => $manifestpath,
'variable' => 'nonquizmanifestpath',
'valuerequired' => true,
],
[
'longopt' => 'quizmanifestpath',
'shortopt' => 'f',
'description' => 'Filepath of quiz manifest file relative to root directory.',
'default' => null,
'variable' => 'quizmanifestpath',
'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' => 'modulename',
'shortopt' => 'm',
'description' => 'Unique (within course) quiz name.',
'default' => null,
'variable' => 'modulename',
'valuerequired' => true,
],
[
'longopt' => 'instanceid',
'shortopt' => 'n',
'description' => 'Numerical course module id of quiz.',
'default' => null,
'variable' => 'instanceid',
'valuerequired' => true,
]
];

if (!function_exists('simplexml_load_file')) {
echo 'Please install the PHP library SimpleXML.' . "\n";
exit;
}
$clihelper = new cli_helper($options);
$exportquiz = new export_quiz($clihelper, $moodleinstances);
if ($exportquiz->coursemanifestpath) {
echo 'Checking course repo...';
$clihelper->check_for_changes($exportquiz->coursemanifestpath);
}
if ($exportquiz->quizmanifestpath) {
echo 'Checking quiz repo...';
$clihelper->check_for_changes($exportquiz->quizmanifestpath);
}
$exportquiz->process();

0 comments on commit 2ebe5cd

Please sign in to comment.