Autoría | Ultima modificación | Ver Log |
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle 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.
//
// Moodle 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 Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace mod_questionnaire\question;
use mod_questionnaire\edit_question_form;
use mod_questionnaire\responsetype\response\response;
use \questionnaire;
defined('MOODLE_INTERNAL') || die();
use \html_writer;
/**
* This file contains the parent class for questionnaire question types.
*
* @author Mike Churchward
* @copyright 2016 onward Mike Churchward (mike.churchward@poetopensource.org)
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @package mod_questionnaire
*/
// Constants.
define('QUESCHOOSE', 0);
define('QUESYESNO', 1);
define('QUESTEXT', 2);
define('QUESESSAY', 3);
define('QUESRADIO', 4);
define('QUESCHECK', 5);
define('QUESDROP', 6);
define('QUESRATE', 8);
define('QUESDATE', 9);
define('QUESNUMERIC', 10);
define('QUESSLIDER', 11);
define('QUESPAGEBREAK', 99);
define('QUESSECTIONTEXT', 100);
global $idcounter, $CFG;
$idcounter = 0;
require_once($CFG->dirroot.'/mod/questionnaire/locallib.php');
/**
* Class for describing a question
*
* @author Mike Churchward
* @copyright 2016 onward Mike Churchward (mike.churchward@poetopensource.org)
* @package mod_questionnaire
*/
abstract class question {
// Class Properties.
/** @var int $id The database id of this question. */
public $id = 0;
/** @var int $surveyid The database id of the survey this question belongs to. */
public $surveyid = 0;
/** @var string $name The name of this question. */
public $name = '';
/** @var string $type The name of the question type. */
public $type = '';
/** @var array $choices Array holding any choices for this question. */
public $choices = [];
/** @var array $dependencies Array holding any dependencies for this question. */
public $dependencies = [];
/** @var string $responsetable The table name for responses. */
public $responsetable = '';
/** @var int $length The length field. */
public $length = 0;
/** @var int $precise The precision field. */
public $precise = 0;
/** @var int $position Position in the questionnaire */
public $position = 0;
/** @var string $content The question's content. */
public $content = '';
/** @var string $allchoices The list of all question's choices. */
public $allchoices = '';
/** @var boolean $required The required flag. */
public $required = 'n';
/** @var boolean $deleted The deleted flag. */
public $deleted = 'n';
/** @var mixed $extradata Any custom data for the question type. */
public $extradata = '';
/** @var array $qtypenames List of all question names. */
private static $qtypenames = [
QUESYESNO => 'yesno',
QUESTEXT => 'text',
QUESESSAY => 'essay',
QUESRADIO => 'radio',
QUESCHECK => 'check',
QUESDROP => 'drop',
QUESRATE => 'rate',
QUESDATE => 'date',
QUESNUMERIC => 'numerical',
QUESPAGEBREAK => 'pagebreak',
QUESSECTIONTEXT => 'sectiontext',
QUESSLIDER => 'slider',
];
/** @var array $notifications Array of extra messages for display purposes. */
private $notifications = [];
// Class Methods.
/**
* The class constructor
* @param int $id
* @param \stdClass $question
* @param \context $context
* @param array $params
*/
public function __construct($id = 0, $question = null, $context = null, $params = []) {
global $DB;
static $qtypes = null;
if ($qtypes === null) {
$qtypes = $DB->get_records('questionnaire_question_type', [], 'typeid',
'typeid, type, has_choices, response_table') ?? [];
}
if ($id) {
$question = $DB->get_record('questionnaire_question', ['id' => $id]);
}
if (is_object($question)) {
$this->id = $question->id;
$this->surveyid = $question->surveyid;
$this->name = $question->name;
$this->length = $question->length;
$this->precise = $question->precise;
$this->position = $question->position;
$this->content = $question->content;
$this->required = $question->required;
$this->deleted = $question->deleted;
$this->extradata = $question->extradata;
$this->type_id = $question->type_id;
$this->type = $qtypes[$this->type_id]->type;
$this->responsetable = $qtypes[$this->type_id]->response_table;
if (!empty($question->choices)) {
$this->choices = $question->choices;
} else if ($qtypes[$this->type_id]->has_choices == 'y') {
$this->get_choices();
}
// Added for dependencies.
$this->get_dependencies();
}
$this->context = $context;
foreach ($params as $property => $value) {
$this->$property = $value;
}
if ($respclass = $this->responseclass()) {
$this->responsetype = new $respclass($this);
}
}
/**
* Short name for this question type - no spaces, etc..
* @return string
*/
abstract public function helpname();
/**
* Build a question from data.
* @param int $qtype
* @param int|array $qdata
* @param \stdClass $context
* @return mixed
*/
public static function question_builder($qtype, $qdata = null, $context = null) {
$qclassname = '\\mod_questionnaire\\question\\'.self::qtypename($qtype);
$qid = 0;
if (!empty($qdata) && is_array($qdata)) {
$qdata = (object)$qdata;
} else if (!empty($qdata) && is_int($qdata)) {
$qid = $qdata;
}
return new $qclassname($qid, $qdata, $context, ['type_id' => $qtype]);
}
/**
* Return the different question type names.
* @param int $qtype
* @return string
*/
public static function qtypename($qtype) {
if (array_key_exists($qtype, self::$qtypenames)) {
return self::$qtypenames[$qtype];
} else {
return('');
}
}
/**
* Return all of the different question type names.
* @return array
*/
public static function qtypenames() {
return self::$qtypenames;
}
/**
* Override and return true if the question has choices.
* @return bool
*/
public function has_choices() {
return false;
}
/**
* Load any choices into the object.
* @throws \dml_exception
*/
private function get_choices() {
global $DB;
if ($choices = $DB->get_records('questionnaire_quest_choice', ['question_id' => $this->id], 'id ASC')) {
foreach ($choices as $choice) {
$this->choices[$choice->id] = \mod_questionnaire\question\choice::create_from_data($choice);
}
} else {
$this->choices = [];
}
}
/**
* Return true if this question has been marked as required.
* @return bool
*/
public function required() {
return ($this->required == 'y');
}
/**
* Return true if the question has defined dependencies.
* @return bool
*/
public function has_dependencies() {
return !empty($this->dependencies);
}
/**
* Override this and return true if the question type allows dependent questions.
* @return bool
*/
public function allows_dependents() {
return false;
}
/**
* Load any dependencies.
*/
private function get_dependencies() {
global $DB;
$this->dependencies = [];
if ($dependencies = $DB->get_records('questionnaire_dependency',
['questionid' => $this->id , 'surveyid' => $this->surveyid], 'id ASC')) {
foreach ($dependencies as $dependency) {
$this->dependencies[$dependency->id] = new \stdClass();
$this->dependencies[$dependency->id]->dependquestionid = $dependency->dependquestionid;
$this->dependencies[$dependency->id]->dependchoiceid = $dependency->dependchoiceid;
$this->dependencies[$dependency->id]->dependlogic = $dependency->dependlogic;
$this->dependencies[$dependency->id]->dependandor = $dependency->dependandor;
}
}
}
/**
* Returns an array of dependency options for the question as an array of id value / display value pairs. Override in specific
* question types that support this differently.
* @return array An array of valid pair options.
*/
protected function get_dependency_options() {
$options = [];
if ($this->allows_dependents() && $this->has_choices()) {
foreach ($this->choices as $key => $choice) {
$contents = questionnaire_choice_values($choice->content);
if (!empty($contents->modname)) {
$choice->content = $contents->modname;
} else if (!empty($contents->title)) { // Must be an image; use its title for the dropdown list.
$choice->content = format_string($contents->title);
} else {
$choice->content = format_string($contents->text);
}
$options[$this->id . ',' . $key] = $this->name . '->' . $choice->content;
}
}
return $options;
}
/**
* Return true if all dependencies or this question have been fulfilled, or there aren't any.
* @param int $rid The response ID to check.
* @param array $questions An array containing all possible parent question objects.
* @return bool
*/
public function dependency_fulfilled($rid, $questions) {
if (!$this->has_dependencies()) {
$fulfilled = true;
} else {
foreach ($this->dependencies as $dependency) {
$choicematches = $questions[$dependency->dependquestionid]->response_has_choice($rid, $dependency->dependchoiceid);
// Note: dependencies are sorted, first all and-dependencies, then or-dependencies.
if ($dependency->dependandor == 'and') {
$dependencyandfulfilled = false;
// This answer given.
if (($dependency->dependlogic == 1) && $choicematches) {
$dependencyandfulfilled = true;
}
// This answer NOT given.
if (($dependency->dependlogic == 0) && !$choicematches) {
$dependencyandfulfilled = true;
}
// Something mandatory not fulfilled? Stop looking and continue to next question.
if ($dependencyandfulfilled == false) {
break;
}
// In case we have no or-dependencies.
$dependencyorfulfilled = true;
}
// Note: dependencies are sorted, first all and-dependencies, then or-dependencies.
if ($dependency->dependandor == 'or') {
$dependencyorfulfilled = false;
// To reach this point, the and-dependencies have all been fultilled or do not exist, so set them ok.
$dependencyandfulfilled = true;
// This answer given.
if (($dependency->dependlogic == 1) && $choicematches) {
$dependencyorfulfilled = true;
}
// This answer NOT given.
if (($dependency->dependlogic == 0) && !$choicematches) {
$dependencyorfulfilled = true;
}
// Something fulfilled? A single match is sufficient so continue to next question.
if ($dependencyorfulfilled == true) {
break;
}
}
}
$fulfilled = ($dependencyandfulfilled && $dependencyorfulfilled);
}
return $fulfilled;
}
/**
* Return the responsetype table for this question.
* @return string
*/
public function response_table() {
return $this->responsetype->response_table();
}
/**
* Return true if the specified response for this question contains the specified choice.
* @param int $rid
* @param int $choiceid
* @return bool
*/
public function response_has_choice($rid, $choiceid) {
global $DB;
$choiceval = $this->responsetype->transform_choiceid($choiceid);
return $DB->record_exists($this->response_table(),
['response_id' => $rid, 'question_id' => $this->id, 'choice_id' => $choiceval]);
}
/**
* Insert response data method.
* @param \stdClass $responsedata All of the responsedata.
* @return bool
*/
public function insert_response($responsedata) {
if (isset($this->responsetype) && is_object($this->responsetype) &&
is_subclass_of($this->responsetype, '\\mod_questionnaire\\responsetype\\responsetype')) {
return $this->responsetype->insert_response($responsedata);
} else {
return false;
}
}
/**
* Get results data method.
* @param array|bool $rids
* @return array|false
*/
public function get_results($rids = false) {
if (isset ($this->responsetype) && is_object($this->responsetype) &&
is_subclass_of($this->responsetype, '\\mod_questionnaire\\responsetype\\responsetype')) {
return $this->responsetype->get_results($rids);
} else {
return false;
}
}
/**
* Display results method.
* @param bool $rids
* @param string $sort
* @param bool $anonymous
* @return false|string
*/
public function display_results($rids=false, $sort='', $anonymous=false) {
if (isset ($this->responsetype) && is_object($this->responsetype) &&
is_subclass_of($this->responsetype, '\\mod_questionnaire\\responsetype\\responsetype')) {
return $this->responsetype->display_results($rids, $sort, $anonymous);
} else {
return false;
}
}
/**
* Add a notification.
* @param string $message
*/
public function add_notification($message) {
$this->notifications[] = $message;
}
/**
* Get any notifications.
* @return array | boolean The notifications array or false.
*/
public function get_notifications() {
if (empty($this->notifications)) {
return false;
} else {
return $this->notifications;
}
}
/**
* Each question type must define its response class.
* @return object The response object based off of questionnaire_response_base.
*/
abstract protected function responseclass();
/**
* True if question type allows responses.
* @return bool
*/
public function supports_responses() {
return !empty($this->responseclass());
}
/**
* True if question type supports feedback options. False by default.
* @return bool
*/
public function supports_feedback() {
return false;
}
/**
* True if question type supports feedback scores and weights. Same as supports_feedback() by default.
* @return bool
*/
public function supports_feedback_scores() {
return $this->supports_feedback();
}
/**
* True if the question supports feedback and has valid settings for feedback. Override if the default logic is not enough.
* @return bool
*/
public function valid_feedback() {
if ($this->supports_feedback() && $this->has_choices() && $this->required() && !empty($this->name)) {
foreach ($this->choices as $choice) {
if ($choice->value != null) {
return true;
}
}
}
return false;
}
/**
* Provide the feedback scores for all requested response id's. This should be provided only by questions that provide feedback.
* @param array $rids
* @return array|bool
*/
public function get_feedback_scores(array $rids) {
if ($this->valid_feedback() && isset($this->responsetype) && is_object($this->responsetype) &&
is_subclass_of($this->responsetype, '\\mod_questionnaire\\responsetype\\responsetype')) {
return $this->responsetype->get_feedback_scores($rids);
} else {
return false;
}
}
/**
* Get the maximum score possible for feedback if appropriate. Override if default behaviour is not correct.
* @return int|bool
*/
public function get_feedback_maxscore() {
if ($this->valid_feedback()) {
$maxscore = 0;
foreach ($this->choices as $choice) {
if (isset($choice->value) && ($choice->value != null)) {
if ($choice->value > $maxscore) {
$maxscore = $choice->value;
}
}
}
} else {
$maxscore = false;
}
return $maxscore;
}
/**
* Check question's form data for complete response.
* @param \stdClass $responsedata The data entered into the response.
* @return bool
*/
public function response_complete($responsedata) {
if (is_a($responsedata, 'mod_questionnaire\responsetype\response\response')) {
// If $responsedata is a response object, look through the answers.
if (isset($responsedata->answers[$this->id]) && !empty($responsedata->answers[$this->id])) {
$answer = $responsedata->answers[$this->id][0];
if (!empty($answer->choiceid) && isset($this->choices[$answer->choiceid]) &&
$this->choices[$answer->choiceid]->is_other_choice()) {
$answered = !empty($answer->value);
} else {
$answered = (!empty($answer->choiceid) || !empty($answer->value));
}
} else {
$answered = false;
}
} else {
// If $responsedata is webform data, check that its not empty.
$answered = isset($responsedata->{'q'.$this->id}) && ($responsedata->{'q'.$this->id} != '');
}
return !($this->required() && ($this->deleted == 'n') && !$answered);
}
/**
* Check question's form data for valid response. Override this if type has specific format requirements.
* @param \stdClass $responsedata The data entered into the response.
* @return bool
*/
public function response_valid($responsedata) {
return true;
}
/**
* Update data record from object or optional question data.
* @param \stdClass $questionrecord An object with all updated question record data.
* @param bool $updatechoices True if choices should also be updated.
*/
public function update($questionrecord = null, $updatechoices = true) {
global $DB;
if ($questionrecord === null) {
$questionrecord = new \stdClass();
$questionrecord->id = $this->id;
$questionrecord->surveyid = $this->surveyid;
$questionrecord->name = $this->name;
$questionrecord->type_id = $this->type_id;
$questionrecord->result_id = $this->result_id;
$questionrecord->length = $this->length;
$questionrecord->precise = $this->precise;
$questionrecord->position = $this->position;
$questionrecord->content = $this->content;
$questionrecord->required = $this->required;
$questionrecord->deleted = $this->deleted;
$questionrecord->extradata = $this->extradata;
$questionrecord->dependquestion = $this->dependquestion;
$questionrecord->dependchoice = $this->dependchoice;
} else {
// Make sure the "id" field is this question's.
if (isset($this->qid) && ($this->qid > 0)) {
$questionrecord->id = $this->qid;
} else {
$questionrecord->id = $this->id;
}
}
$DB->update_record('questionnaire_question', $questionrecord);
if ($updatechoices && $this->has_choices()) {
$this->update_choices();
}
}
/**
* Add the question to the database from supplied arguments.
* @param \stdClass $questionrecord The required data for adding the question.
* @param array $choicerecords An array of choice records with 'content' and 'value' properties.
* @param boolean $calcposition Whether or not to calculate the next available position in the survey.
*/
public function add($questionrecord, array $choicerecords = null, $calcposition = true) {
global $DB;
// Create new question.
if ($calcposition) {
// Set the position to the end.
$sql = 'SELECT MAX(position) as maxpos '.
'FROM {questionnaire_question} '.
'WHERE surveyid = ? AND deleted = ?';
$params = ['surveyid' => $questionrecord->surveyid, 'deleted' => 'n'];
if ($record = $DB->get_record_sql($sql, $params)) {
$questionrecord->position = $record->maxpos + 1;
} else {
$questionrecord->position = 1;
}
}
// Make sure we add all necessary data.
if (!isset($questionrecord->type_id) || empty($questionrecord->type_id)) {
$questionrecord->type_id = $this->type_id;
}
$this->qid = $DB->insert_record('questionnaire_question', $questionrecord);
if ($this->has_choices() && !empty($choicerecords)) {
foreach ($choicerecords as $choicerecord) {
$choicerecord->question_id = $this->qid;
$this->add_choice($choicerecord);
}
}
}
/**
* Update all choices.
* @return bool
*/
public function update_choices() {
$retvalue = true;
if ($this->has_choices() && isset($this->choices)) {
// Need to fix this messed-up qid/id issue.
if (isset($this->qid) && ($this->qid > 0)) {
$qid = $this->qid;
} else {
$qid = $this->id;
}
foreach ($this->choices as $key => $choice) {
$choicerecord = new \stdClass();
$choicerecord->id = $key;
$choicerecord->question_id = $qid;
$choicerecord->content = $choice->content;
$choicerecord->value = $choice->value;
$retvalue &= $this->update_choice($choicerecord);
}
}
return $retvalue;
}
/**
* Update the choice with the choicerecord.
* @param \stdClass $choicerecord
* @return bool
*/
public function update_choice($choicerecord) {
global $DB;
return $DB->update_record('questionnaire_quest_choice', $choicerecord);
}
/**
* Add a new choice to the database.
* @param \stdClass $choicerecord
* @return bool
*/
public function add_choice($choicerecord) {
global $DB;
$retvalue = true;
if ($cid = $DB->insert_record('questionnaire_quest_choice', $choicerecord)) {
$this->choices[$cid] = new \stdClass();
$this->choices[$cid]->content = $choicerecord->content;
$this->choices[$cid]->value = isset($choicerecord->value) ? $choicerecord->value : null;
} else {
$retvalue = false;
}
return $retvalue;
}
/**
* Delete the choice from the question object and the database.
* @param int|\stdClass $choice Either the integer id of the choice, or the choice record.
*/
public function delete_choice($choice) {
$retvalue = true;
if (is_int($choice)) {
$cid = $choice;
} else {
$cid = $choice->id;
}
if (\mod_questionnaire\question\choice::delete_from_db_by_id($cid)) {
unset($this->choices[$cid]);
} else {
$retvalue = false;
}
return $retvalue;
}
/**
* Insert extradata field into db. This will be stored as a string. If a question needs a different format, override this.
* @param string $extradata
* @return bool
*/
public function insert_extradata($extradata) {
global $DB;
return $DB->set_field('questionnaire_question', 'extradata', $extradata, ['id' => $this->id]);
}
/**
* Update the dependency record.
* @param \stdClass $dependencyrecord
* @return bool
*/
public function update_dependency($dependencyrecord) {
global $DB;
return $DB->update_record('questionnaire_dependency', $dependencyrecord);
}
/**
* Add a dependency record.
* @param \stdClass $dependencyrecord
* @return bool
*/
public function add_dependency($dependencyrecord) {
global $DB;
$retvalue = true;
if ($did = $DB->insert_record('questionnaire_dependency', $dependencyrecord)) {
$this->dependencies[$did] = new \stdClass();
$this->dependencies[$did]->dependquestionid = $dependencyrecord->dependquestionid;
$this->dependencies[$did]->dependchoiceid = $dependencyrecord->dependchoiceid;
$this->dependencies[$did]->dependlogic = $dependencyrecord->dependlogic;
$this->dependencies[$did]->dependandor = $dependencyrecord->dependandor;
} else {
$retvalue = false;
}
return $retvalue;
}
/**
* Delete the dependency from the question object and the database.
* @param int|\stdClass $dependency Either the integer id of the dependency, or the dependency record.
*/
public function delete_dependency($dependency) {
global $DB;
$retvalue = true;
if (is_int($dependency)) {
$did = $dependency;
} else {
$did = $dependency->id;
}
if ($DB->delete_records('questionnaire_dependency', ['id' => $did])) {
unset($this->dependencies[$did]);
} else {
$retvalue = false;
}
return $retvalue;
}
/**
* Set the question required field in the object and database.
* @param bool $required Whether question should be required or not.
*/
public function set_required($required) {
global $DB;
$rval = $required ? 'y' : 'n';
// Need to fix this messed-up qid/id issue.
if (isset($this->qid) && ($this->qid > 0)) {
$qid = $this->qid;
} else {
$qid = $this->id;
}
$this->required = $rval;
return $DB->set_field('questionnaire_question', 'required', $rval, ['id' => $qid]);
}
/**
* Question specific display method.
* @param \stdClass $formdata
* @param array $descendantsdata
* @param bool $blankquestionnaire
*
*/
abstract protected function question_survey_display($formdata, $descendantsdata, $blankquestionnaire);
/**
* Question specific response display method.
* @param \stdClass $data
*
*/
abstract protected function response_survey_display($data);
/**
* Override and return a form template if provided. Output of question_survey_display is iterpreted based on this.
* @return bool|string
*/
public function question_template() {
return false;
}
/**
* Override and return a form template if provided. Output of response_survey_display is iterpreted based on this.
* @return bool|string
*/
public function response_template() {
return false;
}
/**
* Override and return a form template if provided. Output of results_output is iterpreted based on this.
* @param bool $pdf
* @return bool|string
*/
public function results_template($pdf = false) {
if (isset ($this->responsetype) && is_object($this->responsetype) &&
is_subclass_of($this->responsetype, '\\mod_questionnaire\\responsetype\\responsetype')) {
return $this->responsetype->results_template($pdf);
} else {
return false;
}
}
/**
* Get the output for question renderers / templates.
* @param \mod_questionnaire\responsetype\response\response $response
* @param boolean $blankquestionnaire
* @param array $dependants Array of all questions/choices depending on this question.
* @param int $qnum
* @return \stdClass
*/
public function question_output($response, $blankquestionnaire, $dependants=[], $qnum='') {
$pagetags = $this->questionstart_survey_display($qnum, $response);
$pagetags->qformelement = $this->question_survey_display($response, $dependants, $blankquestionnaire);
return $pagetags;
}
/**
* Get the output for question renderers / templates.
* @param \mod_questionnaire\responsetype\response\response $response
* @param string $qnum
* @return \stdClass
*/
public function response_output($response, $qnum='') {
$pagetags = $this->questionstart_survey_display($qnum, $response);
$pagetags->qformelement = $this->response_survey_display($response);
return $pagetags;
}
/**
* Get the output for the start of the questions in a survey.
* @param int $qnum
* @param \mod_questionnaire\responsetype\response\response $response
* @return \stdClass
*/
public function questionstart_survey_display($qnum, $response=null) {
global $OUTPUT, $SESSION, $questionnaire, $PAGE;
$pagetags = new \stdClass();
$currenttab = $SESSION->questionnaire->current_tab;
$pagetype = $PAGE->pagetype;
$skippedclass = '';
// If no questions autonumbering.
$nonumbering = false;
if (!$questionnaire->questions_autonumbered()) {
$qnum = '';
$nonumbering = true;
}
// For now, check what the response type is until we've got it all refactored.
if ($response instanceof \mod_questionnaire\responsetype\response\response) {
$skippedquestion = !isset($response->answers[$this->id]);
} else {
$skippedquestion = !empty($response) && !isset($response->{'q'.$this->id});
}
// If we are on report page and this questionnaire has dependquestions and this question was skipped.
if (($pagetype == 'mod-questionnaire-myreport' || $pagetype == 'mod-questionnaire-report') &&
($nonumbering == false) && !empty($this->dependencies) && $skippedquestion) {
$skippedclass = ' unselected';
$qnum = '<span class="'.$skippedclass.'">('.$qnum.')</span>';
}
// In preview mode, hide children questions that have not been answered.
// In report mode, If questionnaire is set to no numbering,
// also hide answers to questions that have not been answered.
$displayclass = 'qn-container';
if ($pagetype == 'mod-questionnaire-preview' || ($nonumbering &&
($currenttab == 'mybyresponse' || $currenttab == 'individualresp'))) {
// This needs to be done to ensure all dependency data is loaded.
// TODO - Perhaps this should be a function called by the questionnaire after it loads all questions?
$questionnaire->load_parents($this);
// Want this to come from the renderer, meaning we need $questionnaire.
$pagetags->dependencylist = $questionnaire->renderer->get_dependency_html($this->id, $this->dependencies);
}
$pagetags->fieldset = (object)['id' => $this->id, 'class' => $displayclass];
// Do not display the info box for the label question type.
if ($this->type_id != QUESSECTIONTEXT) {
if (!$nonumbering) {
$pagetags->qnum = $qnum;
}
$required = '';
if ($this->required()) {
$required = html_writer::start_tag('div', ['class' => 'accesshide']);
$required .= get_string('required', 'questionnaire');
$required .= html_writer::end_tag('div');
$required .= html_writer::empty_tag('img', ['class' => 'req', 'title' => get_string('required', 'questionnaire'),
'alt' => get_string('required', 'questionnaire'), 'src' => $OUTPUT->image_url('req')]);
}
$pagetags->required = $required; // Need to replace this with better renderer / template?
}
// If question text is "empty", i.e. 2 non-breaking spaces were inserted, empty it.
if ($this->content == '<p> </p>') {
$this->content = '';
}
$pagetags->skippedclass = $skippedclass;
if ($this->type_id == QUESNUMERIC || $this->type_id == QUESTEXT) {
$pagetags->label = (object)['for' => self::qtypename($this->type_id) . $this->id];
} else if ($this->type_id == QUESDROP) {
$pagetags->label = (object)['for' => self::qtypename($this->type_id) . $this->name];
} else if ($this->type_id == QUESESSAY) {
$pagetags->label = (object)['for' => 'edit-q' . $this->id];
}
$options = ['noclean' => true, 'para' => false, 'filter' => true, 'context' => $this->context, 'overflowdiv' => true];
$content = format_text(file_rewrite_pluginfile_urls($this->content, 'pluginfile.php',
$this->context->id, 'mod_questionnaire', 'question', $this->id), FORMAT_HTML, $options);
$pagetags->qcontent = $content;
return $pagetags;
}
// This section contains functions for editing the specific question types.
// There are required methods that must be implemented, and helper functions that can be used.
// Required functions that can be overridden by the question type.
/**
* Override this, or any of the internal methods, to provide specific form data for editing the question type.
* The structure of the elements here is the default layout for the question form.
* @param edit_question_form $form The main moodleform object.
* @param questionnaire $questionnaire The questionnaire being edited.
* @return bool
*/
public function edit_form(edit_question_form $form, questionnaire $questionnaire) {
$mform =& $form->_form;
$this->form_header($mform);
$this->form_name($mform);
$this->form_required($mform);
$this->form_length($mform);
$this->form_precise($mform);
$this->form_question_text($mform, ($form->_customdata['modcontext'] ?? ''));
if ($this->has_choices()) {
// This is used only by the question editing form.
$this->allchoices = $this->form_choices($mform);
}
$this->form_extradata($mform);
// Added for advanced dependencies, parameter $editformobject is needed to use repeat_elements.
if ($questionnaire->navigate > 0) {
$this->form_dependencies($form, $questionnaire->questions);
}
// Exclude the save/cancel buttons from any collapsing sections.
$mform->closeHeaderBefore('buttonar');
// Hidden fields.
$mform->addElement('hidden', 'id', 0);
$mform->setType('id', PARAM_INT);
$mform->addElement('hidden', 'qid', 0);
$mform->setType('qid', PARAM_INT);
$mform->addElement('hidden', 'sid', 0);
$mform->setType('sid', PARAM_INT);
$mform->addElement('hidden', 'type_id', $this->type_id);
$mform->setType('type_id', PARAM_INT);
$mform->addElement('hidden', 'action', 'question');
$mform->setType('action', PARAM_ALPHA);
// Buttons.
$buttonarray[] = &$mform->createElement('submit', 'submitbutton', get_string('savechanges'));
if (isset($this->qid)) {
$buttonarray[] = &$mform->createElement('submit', 'makecopy', get_string('saveasnew', 'questionnaire'));
}
$buttonarray[] = &$mform->createElement('cancel');
$mform->addGroup($buttonarray, 'buttonar', '', [' '], false);
return true;
}
/**
* Add the form header.
* @param \MoodleQuickForm $mform
* @param string $helpname
*/
protected function form_header(\MoodleQuickForm $mform, $helpname = '') {
// Display different messages for new question creation and existing question modification.
if (isset($this->qid) && !empty($this->qid)) {
$header = get_string('editquestion', 'questionnaire', questionnaire_get_type($this->type_id));
} else {
$header = get_string('addnewquestion', 'questionnaire', questionnaire_get_type($this->type_id));
}
if (empty($helpname)) {
$helpname = $this->helpname();
}
$mform->addElement('header', 'questionhdredit', $header);
$mform->addHelpButton('questionhdredit', $helpname, 'questionnaire');
}
/**
* Add the form name field.
* @param \MoodleQuickForm $mform
* @return \MoodleQuickForm
*/
protected function form_name(\MoodleQuickForm $mform) {
$mform->addElement('text', 'name', get_string('optionalname', 'questionnaire'),
['size' => '30', 'maxlength' => '30']);
$mform->setType('name', PARAM_TEXT);
$mform->addHelpButton('name', 'optionalname', 'questionnaire');
return $mform;
}
/**
* Add the form required field.
* @param \MoodleQuickForm $mform
* @return \MoodleQuickForm
*/
protected function form_required(\MoodleQuickForm $mform) {
$reqgroup = [];
$reqgroup[] =& $mform->createElement('radio', 'required', '', get_string('yes'), 'y');
$reqgroup[] =& $mform->createElement('radio', 'required', '', get_string('no'), 'n');
$mform->addGroup($reqgroup, 'reqgroup', get_string('required', 'questionnaire'), ' ', false);
$mform->addHelpButton('reqgroup', 'required', 'questionnaire');
return $mform;
}
/**
* Return the length form element.
* @param \MoodleQuickForm $mform
* @param string $helpname
*/
protected function form_length(\MoodleQuickForm $mform, $helpname = '') {
self::form_length_text($mform, $helpname);
}
/**
* Return the precision form element.
* @param \MoodleQuickForm $mform
* @param string $helpname
*/
protected function form_precise(\MoodleQuickForm $mform, $helpname = '') {
self::form_precise_text($mform, $helpname);
}
/**
* Determine form dependencies.
* @param \MoodleQuickForm $form The moodle form to add elements to.
* @param array $questions
* @return bool
*/
protected function form_dependencies($form, $questions) {
// Create a new area for multiple dependencies.
$mform = $form->_form;
$position = ($this->position !== 0) ? $this->position : count($questions) + 1;
$dependencies = [];
$dependencies[''][0] = get_string('choosedots');
foreach ($questions as $question) {
if (($question->position < $position) && !empty($question->name) &&
!empty($dependopts = $question->get_dependency_options())) {
$dependencies[$question->name] = $dependopts;
}
}
$children = [];
if (isset($this->qid)) {
// Use also for the delete dialogue later.
foreach ($questions as $questionlistitem) {
if ($questionlistitem->has_dependencies()) {
foreach ($questionlistitem->dependencies as $key => $outerdependencies) {
if ($outerdependencies->dependquestionid == $this->qid) {
$children[$key] = $outerdependencies;
}
}
}
}
}
if (count($dependencies) > 1) {
$mform->addElement('header', 'dependencies_hdr', get_string('dependencies', 'questionnaire'));
$mform->setExpanded('dependencies_hdr');
$mform->closeHeaderBefore('qst_and_choices_hdr');
$dependenciescountand = 0;
$dependenciescountor = 0;
foreach ($this->dependencies as $dependency) {
if ($dependency->dependandor == "and") {
$dependenciescountand++;
} else if ($dependency->dependandor == "or") {
$dependenciescountor++;
}
}
/* I decided to allow changing dependencies of parent questions, because forcing the editor to remove dependencies
* bottom up, starting at the lowest child question is a pain for large questionnaires.
* So the following "if" becomes the default and the else-branch is completely commented.
* TODO Since the best way to get the list of child questions is currently to click on delete (and choose not to
* delete), one might consider to list the child questions in addition here.
*/
// Area for "must"-criteria.
$mform->addElement('static', 'mandatory', '',
'<div class="dimmed_text">' . get_string('mandatory', 'questionnaire') . '</div>');
$selectand = $mform->createElement('select', 'dependlogic_and', get_string('condition', 'questionnaire'),
[get_string('answernotgiven', 'questionnaire'), get_string('answergiven', 'questionnaire')]);
$selectand->setSelected('1');
$groupitemsand = [];
$groupitemsand[] =& $mform->createElement('selectgroups', 'dependquestions_and',
get_string('parent', 'questionnaire'), $dependencies);
$groupitemsand[] =& $selectand;
$groupand = $mform->createElement('group', 'selectdependencies_and', get_string('dependquestion', 'questionnaire'),
$groupitemsand, ' ', false);
$form->repeat_elements([$groupand], $dependenciescountand + 1, [],
'numdependencies_and', 'adddependencies_and', 2, null, true);
// Area for "can"-criteria.
$mform->addElement('static', 'optional', '',
'<div class="dimmed_text">' . get_string('optional', 'questionnaire') . '</div>');
$selector = $mform->createElement('select', 'dependlogic_or', get_string('condition', 'questionnaire'),
[get_string('answernotgiven', 'questionnaire'), get_string('answergiven', 'questionnaire')]);
$selector->setSelected('1');
$groupitemsor = [];
$groupitemsor[] =& $mform->createElement('selectgroups', 'dependquestions_or',
get_string('parent', 'questionnaire'), $dependencies);
$groupitemsor[] =& $selector;
$groupor = $mform->createElement('group', 'selectdependencies_or', get_string('dependquestion', 'questionnaire'),
$groupitemsor, ' ', false);
$form->repeat_elements([$groupor], $dependenciescountor + 1, [], 'numdependencies_or',
'adddependencies_or', 2, null, true);
}
return true;
}
/**
* Return the question text element.
* @param \MoodleQuickForm $mform
* @param string $context
* @return \MoodleQuickForm
*/
protected function form_question_text(\MoodleQuickForm $mform, $context) {
$editoroptions = ['maxfiles' => EDITOR_UNLIMITED_FILES, 'trusttext' => true, 'context' => $context];
$mform->addElement('editor', 'content', get_string('text', 'questionnaire'), null, $editoroptions);
$mform->setType('content', PARAM_RAW);
$mform->addRule('content', null, 'required', null, 'client');
return $mform;
}
/**
* Add the choices to the form.
* @param \MoodleQuickForm $mform
* @return string
*/
protected function form_choices(\MoodleQuickForm $mform) {
if ($this->has_choices()) {
$numchoices = count($this->choices);
$allchoices = '';
foreach ($this->choices as $choice) {
if (!empty($allchoices)) {
$allchoices .= "\n";
}
$allchoices .= $choice->content;
}
$helpname = $this->helpname();
$mform->addElement('html', '<div class="qoptcontainer">');
$options = ['wrap' => 'virtual', 'class' => 'qopts'];
$mform->addElement('textarea', 'allchoices', get_string('possibleanswers', 'questionnaire'), $options);
$mform->setType('allchoices', PARAM_RAW);
$mform->addRule('allchoices', null, 'required', null, 'client');
$mform->addHelpButton('allchoices', $helpname, 'questionnaire');
$mform->addElement('html', '</div>');
$mform->addElement('hidden', 'num_choices', $numchoices);
$mform->setType('num_choices', PARAM_INT);
}
return $allchoices;
}
/**
* Override if the question uses the extradata field.
* @param \MoodleQuickForm $mform
* @param string $helpname
* @return \MoodleQuickForm
*/
protected function form_extradata(\MoodleQuickForm $mform, $helpname = '') {
$mform->addElement('hidden', 'extradata');
$mform->setType('extradata', PARAM_INT);
return $mform;
}
// Helper functions for commonly used editing functions.
/**
* Add the length element as hidden.
* @param \MoodleQuickForm $mform
* @param int $value
* @return \MoodleQuickForm
*/
public static function form_length_hidden(\MoodleQuickForm $mform, $value = 0) {
$mform->addElement('hidden', 'length', $value);
$mform->setType('length', PARAM_INT);
return $mform;
}
/**
* Add the length element as text.
* @param \MoodleQuickForm $mform
* @param string $helpname
* @param int $value
* @return \MoodleQuickForm
*/
public static function form_length_text(\MoodleQuickForm $mform, $helpname = '', $value = 0) {
$mform->addElement('text', 'length', get_string($helpname, 'questionnaire'), ['size' => '1'], $value);
$mform->setType('length', PARAM_INT);
if (!empty($helpname)) {
$mform->addHelpButton('length', $helpname, 'questionnaire');
}
return $mform;
}
/**
* Add the precise element as hidden.
* @param \MoodleQuickForm $mform
* @param int $value
* @return \MoodleQuickForm
*/
public static function form_precise_hidden(\MoodleQuickForm $mform, $value = 0) {
$mform->addElement('hidden', 'precise', $value);
$mform->setType('precise', PARAM_INT);
return $mform;
}
/**
* Add the precise element as text.
* @param \MoodleQuickForm $mform
* @param string $helpname
* @param int $value
* @return \MoodleQuickForm
* @throws \coding_exception
*/
public static function form_precise_text(\MoodleQuickForm $mform, $helpname = '', $value = 0) {
$mform->addElement('text', 'precise', get_string($helpname, 'questionnaire'), ['size' => '1']);
$mform->setType('precise', PARAM_INT);
if (!empty($helpname)) {
$mform->addHelpButton('precise', $helpname, 'questionnaire');
}
return $mform;
}
/**
* Create and update question data from the forms.
* @param \stdClass $formdata
* @param questionnaire $questionnaire
*/
public function form_update($formdata, $questionnaire) {
global $DB;
$this->form_preprocess_data($formdata);
if (!empty($formdata->qid)) {
// Update existing question.
// Handle any attachments in the content.
$formdata->itemid = $formdata->content['itemid'];
$formdata->format = $formdata->content['format'];
$formdata->content = $formdata->content['text'];
$formdata->content = file_save_draft_area_files($formdata->itemid, $questionnaire->context->id, 'mod_questionnaire',
'question', $formdata->qid, ['subdirs' => true], $formdata->content);
$fields = ['name', 'type_id', 'length', 'precise', 'required', 'content', 'extradata'];
$questionrecord = new \stdClass();
$questionrecord->id = $formdata->qid;
foreach ($fields as $f) {
if (isset($formdata->$f)) {
$questionrecord->$f = trim($formdata->$f);
}
}
$this->update($questionrecord, false);
if ($questionnaire->has_dependencies()) {
questionnaire_check_page_breaks($questionnaire);
}
} else {
// Create new question:
// Need to update any image content after the question is created, so create then update the content.
$formdata->surveyid = $formdata->sid;
$fields = ['surveyid', 'name', 'type_id', 'length', 'precise', 'required', 'position', 'extradata'];
$questionrecord = new \stdClass();
foreach ($fields as $f) {
if (isset($formdata->$f)) {
$questionrecord->$f = trim($formdata->$f);
}
}
$questionrecord->content = '';
$this->add($questionrecord);
// Handle any attachments in the content.
$formdata->itemid = $formdata->content['itemid'];
$formdata->format = $formdata->content['format'];
$formdata->content = $formdata->content['text'];
$content = file_save_draft_area_files($formdata->itemid, $questionnaire->context->id, 'mod_questionnaire',
'question', $this->qid, ['subdirs' => true], $formdata->content);
$DB->set_field('questionnaire_question', 'content', $content, ['id' => $this->qid]);
}
if ($this->has_choices()) {
// Now handle any choice updates.
$cidx = 0;
if (isset($this->choices) && !isset($formdata->makecopy)) {
$oldcount = count($this->choices);
$echoice = reset($this->choices);
$ekey = key($this->choices);
} else {
$oldcount = 0;
}
$newchoices = explode("\n", $formdata->allchoices);
$nidx = 0;
$newcount = count($newchoices);
while (($nidx < $newcount) && ($cidx < $oldcount)) {
if ($newchoices[$nidx] != $echoice->content) {
$choicerecord = new \stdClass();
$choicerecord->id = $ekey;
$choicerecord->question_id = $this->qid;
$choicerecord->content = trim($newchoices[$nidx]);
$r = preg_match_all("/^(\d{1,2})(=.*)$/", $newchoices[$nidx], $matches);
// This choice has been attributed a "score value" OR this is a rate question type.
if ($r) {
$newscore = $matches[1][0];
$choicerecord->value = $newscore;
} else { // No score value for this choice.
$choicerecord->value = null;
}
$this->update_choice($choicerecord);
}
$nidx++;
$echoice = next($this->choices);
$ekey = key($this->choices);
$cidx++;
}
while ($nidx < $newcount) {
// New choices.
$choicerecord = new \stdClass();
$choicerecord->question_id = $this->qid;
$choicerecord->content = trim($newchoices[$nidx]);
$r = preg_match_all("/^(\d{1,2})(=.*)$/", $choicerecord->content, $matches);
// This choice has been attributed a "score value" OR this is a rate question type.
if ($r) {
$choicerecord->value = $matches[1][0];
}
$this->add_choice($choicerecord);
$nidx++;
}
while ($cidx < $oldcount) {
end($this->choices);
$ekey = key($this->choices);
$this->delete_choice($ekey);
$cidx++;
}
}
// Now handle the dependencies the same way as choices.
// Shouldn't the MOODLE-API provide this case of insert/update/delete?.
// First handle dependendies updates.
if (!isset($formdata->fixed_deps)) {
if ($this->has_dependencies() && !isset($formdata->makecopy)) {
$oldcount = count($this->dependencies);
$edependency = reset($this->dependencies);
$ekey = key($this->dependencies);
} else {
$oldcount = 0;
}
$cidx = 0;
$nidx = 0;
// All 3 arrays in this object have the same length.
if (isset($formdata->dependquestion)) {
$newcount = count($formdata->dependquestion);
} else {
$newcount = 0;
}
while (($nidx < $newcount) && ($cidx < $oldcount)) {
if ($formdata->dependquestion[$nidx] != $edependency->dependquestionid ||
$formdata->dependchoice[$nidx] != $edependency->dependchoiceid ||
$formdata->dependlogic_cleaned[$nidx] != $edependency->dependlogic ||
$formdata->dependandor[$nidx] != $edependency->dependandor) {
$dependencyrecord = new \stdClass();
$dependencyrecord->id = $ekey;
$dependencyrecord->questionid = $this->qid;
$dependencyrecord->surveyid = $this->surveyid;
$dependencyrecord->dependquestionid = $formdata->dependquestion[$nidx];
$dependencyrecord->dependchoiceid = $formdata->dependchoice[$nidx];
$dependencyrecord->dependlogic = $formdata->dependlogic_cleaned[$nidx];
$dependencyrecord->dependandor = $formdata->dependandor[$nidx];
$this->update_dependency($dependencyrecord);
}
$nidx++;
$edependency = next($this->dependencies);
$ekey = key($this->dependencies);
$cidx++;
}
while ($nidx < $newcount) {
// New dependencies.
$dependencyrecord = new \stdClass();
$dependencyrecord->questionid = $this->qid;
$dependencyrecord->surveyid = $formdata->sid;
$dependencyrecord->dependquestionid = $formdata->dependquestion[$nidx];
$dependencyrecord->dependchoiceid = $formdata->dependchoice[$nidx];
$dependencyrecord->dependlogic = $formdata->dependlogic_cleaned[$nidx];
$dependencyrecord->dependandor = $formdata->dependandor[$nidx];
$this->add_dependency($dependencyrecord);
$nidx++;
}
while ($cidx < $oldcount) {
end($this->dependencies);
$ekey = key($this->dependencies);
$this->delete_dependency($ekey);
$cidx++;
}
}
}
/**
* Any preprocessing of general data.
* @param \stdClass $formdata
* @return bool
*/
protected function form_preprocess_data($formdata) {
if ($this->has_choices()) {
// Eliminate trailing blank lines.
$formdata->allchoices = preg_replace("/(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+/", "\n", $formdata->allchoices);
// Trim to eliminate potential trailing carriage return.
$formdata->allchoices = trim($formdata->allchoices);
$this->form_preprocess_choicedata($formdata);
}
// Dependencies logic does not (yet) need preprocessing, might change with more complex conditions.
// Check, if entries exist and whether they are not only 0 (form elements created but no value selected).
if (isset($formdata->dependquestions_and) &&
!(count(array_keys($formdata->dependquestions_and, 0, true)) == count($formdata->dependquestions_and))) {
for ($i = 0; $i < count($formdata->dependquestions_and); $i++) {
$dependency = explode(",", $formdata->dependquestions_and[$i]);
if ($dependency[0] != 0) {
$formdata->dependquestion[] = $dependency[0];
$formdata->dependchoice[] = $dependency[1];
$formdata->dependlogic_cleaned[] = $formdata->dependlogic_and[$i];
$formdata->dependandor[] = "and";
}
}
}
if (isset($formdata->dependquestions_or) &&
!(count(array_keys($formdata->dependquestions_or, 0, true)) == count($formdata->dependquestions_or))) {
for ($i = 0; $i < count($formdata->dependquestions_or); $i++) {
$dependency = explode(",", $formdata->dependquestions_or[$i]);
if ($dependency[0] != 0) {
$formdata->dependquestion[] = $dependency[0];
$formdata->dependchoice[] = $dependency[1];
$formdata->dependlogic_cleaned[] = $formdata->dependlogic_or[$i];
$formdata->dependandor[] = "or";
}
}
}
return true;
}
/**
* Override this function for question specific choice preprocessing.
* @param \stdClass $formdata
* @return false
*/
protected function form_preprocess_choicedata($formdata) {
if (empty($formdata->allchoices)) {
error (get_string('enterpossibleanswers', 'questionnaire'));
}
return false;
}
/**
* True if question provides mobile support.
* @return bool
*/
public function supports_mobile() {
return false;
}
/**
* Override and return false if not supporting mobile app.
* @param int $qnum
* @param bool $autonum
* @return \stdClass
*/
public function mobile_question_display($qnum, $autonum = false) {
$options = ['noclean' => true, 'para' => false, 'filter' => true,
'context' => $this->context, 'overflowdiv' => true];
$mobiledata = (object)[
'id' => $this->id,
'name' => $this->name,
'type_id' => $this->type_id,
'length' => $this->length,
'content' => format_text(file_rewrite_pluginfile_urls($this->content, 'pluginfile.php', $this->context->id,
'mod_questionnaire', 'question', $this->id), FORMAT_HTML, $options),
'content_stripped' => strip_tags($this->content),
'required' => ($this->required == 'y') ? 1 : 0,
'deleted' => $this->deleted,
'response_table' => $this->responsetable,
'fieldkey' => $this->mobile_fieldkey(),
'precise' => $this->precise,
'qnum' => $qnum,
'errormessage' => get_string('required') . ': ' . $this->name
];
$mobiledata->choices = $this->mobile_question_choices_display();
if ($this->mobile_question_extradata_display()) {
$mobiledata->extradata = json_decode($this->extradata);
}
if ($autonum) {
$mobiledata->content = $qnum . '. ' . $mobiledata->content;
$mobiledata->content_stripped = $qnum . '. ' . $mobiledata->content_stripped;
}
$mobiledata->responses = '';
return $mobiledata;
}
/**
* Override and return false if not supporting mobile app.
* @return array
*/
public function mobile_question_choices_display() {
$choices = [];
$cnum = 0;
if ($this->has_choices()) {
foreach ($this->choices as $choice) {
$choices[$cnum] = clone($choice);
$contents = questionnaire_choice_values($choice->content);
$choices[$cnum]->content = format_text($contents->text, FORMAT_HTML, ['noclean' => true]).$contents->image;
$cnum++;
}
}
return $choices;
}
/**
* Return a field key to be used by the mobile app.
* @param int $choiceid
* @return string
*/
public function mobile_fieldkey($choiceid = 0) {
$choicefield = '';
if ($choiceid !== 0) {
$choicefield = '_' . $choiceid;
}
return 'response_' . $this->type_id . '_' . $this->id . $choicefield;
}
/**
* Return the mobile response data.
* @param response $response
* @return array
*/
public function get_mobile_response_data($response) {
$resultdata = [];
if (isset($response->answers[$this->id][0])) {
$resultdata[$this->mobile_fieldkey()] = $response->answers[$this->id][0]->value;
} else {
$resultdata[$this->mobile_fieldkey()] = false;
}
return $resultdata;
}
/**
* True if question need extradata for mobile app.
*
* @return bool
*/
public function mobile_question_extradata_display() {
return false;
}
/**
* Return the otherdata to be used by the mobile app.
*
* @return array
*/
public function mobile_otherdata() {
return [];
}
}