.
namespace assignsubmission_dta;
use assignsubmission_dta\dta_db_utils;
use assignsubmission_dta\dta_backend_utils;
use assignsubmission_dta\models\dta_result;
use assignsubmission_dta\models\dta_result_summary;
use assignsubmission_dta\models\dta_recommendation;
/**
* Utility class for DTA submission plugin result display.
*
* @package assignsubmission_dta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class dta_view_submission_utils {
/**
* Broadly used in logic, parametrized for easier change.
*/
public const ASSIGNSUBMISSION_DTA_COMPONENT_NAME = 'assignsubmission_dta';
/**
* Generates a short summary HTML (like your old plugin).
*
* @param int $assignmentid The assignment ID.
* @param int $submissionid The submission ID to create a report for.
* @return string The HTML summary.
*/
public static function assignsubmission_dta_generate_summary_html(
int $assignmentid,
int $submissionid
): string {
// 1) Retrieve the summary data from the DB (adjust your DB-utils class as needed).
$summary = dta_db_utils::assignsubmission_dta_get_result_summary_from_database($assignmentid, $submissionid);
// 2) Prepare an HTML buffer.
$html = '';
// 3) Extract counts from your new method names:
$unknowncount = $summary->assignsubmission_dta_unknown_count();
$compilecount = $summary->assignsubmission_dta_compilation_error_count();
$successcount = $summary->assignsubmission_dta_successful_count();
$failcount = $summary->assignsubmission_dta_failed_count();
$totalcount = $summary->assignsubmission_dta_result_count();
// 4) Compute success rate if no unknown/compile errors and total>0.
$successrate = '?';
if ($unknowncount === 0 && $compilecount === 0 && $totalcount > 0) {
$successrate = round(($successcount / $totalcount) * 100, 2);
}
// 5) “X/Y (Z%) tests successful” line:
// If either compile errors or unknown exist -> we show "?"
// else X/Y (rate%).
$html .= $successcount . '/';
if ($compilecount === 0 && $unknowncount === 0) {
$html .= ($totalcount > 0)
? ($totalcount . ' (' . $successrate . '%)')
: ('0 (' . $successrate . ')');
} else {
$html .= '?';
}
$html .= get_string('tests_successful', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME) . "
";
// 6) If there are compilation errors, show them:
if ($compilecount > 0) {
$html .= $compilecount
. get_string('compilation_errors', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME)
. "
";
}
// 7) If there are unknown results, show them:
if ($unknowncount > 0) {
$html .= $unknowncount
. get_string('unknown_state', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME)
. "
";
}
// 8) Competencies (like your old snippet):
$showncompetencies = explode(';', $summary->successfultestcompetencies);
$overallcompetencies = explode(';', $summary->overalltestcompetencies);
$tmp = '';
$size = count($showncompetencies);
for ($i = 0; $i < $size; $i++) {
$shown = $showncompetencies[$i];
$comp = $overallcompetencies[$i];
// If the competency was actually used (non-zero?), show a row.
if ($shown !== '0') {
$shownval = floatval($shown);
$compval = floatval($comp);
// Guard division by zero:
$pct = 0;
if ($compval > 0) {
$pct = 100.0 * $shownval / $compval;
}
// “compX XX%
”
$tmp .= get_string('comp' . $i, self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME)
. ' ' . round($pct, 2) . '%
';
}
}
$html .= get_string('success_competencies', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME)
. "
" . $tmp . "
";
// 9) Wrap it in a DIV for styling, and return.
return \html_writer::div($html, "dtaSubmissionSummary");
}
/**
* Generates detailed view HTML.
*
* @param int $assignmentid The assignment ID.
* @param int $submissionid The submission to create a report for.
* @return string HTML detail view.
*/
public static function assignsubmission_dta_generate_detail_html(
int $assignmentid,
int $submissionid
): string {
// Fetch data.
$summary = dta_db_utils::assignsubmission_dta_get_result_summary_from_database(
$assignmentid,
$submissionid
);
$recommendations = dta_db_utils::assignsubmission_dta_get_recommendations_from_database(
$assignmentid,
$submissionid
);
$html = '';
// *** Summary Table ***
$tableheaderrowattributes = ['class' => 'dtaTableHeaderRow'];
$tablerowattributes = ['class' => 'dtaTableRow'];
$resultrowattributes = $tablerowattributes;
$unknownattributes = 'dtaResultUnknown';
$successattributes = 'dtaResultSuccess';
$failureattributes = 'dtaResultFailure';
$compilationerrorattributes = 'dtaResultCompilationError';
$attributes = ['class' => 'dtaTableData'];
// Build the summary table header.
$tmp = \html_writer::tag(
'th',
get_string('summary', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME),
['class' => 'dtaTableHeader']
);
$tmp .= \html_writer::empty_tag('th', ['class' => 'dtaTableHeader']);
$header = \html_writer::tag('tr', $tmp, $tableheaderrowattributes);
$header = \html_writer::tag('thead', $header);
$body = '';
// Pull the counters from the summary object.
$resultcount = $summary->assignsubmission_dta_result_count();
$successfulcount = $summary->assignsubmission_dta_successful_count();
$failedcount = $summary->assignsubmission_dta_failed_count();
$compilationcount = $summary->assignsubmission_dta_compilation_error_count();
$unknowncount = $summary->assignsubmission_dta_unknown_count();
// Total items.
$tmp = '';
$tmp .= \html_writer::tag(
'td',
get_string('total_items', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME),
$attributes
);
$tmp .= \html_writer::tag('td', $resultcount, $attributes);
$resultrowattributes = $tablerowattributes;
// Original code colors this row as unknown by default:
$resultrowattributes['class'] .= ' ' . $unknownattributes;
$body .= \html_writer::tag('tr', $tmp, $resultrowattributes);
// Tests successful.
$tmp = '';
$tmp .= \html_writer::tag(
'td',
get_string('tests_successful', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME),
$attributes
);
$tmp .= \html_writer::tag('td', $successfulcount, $attributes);
$resultrowattributes = $tablerowattributes;
// Compute success rate if no unknown or compilation errors, and resultcount > 0.
$successrate = '?';
if ($unknowncount == 0 && $compilationcount == 0 && $resultcount > 0) {
$successrate = round(($successfulcount / $resultcount) * 100, 2);
if ($successrate < 50) {
$resultrowattributes['class'] .= ' ' . $compilationerrorattributes;
} else if ($successrate < 75) {
$resultrowattributes['class'] .= ' ' . $failureattributes;
} else {
$resultrowattributes['class'] .= ' ' . $successattributes;
}
} else {
// If unknown or compilation errors => highlight as unknown.
$resultrowattributes['class'] .= ' ' . $unknownattributes;
}
$body .= \html_writer::tag('tr', $tmp, $resultrowattributes);
// Failures.
$tmp = '';
$tmp .= \html_writer::tag(
'td',
get_string('failures', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME),
$attributes
);
$tmp .= \html_writer::tag('td', $failedcount, $attributes);
$resultrowattributes = $tablerowattributes;
if ($failedcount > 0) {
$resultrowattributes['class'] .= ' ' . $failureattributes;
} else {
$resultrowattributes['class'] .= ' ' . $successattributes;
}
$body .= \html_writer::tag('tr', $tmp, $resultrowattributes);
// Compilation errors.
$tmp = '';
$tmp .= \html_writer::tag(
'td',
get_string('compilation_errors', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME),
$attributes
);
$tmp .= \html_writer::tag('td', $compilationcount, $attributes);
$resultrowattributes = $tablerowattributes;
if ($compilationcount > 0) {
$resultrowattributes['class'] .= ' ' . $compilationerrorattributes;
} else {
$resultrowattributes['class'] .= ' ' . $successattributes;
}
$body .= \html_writer::tag('tr', $tmp, $resultrowattributes);
// Unknown state.
$tmp = '';
$tmp .= \html_writer::tag(
'td',
get_string('unknown_state', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME),
$attributes
);
$tmp .= \html_writer::tag('td', $unknowncount, $attributes);
$resultrowattributes = $tablerowattributes;
if ($unknowncount > 0) {
$resultrowattributes['class'] .= ' ' . $unknownattributes;
} else {
$resultrowattributes['class'] .= ' ' . $successattributes;
}
$body .= \html_writer::tag('tr', $tmp, $resultrowattributes);
// Success rate row.
$tmp = '';
$tmp .= \html_writer::tag(
'td',
\html_writer::tag('b', get_string('success_rate', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME)),
$attributes
);
// If no compilation errors or unknown => show successrate, else "?".
$suffix = ($compilationcount == 0 && $unknowncount == 0 && $resultcount > 0)
? ($resultcount . ' (' . $successrate . '%)')
: '?';
$tmp .= \html_writer::tag(
'td',
\html_writer::tag('b', $successfulcount . '/' . $suffix),
$attributes
);
$resultrowattributes = $tablerowattributes;
if ($compilationcount == 0 && $unknowncount == 0 && $resultcount > 0) {
if ($successrate !== '?' && $successrate < 50) {
$resultrowattributes['class'] .= ' ' . $compilationerrorattributes;
} else if ($successrate !== '?' && $successrate < 75) {
$resultrowattributes['class'] .= ' ' . $failureattributes;
} else {
$resultrowattributes['class'] .= ' ' . $successattributes;
}
} else {
$resultrowattributes['class'] .= ' ' . $unknownattributes;
}
$body .= \html_writer::tag('tr', $tmp, $resultrowattributes);
// Finalize the summary table.
$body = \html_writer::tag('tbody', $body);
$table = \html_writer::tag('table', $header . $body, ['class' => 'dtaTable']);
$html .= $table;
// Spacing after the summary table.
$html .= \html_writer::empty_tag('div', ['class' => 'dtaSpacer']);
// *** Recommendations Table ***
if (!empty($recommendations)) {
$allowedsortfields = ['topic', 'exercise_name', 'difficulty', 'score'];
$allowedsortdirs = ['asc', 'desc'];
$sortby = $_POST['sortby'] ?? 'score';
$sortdir = $_POST['sortdir'] ?? 'asc';
if (!in_array($sortby, $allowedsortfields)) {
$sortby = 'score';
}
if (!in_array($sortdir, $allowedsortdirs)) {
$sortdir = 'asc';
}
usort($recommendations, function ($a, $b) use ($sortby, $sortdir) {
$valuea = $a->{$sortby};
$valueb = $b->{$sortby};
if (is_numeric($valuea) && is_numeric($valueb)) {
$comparison = $valuea - $valueb;
} else {
$comparison = strnatcasecmp($valuea, $valueb);
}
if ($comparison === 0) {
return 0;
}
if ($sortdir === 'asc') {
return ($comparison < 0) ? -1 : 1;
} else {
return ($comparison < 0) ? 1 : -1;
}
});
$html .= \html_writer::tag(
'h3',
get_string('recommendations', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME)
);
$generatesortableheader = function ($columnname, $displayname) use ($sortby, $sortdir) {
$newsortdir = ($sortby === $columnname && $sortdir === 'asc') ? 'desc' : 'asc';
$class = 'dtaTableHeader';
if ($sortby === $columnname) {
$class .= ' sorted ' . $sortdir;
}
// Sort button.
$button = \html_writer::empty_tag('input', [
'type' => 'submit',
'name' => 'sortbutton',
'value' => ($newsortdir === 'asc' ? '↑' : '↓'),
'class' => 'sort-button',
]);
// Hidden inputs.
$hiddeninputs = \html_writer::empty_tag('input', [
'type' => 'hidden',
'name' => 'sortby',
'value' => $columnname,
]);
$hiddeninputs .= \html_writer::empty_tag('input', [
'type' => 'hidden',
'name' => 'sortdir',
'value' => $newsortdir,
]);
$form = \html_writer::start_tag('form', [
'method' => 'post',
'style' => 'display:inline',
]);
$form .= $hiddeninputs;
$form .= $displayname . ' ' . $button;
$form .= \html_writer::end_tag('form');
return \html_writer::tag('th', $form, ['class' => $class]);
};
// Build the recommendations table header.
$tableheader = '';
$tableheader .= $generatesortableheader(
'topic',
get_string('topic', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME)
);
$tableheader .= $generatesortableheader(
'exercise_name',
get_string('exercise_name', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME)
);
$tableheader .= \html_writer::tag(
'th',
get_string('url', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME),
['class' => 'dtaTableHeader']
);
$tableheader .= $generatesortableheader(
'difficulty',
get_string('difficulty', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME)
);
$tableheader .= $generatesortableheader(
'score',
get_string('score', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME)
);
$tableheader = \html_writer::tag('tr', $tableheader, ['class' => 'dtaTableHeaderRow']);
$tableheader = \html_writer::tag('thead', $tableheader);
// Table body for recommendations.
$tablebody = '';
foreach ($recommendations as $recommendation) {
$row = '';
$row .= \html_writer::tag('td', $recommendation->topic, $attributes);
$row .= \html_writer::tag('td', $recommendation->exercise_name, $attributes);
$row .= \html_writer::tag(
'td',
\html_writer::link($recommendation->url, $recommendation->url),
$attributes
);
$row .= \html_writer::tag('td', $recommendation->difficulty, $attributes);
$row .= \html_writer::tag('td', $recommendation->score, $attributes);
$tablebody .= \html_writer::tag('tr', $row, $tablerowattributes);
}
$tablebody = \html_writer::tag('tbody', $tablebody);
$html .= \html_writer::tag('table', $tableheader . $tablebody, ['class' => 'dtaTable']);
// Spacing after recommendations.
$html .= \html_writer::empty_tag('div', ['class' => 'dtaSpacer']);
}
// *** Competency Assessment Table ***
$body = '';
$tmp = '';
$tmp .= \html_writer::tag(
'th',
get_string('competencies', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME),
['class' => 'dtaTableHeader']
);
$tmp .= \html_writer::empty_tag('th', ['class' => 'dtaTableHeader']);
$header = \html_writer::tag('tr', $tmp, $tableheaderrowattributes);
$header = \html_writer::tag('thead', $header);
$showncompetencies = explode(';', $summary->successfultestcompetencies);
$overallcompetencies = explode(';', $summary->overalltestcompetencies);
for ($index = 0, $size = count($overallcompetencies); $index < $size; $index++) {
$comp = $overallcompetencies[$index];
$shown = $showncompetencies[$index];
// If the competency was actually assessed, add a row in the table.
if ($comp !== '0') {
$compval = floatval($comp);
$shownval = floatval($shown);
// Guard division by zero:
$pct = 0;
if ($compval > 0) {
$pct = (100.0 * $shownval / $compval);
}
$resultrowattributes = $tablerowattributes;
$tmp = '';
$tmp .= \html_writer::tag(
'td',
get_string('comp' . $index, self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME),
$resultrowattributes
);
$tmp .= \html_writer::tag(
'td',
round($pct, 2) . '% (' . $shown . ' / ' . $comp . ')',
$resultrowattributes
);
$tmp .= \html_writer::tag(
'td',
get_string('comp_expl' . $index, self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME),
$resultrowattributes
);
$body .= \html_writer::tag('tr', $tmp, $resultrowattributes);
}
}
$body = \html_writer::tag('tbody', $body);
$html .= \html_writer::tag('table', $header . $body, ['class' => 'dtaTable']);
// Add empty div for spacing.
$html .= \html_writer::empty_tag('div', ['class' => 'dtaSpacer']);
// *** Details Table ***
$tmp = '';
$tmp .= \html_writer::tag(
'th',
get_string('details', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME),
['class' => 'dtaTableHeader']
);
$tmp .= \html_writer::empty_tag('th', ['class' => 'dtaTableHeader']);
$header = \html_writer::tag('tr', $tmp, $tableheaderrowattributes);
$header = \html_writer::tag('thead', $header);
$body = '';
$spacerrow = null;
foreach ($summary->results as $r) {
// Add spacer first if not null.
if (!is_null($spacerrow)) {
$body .= $spacerrow;
}
$resultrowattributes = $tablerowattributes;
// Set CSS class for colored left-border according to results state.
if ($r->state === 0) {
$resultrowattributes['class'] .= ' dtaResultUnknown';
} else if ($r->state === 1) {
$resultrowattributes['class'] .= ' dtaResultSuccess';
} else if ($r->state === 2) {
$resultrowattributes['class'] .= ' dtaResultFailure';
} else if ($r->state === 3) {
$resultrowattributes['class'] .= ' dtaResultCompilationError';
}
$tmp = '';
$tmp .= \html_writer::tag(
'td',
get_string('package_name', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME),
$attributes
);
$tmp .= \html_writer::tag('td', $r->packagename, $attributes);
$tmp .= \html_writer::tag(
'td',
get_string('unit_name', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME),
$attributes
);
$tmp .= \html_writer::tag('td', $r->classname, $attributes);
$tmp .= \html_writer::tag(
'td',
get_string('test_name', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME),
$attributes
);
$tmp .= \html_writer::tag('td', $r->name, $attributes);
$body .= \html_writer::tag('tr', $tmp, $resultrowattributes);
$tmp = '';
$tmp .= \html_writer::tag(
'td',
get_string('status', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME),
$attributes
);
$tmp .= \html_writer::tag(
'td',
dta_result::assignsubmission_dta_get_statename($r->state),
$attributes
);
$body .= \html_writer::tag('tr', $tmp, $resultrowattributes);
// If state != 1, show additional info.
if ($r->state !== 1) {
$tmp = '';
$tmp .= \html_writer::tag(
'td',
get_string('failure_type', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME),
$attributes
);
$tmp .= \html_writer::tag('td', $r->failureType, $attributes);
$body .= \html_writer::tag('tr', $tmp, $resultrowattributes);
$tmp = '';
$tmp .= \html_writer::tag(
'td',
get_string('failure_reason', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME),
$attributes
);
$tmp .= \html_writer::tag('td', $r->failureReason, $attributes);
$body .= \html_writer::tag('tr', $tmp, $resultrowattributes);
if (!is_null($r->lineNumber) && $r->lineNumber > 0) {
$tmp = '';
$tmp .= \html_writer::tag(
'td',
get_string('line_no', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME),
$attributes
);
$tmp .= \html_writer::tag('td', $r->lineNumber, $attributes);
$body .= \html_writer::tag('tr', $tmp, $resultrowattributes);
}
if (!is_null($r->columnNumber) && $r->columnNumber > 0) {
$tmp = '';
$tmp .= \html_writer::tag(
'td',
get_string('col_no', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME),
$attributes
);
$tmp .= \html_writer::tag('td', $r->columnNumber, $attributes);
$body .= \html_writer::tag('tr', $tmp, $resultrowattributes);
}
if (!is_null($r->position) && $r->position > 0) {
$tmp = '';
$tmp .= \html_writer::tag(
'td',
get_string('pos', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME),
$attributes
);
$tmp .= \html_writer::tag('td', $r->position, $attributes);
$body .= \html_writer::tag('tr', $tmp, $resultrowattributes);
}
$tmp = '';
$tmp .= \html_writer::tag(
'td',
get_string('stacktrace', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME),
$attributes
);
$tmp .= \html_writer::tag(
'td',
\html_writer::tag('details', $r->stacktrace, ['class' => 'dtaStacktraceDetails']),
$attributes
);
$body .= \html_writer::tag('tr', $tmp, $resultrowattributes);
}
if (is_null($spacerrow)) {
$spacerrow = \html_writer::empty_tag('tr', ['class' => 'dtaTableSpacer']);
}
}
$html .= \html_writer::tag('table', $header . $body, ['class' => 'dtaTable']);
// Wrap generated HTML into final div.
$html = \html_writer::div($html, 'dtaSubmissionDetails');
return $html;
}
}