'po roce', 'MESIC' => 'po měsíci', 'NEPRETRZITE' => 'nepřetržitě', ]; public static function normalizeObdobi(string $obdobi): string { $obdobi = strtoupper(trim($obdobi)); return isset(self::OBDOBI[$obdobi]) ? $obdobi : self::OBDOBI_VYCHOZI; } public static function normalizeSplatnost(int $dny): int { return max(0, min(365, $dny)); } /** * Maska musí obsahovat právě značku pořadového čísla a vejít se do čísla faktury (20 znaků). */ public static function validateMaska(string $maska): ?string { $maska = trim($maska); if ($maska === '') { return 'Zadejte masku čísla faktury.'; } if (mb_strlen($maska) > 40) { return 'Maska může mít nejvýše 40 znaků.'; } if (!preg_match('/^(?:[^{}]|\{(?:ROK|RRRR|RR|MESIC|MM|CISLO\d?)\})+$/u', $maska)) { return 'Maska obsahuje neznámou značku. Povolené jsou {ROK}, {RR}, {MESIC} a {CISLO} nebo {CISLO4}.'; } if (!preg_match('/\{CISLO\d?\}/', $maska)) { return 'Maska musí obsahovat značku pořadového čísla, například {CISLO4}.'; } if (preg_match_all('/\{CISLO(\d)?\}/', $maska, $shody) && count($shody[0]) > 1) { return 'Maska může obsahovat značku pořadového čísla jen jednou.'; } if (isset($shody[1][0]) && $shody[1][0] !== '' && ((int) $shody[1][0] < 1 || (int) $shody[1][0] > 8)) { return 'Počet číslic u {CISLO} musí být 1 až 8, například {CISLO4}.'; } $priklad = self::format($maska, 2026, 1, 1); if (mb_strlen($priklad) > 20) { return 'Podle této masky by číslo faktury přesáhlo 20 znaků.'; } return null; } public static function format(string $maska, int $rok, int $mesic, int $cislo): string { $mesicText = str_pad((string) $mesic, 2, '0', STR_PAD_LEFT); $vysledek = strtr($maska, [ '{ROK}' => (string) $rok, '{RRRR}' => (string) $rok, '{RR}' => substr((string) $rok, -2), '{MESIC}' => $mesicText, '{MM}' => $mesicText, ]); return (string) preg_replace_callback( '/\{CISLO(\d)?\}/', static function (array $shoda) use ($cislo): string { $text = (string) $cislo; $sirka = ($shoda[1] ?? '') !== '' ? (int) $shoda[1] : 0; return $sirka > 0 ? str_pad($text, $sirka, '0', STR_PAD_LEFT) : $text; }, $vysledek ); } }