Skip to content
Snippets Groups Projects
vpl_submission.class.php 48.4 KiB
Newer Older
<?php
// This file is part of VPL for Moodle - http://vpl.dis.ulpgc.es/
//
// VPL for 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.
//
// VPL for 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 VPL for Moodle.  If not, see <http://www.gnu.org/licenses/>.
/**
 * Submission class definition
 *
 * @package mod_vpl
 * @copyright 2012 onwards Juan Carlos Rodríguez-del-Pino
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 * @author Juan Carlos Rodríguez-del-Pino <jcrodriguez@dis.ulpgc.es>
 */
/**
 * Module instance files
 * path= vpl_data//vpl_instance#
 * Submission info
 * path/usersdata/userid#/submissionid#/submittedfiles.lst
 * path/usersdata/userid#/submissionid#/submittedfiles/
 * path/usersdata/userid#/submissionid#/grade_comments.txt
 * path/usersdata/userid#/submissionid#/teachertest.txt
 * path/usersdata/userid#/submissionid#/studenttest.txt
 */
defined('MOODLE_INTERNAL') || die();
require_once(dirname(__FILE__).'/vpl.class.php');
require_once(dirname(__FILE__).'/views/sh_factory.class.php');
require_once(dirname(__FILE__).'/views/show_hide_div.class.php');
require_once($CFG->dirroot . '/grade/grading/lib.php');
// Non static due to usort error.
function vpl_compare_filenamebylengh($f1, $f2) {
    return strlen( $f2 ) - strlen( $f1 );
}
class mod_vpl_submission {
    protected $vpl;
    protected $instance;
    /**
     * Internal var object to submitted file group manager
     *
     * @var object of file group manager
     */
    protected $submittedfgm;
    /**
     * Constructor
     *
     * @param $vpl object
     *            vpl instance
     * @param $mix submission
     *            instance object or id
     */
    public function __construct(mod_vpl $vpl, $mix = false) {
        global $DB;
        $this->vpl = $vpl;
        if (is_object( $mix )) {
            $this->instance = $mix;
        } else if ($mix === false) {
            throw new Exception( 'vpl_submission id error' );
        } else {
            $this->instance = $DB->get_record( 'vpl_submissions', array (
                    'id' => $mix
            ) );
            if (! $this->instance) {
                throw new Exception( 'vpl_submission id error' );
            }
        }
        $this->submittedfgm = null;
    }
     * get submission instance
     *
     * @return object submission instance
     */
    public function get_instance() {
        return $this->instance;
    }
    /**
     * get vpl object related
     *
     * @return object vpl
     */
    public function get_vpl() {
        return $this->vpl;
    }
    /**
     * Return the proper userid
     *
     * @return user id
     */
    public function get_userid() {
        if ($this->vpl->is_group_activity()) {
            $users = $this->vpl->get_group_members($this->instance->groupid);
            if ( count($users) == 0 ) {
                return $this->instance->userid;
            }
            $user = reset( $users );
            return $user->id;
        }
        return $this->instance->userid;
    }
    /**
     * get path to data submission directory
     *
     * @return string submission data directory
     */
    public function get_data_directory() {
        return $this->vpl->get_users_data_directory() . '/' . $this->instance->userid . '/' . $this->instance->id . '/';
    /**
     * get path to files submission directory
     *
     * @return string files submission directory
     */
    public function get_submission_directory() {
        return $this->get_data_directory() . 'submittedfiles/';
    }
    /**
     * get absolute path to name of file with list of submitted files
     *
     * @return string file name
     */
    public function get_submissionfilelistname() {
        return $this->get_data_directory() . 'submittedfiles.lst';
    }
    /**
     *
     * @return object file group manager for submitted files
     */
    public function get_submitted_fgm() {
        if (! $this->submittedfgm) {
            $this->submittedfgm = new file_group_process( $this->get_submissionfilelistname(), $this->get_submission_directory() );
        }
        return $this->submittedfgm;
    }
    /**
     * get absolute path to the grade comments file name
     *
     * @return string file name
     */
    public function get_gradecommentsfilename() {
        return $this->get_data_directory() . 'grade_comments.txt';
    }
    /**
     * get submitted files
     *
     * @return array of array 'name' and 'data'
     */
    public function get_submitted_files() {
        $fg = $this->get_submitted_fgm();
        return $fg->getallfiles();
    }
    public function set_submitted_file($files, $othersub = false) {
        $fg = $this->get_submitted_fgm();
        if ($othersub != false) {
            $otherdir = $othersub->get_submission_directory();
            $otherfln = $othersub->get_submissionfilelistname();
            $fg->addallfiles( $files, $otherdir, $otherfln);
        } else {
            $fg->addallfiles($files);
        }
    }
    public function is_equal_to(&$files, $comment = '') {
        if ($this->instance->comments != $comment) {
            return false;
        }
        $subfiles = $this->get_submitted_files();
        if (count( $files ) != count( $subfiles )) {
            return false;
        }
        $oldnames = array_keys( $subfiles );
        $newnames = array_keys( $files );
        foreach ($oldnames as $pos => $name) {
            if ( $name != $newnames [$pos] ) {
                return false;
            }
        }
        foreach ($files as $name => $data) {
            if ( ! isset ($subfiles [$name]) ) {
                return false;
            }
            if ($subfiles [$name] != $data ) {
                return false;
            }
        }
             return true;
    }
    /**
     * Delete submitted files and own record
     *
     * @return void
     *
     */
    public function delete() {
        global $DB;
        \mod_vpl\event\submission_deleted::log( $this );
        vpl_delete_dir( $this->get_data_directory() );
        $DB->delete_records( 'vpl_submissions', array (
                'id' => $this->instance->id
        ) );
    }
    /**
     * Return if submission has been graded
     *
     * @return bool
     */
    public function is_graded() {
        return $this->instance->dategraded > 0;
    }
    /**
     * Remove grade
     *
     * @return true if removed and false if not
     */
    public function remove_grade() {
        global $USER;
        global $CFG;
        global $DB;
        ignore_user_abort( true );
        if ($this->vpl->is_group_activity()) {
            $usersid = array ();
            foreach ($this->vpl->get_group_members( $this->instance->groupid ) as $user) {
                $usersid [] = $user->id;
            }
        } else {
            $usersid = array (
                    $this->instance->userid
            );
        }
        $grades = array ();
        $gradeinfo = array ();
        $gradeinfo ['userid'] = $this->instance->userid;
        $gradeinfo ['rawgrade'] = null;
        $gradeinfo ['feedback'] = '';
        foreach ($usersid as $userid) {
            $gradeinfo ['userid'] = $userid;
            $grades [$userid] = $gradeinfo;
        }
        $vplinstance = $this->vpl->get_instance();
        if (vpl_grade_item_update( $vplinstance, $grades ) != GRADE_UPDATE_OK) {
            return false;
        }
        if (! empty( $CFG->enableoutcomes )) {
            foreach ($usersid as $userid) {
                $gradinginfo = grade_get_grades( $this->vpl->get_course()->id
                                                 , 'mod'
                                                 , 'vpl'
                                                 , $this->vpl->get_instance()->id
                                                 , $userid );
                if (! empty( $gradinginfo->outcomes )) {
                    $outcomes = array ();
                    foreach ($gradinginfo->outcomes as $oid => $dummy) {
                        $outcomes [$oid] = null;
                    }
                    grade_update_outcomes( 'mod/vpl'
                                          , $this->vpl->get_course()->id
                                          , 'mod'
                                          , VPL
                                          , $this->vpl->get_instance()->id
                                          , $userid, $outcomes );
                }
            }
        }
        $this->instance->grader = 0;
         $this->instance->dategraded = 0;
        $this->instance->grade = null;
        $fn = $this->get_gradecommentsfilename();
        if (! $DB->update_record( 'vpl_submissions', $this->instance )) {
            print_error( 'DB error updating submission grade info' );
        } else {
            if (file_exists( $fn )) {
                unlink( $fn );
            }
        }
        return true;
    }
    /**
     * get current grade reduction
     *
     * @param & $reduction
     *          value or factor
     * @param & $percent bool
     *          if true then $reduction is factor
     * @return grade reduction string
     */
    public function grade_reduction(& $reduction, & $percent) {
        $reduction = 0;
        $percent = false;
        $vplinstance = $this->vpl->get_instance();
        if (! ($vplinstance->reductionbyevaluation > 0) ||
            $vplinstance->freeevaluations >= $this->instance->nevaluations ) {
            return;
        }
        $mul = $this->instance->nevaluations - $vplinstance->freeevaluations;
        if ( substr($vplinstance->reductionbyevaluation, -1, 1) == '%' ) {
            $reduction = substr($vplinstance->reductionbyevaluation, 0, -1);
            $reduction = pow( (100.0 - $reduction) / 100.0, $mul);
            $percent = true;
        } else {
            $reduction = $vplinstance->reductionbyevaluation * $mul;
        }
    }
     * String with the reduction policy
     *
     * @param $grade
     *            optional grade value
     * @return grade reduction string
     */
    public function reduce_grade_string() {
        global $OUTPUT;
        $vplinstance = $this->vpl->get_instance();
        if ( ! ($vplinstance->reductionbyevaluation > 0 ) ) {
            return '';
        }
        $this->grade_reduction($reduction, $percent);
        $value = $reduction;
        if ($percent) {
            $value = (100 - ( $value * 100 ) );
            $value = format_float($value, 2, true, true) . '%';
        } else {
            $value = format_float($value, 2, true, true);
        }
        $vplinstance = $this->vpl->get_instance();
        $html = $this->vpl->str_restriction('finalreduction', $value);
        $html .= ' [' . $this->instance->nevaluations;
        $html .= ' / ' . $vplinstance->freeevaluations;
        $html .= ' -' . $vplinstance->reductionbyevaluation . ']';
        $html .= $OUTPUT->help_icon('finalreduction', VPL);
        return $html;
    }
    /**
     * Reduce grade based en number of evaluations
     *
     * @param $grade
     *            grade value
     * @return new grade
     */
    public function reduce_grade($grade) {
        $this->grade_reduction($reduction, $percent);
        if ($reduction > 0) {
            if ($percent) {
                return $grade * $reduction;
            } else {
                return $grade - $reduction;
            }
        }
        return $grade;
    }
    /**
     * Set/update grade
     *
     * @param $info object
     *            with grade and comments fields
     * @param $automatic if
     *            automatic grading (default false)
     * @return boolean
     *            true => OK
     */
    public function set_grade($info, $automatic = false) {
        global $USER;
        global $CFG;
        global $DB;
        ignore_user_abort( true );
        $scaleid = $this->vpl->get_grade();
        if ($scaleid == 0 && empty( $CFG->enableoutcomes )) { // No scale no outcomes.
            return false;
        }
        if ($automatic) { // Who grade.
            $this->instance->grader = 0;
        } else {
            $this->instance->grader = $USER->id;
        }
        if ($this->vpl->is_group_activity()) {
            $usersid = array ();
            foreach ($this->vpl->get_group_members( $this->instance->groupid ) as $user) {
                $usersid [] = $user->id;
            }
        } else {
            $usersid = array (
                    $this->instance->userid
            );
        }
        $this->instance->dategraded = time();
        if ($scaleid != 0) {
            // Sanitize grade.
            if ($scaleid > 0) {
                 $floatn = unformat_float($info->grade);
                if ( $floatn !== null && $floatn !== false ) {
                    $info->grade = $floatn;
                }
            } else {
                $info->grade = ( int ) $info->grade;
            }
            $this->instance->grade = $info->grade;
            // Save assessment comments.
            $comments = $info->comments;
            $fn = $this->get_gradecommentsfilename();
            if ($comments) {
                $fp = vpl_fopen( $fn );
                fwrite( $fp, $comments );
                fclose( $fp );
            } else if (file_exists( $fn )) {
                unlink( $fn );
            }
            // Update gradebook.
            $gradinginstance = $this->get_grading_instance();
            if ($gradinginstance) {
                $advancedgrading = $gradinginstance->submit_and_get_grade($info->advancedgrading,
                                                                       $this->instance->id);
            }
            $grades = array ();
            $gradeinfo = array ();
            // If no grade then don't set rawgrade and feedback.
            if ( $scaleid != 0 ) {
                $gradeinfo ['rawgrade'] = $this->reduce_grade($info->grade);
                $gradeinfo ['feedback'] = $this->result_to_html( $comments, false );
                $gradeinfo ['feedbackformat'] = FORMAT_HTML;
            }
            if ($this->instance->grader > 0) { // Don't add grader if automatic.
                $gradeinfo ['usermodified'] = $this->instance->grader;
            } else { // This avoid to use an unexisting userid (0) in the gradebook.
                $gradeinfo ['usermodified'] = $USER->id;
            }
            $gradeinfo ['datesubmitted'] = $this->instance->datesubmitted;
            $gradeinfo ['dategraded'] = $this->instance->dategraded;
            foreach ($usersid as $userid) {
                $gradeinfo ['userid'] = $userid;
                $grades [$userid] = $gradeinfo;
            }
            if (vpl_grade_item_update( $this->vpl->get_instance(), $grades ) != GRADE_UPDATE_OK ) {
                return false;
            }
            // The function vpl_grade_item_update say OK but may be overridden.
            // Check if grade is overridden by comparing save time.
            // Other option is checking the grade_item state.
            $vplinstance = $this->vpl->get_instance();
            $gradesaved = grade_get_grades($vplinstance->course, 'mod', 'vpl',
                    $vplinstance->id, $usersid[0]);
            try {
                $dategraded = $gradesaved->items[0]->grades[$usersid[0]]->dategraded;
            } catch (Exception $e) {
                return false;
            }
            if ($dategraded != $gradeinfo ['dategraded']) {
                return false;
            }
        }
        if (! empty( $CFG->enableoutcomes )) {
            foreach ($usersid as $userid) {
                $gradinginfo = grade_get_grades( $this->vpl->get_course()->id, 'mod'
                                                , 'vpl', $this->vpl->get_instance()->id, $userid );
                if (! empty( $gradinginfo->outcomes )) {
                    $outcomes = array ();
                    foreach ($gradinginfo->outcomes as $oid => $dummy) {
                        $field = 'outcome_grade_' . $oid;
                        if (isset( $info->$field )) {
                            $outcomes [$oid] = $info->$field;
                        } else {
                            $outcomes [$oid] = null;
                        }
                    }
                     $ret = grade_update_outcomes( 'mod/vpl'
                                           , $this->vpl->get_course()->id
                                           , 'mod'
                                           , VPL
                                           , $this->vpl->get_instance()->id
                                           , $userid, $outcomes );
                    if ( ! $ret ) {
                        return false;
                    }
                }
            }
        }
        if (! $DB->update_record( 'vpl_submissions', $this->instance )) {
            print_error( 'DB error updating submission grade info' );
        }
        return true;
    }
    /**
     * Get grade comments
     *
     * @return string
     */
    public function get_grade_comments() {
        $ret = '';
        $fn = $this->get_gradecommentsfilename();
        if (file_exists( $fn )) {
            $ret = file_get_contents( $fn );
            // Remove grade reduction information from titles [-*(-#)] .
            if ( ! $this->vpl->has_capability(VPL_GRADE_CAPABILITY) ) {
                $ret = preg_replace('/^(-.*)(\([ \t]*-[0-9]*\.?[0-9]+[ \t]*\)[ \t]*)$/m', '$1', $ret);
            }
        }
        return $ret;
    }
    /**
     * is visible this submission instance
     *
     * @return bool
     */
    public function is_visible() {
        global $USER;
        $cm = $this->vpl->get_course_module();
        $instance = $this->instance;
        $ret = $this->vpl->is_visible();
        // Submission owner?
        $ret = $ret && ($USER->id == $instance->userid);
        if ($ret) {
            // Is last submission?
            $lastsub = $this->vpl->last_user_submission( $instance->userid );
            $ret = $ret && ($instance->id == $lastsub->id);
        }
        $ret = $ret || $this->vpl->has_capability( VPL_GRADE_CAPABILITY );
        return $ret;
    }
    /**
     * is possible to grade/update this submission instance
     *
     * @return bool
     */
    public function is_grade_able() {
        global $USER;
        if ($this->vpl->get_grade() == 0) { // Is grade_able the instance.
            return false;
        }
        $instance = $this->instance;
        $ret = $this->vpl->has_capability( VPL_GRADE_CAPABILITY );
        // New grade or update if grader.
        $ret = $ret && ($instance->dategraded || $USER->id == $instance->grader);
        $ret = $ret || $this->vpl->has_capability( VPL_MANAGE_CAPABILITY );
        if ($ret) {
            // Is last submission?
            $lastsub = $this->vpl->last_user_submission( $instance->userid );
            $ret = $ret && ($instance->id == $lastsub->id);
        }
        return $ret;
    }
    /**
     *
     * @var array of users(graders) objects
     */
    protected static $graders = array ();
    /**
     * Return user from DB with cache (automatic grader info for $id===0)
     *
     * @param
     *            $id
     * @return FALSE/user object
     */
    public static function get_grader($id) {
        global $DB;
        if ($id === null) {
            $id = 0;
        }
        if (isset( self::$graders [$id] )) {
            $graderuser = self::$graders [$id];
        } else {
            if ($id <= 0) { // Automatic grading.
                $graderuser = new StdClass();
                if (function_exists( 'get_all_user_name_fields' )) {
                    $fields = get_all_user_name_fields();
                    foreach ($fields as $name => $value) {
                        $graderuser->$name = '';
                    }
                }
                $graderuser->firstname = '';
                $graderuser->lastname = get_string( 'automaticgrading', VPL );
            } else {
                $graderuser = $DB->get_record( 'user', array (
                        'id' => $id
                ) );
            }
            self::$graders [$id] = $graderuser;
        }
        return $graderuser;
    }
     * Get an instance of a grading form if advanced grading is enabled.
     * This is specific to the assignment, marker and student.
     *
     * @return mixed gradingform_instance|null $gradinginstance
     */
    public function get_grading_instance() {
        global $CFG, $USER;
        $grademenu = make_grades_menu($this->get_instance()->grade);
        $allowgradedecimals = $this->get_instance()->grade > 0;
        $advancedgradingwarning = false;
        $gradingmanager = get_grading_manager($this->vpl->get_context(), 'mod_vpl', 'submissions');
        $gradinginstance = null;
        if ($gradingmethod = $gradingmanager->get_active_method()) {
            $controller = $gradingmanager->get_controller($gradingmethod);
            if ($controller->is_form_available()) {
                $instanceid = optional_param('advancedgradinginstanceid', 0, PARAM_INT);
                $gradinginstance = $controller->get_or_create_instance($instanceid,
                                                                       $USER->id,
                                                                       $this->instance->id);
            } else {
                $advancedgradingwarning = $controller->form_unavailable_notification();
            }
        }
        if ($gradinginstance) {
            $gradinginstance->get_controller()->set_grade_range($grademenu, $allowgradedecimals);
        }
        return $gradinginstance;
    }
    /**
     * Get core grade @parm optional grade to show
     *
     * @return string
     */
    public function get_grade_core($grade = null) {
        global $CFG;
        $ret = '';
        $inst = $this->instance;
        if ($inst->dategraded > 0 || $grade != null) {
            $vplinstance = $this->vpl->get_instance();
            $scaleid = $this->vpl->get_grade();
            $options = array ();
            if ($scaleid == 0) {
                return get_string( 'nograde' );
            } else if ($grade == null) {
                if (! function_exists( 'grade_get_grades' )) {
                    require_once($CFG->libdir . '/gradelib.php');
                }
                $userid = $this->get_userid();
                $grades = grade_get_grades($vplinstance->course, 'mod', 'vpl',
                        $vplinstance->id, $userid);
                try {
                    $gradeobj = $grades->items[0]->grades[$userid];
                    $gradestr = $gradeobj->str_long_grade;
                    if ( $this->vpl->has_capability(VPL_GRADE_CAPABILITY) ) {
                        $gradestr .= $gradeobj->hidden ? (' <b>' . get_string( 'hidden', 'core_grades' )) . '</b>' : '';
                        $gradestr .= $gradeobj->locked ? (' <b>' . get_string( 'locked', 'core_grades' )) : '</b>';
                        $gradestr .= $gradeobj->overridden ? (' <b>' . get_string( 'overridden', 'core_grades' )) . '</b>' : '';
                    }
                    return $gradestr;
                } catch ( Exception $e ) {
                    $error = $e;
                    // This try will avoid many checking.
                }
            }
            if ($grade === null) {
                return '';
            }
            if ($scaleid > 0) {
                $grade = format_float($this->reduce_grade($grade), 2, true, true);
                $ret = $grade . ' / ' . $scaleid;
            } else if ($scaleid < 0) {
                $scaleid = - $scaleid;
                $grade = ( int ) $grade;
                if ($scale = $this->vpl->get_scale()) {
                    $options = array ();
                    $options [- 1] = get_string( 'nograde' );
                    $options = $options + make_menu_from_list( $scale->scale );
                    if (isset( $options [$grade] )) {
                        $ret = $options [$grade];
                    }
                }
            }
        }
        return $ret;
    }
    /**
     * Print sudmission grade
     *
     * @param $detailed true
     *            show detailed grade (default false)
     * @param $return true
     *            return string/ false print grade (default false)
     * @return mix string/void
     */
    public function print_grade($detailed = false, $return = false) {
        global $CFG, $OUTPUT, $PAGE, $USER;
        $ret = '';
        $inst = $this->instance;
        if ($inst->dategraded > 0) {
            $grader = $this->get_grader( $inst->grader );
            $a = new stdClass();
            $a->date = userdate( $inst->dategraded );
            $a->gradername = fullname( $grader );
            $ret .= get_string( 'gradedonby', VPL, $a ) . '<br />';
            if ($this->vpl->get_grade() != 0) {
                $ret .= $this->vpl->str_restriction(get_string('grade'), $this->get_grade_core(), true) . '<br>';
                if ($detailed) {
                    $ret .= $this->reduce_grade_string() . '<br>';
                    $feedback = $this->get_grade_comments();
                    if ($feedback) {
                        $div = new vpl_hide_show_div( true );
                        $tagid = 'gradercomments' . $this->get_instance()->id;
                        $ret .= '<b>' . get_string( 'gradercomments', VPL ) . $div->generate( true ) . '</b><br />';
                        $ret .= $div->create_div(s($feedback));
                        $PAGE->requires->js_call_amd('mod_vpl/vplutil', 'addResults', array($div->get_div_id(), false, true));
            require_once($CFG->libdir . '/gradelib.php');
            $gradinginstance = $this->get_grading_instance();
            if ($gradinginstance) {
                $cangrade = has_capability(VPL_GRADE_CAPABILITY, $this->vpl->get_context());
                // Only show the grade if it is not hidden in gradebook.
                $userid = ($cangrade || has_capability( VPL_MANAGE_CAPABILITY, $this->vpl->get_context() )) ? null : $USER->id;
                $gradeinfo = grade_get_grades( $this->vpl->get_course()->id, 'mod', 'vpl', $this->vpl->get_instance()->id, $userid );
                $ret .= $gradinginstance->get_controller()->render_grade($PAGE,
                                                             $this->instance->id,
                                                             $gradeinfo,
                                                             $this->instance->grade,
                                                             $cangrade);
            }
            if (! empty( $CFG->enableoutcomes )) {
                $gradinginfo = grade_get_grades( $this->vpl->get_course()->id, 'mod'
                                                 , 'vpl', $this->vpl->get_instance()->id
                                                 , $this->instance->userid );
                if (! empty( $gradinginfo->outcomes )) {
                    $ret .= '<b>' . get_string( 'outcomes', 'grades' ) . '</b><br />';
                    foreach ($gradinginfo->outcomes as $oid => $outcome) {
                        $ret .= s( $outcome->name );
                        $ret .= ' ' . s( $outcome->grades [$inst->userid]->str_grade ) . '<br />';
                    }
                }
            }
        }
        if ($return) {
          return $ret;
        } else {
            if ($ret) {
                echo $OUTPUT->box( $ret );
    /**
     * Print sudmission info
     */
    public function print_info($autolink = false) {
        // TODO improve show submission info.
        global $OUTPUT, $DB;
        $id = $this->vpl->get_course_module()->id;
        $userid = $this->instance->userid;
        $submissionid = $this->instance->id;
        if ($autolink) {
            $url = vpl_mod_href( 'forms/submissionview.php', 'id', $id, 'userid', $userid, 'submissionid', $submissionid );
            echo '<a href="' . $url . '">';
        }
        $subdate = userdate( $this->instance->datesubmitted );
        p( get_string( 'submittedonp', VPL, $subdate ) );
        if ($autolink) {
            echo '</a>';
        }
        $url = vpl_mod_href( 'views/downloadsubmission.php', 'id', $id, 'userid', $userid, 'submissionid', $submissionid );
        echo ' (<a href="' . $url . '">' . get_string( 'download', VPL );
        echo '</a>)';
        // Show evaluation link.
        $ce = $this->getce();
        if ($this->vpl->get_instance()->evaluate && ! $this->is_graded()) {
            $url = vpl_mod_href( 'forms/evaluation.php', 'id', $id, 'userid', $userid );
            echo ' (<a href="' . $url . '">' . get_string( 'evaluate', VPL );
            echo '</a>)';
        }
        echo '<br />';
        if ( $this->vpl->is_group_activity() ) {
            $user = $DB->get_record( 'user', array (
                        'id' => $userid
                ) );
            if ( $user ) {
                $userinfo = $OUTPUT->user_picture( $user ) . ' '  . fullname( $user );
                echo get_string( 'submittedby', VPL, $userinfo );
            }
            $users = $this->vpl->get_group_members($this->instance->groupid);
            foreach ($users as $u) {
                if ( $u->id != $userid ) {
                    echo '<br>';
                    break;
                }
            }
            foreach ($users as $u) {
                if ( $u->id != $userid ) {
                    echo $OUTPUT->user_picture( $u ) . ' ' . fullname( $u );
                }
            }
        }
        $commmets = $this->instance->comments;
        if ($commmets > '') {
            echo '<br>';
            echo '<h4>' . get_string( 'comments', VPL ) . '</h4>';
            echo $OUTPUT->box( nl2br( s( $commmets ) ) );
        }
    }
    /**
     * Print compilation and execution
     *
     * @return void
     */
    public function print_ce() {
        global $OUTPUT, $PAGE;
        $ce = $this->getce();
        if ($ce ['compilation'] === 0) {
            return;
        }
        $this->get_ce_html( $ce, $compilation, $execution, $grade, true, true );
        if (strlen( $compilation ) + strlen( $execution ) + strlen( $grade ) > 0) {
            $div = new vpl_hide_show_div( ! $this->is_graded() || ! $this->vpl->get_visiblegrade() );
            echo '<h3>' . get_string( 'automaticevaluation', VPL ) . $div->generate( true ) . '</h3>';
            $div->begin_div();
            echo $OUTPUT->box_start();
            if (strlen( $grade ) > 0) {
                echo '<b>' . $grade . '</b><br>';
                echo $this->reduce_grade_string() . '<br>';
            }
            $compilation = $ce ['compilation'];
            if (strlen( $compilation ) > 0) {
                $div = new vpl_hide_show_div( true );
                echo '<b>' . get_string( 'compilation', VPL ) . $div->generate( true ) . '</b><br />';
                echo $div->create_div(s($compilation));
                $PAGE->requires->js_call_amd('mod_vpl/vplutil', 'addResults', array($div->get_div_id(), false, true));
            }
            if (strlen( $execution ) > 0) {
                $proposedcomments = $this->proposedcomment( $ce ['execution'] );
                if (strlen( $execution )) {
                    $div = new vpl_hide_show_div( true );
                    echo '<b>' . get_string( 'comments', VPL ) . $div->generate( true ) . "</b><br />";
                    echo $div->create_div(s($proposedcomments));
                    $PAGE->requires->js_call_amd('mod_vpl/vplutil', 'addResults', array($div->get_div_id(), false, true));
                }
            }
            echo $OUTPUT->box_end();
            $div->end_div();
        }
    }
    /**
     * Print sudmission
     */
    public function print_submission() {
        $this->print_info();
        // Not automatic graded show proposed evaluation.
        if (! $this->is_graded() || ! $this->vpl->get_visiblegrade() || $this->vpl->has_capability( VPL_GRADE_CAPABILITY )) {
            $this->print_CE();
        }
        $this->get_submitted_fgm()->print_files();
    }
    const GRADETAG = 'Grade :=>>';
    const COMMENTTAG = 'Comment :=>>';
    const BEGINCOMMENTTAG = '<|--';
    const ENDCOMMENTTAG = '--|>';
    public function proposedgrade($text) {
        $ret = '';
        $nl = vpl_detect_newline( $text );
        $lines = explode( $nl, $text );
        foreach ($lines as $line) {
            if (strpos( $line, self::GRADETAG ) === 0) {
                $ret = trim( substr( $line, strlen( self::GRADETAG ) ) );
            }
        }
        return $ret;
    }
    public function proposedcomment($text) {
        $incomment = false;
        $ret = '';
        $nl = vpl_detect_newline( $text );
        $lines = explode( $nl, $text );
        foreach ($lines as $line) {
            $line = rtrim( $line ); // Remove \r, spaces & tabs.
            $tline = trim( $line );
            if ($incomment) {
                if ($tline == self::ENDCOMMENTTAG) {
                    $incomment = false;
                } else {
                    $ret .= $line . "\n";
                }
            } else {
                if (strpos( $line, self::COMMENTTAG ) === 0) {
                    $ret .= substr( $line, strlen( self::COMMENTTAG ) ) . "\n";
                } else if ($tline == self::BEGINCOMMENTTAG) {
                    $incomment = true;
                }
            }
        }
        return $ret;
    }
    /**
     * Add link to file line format filename:linenumber:
     *
     * @param
     *            text to be converted
     * @return string text with links
     */
    public function add_filelink($text) {
      // Format filename:linenumber.
        $ret = '';
        $list = $this->get_submitted_fgm()->getFileList();
        usort( $list, 'vpl_compare_filenamebylengh' );
        $nl = vpl_detect_newline( $text );
        $lines = explode( $nl, $text );
        // Prepare reg expressions.
        $regexps = array ();
        foreach ($list as $filename) {
            $escapefilename = preg_quote( $filename, "/" );
            $regexps [] = '/(.*?)(' . $escapefilename . ')\:( *)([0-9]+)(.*)/';
        }
        // Process lines.
        foreach ($lines as $line) {
            foreach ($regexps as $regexp) {
                if (preg_match( $regexp, $line, $r )) {
                    $line = $r [1] . '<a href="#' . $r [2] . '.' . $r [4] . '">' . $r [2] . ':' . $r [3] . $r [4] . '</a>' . $r [5];
                    break;
                }
            }
            $ret .= $line . "\n";
        }
        return $ret;
    }
    /**
     * Convert compilation/execution result to HTML
     *
     * @param
     *            text to be converted
     * @return string HTML
     */
    private function get_last_comment($title, &$comment, $dropdown) {
        $html = '';
        if ($title > '') { // Previous comment.
            if ($comment == '' || ! $dropdown) {
                $html .= '<b>';
                $html .= s( $title );
                $html .= '</b><br />';
                $html .= $comment;
            } else {
             $div = new vpl_hide_show_div( false );
                $html .= $div->generate( true );
                $html .= '<b>';
                $html .= s( $title );
                $html .= '</b><br />';
                $html .= $div->create_div($comment);
            }
        } else if ($comment > '') { // No title comment.
            $html .= $comment;
        }
        $comment = ''; // Reset comment.
        return $html;
    }
    /**
     * Convert compilation/execution result to HTML
     *
     * @param
     *            text to be converted
     * @return string HTML
     */
    public function result_to_html($text, $dropdown = true) {
        if ($text == '' || $text == null) {
            return '';
        }
        $html = ''; // Total html output.
        $title = ''; // Title of comment.
        $comment = ''; // Comment.
        $nl = vpl_detect_newline( $text );
        $lines = explode( $nl, $text );
        $casetoshow = ''; // Pre to show.
        foreach ($lines as $line) {
            $clean = trim( $line );