needlogin = true; $this->me = new User(); $this->me->loadMe(); $this->layout()->set("me", $this->me); if (!$this->me->is("Admin")) { $this->redirect("Dashboard"); } $rawInput = file_get_contents('php://input'); if ($rawInput) $this->postData = json_decode($rawInput, true) ?? []; } protected function indexAction(): void { Helper::renderVue3($this, $this->mod, "Netzgebietverwaltung", [ "GET_URL" => $this::getUrl("ADBNetzgebiet/getNetzgebiete"), "SAVE_URL" => $this::getUrl("ADBNetzgebiet/save"), "HISTORY_URL" => $this::getUrl("ADBNetzgebiet/getHistory"), "START_RIMO_IMPORT_URL" => $this::getUrl("ADBNetzgebiet/startRimoImport"), "GET_RIMO_IMPORT_STATUS_URL" => $this::getUrl("ADBNetzgebiet/getRimoImportStatus"), "GET_RIMO_IMPORT_LOG_URL" => $this::getUrl("ADBNetzgebiet/getRimoImportLog"), "NETWORK_URL" => $this::getUrl("Network/Index"), "NETWORK_CREATE_URL" => $this::getUrl("Network/add"), "CAMPAIGN_URL" => $this::getUrl("Preordercampaign/edit"), "CAMPAIGN_CREATE_URL" => $this::getUrl("Preordercampaign/add"), "CONSENT_URL" => $this::getUrl("ConstructionConsentProject/edit"), "CONSENT_CREATE_URL" => $this::getUrl("ConstructionConsentProject/add"), "HIDE_PAGE_TITLE" => true, "USER_ID" => $this->me->id, ]); } protected function getNetzgebieteAction(): void { $filter = []; if (!empty($_GET['name'])) $filter['name'] = $_GET['name']; if (!empty($_GET['extref'])) $filter['extref'] = $_GET['extref']; if (!empty($_GET['source'])) $filter['=source'] = $_GET['source']; if (!empty($_GET['source_id'])) $filter['source_id'] = $_GET['source_id']; $allNetzgebiete = ADBNetzgebiet::getAll($filter, null, 0, ['column' => 'name', 'dir' => 'ASC']); $response = []; foreach ($allNetzgebiete as $netzgebiet) { $response[] = [ 'netzgebiet' => $netzgebiet->toArray(), 'related' => [ 'networks' => $netzgebiet->relations->networks, 'campaigns' => $netzgebiet->relations->campaigns, 'consent_projects' => $netzgebiet->relations->consentProjects ] ]; } self::returnJson(['success' => true, 'data' => $response, 'total' => count($response)]); } protected function saveAction(): void { $data = $this->postData; if (empty($data)) { self::sendError("No data received."); return; } $isNew = empty($data['id']); $model = $isNew ? new ADBNetzgebiet() : ADBNetzgebiet::get($data['id']); if (!$model) { self::sendError("Netzgebiet not found."); return; } if (isset($data['name'])) $model->name = trim($data['name']) ?: null; if (array_key_exists('extref', $data)) $model->extref = trim($data['extref']) ?: null; if (array_key_exists('rimo_id', $data)) $model->rimo_id = trim($data['rimo_id']) ?: null; if (isset($data['source'])) $model->source = $data['source'] ?: null; if (array_key_exists('source_id', $data)) $model->source_id = trim($data['source_id']) ?: null; if (array_key_exists('borderpoly', $data)) $model->borderpoly = $data['borderpoly'] ?: null; if (isset($data['freigabe'])) { $model->freigabe = is_array($data['freigabe']) ? json_encode(array_values($data['freigabe'])) : $data['freigabe']; } if (isset($data['options'])) { if (is_array($data['options'])) { $options = $data['options']; if (isset($options['mph_min_homes_tool_automatic_count'])) { $options['mph_min_homes_tool_automatic_count'] = (int)$options['mph_min_homes_tool_automatic_count']; } $boolFields = ['create_address_parts', 'update_freigabe', 'update_address', 'hausnummer_dont_overwrite_netzgebiet', 'create_preorder', 'preorder_only_oaid', 'wo_ignore_status', 'delete_units', 'unit_create_oaid']; foreach ($boolFields as $field) { if (isset($options[$field])) $options[$field] = $options[$field] ? 1 : 0; } $model->options = json_encode($options); } else { $model->options = $data['options']; } } if (!$model->save()) { self::sendError("Failed to save Netzgebiet."); return; } self::returnJson([ 'success' => true, 'message' => $isNew ? 'Netzgebiet created.' : 'Netzgebiet saved.', 'id' => $model->getId() ]); } protected function getHistoryAction(): void { $id = $_GET['id'] ?? $this->postData['id'] ?? null; if (empty($id)) { self::sendError("ID required."); return; } $model = ADBNetzgebiet::get($id); if (!$model) { self::sendError("Netzgebiet not found."); return; } $history = $model->getJournalHistory(); $userIds = array_unique(array_filter(array_column($history, 'user_id'))); $users = []; foreach ($userIds as $userId) { $user = new User($userId); if ($user->id) $users[$user->id] = $user->name ?? 'User #' . $user->id; } foreach ($history as $entry) { $entry->user_name = $users[$entry->user_id] ?? 'System'; } self::returnJson(['success' => true, 'data' => $history]); } protected function startRimoImportAction(): void { $id = $_GET['id'] ?? null; if (empty($id)) { self::returnJson(['success' => false, 'message' => "Netzgebiet ID required."]); return; } $netzgebiet = ADBNetzgebiet::get($id); if (!$netzgebiet || !$netzgebiet->id) { self::returnJson(['success' => false, 'message' => "Netzgebiet not found."]); return; } if (strpos($netzgebiet->source, 'rimo-') !== 0) { self::returnJson(['success' => false, 'message' => "This action is only for RIMO-source Netzgebiete."]); return; } if (empty($netzgebiet->source_id)) { self::returnJson(['success' => false, 'message' => "Netzgebiet has no Source ID."]); return; } $safeSourceId = preg_replace('/[^a-zA-Z0-9_-]/', '_', $netzgebiet->source_id); $importTempDir = TEMP_DIR . "/ADBNetzgebietRimoImport/"; $logDir = $importTempDir . $safeSourceId; $logFile = $logDir . "/import.log"; $lockFile = $logDir . "/import.lock"; if (is_dir($importTempDir)) { foreach (glob($importTempDir . "*") as $dir) { if (is_dir($dir) && (time() - filemtime($dir)) > 86400) { // simple cleanup if (file_exists($dir . "/import.log")) @unlink($dir . "/import.log"); if (file_exists($dir . "/import.lock")) @unlink($dir . "/import.lock"); @rmdir($dir); } } } if (!is_dir($logDir)) { mkdir($logDir, 0755, true); } if (file_exists($lockFile)) { if ((time() - filemtime($lockFile)) > 3600) { // stale lock for 1h @unlink($lockFile); } else { self::returnJson(['success' => false, 'message' => "Import is already running.", 'status' => 'running']); return; } } if (file_exists($logFile) && (time() - filemtime($logFile)) < 900) { $remaining = 900 - (time() - filemtime($logFile)); self::returnJson(['success' => false, 'message' => "Please wait before starting another import.", 'status' => 'cooldown', 'remaining' => $remaining]); return; } touch($lockFile); $projectRoot = dirname(dirname(__DIR__)); $scriptRelativePath = 'scripts/adb-rimo-import/rimo-import.php'; $scriptFullPath = $projectRoot . '/' . $scriptRelativePath; if (!file_exists($scriptFullPath)) { self::returnJson(['success' => false, 'message' => "Import script not found."]); return; } $php_executable = "php"; $command = "$php_executable $scriptRelativePath " . escapeshellarg($netzgebiet->source_id); $bgCommand = 'cd ' . escapeshellarg($projectRoot) . ' && ' . $command . ' > ' . escapeshellarg($logFile) . ' 2>&1 & echo $!'; $pid = shell_exec($bgCommand); if(empty($pid) || !is_numeric(trim($pid))) { self::returnJson(['success' => false, 'message' => "Failed to start background process."]); return; } file_put_contents($lockFile, trim($pid)); self::returnJson(['success' => true, 'message' => 'RIMO import started.']); } protected function getRimoImportStatusAction(): void { $ids = $this->postData['ids'] ?? []; if (empty($ids)) { self::returnJson(['success' => true, 'data' => []]); return; } $statuses = []; foreach ($ids as $id) { $netzgebiet = ADBNetzgebiet::get($id); if (!$netzgebiet || !$netzgebiet->id || strpos($netzgebiet->source, 'rimo-') !== 0 || empty($netzgebiet->source_id)) { $statuses[$id] = ['status' => 'not_applicable']; continue; } $safeSourceId = preg_replace('/[^a-zA-Z0-9_-]/', '_', $netzgebiet->source_id); $logDir = TEMP_DIR . "/ADBNetzgebietRimoImport/" . $safeSourceId; $logFile = $logDir . "/import.log"; $lockFile = $logDir . "/import.lock"; if (file_exists($lockFile)) { $pid = trim(file_get_contents($lockFile)); // Check if process is still running. posix_getpgid returns false if process does not exist. if (is_numeric($pid) && posix_getpgid((int)$pid) !== false) { $statuses[$id] = ['status' => 'running']; } else { // Stale lock file, process is gone. @unlink($lockFile); // Check for cooldown based on log file from the finished process if (file_exists($logFile) && (time() - filemtime($logFile)) < 900) { $statuses[$id] = [ 'status' => 'cooldown', 'remaining' => 900 - (time() - filemtime($logFile)) ]; } else { $statuses[$id] = ['status' => 'idle']; } } } elseif (file_exists($logFile) && (time() - filemtime($logFile)) < 900) { $statuses[$id] = [ 'status' => 'cooldown', 'remaining' => 900 - (time() - filemtime($logFile)) ]; } else { $statuses[$id] = ['status' => 'idle']; } } self::returnJson(['success' => true, 'data' => $statuses]); } protected function getRimoImportLogAction(): void { $id = $_GET['id'] ?? null; if (empty($id)) { self::returnJson(['success' => false, 'message' => "Netzgebiet ID required."]); return; } $netzgebiet = ADBNetzgebiet::get($id); if (!$netzgebiet || !$netzgebiet->id || empty($netzgebiet->source_id)) { self::returnJson(['success' => false, 'message' => "Netzgebiet not found or not applicable."]); return; } $safeSourceId = preg_replace('/[^a-zA-Z0-9_-]/', '_', $netzgebiet->source_id); $logDir = TEMP_DIR . "/ADBNetzgebietRimoImport/" . $safeSourceId; $logFile = $logDir . "/import.log"; $lockFile = $logDir . "/import.lock"; $logContent = ""; if (file_exists($logFile)) { $logContent = file_get_contents($logFile); } $status = 'idle'; if (file_exists($lockFile)) { $pid = trim(file_get_contents($lockFile)); if (is_numeric($pid) && posix_getpgid((int)$pid) !== false) { $status = 'running'; } else { @unlink($lockFile); // Stale lock, process is gone } } if ($status !== 'running') { if (file_exists($logFile) && (time() - filemtime($logFile)) < 900) { $status = 'cooldown'; } else { $status = file_exists($logFile) ? 'finished' : 'idle'; } } self::returnJson([ 'success' => true, 'data' => [ 'log' => $logContent, 'status' => $status, 'timestamp' => file_exists($logFile) ? filemtime($logFile) : null ] ]); } // TODO: Implement RIMO API check protected function checkRimoSourceIdAction(): void { self::returnJson(['success' => false, 'message' => "RIMO API check not available."]); } }