示例#1
0
 public static function impl_formulation_and_controls(qtype_multichoice_renderer_base $renderer, question_attempt $qa, question_display_options $options)
 {
     global $CFG, $PAGE, $OUTPUT;
     // get the current question
     $question = $qa->get_question();
     // get the response
     $response = $question->get_response($qa);
     // answer prefix
     $answer_input_name = $qa->get_qt_field_name('answer');
     // set the ID of the OmeroImageViewer
     $omero_frame_id = self::to_unique_identifier($qa, "omero-image-viewer");
     // set the ID the ModalImagePanel
     $modal_image_panel_id = $omero_frame_id . "-" . qtype_omerocommon_renderer_helper::MODAL_VIEWER_ELEMENT_ID;
     // set the name of the feedback image class
     $feedback_image_class = $omero_frame_id . "-feedbackimage";
     // ID of the question answer container
     $question_answer_container = self::to_unique_identifier($qa, "omero-interactive-question-container");
     // the OMERO image URL
     $omero_image_url = $question->omeroimageurl;
     // extract the omero server
     $OMERO_SERVER = get_config('omero', 'omero_restendpoint');
     // parse the URL to get the image ID and its related params
     $matches = array();
     $pattern = '/\\/([0123456789]+)(\\?.*)?/';
     if (preg_match($pattern, $omero_image_url, $matches)) {
         $omero_image = $matches[1];
         $omero_image_params = count($matches) === 3 ? $matches[2] : "";
     }
     // check
     $multi_correct_answer = $question instanceof qtype_omerointeractive_multi_question;
     $no_max_markers = 0;
     $answer_shape_map = array();
     $shape_grade_map = new stdClass();
     $available_shapes = array();
     $shape_groups = array();
     foreach ($question->get_order($qa) as $ans_idx => $ansid) {
         $ans = $question->answers[$ansid];
         $value = $ans->answer;
         $shape_group = array_map("intval", explode(",", $ans->answer));
         $shape_group_cardinality = count($shape_group);
         // update the max number of markers allowed
         if ($ans->fraction > 0 && !empty($value)) {
             $no_max_markers += $shape_group_cardinality;
         }
         // compute the shape grade
         $shape_group_fraction = 0;
         if ($shape_group_cardinality > 0) {
             $shape_group_fraction = $multi_correct_answer ? $ans->fraction / $shape_group_cardinality : $ans->fraction;
         }
         foreach ($shape_group as $shape_id) {
             $shape_grade_map->{$shape_id} = $shape_group_fraction;
             array_push($available_shapes, $shape_id);
             $answer_shape_map[$shape_id] = $ans;
         }
         array_push($shape_groups, array("shapes" => $shape_group, "shape_grade" => $shape_group_fraction));
     }
     // Fix the max number of markers
     if (!$multi_correct_answer) {
         $no_max_markers = 1;
     }
     $answer_order = "";
     $answer_options = array();
     $feedbackimages = array();
     foreach ($ans->feedbackimages as $image_id => $image) {
         array_push($feedbackimages, $image_id);
     }
     $feedbackimages_html = "";
     if (count($ans->feedbackimages) > 0) {
         $feedbackimages_html = '<div style="display: block; float: right;">[ ' . get_string("see", "qtype_omerocommon") . " ";
         $current_language = current_language();
         foreach ($ans->feedbackimages as $image) {
             $feedbackimages_html .= '<span class="' . $feedback_image_class . '" imageid="' . $image->id . '"' . ' imagename="' . $image->name . '"' . ' imagedescription="' . $image->description_locale_map->{$current_language} . '"' . ' imagelock="' . $image->lock . '"' . ' imageproperties="' . htmlspecialchars(json_encode($image->properties)) . '"' . ' visiblerois="' . implode(",", $image->visiblerois) . '"' . ' focusablerois="' . implode(",", $image->focusablerois) . '"' . '>' . '<i class="glyphicon glyphicon-book" style="margin-left: 2px; margin-right: 5px;"></i>' . '"' . $image->name . '"</span>';
         }
         $feedbackimages_html .= ' ]</div>';
     }
     $feedbackimg = array();
     $classes = array();
     // Show correct/wrong markers
     if ($options->correctness) {
         foreach ($response->markers as $index => $marker) {
             $shape = $response->shapes[$index];
             $isselected = true;
             $hidden = '';
             $answer_options_attributes = array();
             $marker_correction = $hidden;
             $marker_correction .= html_writer::empty_tag('li', $answer_options_attributes);
             $marker_correction_text = get_string("marker", "qtype_omerointeractive") . " " . html_writer::tag("i", " ", array("class" => "glyphicon glyphicon-map-marker roi-shape-info", "roi-shape-id" => $marker->shape_id)) . " ";
             if ($shape !== "none") {
                 $marker_correction_text .= get_string("your_marker_inside", "qtype_omerointeractive") . " " . html_writer::tag("i", " ", array("class" => "glyphicon glyphicon-map-marker roi-shape-info", "roi-shape-id" => $shape->shape_id)) . " [" . $shape->shape_id . "] ";
             } else {
                 $marker_correction_text .= get_string("your_marker_outside", "qtype_omerointeractive");
             }
             $marker_correction_text .= '<span class="pull-right">' . $renderer->feedback_image($renderer->is_right_marker($shape_grade_map, $response, $index)) . html_writer::tag("i", " ", array("class" => "glyphicon glyphicon-eye-open roi-shape-visibility", "roi-shape-id" => $marker->shape_id, "style" => "margin-right: 5px")) . '</span>';
             if ($shape !== "none" && !empty(strip_tags($answer_shape_map[$shape->shape_id]->feedback))) {
                 $shape_answer = $answer_shape_map[$shape->shape_id];
                 $marker_correction_text .= html_writer::tag("div", html_writer::tag("i", " ", array("class" => "pull-left glyphicon glyphicon-record", "style" => "margin-right: 5px")) . format_text($shape_answer->feedback) . $feedbackimages_html, array("class" => "outcome", "style" => "padding: 20px 30px 20px;"));
             }
             $marker_correction .= html_writer::tag('label', $marker_correction_text);
             $answer_options[] = $marker_correction;
             $class = 'r' . $value % 2;
             $answer_options_attributes['checked'] = 'checked';
             $feedback_class = $renderer->feedback_image($renderer->is_right_marker($shape_grade_map, $response, $index));
             $feedbackimg[] = $renderer->feedback_image($renderer->is_right_marker($shape_grade_map, $response, $index));
             $class .= ' ' . $renderer->feedback_class($renderer->is_right_marker($shape_grade_map, $response, $index));
             $classes[] = $class;
         }
     }
     foreach ($question->get_order($qa) as $value => $ansid) {
         $ans = $question->answers[$ansid];
         $answer_order .= $ans->answer;
         $answer_options_attributes['name'] = $renderer->get_input_name($qa, $value);
         $answer_options_attributes['value'] = $renderer->get_input_value($value);
         $answer_options_attributes['id'] = $renderer->get_input_id($qa, $value);
         $hidden = '';
         if (!$options->readonly && $renderer->get_input_type() == 'checkbox') {
             $hidden .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => $answer_options_attributes['name'], 'value' => 0));
         }
     }
     /**
      * Render the question
      */
     $result = '';
     // add the ModalImagePanel template
     $result .= qtype_omerocommon_renderer_helper::modal_viewer(true, true, true, $modal_image_panel_id);
     // main question_answer_container
     $result .= html_writer::start_tag('div', array('id' => $question_answer_container, 'class' => 'ablock'));
     // question text
     $result .= html_writer::tag('div', $question->format_questiontext($qa), array('class' => 'qtext'));
     // viewer of the question image
     $result .= '<div class="image-viewer-with-controls-container">';
     $result .= '<!-- TOOLBAR -->
             <div class="btn-group interactive-player-toolbar pull-right" style="margin-left: 5px;" data-toggle="buttons" aria-pressed="false" autocomplete="true">
                 <a href="#" id="' . self::to_unique_identifier($qa, self::IMAGE_CLEAR_MARKER_CTRL) . '" class="btn btn-default disabled" aria-label="Left Align">
                     <i class="glyphicon glyphicon-remove"></i> ' . get_string('clear_markers', 'qtype_omerointeractive') . '</a>
             </div>
             <div class="btn-group interactive-player-toolbar pull-right" data-toggle="buttons" aria-pressed="false" autocomplete="off">
                 <a href="#" id="' . self::to_unique_identifier($qa, self::IMAGE_ADD_MARKER_CTRL) . '" class="btn btn-default disabled"  aria-label="Left Align">
                     <i class="glyphicon glyphicon-plus"></i> ' . get_string('add_marker', 'qtype_omerointeractive') . '</a>
                 <a href="#" id="' . self::to_unique_identifier($qa, self::IMAGE_EDIT_MARKER_CTRL) . '" class="btn btn-default disabled" aria-label="Left Align">
                     <i class="glyphicon glyphicon-edit"></i> ' . get_string('edit_marker', 'qtype_omerointeractive') . '</a>
             </div>';
     $result .= '<div id="' . self::to_unique_identifier($qa, "graphics_container") . '" class="image-viewer-container" style="position: relative;" >
         <div id="' . self::to_unique_identifier($qa, self::IMAGE_VIEWER_CONTAINER) . '" style="position: absolute; width: 100%; height: 500px; margin: auto; z-index: 0;"></div>
         <canvas id="' . self::to_unique_identifier($qa, 'annotations_canvas') . '" style="position: absolute; width: 100%; height: 500px; margin: auto; z-index: 1;"></canvas>
         <div id="' . self::to_unique_identifier($qa, self::IMAGE_VIEWER_CONTAINER) . '-loading-dialog" class="image-viewer-loading-dialog"></div>
     </div>';
     $image_properties = null;
     $image_properties = json_decode($question->omeroimageproperties);
     $result .= '<div class="image_position_button">' . '<span class="sm">' . ($question->omeroimageproperties ? '<b>(x,y):</b> ' . $image_properties->center->x . ", " . $image_properties->center->y . '<i class="restore-image-center-btn glyphicon glyphicon-screenshot" style="margin-left: 10px;"></i>' : "") . '</span></div>';
     if (!empty($question->focusablerois)) {
         $result .= '<div id="' . self::to_unique_identifier($qa, self::FOCUS_AREAS_CONTAINER) . '" ' . ' class="focus_areas_container">' . '<span class="focus-areas-text">* ' . get_string("focusareas", "qtype_omerointeractive") . '</span> ' . '</div>';
     }
     $result .= '<div id="' . self::to_unique_identifier($qa, self::MARKER_REMOVERS_CONTAINER) . '" ' . ' class="remove_marker_button_group">' . '<span class="yourmarkers-text">* ' . get_string("yourmarkers", "qtype_omerointeractive") . '</span> ' . '</div>';
     $result .= '</div>';
     $answer_input_name = $qa->get_qt_field_name('answer');
     $answer_options_attributes = array('type' => $renderer->get_input_type(), 'name' => $answer_input_name);
     if ($options->readonly) {
         $answer_options_attributes['disabled'] = 'disabled';
     }
     if ($options->correctness && count($response->markers) > 0) {
         $result .= html_writer::start_tag('div', array('class' => 'question-summary hidden'));
         $result .= html_writer::tag('div', count($response->markers) === 1 ? get_string("notice_your_answer", "qtype_omerocommon") : get_string("notice_your_answers", "qtype_omerocommon"), array("class" => "answer-summary-fixed-text"));
         $result .= html_writer::start_tag('ul', array('class' => 'answer'));
         foreach ($answer_options as $key => $answer_option) {
             $result .= html_writer::tag('div', $answer_option, array('class' => $classes[$key])) . "\n";
         }
         $result .= html_writer::end_tag('ul');
         // Answer.
         $result .= html_writer::end_tag('div');
         // Answer.
     }
     $result .= html_writer::tag('div', '', array('id' => $question_answer_container . '-invalidator-panel', 'class' => "invalidator-panel"));
     $result .= html_writer::end_tag('div');
     // Ablock.
     // support for dialog message
     $result .= html_writer::tag('div', '
      <div class="modal fade" id="modal-frame-' . $omero_frame_id . '" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
           <div class="modal-dialog" role="document">
             <div class="modal-content">
               <div class="modal-header">
                 <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
                 <h4 class="modal-title text-warning" id="modal-frame-label-' . $omero_frame_id . '">
                     <i class="glyphicon glyphicon-warning-sign"></i> ' . get_string('validate_warning', 'qtype_omerocommon') . '</h4>
               </div>
               <div class="modal-body text-left">
                 <span id="modal-frame-text-' . $omero_frame_id . '"></span>
               </div>
               <div class="modal-footer text-center">
                 <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
               </div>
           </div>
      </div>');
     $player_config = array("image_id" => $omero_image, "image_properties" => json_decode($question->omeroimageproperties), "image_frame_id" => $omero_frame_id, "image_annotations_canvas_id" => self::to_unique_identifier($qa, "annotations_canvas"), "modal_image_panel_id" => $modal_image_panel_id, "feedback_image_class" => $feedback_image_class, "image_server" => $OMERO_SERVER, "image_viewer_container" => self::to_unique_identifier($qa, self::IMAGE_VIEWER_CONTAINER), "image_navigation_locked" => (bool) $question->omeroimagelocked, "viewer_model_server" => $CFG->omero_image_server, "qname" => $question->name, "question_answer_container" => $question_answer_container, "enable_add_makers_ctrl_id" => self::to_unique_identifier($qa, self::IMAGE_ADD_MARKER_CTRL), "enable_edit_markers_ctrl_id" => self::to_unique_identifier($qa, self::IMAGE_EDIT_MARKER_CTRL), "remove_marker_ctrl_id" => self::to_unique_identifier($qa, self::IMAGE_DEL_MARKER_CTRL), "clear_marker_ctrl_id" => self::to_unique_identifier($qa, self::IMAGE_CLEAR_MARKER_CTRL), "marker_removers_container" => self::to_unique_identifier($qa, self::MARKER_REMOVERS_CONTAINER), "focus_areas_container" => self::to_unique_identifier($qa, self::FOCUS_AREAS_CONTAINER), "answer_input_name" => $answer_input_name, "available_shapes" => $available_shapes, "shape_groups" => $shape_groups, "visible_rois" => empty($question->visiblerois) ? [] : explode(",", $question->visiblerois), "focusable_rois" => empty($question->focusablerois) ? [] : explode(",", $question->focusablerois), "correction_mode" => (bool) $options->correctness, "response" => $response, "answers" => $response, "answer_fraction" => $shape_grade_map, "max_markers" => $no_max_markers);
     $player_config_element_id = self::to_unique_identifier($qa, "viewer-config");
     $result .= html_writer::empty_tag("input", array("id" => $player_config_element_id, "type" => "hidden", "value" => json_encode($player_config)));
     if ($qa->get_state() == question_state::$invalid) {
         $result .= html_writer::nonempty_tag('div', $question->get_validation_error($qa->get_last_qt_data()), array('class' => 'validationerror'));
     }
     $result .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => "answer_input_name", 'value' => $answer_input_name));
     global $PAGE;
     $PAGE->requires->string_for_js('marker', 'qtype_omerointeractive');
     $PAGE->requires->js_call_amd("qtype_omerointeractive/question-player-interactive", "start", array($player_config_element_id));
     return $result;
 }
示例#2
0
    protected function num_parts_correct(question_attempt $qa) {
        if ($qa->get_question()->get_num_selected_choices($qa->get_last_qt_data()) >
                $qa->get_question()->get_num_correct_choices()) {
            return get_string('toomanyselected', 'qtype_multichoice');
        }

        return parent::num_parts_correct($qa);
    }
示例#3
0
    public static function impl_formulation_and_controls(qtype_multichoice_renderer_base $renderer, question_attempt $qa, question_display_options $options)
    {
        global $CFG, $PAGE;
        // get the current question
        $question = $qa->get_question();
        // get the response
        $response = $question->get_response($qa);
        // answer prefix
        $answer_input_name = $qa->get_qt_field_name('answer');
        // set the ID of the OmeroImageViewer
        $omero_frame_id = self::to_unique_identifier($qa, "omero-image-viewer");
        // set the ID of the ModalImagePanel
        $modal_image_panel_id = $omero_frame_id . "-" . qtype_omerocommon_renderer_helper::MODAL_VIEWER_ELEMENT_ID;
        // set the name of feedback image class
        $feedback_image_class = $omero_frame_id . "-feedbackimage";
        // set the ID of the answer container
        $question_answer_container = self::to_unique_identifier($qa, "omero-multichoice-question-container");
        // the OMERO image URL
        $omero_image_url = $question->omeroimageurl;
        // extract the omero server
        $OMERO_SERVER = get_config('omero', 'omero_restendpoint');
        // parse the URL to get the image ID and its related params
        $matches = array();
        $pattern = '/\\/([0123456789]+)(\\?.*)?/';
        if (preg_match($pattern, $omero_image_url, $matches)) {
            $omero_image = $matches[1];
            $omero_image_params = count($matches) === 3 ? $matches[2] : "";
        }
        $no_max_markers = 0;
        $available_answers = array();
        foreach ($question->get_order($qa) as $ans_idx => $ansid) {
            $ans = $question->answers[$ansid];
            $value = $ans->answer;
            array_push($available_answers, $value);
            if ($ans->fraction > 0 && !empty($value)) {
                $shape_group = explode(",", $ans->answer);
                $no_max_markers += count($shape_group);
            }
        }
        $multi_correct_answer = $question instanceof qtype_omeromultichoice_multi_renderer;
        if (!$multi_correct_answer) {
            $no_max_markers = 1;
        }
        $inputname = $qa->get_qt_field_name('answer');
        $inputattributes = array('type' => $renderer->get_input_type(), 'name' => $inputname);
        if ($options->readonly) {
            $inputattributes['disabled'] = 'disabled';
        }
        $radiobuttons = array();
        $feedbackimg = array();
        $feedback = array();
        $classes = array();
        $num_of_response = 0;
        foreach ($question->get_order($qa) as $value => $ansid) {
            $ans = $question->answers[$ansid];
            $inputattributes['name'] = $renderer->get_input_name($qa, $value);
            $inputattributes['value'] = $renderer->get_input_value($value);
            $inputattributes['id'] = $renderer->get_input_id($qa, $value . "XXX");
            $isselected = $question->is_choice_selected($response, $value);
            if ($isselected) {
                $inputattributes['checked'] = 'checked';
                $num_of_response++;
            } else {
                unset($inputattributes['checked']);
            }
            $hidden = '';
            if (!$options->readonly && $renderer->get_input_type() == 'checkbox') {
                $hidden = html_writer::empty_tag('input', array('type' => 'hidden', 'name' => $inputattributes['name'], 'value' => 0));
            }
            $feedbackimages = array();
            foreach ($ans->feedbackimages as $image_id => $image) {
                array_push($feedbackimages, $image_id);
            }
            $feedbackimages_html = "";
            if (count($ans->feedbackimages) > 0) {
                $feedbackimages_html = '<div style="display: block; float: right;">[ ' . get_string("see", "qtype_omerocommon") . " ";
                $current_language = current_language();
                foreach ($ans->feedbackimages as $image) {
                    $feedbackimages_html .= '<span class="' . $feedback_image_class . '" imageid="' . $image->id . '"' . ' currentlanguage="' . $current_language . '"' . ' imagename="' . $image->name . '"' . ' imagedescription="' . htmlspecialchars($image->description_locale_map->{$current_language}) . '"' . ' imagelock="' . ($image->lock ? "true" : "false") . '"' . ' imageproperties="' . htmlspecialchars(json_encode($image->properties)) . '"' . ' visiblerois="' . implode(",", $image->visiblerois) . '"' . ' focusablerois="' . implode(",", $image->focusablerois) . '"' . '>' . '<i class="glyphicon glyphicon-book" style="margin-left: 2px; margin-right: 5px;"></i>' . '"' . $image->name . '"</span>';
                }
                $feedbackimages_html .= ' ]</div>';
            }
            // Param $options->suppresschoicefeedback is a hack specific to the
            // oumultiresponse question type. It would be good to refactor to
            // avoid refering to it here.
            $feedback_content = null;
            if ($options->feedback && empty($options->suppresschoicefeedback) && $isselected) {
                $feedback_content = $isselected ? '<span class="pull-right">' . $renderer->feedback_image($renderer->is_right($ans)) . '</span>' : "";
                $feedback_text = trim($ans->feedback);
                if (!empty(strip_tags($feedback_text))) {
                    $feedback_content .= html_writer::tag("div", html_writer::tag("i", " ", array("class" => "pull-left glyphicon glyphicon-record", "style" => "margin-left: 10px; margin-right: 5px")) . format_text($feedback_text) . $feedbackimages_html, array("class" => "outcome", "style" => "padding: 20px 15px;"));
                }
                $feedback[] = $feedback_content;
            } else {
                $feedback[] = '';
            }
            $class = 'r' . $value % 2;
            if ($options->correctness && $isselected) {
                $feedbackimg[] = $renderer->feedback_image($renderer->is_right($ans));
                $class .= ' ' . $renderer->feedback_class($renderer->is_right($ans));
            } else {
                $feedbackimg[] = '';
            }
            $classes[] = $class;
            $radiobutton_label = html_writer::tag('label', $question->format_text($renderer->number_in_style($value, $question->answernumbering) . preg_replace('/<p[^>]*>(.*)<\\/p[^>]*>/i', '$1', $ans->answer), $ans->answerformat, $qa, 'question', 'answer', $ansid), array('for' => $inputattributes['id']));
            $radiobuttons[] = $hidden . html_writer::empty_tag('input', $inputattributes) . qtype_omeromultichoice_base_renderer::number_answer($value, $question->answernumbering) . $radiobutton_label;
        }
        /**
         * Render the question
         */
        $result = '';
        // add the ModalImagePanel
        $result .= qtype_omerocommon_renderer_helper::modal_viewer(true, true, true, $modal_image_panel_id);
        // main question_answer_container
        $result .= html_writer::start_tag('div', array('id' => $question_answer_container, 'class' => 'ablock'));
        // question text
        $result .= html_writer::tag('div', $question->format_questiontext($qa), array('class' => 'qtext'));
        // viewer of the question image
        $result .= '<div class="image-viewer-with-controls-container">';
        $result .= '<div id="' . self::to_unique_identifier($qa, "graphics_container") . '" class="image-viewer-container" style="position: relative;" >
            <div id="' . self::to_unique_identifier($qa, self::IMAGE_VIEWER_CONTAINER) . '" style="position: absolute; width: 100%; height: 500px; margin: auto;"></div>
            <canvas id="' . self::to_unique_identifier($qa, 'annotations_canvas') . '" style="position: absolute; width: 100%; height: 500px; margin: auto;"></canvas>
            <div id="' . self::to_unique_identifier($qa, self::IMAGE_VIEWER_CONTAINER) . '-loading-dialog" class="image-viewer-loading-dialog"></div>
        </div>';
        $image_properties = null;
        if ($question->omeroimageproperties) {
            $image_properties = json_decode($question->omeroimageproperties);
            $result .= '<div class="image_position_button">' . '<span class="sm">' . '<b>(x,y):</b> ' . $image_properties->center->x . ", " . $image_properties->center->y . '<i class="restore-image-center-btn glyphicon glyphicon-screenshot" style="margin-left: 10px;">' . '</i></span></div>';
        }
        $result .= '</div>';
        if (!empty($question->focusablerois)) {
            $result .= '<div id="' . self::to_unique_identifier($qa, self::FOCUS_AREAS_CONTAINER) . '" ' . ' class="focus_areas_container">' . '<span class="focus-areas-text">* ' . get_string("focusareas", "qtype_omerointeractive") . '</span> ' . '</div>';
        }
        $result .= html_writer::start_tag('div', array('class' => 'multichoice-options-container'));
        $result .= html_writer::tag('div', !$options->correctness ? $renderer->prompt() : ($num_of_response === 1 ? get_string("notice_your_answer", "qtype_omerocommon") : get_string("notice_your_answers", "qtype_omerocommon")), array('class' => 'prompt'));
        $result .= html_writer::start_tag('div', array('class' => 'answer'));
        foreach ($radiobuttons as $key => $radio) {
            $result .= html_writer::tag('div', $radio . ' ' . $feedback[$key], array('class' => $classes[$key]));
        }
        $result .= html_writer::end_tag('div');
        // Answer.
        $result .= html_writer::tag('div', '', array('id' => $question_answer_container . '-invalidator-panel', 'class' => "invalidator-panel"));
        $result .= html_writer::end_tag('div');
        // Ablock.
        // support for dialog message
        $result .= html_writer::tag('div', '
         <div class="modal fade" id="modal-frame-' . $omero_frame_id . '" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
        <h4 class="modal-title text-warning" id="modal-frame-label-' . $omero_frame_id . '">
            <i class="glyphicon glyphicon-warning-sign"></i> ' . get_string('validate_warning', 'qtype_omerocommon') . '</h4>
      </div>
      <div class="modal-body text-left">
        <span id="modal-frame-text-' . $omero_frame_id . '"></span>
      </div>
      <div class="modal-footer text-center">
        <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
      </div>
    </div>
  </div>
</div>');
        if ($qa->get_state() == question_state::$invalid) {
            $result .= html_writer::nonempty_tag('div', $question->get_validation_error($qa->get_last_qt_data()), array('class' => 'validationerror'));
        }
        // set the player configuration
        $player_config = array("image_id" => $omero_image, "image_properties" => json_decode($question->omeroimageproperties), "image_frame_id" => $omero_frame_id, "image_annotations_canvas_id" => self::to_unique_identifier($qa, "annotations_canvas"), "modal_image_panel_id" => $modal_image_panel_id, "feedback_image_class" => $feedback_image_class, "image_server" => $OMERO_SERVER, "viewer_model_server" => $CFG->omero_image_server, "image_viewer_container" => self::to_unique_identifier($qa, self::IMAGE_VIEWER_CONTAINER), "image_navigation_locked" => (bool) $question->omeroimagelocked, "qname" => $question->name, "question_answer_container" => $question_answer_container, "focus_areas_container" => self::to_unique_identifier($qa, self::FOCUS_AREAS_CONTAINER), "visible_rois" => empty($question->visiblerois) ? [] : explode(",", $question->visiblerois), "focusable_rois" => empty($question->focusablerois) ? [] : explode(",", $question->focusablerois), "answer_input_name" => $answer_input_name);
        // embed the player configuration within an hidden input element
        $player_config_element_id = self::to_unique_identifier($qa, "player-config");
        $result .= html_writer::empty_tag("input", array("id" => $player_config_element_id, "type" => "hidden", "value" => json_encode($player_config)));
        // start the pplayer
        $PAGE->requires->js_call_amd("qtype_omeromultichoice/question-player-multichoice", "start", array($player_config_element_id));
        return $result;
    }