Does anyone know ...

Does anyone know ...

by Tim Hunt -
Number of replies: 15
Core developers की तस्वीर Documentation writers की तस्वीर Particularly helpful Moodlers की तस्वीर Peer reviewers की तस्वीर Plugin developers की तस्वीर

Suppose you create a matching question in Moodle 2.2.x, with images in the combined feedback, and then backup that course, and restore it. Do the images survive in the new course, or do we have a backup and restore bug here?

(I don't have time to test it myself now, so I am being lazy and just asking here.)

In reply to Tim Hunt

Re: Does anyone know ...

by Pierre Pichet -

I create a match quesion with images in each questions and the feedbacks.

I did not put it in a quiz to test if it will be included in the backup

I created a backup then restore as a new course.

Everything seems to work correctly an givn the question id , new questions were created.

Pierre

P.S. I did not test images in hints

In reply to Pierre Pichet

Re: Does anyone know ...

by Tim Hunt -
Core developers की तस्वीर Documentation writers की तस्वीर Particularly helpful Moodlers की तस्वीर Peer reviewers की तस्वीर Plugin developers की तस्वीर

Great thanks for testing.

The reason I ask is that I was looking for the code that backs up and restores those files, and I could not find it anywhere. (I wanted to write some similar code, and was looking for code I could copy as a starting point.) So, now we have the mystery that it works, but I have no idea how it works आँख मारो

In reply to Tim Hunt

Re: Does anyone know ...

by Pierre Pichet -

"but I have no idea how it works "...

Really मुस्कान

i.e. https://github.com/moodle/moodle/blob/master/backup/moodle2/backup_qtype_plugin.class.php

and you are one of the contributor...

So, have a good weekend.

Pierre

In reply to Pierre Pichet

Re: Does anyone know ...

by Tim Hunt -
Core developers की तस्वीर Documentation writers की तस्वीर Particularly helpful Moodlers की तस्वीर Peer reviewers की तस्वीर Plugin developers की तस्वीर

Yes, exactly! That is the code I was looking at, and also the code in the different question types like match that use combined feedback (https://github.com/moodle/moodle/blob/master/question/type/match/backup/moodle2/backup_qtype_match_plugin.class.php), and I cannot see any code that seems to say "backup the files in the combined feedback". It is very strange.

In reply to Tim Hunt

Re: Does anyone know ...

by Pierre Pichet -
    /**
* Returns all the components and fileareas used by all the installed qtypes
*
* The method introspects each qtype, asking it about fileareas used. Then,
* one 2-level array is returned. 1st level is the component name (qtype_xxxx)
* and 2nd level is one array of filearea => mappings to look
*
* Note that this function is used both in backup and restore, so it is important
* to use the same mapping names (usually, name of the table in singular) always
*
* TODO: Surely this can be promoted to backup_plugin easily and make it to
* work for ANY plugin, not only qtypes (but we don't need it for now)
*/
    public static function get_components_and_fileareas($filter = null) {
        $components = array();
        // Get all the plugins of this type
        $qtypes = get_plugin_list('qtype');
        foreach ($qtypes as $name => $path) {
            // Apply filter if specified
            if (!is_null($filter) && $filter != $name) {
                continue;
            }
            // Calculate the componentname
            $componentname = 'qtype_' . $name;
            // Get the plugin fileareas (all them MUST belong to the same component)
            $classname = 'backup_qtype_' . $name . '_plugin';
            if (class_exists($classname)) {
                $elements = call_user_func(array($classname, 'get_qtype_fileareas'));
                if ($elements) {
                    // If there are elements, add them to $components
                    $components[$componentname] = $elements;
                }
            }
        }
        return $components;
    }
 
    /**
* Returns one array with filearea => mappingname elements for the qtype
*
* Used by {@link get_components_and_fileareas} to know about all the qtype
* files to be processed both in backup and restore.
*/
    public static function get_qtype_fileareas() {
        // By default, return empty array, only qtypes having own fileareas will override this
        return array();
    }
}
backup_qtype_' . $name . '_plugin so backup_qtype_' . 'match' . '_plugin

/**
* Provides the information to backup match questions
*
* @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class backup_qtype_match_plugin extends backup_qtype_plugin {
    /**
* Returns the qtype information to attach to question element
*/
    protected function define_question_plugin_structure() {
 
        // Define the virtual plugin element with the condition to fulfill
        $plugin = $this->get_plugin_element(null, '../../qtype', 'match');
 
        // Create one standard named plugin element (the visible container)
        $pluginwrapper = new backup_nested_element($this->get_recommended_name());
 
        // connect the visible container ASAP
        $plugin->add_child($pluginwrapper);
 
        // Now create the qtype own structures
        $matchoptions = new backup_nested_element('matchoptions', array('id'), array(
            'subquestions', 'shuffleanswers', 'correctfeedback', 'correctfeedbackformat',
            'partiallycorrectfeedback', 'partiallycorrectfeedbackformat',
            'incorrectfeedback', 'incorrectfeedbackformat', 'shownumcorrect'));
 
        $matches = new backup_nested_element('matches');
 
        $match = new backup_nested_element('match', array('id'), array(
            'code', 'questiontext', 'questiontextformat', 'answertext'));
 
        // Now the own qtype tree
        $pluginwrapper->add_child($matchoptions);
        $pluginwrapper->add_child($matches);
        $matches->add_child($match);

So  $pluginwrapper have the necessary names to get the say correctfeedback infos.
for the course where I had some questions the last one being a match the sructure is
                                   [optigroup:base_nested_element:private] => 
                                    [used:base_nested_element:private] => Array
                                        (
                                            [0] => qtype_question_plugin
                                            [1] => plugin_qtype_calculated_question
                                            [2] => answers
                                            [3] => answer
                                            [4] => numerical_units
                                            [5] => numerical_unit
                                            [6] => numerical_options
                                            [7] => numerical_option
                                            [8] => dataset_definitions
                                            [9] => dataset_definition
                                            [10] => dataset_items
                                            [11] => dataset_item
                                            [12] => calculated_records
                                            [13] => calculated_record
                                            [14] => calculated_options
                                            [15] => calculated_option
                                            [16] => plugin_qtype_calculatedmulti_question
                                            [17] => plugin_qtype_calculatedsimple_question
                                            [18] => plugin_qtype_essay_question
                                            [19] => essay
                                            [20] => plugin_qtype_match_question
                                            [21] => matchoptions
                                            [22] => matches
                                            [23] => match
                                        )
and this give a complex structure like

                                   [counter:protected] => 0
                                    [final_elements:base_nested_element:private] => Array
                                        (
                                            [subquestions] => backup_final_element Object
                                                (
                                                    [annotationitem:protected] => 
                                                    [attributes:base_final_element:private] => Array
                                                        (
                                                        )

                                                    [parent:base_final_element:private] => backup_nested_element Object
 *RECURSION*
                                                    [name:base_atom:private] => subquestions
                                                    [value:base_atom:private] => 
                                                    [is_set:base_atom:private] => 
                                                )

                                            [shuffleanswers] => backup_final_element Object
                                                (
                                                    [annotationitem:protected] => 
                                                    [attributes:base_final_element:private] => Array
                                                        (
                                                        )

                                                    [parent:base_final_element:private] => backup_nested_element Object
 *RECURSION*
                                                    [name:base_atom:private] => shuffleanswers
                                                    [value:base_atom:private] => 
                                                    [is_set:base_atom:private] => 
                                                )

                                            [correctfeedback] => backup_final_element Object
                                                (
                                                    [annotationitem:protected] => 
                                                    [attributes:base_final_element:private] => Array
                                                        (
                                                        )

                                                    [parent:base_final_element:private] => backup_nested_element Object
 *RECURSION*
                                                    [name:base_atom:private] => correctfeedback
                                                    [value:base_atom:private] => 
                                                    [is_set:base_atom:private] => 
                                                )

                                            [correctfeedbackformat] => backup_final_element Object
                                                (
                                                    [annotationitem:protected] => 
                                                    [attributes:base_final_element:private] => Array
                                                        (
                                                        )

                                                    [parent:base_final_element:private] => backup_nested_element Object
 *RECURSION*
                                                    [name:base_atom:private] => correctfeedbackformat
                                                    [value:base_atom:private] => 
                                                    [is_set:base_atom:private] => 
                                                )

                                            [partiallycorrectfeedback] => backup_final_element Object
                                                (
                                                    [annotationitem:protected] => 
                                                    [attributes:base_final_element:private] => Array
                                                        (
                                                        )

                                                    [parent:base_final_element:private] => backup_nested_element Object
 *RECURSION*
                                                    [name:base_atom:private] => partiallycorrectfeedback
                                                    [value:base_atom:private] => 
                                                    [is_set:base_atom:private] => 
                                                )

                                            [partiallycorrectfeedbackformat] => backup_final_element Object
                                                (
                                                    [annotationitem:protected] => 
                                                    [attributes:base_final_element:private] => Array
                                                        (
                                                        )

                                                    [parent:base_final_element:private] => backup_nested_element Object
 *RECURSION*
                                                    [name:base_atom:private] => partiallycorrectfeedbackformat
                                                    [value:base_atom:private] => 
                                                    [is_set:base_atom:private] => 
                                                )

                                            [incorrectfeedback] => backup_final_element Object
                                                (
                                                    [annotationitem:protected] => 
                                                    [attributes:base_final_element:private] => Array
                                                        (
                                                        )

                                                    [parent:base_final_element:private] => backup_nested_element Object
 *RECURSION*
                                                    [name:base_atom:private] => incorrectfeedback
                                                    [value:base_atom:private] => 
                                                    [is_set:base_atom:private] => 
                                                )

                                            [incorrectfeedbackformat] => backup_final_element Object
                                                (
                                                    [annotationitem:protected] => 
                                                    [attributes:base_final_element:private] => Array
                                                        (
                                                        )

                                                    [parent:base_final_element:private] => backup_nested_element Object
 *RECURSION*
                                                    [name:base_atom:private] => incorrectfeedbackformat
                                                    [value:base_atom:private] => 
                                                    [is_set:base_atom:private] => 
                                                )

                                            [shownumcorrect] => backup_final_element Object
                                                (
                                                    [annotationitem:protected] => 
                                                    [attributes:base_final_element:private] => Array
                                                        (
                                                        )

                                                    [parent:base_final_element:private] => backup_nested_element Object
 *RECURSION*
                                                    [name:base_atom:private] => shownumcorrect
                                                    [value:base_atom:private] => 
                                                    [is_set:base_atom:private] => 
                                                )

                                        )

 the files can be retrieved once you know the name of the filearea under which they are saved like "questiontext" in the files datatable.
the function get_qtype_fileareas() let each questiontype identify the filearea to look for.
 
I intuitively can figure out and can use this structure but in no way I could build it 
मुस्कान
Pierre 
 
 
 
 


In reply to Tim Hunt

Re: Does anyone know ...perhaps everybody

by Pierre Pichet -

Coming to this 24 hours later +-

The answer is that the backup does not use the get_qtype_fileareas()

from
lines 1647 and followings
 
/**
* This step will generate all the file annotations for the already
* annotated (final) question_categories. It calculates the different
* contexts that are being backup and, annotates all the files
* on every context belonging to the "question" component. As far as
* we are always including *complete* question banks it is safe and
* optimal to do that in this (one pass) way
*/
class backup_annotate_all_question_files extends backup_execution_step {
 
    protected function define_execution() {
        global $DB;
 
        // Get all the different contexts for the final question_categories
        // annotated along the whole backup
        $rs = $DB->get_recordset_sql("SELECT DISTINCT qc.contextid
FROM {question_categories} qc
JOIN {backup_ids_temp} bi ON bi.itemid = qc.id
WHERE bi.backupid = ?
AND bi.itemname = 'question_categoryfinal'", array($this->get_backupid()));
        // To know about qtype specific components/fileareas
        $components = backup_qtype_plugin::get_components_and_fileareas();
        // Let's loop
        foreach($rs as $record) {
            // We don't need to specify filearea nor itemid as far as by
            // component and context it's enough to annotate the whole bank files
            // This backups "questiontext", "generalfeedback" and "answerfeedback" fileareas (all them
            // belonging to the "question" component
            backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', null, null);
            // Again, it is enough to pick files only by context and component
            // Do it for qtype specific components
            foreach ($components as $component => $fileareas) {
                backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, $component, null, null);
            }
        }
        $rs->close();
    }
}

As ALL the files related to 'question' are in the component 'question' they are all annotated.
the next step ($filearea is (probably ?) not necessary....)

So its seems that the code use a general shortcut to the the structure... 
 
Pierre


In reply to Pierre Pichet

Re: Does anyone know ...perhaps everybody

by Tim Hunt -
Core developers की तस्वीर Documentation writers की तस्वीर Particularly helpful Moodlers की तस्वीर Peer reviewers की तस्वीर Plugin developers की तस्वीर

Ah, so that explains it. We add all the files belonging to questions without worrying which file area they are in. Now you remind me, I think I do remember discussing it with Eloy when he was implementing this, but it is all a bit mysterious. It would be good to find a way to make the code more self-explanatory.

In reply to Tim Hunt

Re: Does anyone know ...perhaps everybody

by Jean-Michel Védrine -

Thanks Pierre and Tim, I understand a lot of things better after reading this thread.

But what happend for "new" questions types where the component is not "question" but "qtype_xxx" (like "qtype_essay") ?

In reply to Jean-Michel Védrine

Re: Does anyone know ...perhaps everybody

by Tim Hunt -
Core developers की तस्वीर Documentation writers की तस्वीर Particularly helpful Moodlers की तस्वीर Peer reviewers की तस्वीर Plugin developers की तस्वीर

Then the question type has to tell the backup system what is going on, like this: https://github.com/moodle/moodle/blob/master/question/type/essay/backup/moodle2/backup_qtype_essay_plugin.class.php#L73

This is less mysterious, becaues you can search the code for the name of the file area (graderinfo in this case) and you will find the code that causes the files to be saved. My confusion was caused by searching for 'partiallycorrectfeedback' and finding nothing.

In reply to Tim Hunt

Re: Does anyone know ...perhaps everybody

by Pierre Pichet -

However the first requirement is that any questiontype should put its file in the 'question'  files table  component then it will be processed in the general code.

This could also mean that the supplementary code related to filearea is doubling a process already done and should be removed.

I did not have the time to check what happen when working with just one of the 2 processes.

Pierre

 

In reply to Tim Hunt

Re: Does anyone know ...perhaps everybody

by Pierre Pichet -

Looking at the database, things seem more complex as I cannot trace back the images stored in match incorrectfeedbacks.

I have to investigate further tonight

Pierre

P.S. Is there a software that can read .mbz files ? 

In reply to Pierre Pichet

Re: Does anyone know ...perhaps everybody

by Pierre Pichet -

The backup .mbz file contains

<plugin_qtype_match_question>
<matchoptions id="1">
<subquestions>1,2,3</subquestions>
<shuffleanswers>1</shuffleanswers>
<correctfeedback>&lt;p&gt;&lt;img src="https://moodle.org/pluginfile.php/134/mod_forum/post/868553/zonenouvelle.jpg" alt="x" width="577" height="401" /&gt;&lt;/p&gt;</correctfeedback>
<correctfeedbackformat>1</correctfeedbackformat>
<partiallycorrectfeedback>&lt;p&gt;&lt;img src="https://moodle.org/pluginfile.php/134/mod_forum/post/868553/imag010-2.jpg" alt="x" width="520" height="390" /&gt;&lt;/p&gt;</partiallycorrectfeedback>
<partiallycorrectfeedbackformat>1</partiallycorrectfeedbackformat>
<incorrectfeedback>&lt;p&gt;&lt;img src="https://moodle.org/pluginfile.php/134/mod_forum/post/868553/nanoqam-1.jpg" alt="nn" width="375" height="85" /&gt;&lt;/p&gt;</incorrectfeedback>
<incorrectfeedbackformat>1</incorrectfeedbackformat>
<shownumcorrect>0</shownumcorrect>
</matchoptions>
<matches>

so the files are correctly backuped.

In the database the main question feedkacks are stored in the component question and the subquestions images as component qtypematch filearea subquestion

So the code seems correct with the 2 processes.

More later

Pierre

 

 

Pierre