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); }