Module tasks + knowledgecenter_update aus Kundenprojekt übernehmen (bereinigt)
Beide Module wurden aus einem anderen Kundenprojekt kopiert und bereinigt: Knowledgecenter: - Bild-Cache (1.985 Dateien) geleert, Ordnerstruktur mit .gitkeep behalten - Files-Gallery-Lizenzschlüssel entfernt (config.php) - Kundenspezifische Kategorien gelöscht: Teil I/II/III QM-Struktur, nummerierte QM-Kapitel, Gremien (Präsidium/Finanzausschuss/Landesausschuss), Sitzungsservice-Serie, Protokoll-Abteilungen, Testeinträge - 56 generische Kategorien bleiben (Downloads, Onboarding, Personalthemen …) Tasks: - Alle Betriebsdaten gelöscht (114 Tasks, 914 Submissions, 579 Assignments) - Task-Definitionen und Verlinkungen geleert - Kategoriestruktur bleibt (Allgemein, IT, Personal, Buchhaltung …) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
418
module/tasks/endpoints/admin/task_upsert.php
Normal file
418
module/tasks/endpoints/admin/task_upsert.php
Normal file
@@ -0,0 +1,418 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once dirname(dirname(__DIR__)) . '/bootstrap.php';
|
||||
|
||||
if (!function_exists('task_cert_append_query_params')) {
|
||||
function task_cert_append_query_params(string $url, array $params): string
|
||||
{
|
||||
if ($url === '' || $params === []) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
$queryString = http_build_query($params);
|
||||
if ($queryString === '') {
|
||||
return $url;
|
||||
}
|
||||
|
||||
$separator = strpos($url, '?') === false ? '?' : '&';
|
||||
return $url . $separator . $queryString;
|
||||
}
|
||||
}
|
||||
|
||||
if (!TaskCertRequest::isPost()) {
|
||||
TaskCertResponse::error('Methode nicht erlaubt', 405);
|
||||
}
|
||||
|
||||
$auth = TaskCertAuthContext::fromGlobals();
|
||||
if (!$auth->hasPermission('r-task-cert-admin')) {
|
||||
TaskCertResponse::error('Keine Berechtigung', 403);
|
||||
}
|
||||
|
||||
TaskCertCsrf::assertValidFromRequest(true);
|
||||
|
||||
$services = task_cert_services();
|
||||
$taskService = $services['taskDefinitionService'];
|
||||
|
||||
$parseCsvOptions = static function (string $raw): array {
|
||||
$result = [];
|
||||
$parts = preg_split('/[,\n]/', $raw) ?: [];
|
||||
foreach ($parts as $part) {
|
||||
$item = trim((string) $part);
|
||||
if ($item === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$result[] = [
|
||||
'value' => $item,
|
||||
'label' => $item,
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
};
|
||||
|
||||
$slugifyFieldKey = static function (string $label): string {
|
||||
$map = [
|
||||
'ä' => 'ae', 'ö' => 'oe', 'ü' => 'ue', 'ß' => 'ss',
|
||||
'Ä' => 'ae', 'Ö' => 'oe', 'Ü' => 'ue',
|
||||
];
|
||||
$slug = strtr(mb_strtolower($label, 'UTF-8'), $map);
|
||||
$slug = preg_replace('/[^a-z0-9]+/', '_', $slug) ?? '';
|
||||
return trim($slug, '_');
|
||||
};
|
||||
|
||||
$buildMethods = static function () use ($parseCsvOptions, $slugifyFieldKey): array {
|
||||
$methodEnabled = TaskCertRequest::postArray('method_enabled');
|
||||
$orderedTypes = TaskCertConstants::METHOD_TYPES;
|
||||
|
||||
$methods = [];
|
||||
$sortOrder = 0;
|
||||
|
||||
foreach ($orderedTypes as $methodType) {
|
||||
if ((int) ($methodEnabled[$methodType] ?? 0) !== 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$method = [
|
||||
'method_type' => $methodType,
|
||||
'is_required' => 1,
|
||||
'sort_order' => $sortOrder,
|
||||
'active' => 1,
|
||||
'settings' => [],
|
||||
];
|
||||
|
||||
if ($methodType === 'knowledge_read') {
|
||||
$knowledgePosts = [];
|
||||
$postIds = TaskCertRequest::postIntArray('knowledge_post_ids');
|
||||
foreach ($postIds as $index => $postId) {
|
||||
$knowledgePosts[] = [
|
||||
'knowledge_post_id' => $postId,
|
||||
'required' => 1,
|
||||
'sort_order' => $index,
|
||||
];
|
||||
}
|
||||
|
||||
$method['knowledge_posts'] = $knowledgePosts;
|
||||
}
|
||||
|
||||
if ($methodType === 'form_submit') {
|
||||
$formFields = [];
|
||||
$rows = TaskCertRequest::postArray('form_fields');
|
||||
|
||||
foreach ($rows as $idx => $row) {
|
||||
if (!is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$label = trim((string) ($row['label'] ?? ''));
|
||||
if ($label === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fieldKey = trim((string) ($row['field_key'] ?? ''));
|
||||
if ($fieldKey === '') {
|
||||
$base = $slugifyFieldKey($label);
|
||||
if ($base === '') {
|
||||
$base = 'field';
|
||||
}
|
||||
$fieldKey = $base;
|
||||
$usedKeys = array_column($formFields, 'field_key');
|
||||
$suffix = 2;
|
||||
while (in_array($fieldKey, $usedKeys, true)) {
|
||||
$fieldKey = $base . '_' . $suffix;
|
||||
$suffix++;
|
||||
}
|
||||
}
|
||||
|
||||
$fieldType = (string) ($row['field_type'] ?? 'text');
|
||||
if (!TaskCertConstants::isValidFieldType($fieldType)) {
|
||||
$fieldType = 'text';
|
||||
}
|
||||
|
||||
$formFields[] = [
|
||||
'field_key' => $fieldKey,
|
||||
'label' => $label,
|
||||
'field_type' => $fieldType,
|
||||
'is_required' => (int) (($row['is_required'] ?? 0) ? 1 : 0),
|
||||
'options' => $parseCsvOptions((string) ($row['options_text'] ?? '')),
|
||||
'placeholder' => trim((string) ($row['placeholder'] ?? '')),
|
||||
'prefill_column' => ContactPrefillColumns::sanitize(trim((string) ($row['prefill_column'] ?? ''))),
|
||||
'sort_order' => count($formFields),
|
||||
'active' => 1,
|
||||
];
|
||||
}
|
||||
|
||||
$method['form_fields'] = $formFields;
|
||||
}
|
||||
|
||||
if ($methodType === 'quiz') {
|
||||
$quizQuestions = [];
|
||||
$rows = TaskCertRequest::postArray('quiz_questions');
|
||||
|
||||
foreach ($rows as $row) {
|
||||
if (!is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$questionText = trim((string) ($row['question_text'] ?? ''));
|
||||
if ($questionText === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$correctOption = strtolower(trim((string) ($row['correct_option'] ?? 'a')));
|
||||
if (!in_array($correctOption, ['a', 'b', 'c', 'd'], true)) {
|
||||
$correctOption = 'a';
|
||||
}
|
||||
|
||||
$optionsMap = [
|
||||
'a' => trim((string) ($row['option_a'] ?? '')),
|
||||
'b' => trim((string) ($row['option_b'] ?? '')),
|
||||
'c' => trim((string) ($row['option_c'] ?? '')),
|
||||
'd' => trim((string) ($row['option_d'] ?? '')),
|
||||
];
|
||||
|
||||
$options = [];
|
||||
foreach (['a', 'b', 'c', 'd'] as $position => $letter) {
|
||||
$optionText = $optionsMap[$letter];
|
||||
if ($optionText === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$options[] = [
|
||||
'option_text' => $optionText,
|
||||
'is_correct' => $letter === $correctOption ? 1 : 0,
|
||||
'sort_order' => $position,
|
||||
];
|
||||
}
|
||||
|
||||
if ($options === []) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$quizQuestions[] = [
|
||||
'question_text' => $questionText,
|
||||
'sort_order' => count($quizQuestions),
|
||||
'active' => 1,
|
||||
'options' => $options,
|
||||
];
|
||||
}
|
||||
|
||||
$method['quiz_questions'] = $quizQuestions;
|
||||
}
|
||||
|
||||
$methods[] = $method;
|
||||
$sortOrder++;
|
||||
}
|
||||
|
||||
return $methods;
|
||||
};
|
||||
|
||||
$buildTargetRules = static function (): array {
|
||||
$mandantIds = array_values(array_unique(TaskCertRequest::postIntArray('target_mandant_ids')));
|
||||
$departmentIds = array_values(array_unique(TaskCertRequest::postIntArray('target_department_ids')));
|
||||
$roleIds = array_values(array_unique(TaskCertRequest::postIntArray('target_role_ids')));
|
||||
|
||||
$mandantValues = $mandantIds === [] ? [0] : $mandantIds;
|
||||
$departmentValues = $departmentIds === [] ? [0] : $departmentIds;
|
||||
$roleValues = $roleIds === [] ? [0] : $roleIds;
|
||||
|
||||
$rules = [];
|
||||
$count = 0;
|
||||
$maxRules = 5000;
|
||||
|
||||
foreach ($mandantValues as $mandantId) {
|
||||
foreach ($departmentValues as $departmentId) {
|
||||
foreach ($roleValues as $roleId) {
|
||||
if ((int) $mandantId === 0 && (int) $departmentId === 0 && (int) $roleId === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rules[] = [
|
||||
'main_mandant_id' => (int) $mandantId ?: null,
|
||||
'main_department_id' => (int) $departmentId ?: null,
|
||||
'main_role_id' => (int) $roleId ?: null,
|
||||
'active' => 1,
|
||||
];
|
||||
|
||||
$count++;
|
||||
if ($count > $maxRules) {
|
||||
throw new RuntimeException('Zu viele Regelkombinationen. Bitte Auswahl einschränken.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $rules;
|
||||
};
|
||||
|
||||
$buildTargetOverrides = static function (): array {
|
||||
$includeReason = TaskCertRequest::postString('override_include_reason', '');
|
||||
$excludeReason = TaskCertRequest::postString('override_exclude_reason', '');
|
||||
$includeIds = array_values(array_unique(TaskCertRequest::postIntArray('override_include_contact_ids')));
|
||||
$excludeIds = array_values(array_unique(TaskCertRequest::postIntArray('override_exclude_contact_ids')));
|
||||
|
||||
// exclude wins when the same contact is selected in both lists
|
||||
$includeIds = array_values(array_diff($includeIds, $excludeIds));
|
||||
|
||||
$overrides = [];
|
||||
|
||||
foreach ($includeIds as $contactId) {
|
||||
$overrides[] = [
|
||||
'main_contact_id' => $contactId,
|
||||
'override_type' => 'include',
|
||||
'reason' => $includeReason,
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($excludeIds as $contactId) {
|
||||
$overrides[] = [
|
||||
'main_contact_id' => $contactId,
|
||||
'override_type' => 'exclude',
|
||||
'reason' => $excludeReason,
|
||||
];
|
||||
}
|
||||
|
||||
return $overrides;
|
||||
};
|
||||
|
||||
try {
|
||||
$methods = $buildMethods();
|
||||
if ($methods === []) {
|
||||
throw new RuntimeException('Mindestens eine Erledigungsart muss aktiviert sein.');
|
||||
}
|
||||
|
||||
$deadlineRuleType = TaskCertRequest::postString('deadline_rule_type', 'cycle_end');
|
||||
if (!in_array($deadlineRuleType, ['cycle_end', 'offset_days', 'weekday', 'monthday'], true)) {
|
||||
$deadlineRuleType = 'cycle_end';
|
||||
}
|
||||
|
||||
$deadlineOffsetDays = null;
|
||||
$deadlineWeekday = null;
|
||||
$deadlineMonthday = null;
|
||||
|
||||
if ($deadlineRuleType === 'offset_days') {
|
||||
$deadlineOffsetDays = TaskCertRequest::postInt('deadline_offset_days', 0);
|
||||
if ($deadlineOffsetDays <= 0) {
|
||||
throw new RuntimeException('Bitte Frist in Tagen größer 0 angeben.');
|
||||
}
|
||||
}
|
||||
|
||||
if ($deadlineRuleType === 'weekday') {
|
||||
$deadlineWeekday = TaskCertRequest::postInt('deadline_weekday', 0);
|
||||
if ($deadlineWeekday < 1 || $deadlineWeekday > 7) {
|
||||
throw new RuntimeException('Bitte einen gültigen Wochentag (1-7) auswählen.');
|
||||
}
|
||||
}
|
||||
|
||||
if ($deadlineRuleType === 'monthday') {
|
||||
$deadlineMonthday = TaskCertRequest::postInt('deadline_monthday', 0);
|
||||
if ($deadlineMonthday < 1 || $deadlineMonthday > 31) {
|
||||
throw new RuntimeException('Bitte einen gültigen Monatstag (1-31) auswählen.');
|
||||
}
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'actor_id' => $auth->contactId(),
|
||||
'title' => TaskCertRequest::postString('title'),
|
||||
'description' => TaskCertHtml::sanitize(TaskCertRequest::postString('description')),
|
||||
'task_category_id' => TaskCertRequest::postInt('task_category_id', 0),
|
||||
'frequency_type' => TaskCertRequest::postString('frequency_type', 'once'),
|
||||
'recurrence_basis' => TaskCertRequest::postString('recurrence_basis', 'calendar'),
|
||||
'recurrence_unit' => TaskCertRequest::postString('recurrence_unit', ''),
|
||||
'recurrence_interval' => TaskCertRequest::postInt('recurrence_interval', 1),
|
||||
'start_date' => TaskCertRequest::postString('start_date', date('Y-m-d')),
|
||||
'deadline_rule_type' => $deadlineRuleType,
|
||||
'deadline_offset_days' => $deadlineOffsetDays,
|
||||
'deadline_weekday' => $deadlineWeekday,
|
||||
'deadline_monthday' => $deadlineMonthday,
|
||||
'fulfillment_mode' => TaskCertRequest::postString('fulfillment_mode', 'all'),
|
||||
'certificate_enabled' => TaskCertRequest::postInt('certificate_enabled', 1),
|
||||
'certificate_title' => TaskCertRequest::postString('certificate_title', ''),
|
||||
'certificate_validity_mode' => TaskCertRequest::postString('certificate_validity_mode', ''),
|
||||
'certificate_validity_days' => TaskCertRequest::postString('certificate_validity_days', ''),
|
||||
'show_cycle_period' => TaskCertRequest::postInt('show_cycle_period', 0) === 1 ? 1 : 0,
|
||||
'active' => TaskCertRequest::postInt('active', 1),
|
||||
'methods' => $methods,
|
||||
'target_rules' => $buildTargetRules(),
|
||||
'target_overrides' => $buildTargetOverrides(),
|
||||
'responsible_contact_ids' => array_values(array_unique(TaskCertRequest::postIntArray('responsible_contact_ids'))),
|
||||
'reminder_policy' => [
|
||||
'base_mode' => 'due_date',
|
||||
'first_escalation_days' => TaskCertRequest::postInt('reminder_first_escalation_days', 0),
|
||||
'step_days' => TaskCertRequest::postInt('reminder_step_days', 7),
|
||||
'max_level' => TaskCertRequest::postInt('reminder_max_level', 3),
|
||||
'grace_days' => TaskCertRequest::postInt('reminder_grace_days', 7),
|
||||
'active' => TaskCertRequest::postInt('reminder_active', 0) === 1 ? 1 : 0,
|
||||
],
|
||||
];
|
||||
|
||||
$taskId = TaskCertRequest::postInt('task_id', 0);
|
||||
$resetExistingRecords = $taskId > 0 && TaskCertRequest::postInt('reset_existing_records', 0) === 1;
|
||||
$resetStats = [
|
||||
'cycle_key' => '',
|
||||
'assignment_count' => 0,
|
||||
'submission_count' => 0,
|
||||
'certificate_count' => 0,
|
||||
];
|
||||
|
||||
if ($taskId > 0) {
|
||||
$taskService->updateTask($taskId, $payload);
|
||||
if ($resetExistingRecords) {
|
||||
$resetStats = $taskService->resetCurrentCycleProgress($taskId, $auth->contactId());
|
||||
}
|
||||
} else {
|
||||
$taskId = $taskService->createTask($payload);
|
||||
}
|
||||
|
||||
$afterSave = TaskCertRequest::postString('after_save', 'stay');
|
||||
if (!in_array($afterSave, ['stay', 'close'], true)) {
|
||||
$afterSave = 'stay';
|
||||
}
|
||||
|
||||
$redirectTo = TaskCertRequest::postString('redirect_to', '');
|
||||
$redirectToStay = TaskCertRequest::postString('redirect_to_stay', '');
|
||||
$redirectToClose = TaskCertRequest::postString('redirect_to_close', '');
|
||||
|
||||
if ($redirectToStay === '') {
|
||||
$redirectToStay = task_cert_view_url('admin.task_form', [
|
||||
'id' => $taskId,
|
||||
'saved' => 1,
|
||||
]);
|
||||
}
|
||||
|
||||
if ($redirectToClose === '') {
|
||||
$redirectToClose = task_cert_view_url('admin.task_list');
|
||||
}
|
||||
|
||||
$resolvedRedirect = $redirectTo;
|
||||
if ($resolvedRedirect === '') {
|
||||
$resolvedRedirect = $afterSave === 'close' ? $redirectToClose : $redirectToStay;
|
||||
}
|
||||
|
||||
if ($resolvedRedirect !== '') {
|
||||
$resolvedRedirect = str_replace('{task_id}', (string) $taskId, $resolvedRedirect);
|
||||
if ($resetExistingRecords) {
|
||||
$resolvedRedirect = task_cert_append_query_params($resolvedRedirect, [
|
||||
'reset' => 1,
|
||||
'reset_assignments' => (int) ($resetStats['assignment_count'] ?? 0),
|
||||
'reset_submissions' => (int) ($resetStats['submission_count'] ?? 0),
|
||||
'reset_certificates' => (int) ($resetStats['certificate_count'] ?? 0),
|
||||
]);
|
||||
}
|
||||
TaskCertResponse::redirect($resolvedRedirect);
|
||||
}
|
||||
|
||||
TaskCertResponse::json([
|
||||
'success' => true,
|
||||
'task_id' => $taskId,
|
||||
'redirect' => $resolvedRedirect,
|
||||
'reset' => $resetExistingRecords,
|
||||
'reset_stats' => $resetStats,
|
||||
]);
|
||||
} catch (Throwable $throwable) {
|
||||
TaskCertResponse::json([
|
||||
'success' => false,
|
||||
'error' => $throwable->getMessage(),
|
||||
], 400);
|
||||
}
|
||||
Reference in New Issue
Block a user