RfunctionsSan = implode('|', array_map('preg_quote', $this->Rfunctions)); } /* Es wird ein Array mit allen Notentypen pro Gerät, Jahr und Person erwartet, so dass noten_bezeichnung_id als Key verwendet werden kann: $valuesArray = [ noten_bezeichnung_id => value, … ]; */ private function santinzeString(string $string) : string { $replacePatterns = [ '/->R\w+/', // 1. Removes ->R followed by digits '/[^a-zA-Z0-9(){}+*\/.[\]\-]/' // 2. Fixed syntax: Allowed characters list ]; return preg_replace($replacePatterns,'', $string); } private function sanitizeStringWithRun(string $string) : string { $replacePattern = '/[^a-zA-Z0-9(){}+*\/.\-\[\]>]/'; return preg_replace($replacePattern, "", $string); } public function getBenoetigteIds(string $schemaRaw) { $schema = $this->santinzeString($schemaRaw); preg_match_all('/\{(\d+)\}/', $schema, $matches); return $matches[1]; } public function getBenoetigteIdsComplex(string $schemaRaw) { $schema = $this->santinzeString($schemaRaw); $indexedMatches = []; // Pattern breakdown: // \{(\d+) -> Match { and capture the first number (Note ID) // (?:\[(\w*)\])? -> OPTIONALLY match [ and capture alphanumeric content inside (Apparatus ID) // (?:\[(\d+)\])? -> OPTIONALLY match [ and capture number inside (Run Number) // \} -> Match the closing } if (preg_match_all('/\{(\d+)(?:\[(\w*)\])?(?:\[(\d+)\])?\}/', $schema, $matches, PREG_SET_ORDER)) { foreach ($matches as $match) { $noteId = $match[1]; $rawGeraet = $match[2] ?? 'S'; $geraetId = ($rawGeraet === 'S' || $rawGeraet === '') ? 'A' : $rawGeraet; $runNumber = isset($match[3]) ? (int)$match[3] : 1; $indexedMatches[] = [ 'noteId' => $noteId, 'geraetId' => $geraetId, 'run' => $runNumber ]; } } return $indexedMatches; } public function getBenoetigteIdsComplexWithRun(string $schemaRaw) { $schema = $this->sanitizeStringWithRun($schemaRaw); $indexedMatches = []; // Pattern breakdown: // \{(\d+) -> Match { and capture the first number (Note ID) // (?:\[(\w*)\])? -> OPTIONALLY match [ and capture alphanumeric content inside (Apparatus ID) // (?:\[(\d+)\])? -> OPTIONALLY match [ and capture number inside (Run Number) // \} -> Match the closing } if (preg_match_all('/\{(\d+)(?:\[(\w*)\])?(?:\[(\d+)\])?\}/', $schema, $matches, PREG_SET_ORDER)) { foreach ($matches as $match) { $noteId = $match[1]; $rawGeraet = $match[2] ?? 'S'; $geraetId = ($rawGeraet === 'S' || $rawGeraet === '') ? 'A' : $rawGeraet; $runNumber = isset($match[3]) ? (int)$match[3] : 1; $indexedMatches[] = [ 'noteId' => $noteId, 'geraetId' => $geraetId, 'run' => $runNumber ]; } } $arrayRes = $this->getStoreGeraetRun($schemaRaw); $indexedMatches['targetRun'] = $arrayRes['run_num'] ?? 'A'; $indexedMatches['targetGeraet'] = $arrayRes['geraet_id'] ?? 'A'; return $indexedMatches; } public function getStoreGeraetRun(string $schemaRaw): array { $schema = $this->sanitizeStringWithRun($schemaRaw); $pattern = '/->R(?\d+|S)(?:G(?\d+|S))?/'; if (preg_match($pattern, $schema, $matchesTagetRun)) { $run = $matchesTagetRun['run_num'] ?? 'S'; $geraet = $matchesTagetRun['geraet_id'] ?? 'S'; return [ 'run_num' => $run === 'S' ? 'A' : (int)$run, 'geraet_id' => $geraet === 'S' ? 'A' : (int)$geraet, ]; } return [ 'run_num' => 'A', 'geraet_id' => 'A' ]; } private function insertValuesComplex(string $schemaRaw, array $valuesArray, int $currentId, int $runNumber, bool $avr = false) { $schema = $this->santinzeString($schemaRaw); $idsNeededArray = $this->getBenoetigteIdsComplex($schemaRaw); // 1. Validation Loop foreach ($idsNeededArray as $sina) { $noteId = $sina['noteId']; $geraetIdSearch = ($sina['geraetId'] === 'A') ? $currentId : $sina['geraetId']; if (!isset($valuesArray[$geraetIdSearch][$noteId][$runNumber]["value"])) { return ['success' => false, 'value' => "Fehlende Daten für Gerät $geraetIdSearch, Note $noteId, Lauf $runNumber"]; } } try { $returnStr = preg_replace_callback('/\{(\d+)(?:\[(\w*)\])?(?:\[(\d+)\])?\}/', function($m) use ($valuesArray, $currentId, $runNumber, $avr) { $noteId = $m[1]; // Get value inside brackets, default to 'S' if none exists $rawGeraet = $m[2] ?? 'S'; $geraetId = ($rawGeraet === 'S' || $rawGeraet === '') ? $currentId : $rawGeraet; if ($avr && $valuesArray[$geraetId][$noteId][$runNumber]["type"] === 'default_value') { return 'default_value_skip'; } else { // Return value from [Device][Note][Run] return $valuesArray[$geraetId][$noteId][$runNumber]["value"] ?? 0; } }, $schema); return ['success' => true, 'value' => $returnStr]; } catch (\Exception $e) { return ['success' => false, 'value' => 'Problem beim Einsetzen der Werte']; } } private function calculate(string $rechnungRaw) { global $rechner; $rechnung = $this->santinzeString($rechnungRaw); try { $result = $this->rechner->calculate($rechnung); return ['success' => true, 'value' => floatval($result)]; } catch (\ChrisKonnertz\StringCalc\Exceptions\StringCalcException $e) { return ['success' => false, 'value' => 'Problem bei der Berechnung: Ungültige Syntax '. $rechnung]; } catch (\Exception $e) { return ['success' => false, 'value' => 'Problem bei der Berechnung']; } } public function berechneStringComplex(string $schemaRaw, array $valuesArray = [], int $geraetId = -1, int $run_number = 0) { if ($schemaRaw === '') { return ['success' => false, 'value' => 'Leeres Schema']; } if ($geraetId < 0) { return ['success' => false, 'value' => 'Keine Geraet Id angegeben.']; } if ($run_number === 0) { return ['success' => false, 'value' => 'Keine Run_Number angegeben.']; } $rechnungArray = $this->insertValuesComplex($schemaRaw, $valuesArray, $geraetId, $run_number); if (!isset($rechnungArray['success']) || !isset($rechnungArray['value']) || !$rechnungArray['success']) { return ['success' => false, 'value' => $rechnungArray['value'] . " RECHNUNG: " . $schemaRaw ?? 'Fehler beim Einsetzen der Werte oder Werte fehlen']; } return $this->calculate($rechnungArray['value']); } public function berechneStringComplexRun(string $schemaRaw, array $valuesArray = [], int $geraetId = -1, int $programm = 0, array $arrayRuns = []) { if ($schemaRaw === '') { return ['success' => false, 'value' => 'Leeres Schema']; } if ($geraetId < 0) { return ['success' => false, 'value' => 'Keine Geraet Id angegeben.']; } if ($programm === 0) { return ['success' => false, 'value' => 'Kein Programm angegeben.']; } if ($arrayRuns === []) { return ['success' => false, 'value' => 'Leeres Array Runs Noten.']; } $rechnung = $this->insertRunValues($schemaRaw, $valuesArray, $geraetId, $programm, $arrayRuns); if (!isset($rechnung) || $rechnung === '') { return ['success' => false, 'value' => 'Fehler beim Einsetzen der Werte oder Werte fehlen']; } return $this->calculate($rechnung); } private function insertRunValues($schema, $valuesArray, $currentId, $programm, $arrayRuns) { $pattern = '/(?P' . $this->RfunctionsSan . ')\((?P(?:(?R)|[^()])*)\)/'; return preg_replace_callback($pattern, function($m) use ($valuesArray, $currentId, $programm, $arrayRuns) { $name = $m['fn_name']; $content = $m['content']; // Resolve inner nested functions first $content = $this->insertRunValues($content, $valuesArray, $currentId, $programm, $arrayRuns); if (!in_array($name, $this->Rfunctions)) { return $m[0]; } $parts = []; $ids = $this->getBenoetigteIdsComplex($content); $runCount = 0; foreach ($ids as $sid) { $geraetId = ($sid['geraetId'] === 'A') ? $currentId : $sid['geraetId']; $runCountN = $arrayRuns[$sid['noteId']][$geraetId][$programm] ?? $arrayRuns[$sid['noteId']][$geraetId]["all"] ?? $arrayRuns["default"] ?? 1; if ($runCount !== $runCountN && $runCount !== 0) { return; } $runCount = $runCountN; } $string = ""; switch ($name) { case "AVGR": case "SUMR": $nulls = 0; for ($r = 1; $r <= $runCount; $r++) { $res = $this->insertValuesComplex($content, $valuesArray, $currentId, $r, true); if (!$res['success']) { return "0"; } elseif ($res['value'] === 'default_value_skip') { $nulls++; } else { $parts[] = "(" . $res['value'] . ")"; } } $innerMath = implode(" + ", $parts); $div_count = $runCount - $nulls; if ($div_count === 0 || count($parts) < 1) return "0"; $string = ($name === "AVGR") ? "(($innerMath) / $div_count)" : "($innerMath)"; break; case "MAXR": case "MINR": $arrayRunValues = []; for ($r = 1; $r <= $runCount; $r++) { $res = $this->berechneStringComplex($content, $valuesArray, $currentId, $r); // FIXED: Access the ['value'] key of the returned array if ($res['success']) { $arrayRunValues[] = floatval($res['value']); } else { $arrayRunValues[] = 0; } } // Safety check for empty arrays to prevent max() errors if (empty($arrayRunValues)) return "0"; $string = ($name === "MAXR") ? max($arrayRunValues) : min($arrayRunValues); break; } return (string)$string; }, $schema); } public function checkRunFunctions(string $schemaRaw) { if ($schemaRaw === '') { return ['success' => false, 'value' => 'Leeres Schema']; } $schema = $this->santinzeString($schemaRaw); $pattern = '/(?P' . $this->RfunctionsSan . ')\((?:(?R)|[^()])*+\)/'; return (bool) preg_match($pattern, $schema); } }