Files
awo-hamburg-intranet/module/tasks/repo/TaskSubmissionRepository.php

352 lines
10 KiB
PHP
Raw Permalink Normal View History

<?php
declare(strict_types=1);
class TaskSubmissionRepository
{
public function create(
int $assignmentId,
int $methodId,
int $contactId,
string $status,
array $payload,
?int $score = null,
?int $maxScore = null
): int {
return TaskCertDb::insert(
'INSERT INTO task_submission (
assignment_id, method_id, main_contact_id, status, payload_json, score, max_score
) VALUES (?, ?, ?, ?, ?, ?, ?)',
'iiissii',
[
$assignmentId,
$methodId,
$contactId,
$status,
json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
$score,
$maxScore,
]
);
}
public function findById(int $submissionId): ?array
{
$row = TaskCertDb::fetchOne(
'SELECT * FROM task_submission WHERE id = ?',
'i',
[$submissionId]
);
if ($row === null) {
return null;
}
$row['payload'] = $this->decodePayload($row['payload_json'] ?? null);
unset($row['payload_json']);
return $row;
}
public function findLatestByAssignmentAndMethod(int $assignmentId, int $methodId): ?array
{
$row = TaskCertDb::fetchOne(
'SELECT * FROM task_submission
WHERE assignment_id = ? AND method_id = ?
ORDER BY id DESC
LIMIT 1',
'ii',
[$assignmentId, $methodId]
);
if ($row === null) {
return null;
}
$row['payload'] = $this->decodePayload($row['payload_json'] ?? null);
unset($row['payload_json']);
return $row;
}
public function findLatestByAssignmentAndMethodAndStatuses(int $assignmentId, int $methodId, array $statuses): ?array
{
$statuses = array_values(array_filter(array_map('strval', $statuses), static function (string $status): bool {
return trim($status) !== '';
}));
if ($statuses === []) {
return null;
}
$placeholders = implode(',', array_fill(0, count($statuses), '?'));
$types = 'ii' . str_repeat('s', count($statuses));
$params = array_merge([$assignmentId, $methodId], $statuses);
$row = TaskCertDb::fetchOne(
'SELECT * FROM task_submission
WHERE assignment_id = ?
AND method_id = ?
AND status IN (' . $placeholders . ')
ORDER BY id DESC
LIMIT 1',
$types,
$params
);
if ($row === null) {
return null;
}
$row['payload'] = $this->decodePayload($row['payload_json'] ?? null);
unset($row['payload_json']);
return $row;
}
public function listByAssignment(int $assignmentId): array
{
$rows = TaskCertDb::fetchAll(
'SELECT s.*, m.method_type
FROM task_submission s
INNER JOIN task_method m ON m.id = s.method_id
WHERE s.assignment_id = ?
ORDER BY s.id DESC',
'i',
[$assignmentId]
);
foreach ($rows as &$row) {
$row['payload'] = $this->decodePayload($row['payload_json'] ?? null);
unset($row['payload_json']);
}
return $rows;
}
public function listAssignmentIdsWithSubmissions(array $assignmentIds): array
{
$assignmentIds = array_values(array_unique(array_map('intval', $assignmentIds)));
$assignmentIds = array_values(array_filter($assignmentIds, static function (int $assignmentId): bool {
return $assignmentId > 0;
}));
if ($assignmentIds === []) {
return [];
}
$placeholders = implode(',', array_fill(0, count($assignmentIds), '?'));
$rows = TaskCertDb::fetchAll(
'SELECT DISTINCT assignment_id
FROM task_submission
WHERE assignment_id IN (' . $placeholders . ')',
str_repeat('i', count($assignmentIds)),
$assignmentIds
);
$result = [];
foreach ($rows as $row) {
$assignmentId = (int) ($row['assignment_id'] ?? 0);
if ($assignmentId <= 0) {
continue;
}
$result[] = $assignmentId;
}
$result = array_values(array_unique($result));
sort($result);
return $result;
}
public function hasApprovedOrAutoPassedForMethod(int $assignmentId, int $methodId): bool
{
$count = TaskCertDb::scalar(
'SELECT COUNT(*)
FROM task_submission
WHERE assignment_id = ?
AND method_id = ?
AND status IN ("auto_passed", "approved")',
'ii',
[$assignmentId, $methodId]
);
return (int) $count > 0;
}
public function listPendingApprovals(int $taskId = 0, string $searchQuery = ''): array
{
$searchQuery = trim($searchQuery);
$sql = 'SELECT
s.*,
m.method_type,
a.task_id,
a.cycle_key,
d.title AS task_title,
c.name AS contact_name,
c.email AS contact_email
FROM task_submission s
INNER JOIN task_method m ON m.id = s.method_id
INNER JOIN task_assignment a ON a.id = s.assignment_id
INNER JOIN task_definition d ON d.id = a.task_id
LEFT JOIN main_contact c ON c.id = s.main_contact_id
WHERE s.status = "awaiting_approval"';
$types = '';
$params = [];
if ($taskId > 0) {
$sql .= ' AND a.task_id = ?';
$types .= 'i';
$params[] = $taskId;
}
if ($searchQuery !== '') {
$searchLike = '%' . $searchQuery . '%';
$sql .= ' AND (
CAST(s.id AS CHAR) LIKE ?
OR d.title LIKE ?
OR COALESCE(c.name, "") LIKE ?
OR COALESCE(c.email, "") LIKE ?
OR m.method_type LIKE ?
)';
$types .= 'sssss';
$params[] = $searchLike;
$params[] = $searchLike;
$params[] = $searchLike;
$params[] = $searchLike;
$params[] = $searchLike;
}
$sql .= ' ORDER BY s.submitted_at ASC, s.id ASC';
$rows = TaskCertDb::fetchAll($sql, $types, $params);
foreach ($rows as &$row) {
$row['payload'] = $this->decodePayload($row['payload_json'] ?? null);
unset($row['payload_json']);
}
return $rows;
}
public function review(int $submissionId, string $newStatus, int $adminId, string $note): void
{
TaskCertDb::execute(
'UPDATE task_submission
SET status = ?, reviewed_by = ?, reviewed_at = NOW(), review_note = ?
WHERE id = ?',
'sisi',
[$newStatus, $adminId, $note, $submissionId]
);
}
/**
* Sammelt alle in den Payloads referenzierten Datei-Pfade (form_values mit
* file_path) für die gegebenen Assignments damit der Aufrufer die Dateien
* vor dem Löschen der Einreichungen von der Platte entfernen kann.
*
* @return string[] eindeutige relative Pfade
*/
public function collectFilePathsByAssignmentIds(array $assignmentIds): array
{
$assignmentIds = array_values(array_unique(array_map('intval', $assignmentIds)));
$assignmentIds = array_values(array_filter($assignmentIds, static function (int $assignmentId): bool {
return $assignmentId > 0;
}));
if ($assignmentIds === []) {
return [];
}
$placeholders = implode(',', array_fill(0, count($assignmentIds), '?'));
$rows = TaskCertDb::fetchAll(
'SELECT payload_json FROM task_submission WHERE assignment_id IN (' . $placeholders . ')',
str_repeat('i', count($assignmentIds)),
$assignmentIds
);
return $this->extractFilePathsFromRows($rows);
}
/**
* Wie collectFilePathsByAssignmentIds, aber für alle Einreichungen einer
* Aufgabe (für das harte Löschen, das per FK-CASCADE die Zeilen entfernt).
*
* @return string[] eindeutige relative Pfade
*/
public function collectFilePathsByTaskId(int $taskId): array
{
if ($taskId <= 0) {
return [];
}
$rows = TaskCertDb::fetchAll(
'SELECT s.payload_json
FROM task_submission s
INNER JOIN task_assignment a ON a.id = s.assignment_id
WHERE a.task_id = ?',
'i',
[$taskId]
);
return $this->extractFilePathsFromRows($rows);
}
private function extractFilePathsFromRows(array $rows): array
{
$paths = [];
foreach ($rows as $row) {
$payload = $this->decodePayload($row['payload_json'] ?? null);
$formValues = is_array($payload['form_values'] ?? null) ? $payload['form_values'] : [];
foreach ($formValues as $value) {
if (!is_array($value)) {
continue;
}
foreach ($value as $fileMeta) {
if (is_array($fileMeta) && isset($fileMeta['file_path'])) {
$path = trim((string) $fileMeta['file_path']);
if ($path !== '') {
$paths[$path] = true;
}
}
}
}
}
return array_keys($paths);
}
public function deleteByAssignmentIds(array $assignmentIds): int
{
$assignmentIds = array_values(array_unique(array_map('intval', $assignmentIds)));
$assignmentIds = array_values(array_filter($assignmentIds, static function (int $assignmentId): bool {
return $assignmentId > 0;
}));
if ($assignmentIds === []) {
return 0;
}
$placeholders = implode(',', array_fill(0, count($assignmentIds), '?'));
return TaskCertDb::execute(
'DELETE FROM task_submission
WHERE assignment_id IN (' . $placeholders . ')',
str_repeat('i', count($assignmentIds)),
$assignmentIds
);
}
private function decodePayload(?string $json): array
{
if ($json === null || trim($json) === '') {
return [];
}
$decoded = json_decode($json, true);
return is_array($decoded) ? $decoded : [];
}
}