Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403] [Versions 402 and 403]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  
  18  /**
  19   * External question API
  20   *
  21   * @package    core_question
  22   * @category   external
  23   * @copyright  2016 Pau Ferrer <pau@moodle.com>
  24   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  25   */
  26  
  27  use core_external\external_api;
  28  use core_external\external_description;
  29  use core_external\external_value;
  30  use core_external\external_single_structure;
  31  use core_external\external_multiple_structure;
  32  use core_external\external_function_parameters;
  33  use core_external\external_warnings;
  34  
  35  defined('MOODLE_INTERNAL') || die();
  36  
  37  require_once($CFG->dirroot . '/question/engine/lib.php');
  38  require_once($CFG->dirroot . '/question/engine/datalib.php');
  39  require_once($CFG->libdir . '/questionlib.php');
  40  
  41  /**
  42   * Question external functions
  43   *
  44   * @package    core_question
  45   * @category   external
  46   * @copyright  2016 Pau Ferrer <pau@moodle.com>
  47   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  48   * @since Moodle 3.1
  49   */
  50  class core_question_external extends external_api {
  51  
  52      /**
  53       * Returns description of method parameters
  54       *
  55       * @return external_function_parameters
  56       * @since Moodle 3.1
  57       */
  58      public static function update_flag_parameters() {
  59          return new external_function_parameters(
  60              array(
  61                  'qubaid' => new external_value(PARAM_INT, 'the question usage id.'),
  62                  'questionid' => new external_value(PARAM_INT, 'the question id'),
  63                  'qaid' => new external_value(PARAM_INT, 'the question_attempt id'),
  64                  'slot' => new external_value(PARAM_INT, 'the slot number within the usage'),
  65                  'checksum' => new external_value(PARAM_ALPHANUM, 'computed checksum with the last three arguments and
  66                               the users username'),
  67                  'newstate' => new external_value(PARAM_BOOL, 'the new state of the flag. true = flagged')
  68              )
  69          );
  70      }
  71  
  72      /**
  73       * Update the flag state of a question attempt.
  74       *
  75       * @param int $qubaid the question usage id.
  76       * @param int $questionid the question id.
  77       * @param int $qaid the question_attempt id.
  78       * @param int $slot the slot number within the usage.
  79       * @param string $checksum checksum, as computed by {@link get_toggle_checksum()}
  80       *      corresponding to the last three arguments and the users username.
  81       * @param bool $newstate the new state of the flag. true = flagged.
  82       * @return array (success infos and fail infos)
  83       * @since Moodle 3.1
  84       */
  85      public static function update_flag($qubaid, $questionid, $qaid, $slot, $checksum, $newstate) {
  86          global $CFG, $DB;
  87  
  88          $params = self::validate_parameters(self::update_flag_parameters(),
  89              array(
  90                  'qubaid' => $qubaid,
  91                  'questionid' => $questionid,
  92                  'qaid' => $qaid,
  93                  'slot' => $slot,
  94                  'checksum' => $checksum,
  95                  'newstate' => $newstate
  96              )
  97          );
  98  
  99          $warnings = array();
 100          self::validate_context(context_system::instance());
 101  
 102          // The checksum will be checked to provide security flagging other users questions.
 103          question_flags::update_flag($params['qubaid'], $params['questionid'], $params['qaid'], $params['slot'], $params['checksum'],
 104                                      $params['newstate']);
 105  
 106          $result = array();
 107          $result['status'] = true;
 108          $result['warnings'] = $warnings;
 109          return $result;
 110      }
 111  
 112      /**
 113       * Returns description of method result value
 114       *
 115       * @return external_description
 116       * @since Moodle 3.1
 117       */
 118      public static function update_flag_returns() {
 119          return new external_single_structure(
 120              array(
 121                  'status' => new external_value(PARAM_BOOL, 'status: true if success'),
 122                  'warnings' => new external_warnings()
 123              )
 124          );
 125      }
 126  
 127      /**
 128       * Returns description of method parameters.
 129       *
 130       * @return external_function_parameters.
 131       * @deprecated since Moodle 4.0
 132       * @see \qbank_tagquestion\external\qbank_tagquestion_external
 133       * @todo Final deprecation on Moodle 4.4 MDL-72438
 134       */
 135      public static function submit_tags_form_parameters() {
 136          return new external_function_parameters([
 137                  'questionid' => new external_value(PARAM_INT, 'The question id'),
 138                  'contextid' => new external_value(PARAM_INT, 'The editing context id'),
 139                  'formdata' => new external_value(PARAM_RAW, 'The data from the tag form'),
 140          ]);
 141      }
 142  
 143      /**
 144       * Handles the tags form submission.
 145       *
 146       * @param int $questionid The question id.
 147       * @param int $contextid The editing context id.
 148       * @param string $formdata The question tag form data in a URI encoded param string
 149       * @return array The created or modified question tag
 150       * @deprecated since Moodle 4.0
 151       * @see \qbank_tagquestion\external\qbank_tagquestion_external
 152       * @todo Final deprecation on Moodle 4.4 MDL-72438
 153       */
 154      public static function submit_tags_form($questionid, $contextid, $formdata) {
 155          global $DB, $CFG;
 156  
 157          $data = [];
 158          $result = ['status' => false];
 159  
 160          // Parameter validation.
 161          $params = self::validate_parameters(self::submit_tags_form_parameters(), [
 162              'questionid' => $questionid,
 163              'contextid' => $contextid,
 164              'formdata' => $formdata
 165          ]);
 166  
 167          $editingcontext = \context::instance_by_id($contextid);
 168          self::validate_context($editingcontext);
 169          parse_str($params['formdata'], $data);
 170  
 171          if (!$question = $DB->get_record_sql('
 172                  SELECT q.*, qc.contextid
 173                  FROM {question} q
 174                  JOIN {question_categories} qc ON qc.id = q.category
 175                  WHERE q.id = ?', [$questionid])) {
 176              throw new \moodle_exception('questiondoesnotexist', 'question');
 177          }
 178  
 179          require_once($CFG->libdir . '/questionlib.php');
 180  
 181          $cantag = question_has_capability_on($question, 'tag');
 182          $questioncontext = \context::instance_by_id($question->contextid);
 183          $contexts = new \core_question\local\bank\question_edit_contexts($editingcontext);
 184  
 185          $formoptions = [
 186              'editingcontext' => $editingcontext,
 187              'questioncontext' => $questioncontext,
 188              'contexts' => $contexts->all()
 189          ];
 190  
 191          $mform = new \qbank_tagquestion\form\tags_form(null, $formoptions, 'post', '', null, $cantag, $data);
 192  
 193          if ($validateddata = $mform->get_data()) {
 194              if ($cantag) {
 195                  if (isset($validateddata->tags)) {
 196                      // Due to a mform bug, if there's no tags set on the tag element, it submits the name as the value.
 197                      // The only way to discover is checking if the tag element is an array.
 198                      $tags = is_array($validateddata->tags) ? $validateddata->tags : [];
 199  
 200                      core_tag_tag::set_item_tags('core_question', 'question', $validateddata->id,
 201                          $questioncontext, $tags);
 202  
 203                      $result['status'] = true;
 204                  }
 205  
 206                  if (isset($validateddata->coursetags)) {
 207                      $coursetags = is_array($validateddata->coursetags) ? $validateddata->coursetags : [];
 208                      core_tag_tag::set_item_tags('core_question', 'question', $validateddata->id,
 209                          $editingcontext->get_course_context(false), $coursetags);
 210  
 211                      $result['status'] = true;
 212                  }
 213              }
 214          }
 215  
 216          return $result;
 217      }
 218  
 219      /**
 220       * Returns description of method result value.
 221       *
 222       * @deprecated since Moodle 4.0
 223       * @see \qbank_tagquestion\external\qbank_tagquestion_external
 224       * @todo Final deprecation on Moodle 4.4 MDL-72438
 225       */
 226      public static function  submit_tags_form_returns() {
 227          return new external_single_structure([
 228                  'status' => new external_value(PARAM_BOOL, 'status: true if success')
 229          ]);
 230      }
 231  
 232      /**
 233       * Marking the method as deprecated.
 234       *
 235       * @return bool
 236       * @todo Final deprecation on Moodle 4.4 MDL-72438
 237       */
 238      public static function submit_tags_form_is_deprecated() {
 239          return true;
 240      }
 241  
 242      /**
 243       * Returns description of method parameters.
 244       *
 245       * @return external_function_parameters.
 246       */
 247      public static function get_random_question_summaries_parameters() {
 248          return new external_function_parameters([
 249                  'categoryid' => new external_value(PARAM_INT, 'Category id to find random questions'),
 250                  'includesubcategories' => new external_value(PARAM_BOOL, 'Include the subcategories in the search'),
 251                  'tagids' => new external_multiple_structure(
 252                      new external_value(PARAM_INT, 'Tag id')
 253                  ),
 254                  'contextid' => new external_value(PARAM_INT,
 255                      'Context id that the questions will be rendered in (used for exporting)'),
 256                  'limit' => new external_value(PARAM_INT, 'Maximum number of results to return',
 257                      VALUE_DEFAULT, 0),
 258                  'offset' => new external_value(PARAM_INT, 'Number of items to skip from the begging of the result set',
 259                      VALUE_DEFAULT, 0)
 260          ]);
 261      }
 262  
 263      /**
 264       * Gets the list of random questions for the given criteria. The questions
 265       * will be exported in a summaries format and won't include all of the
 266       * question data.
 267       *
 268       * @param int $categoryid Category id to find random questions
 269       * @param bool $includesubcategories Include the subcategories in the search
 270       * @param int[] $tagids Only include questions with these tags
 271       * @param int $contextid The context id where the questions will be rendered
 272       * @param int $limit Maximum number of results to return
 273       * @param int $offset Number of items to skip from the beginning of the result set.
 274       * @return array The list of questions and total question count.
 275       */
 276      public static function get_random_question_summaries(
 277          $categoryid,
 278          $includesubcategories,
 279          $tagids,
 280          $contextid,
 281          $limit = 0,
 282          $offset = 0
 283      ) {
 284          global $DB, $PAGE;
 285  
 286          // Parameter validation.
 287          $params = self::validate_parameters(
 288              self::get_random_question_summaries_parameters(),
 289              [
 290                  'categoryid' => $categoryid,
 291                  'includesubcategories' => $includesubcategories,
 292                  'tagids' => $tagids,
 293                  'contextid' => $contextid,
 294                  'limit' => $limit,
 295                  'offset' => $offset
 296              ]
 297          );
 298          $categoryid = $params['categoryid'];
 299          $includesubcategories = $params['includesubcategories'];
 300          $tagids = $params['tagids'];
 301          $contextid = $params['contextid'];
 302          $limit = $params['limit'];
 303          $offset = $params['offset'];
 304  
 305          $context = \context::instance_by_id($contextid);
 306          self::validate_context($context);
 307  
 308          $categorycontextid = $DB->get_field('question_categories', 'contextid', ['id' => $categoryid], MUST_EXIST);
 309          $categorycontext = \context::instance_by_id($categorycontextid);
 310          $editcontexts = new \core_question\local\bank\question_edit_contexts($categorycontext);
 311          // The user must be able to view all questions in the category that they are requesting.
 312          $editcontexts->require_cap('moodle/question:viewall');
 313  
 314          $loader = new \core_question\local\bank\random_question_loader(new qubaid_list([]));
 315          // Only load the properties we require from the DB.
 316          $properties = \core_question\external\question_summary_exporter::get_mandatory_properties();
 317  
 318          // Transform to filters.
 319          $filters = [
 320              'category' => [
 321                  'jointype' => \qbank_managecategories\category_condition::JOINTYPE_DEFAULT,
 322                  'values' => [$categoryid],
 323                  'filteroptions' => ['includesubcategories' => $includesubcategories],
 324              ],
 325              'qtagids' => [
 326                  'jointype' => \qbank_tagquestion\tag_condition::JOINTYPE_DEFAULT,
 327                  'values' => $tagids,
 328              ],
 329          ];
 330  
 331          $questions = $loader->get_filtered_questions($filters, $limit, $offset, $properties);
 332          $totalcount = $loader->count_filtered_questions($filters);
 333          $renderer = $PAGE->get_renderer('core');
 334  
 335          $formattedquestions = array_map(function($question) use ($context, $renderer) {
 336              $exporter = new \core_question\external\question_summary_exporter($question, ['context' => $context]);
 337              return $exporter->export($renderer);
 338          }, $questions);
 339  
 340          return [
 341              'totalcount' => $totalcount,
 342              'questions' => $formattedquestions
 343          ];
 344      }
 345  
 346      /**
 347       * Returns description of method result value.
 348       */
 349      public static function  get_random_question_summaries_returns() {
 350          return new external_single_structure([
 351              'totalcount' => new external_value(PARAM_INT, 'total number of questions in result set'),
 352              'questions' => new external_multiple_structure(
 353                  \core_question\external\question_summary_exporter::get_read_structure()
 354              )
 355          ]);
 356      }
 357  }