Document gen fixed
This commit is contained in:
@@ -133,6 +133,94 @@ public function updateSettings(UpdateDocumentTemplateRequest $request, DocumentT
|
||||
return redirect()->back()->with('success', 'Nastavitve predloge shranjene.');
|
||||
}
|
||||
|
||||
public function rescanTokens(DocumentTemplate $template)
|
||||
{
|
||||
$this->ensurePermission();
|
||||
|
||||
// Best-effort: read stored DOCX from disk and re-scan tokens
|
||||
$tokens = [];
|
||||
try {
|
||||
/** @var TokenScanner $scanner */
|
||||
$scanner = app(TokenScanner::class);
|
||||
$zip = new \ZipArchive;
|
||||
$tmp = tempnam(sys_get_temp_dir(), 'tmpl');
|
||||
// Copy file from storage to a temp path
|
||||
$disk = 'public';
|
||||
$stream = \Storage::disk($disk)->get($template->file_path);
|
||||
file_put_contents($tmp, $stream);
|
||||
if ($zip->open($tmp) === true) {
|
||||
// Collect main document and header/footer parts
|
||||
$parts = [];
|
||||
for ($i = 0; $i < $zip->numFiles; $i++) {
|
||||
$stat = $zip->statIndex($i);
|
||||
$name = $stat['name'] ?? '';
|
||||
if (preg_match('#^word\/(document|header\d+|footer\d+)\.xml$#i', $name)) {
|
||||
$parts[] = $name;
|
||||
}
|
||||
}
|
||||
if (empty($parts)) {
|
||||
$parts = ['word/document.xml'];
|
||||
}
|
||||
$found = [];
|
||||
foreach ($parts as $name) {
|
||||
$xml = $zip->getFromName($name);
|
||||
if ($xml === false) {
|
||||
continue;
|
||||
}
|
||||
$norm = self::normalizeDocxXmlTokens($xml);
|
||||
$det = $scanner->scan($norm);
|
||||
if (! empty($det)) {
|
||||
$found = array_merge($found, $det);
|
||||
}
|
||||
}
|
||||
$tokens = array_values(array_unique($found));
|
||||
$zip->close();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// swallow scanning errors, keep $tokens as empty
|
||||
}
|
||||
|
||||
if (\Schema::hasColumn('document_templates', 'tokens')) {
|
||||
$template->tokens = $tokens;
|
||||
}
|
||||
// Auto-detect custom.* tokens and ensure meta.custom_default_types has defaults
|
||||
try {
|
||||
$meta = is_array($template->meta) ? $template->meta : [];
|
||||
$types = isset($meta['custom_default_types']) && is_array($meta['custom_default_types']) ? $meta['custom_default_types'] : [];
|
||||
$defaults = isset($meta['custom_defaults']) && is_array($meta['custom_defaults']) ? $meta['custom_defaults'] : [];
|
||||
foreach (($tokens ?? []) as $tok) {
|
||||
if (is_string($tok) && str_starts_with($tok, 'custom.')) {
|
||||
$key = substr($tok, 7);
|
||||
if ($key !== '') {
|
||||
if (! array_key_exists($key, $types)) {
|
||||
$types[$key] = 'string';
|
||||
}
|
||||
if (! array_key_exists($key, $defaults)) {
|
||||
$defaults[$key] = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (! empty($types)) {
|
||||
$meta['custom_default_types'] = $types;
|
||||
}
|
||||
if (! empty($defaults)) {
|
||||
$meta['custom_defaults'] = $defaults;
|
||||
}
|
||||
if ($meta !== ($template->meta ?? [])) {
|
||||
$template->meta = $meta;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// ignore meta typing/defaults failures
|
||||
}
|
||||
$template->updated_by = Auth::id();
|
||||
$template->save();
|
||||
|
||||
$count = is_array($tokens) ? count($tokens) : 0;
|
||||
|
||||
return back()->with('success', "Tokens posodobljeni ({$count} najdenih).");
|
||||
}
|
||||
|
||||
public function store(StoreDocumentTemplateRequest $request)
|
||||
{
|
||||
$this->ensurePermission();
|
||||
@@ -152,7 +240,7 @@ public function store(StoreDocumentTemplateRequest $request)
|
||||
$hash = hash_file('sha256', $file->getRealPath());
|
||||
$path = $file->store("document-templates/{$slug}/v{$nextVersion}", 'public');
|
||||
|
||||
// Scan tokens from uploaded DOCX (best effort)
|
||||
// Scan tokens from uploaded DOCX (best effort) – normalize XML to collapse Word run boundaries
|
||||
$tokens = [];
|
||||
try {
|
||||
/** @var TokenScanner $scanner */
|
||||
@@ -161,10 +249,31 @@ public function store(StoreDocumentTemplateRequest $request)
|
||||
$tmp = tempnam(sys_get_temp_dir(), 'tmpl');
|
||||
copy($file->getRealPath(), $tmp);
|
||||
if ($zip->open($tmp) === true) {
|
||||
$xml = $zip->getFromName('word/document.xml');
|
||||
if ($xml !== false) {
|
||||
$tokens = $scanner->scan($xml);
|
||||
// Collect main document and header/footer parts
|
||||
$parts = [];
|
||||
for ($i = 0; $i < $zip->numFiles; $i++) {
|
||||
$stat = $zip->statIndex($i);
|
||||
$name = $stat['name'] ?? '';
|
||||
if (preg_match('#^word\/(document|header\d+|footer\d+)\.xml$#i', $name)) {
|
||||
$parts[] = $name;
|
||||
}
|
||||
}
|
||||
if (empty($parts)) {
|
||||
$parts = ['word/document.xml'];
|
||||
}
|
||||
$found = [];
|
||||
foreach ($parts as $name) {
|
||||
$xml = $zip->getFromName($name);
|
||||
if ($xml === false) {
|
||||
continue;
|
||||
}
|
||||
$norm = self::normalizeDocxXmlTokens($xml);
|
||||
$det = $scanner->scan($norm);
|
||||
if (! empty($det)) {
|
||||
$found = array_merge($found, $det);
|
||||
}
|
||||
}
|
||||
$tokens = array_values(array_unique($found));
|
||||
$zip->close();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
@@ -224,6 +333,36 @@ public function store(StoreDocumentTemplateRequest $request)
|
||||
if (Schema::hasColumn('document_templates', 'tokens')) {
|
||||
$payload['tokens'] = $tokens;
|
||||
}
|
||||
// Auto-add default string types for any detected custom.* tokens
|
||||
try {
|
||||
$meta = isset($payload['meta']) && is_array($payload['meta']) ? $payload['meta'] : [];
|
||||
$types = isset($meta['custom_default_types']) && is_array($meta['custom_default_types']) ? $meta['custom_default_types'] : [];
|
||||
$defaults = isset($meta['custom_defaults']) && is_array($meta['custom_defaults']) ? $meta['custom_defaults'] : [];
|
||||
foreach (($tokens ?? []) as $tok) {
|
||||
if (is_string($tok) && str_starts_with($tok, 'custom.')) {
|
||||
$key = substr($tok, 7);
|
||||
if ($key !== '') {
|
||||
if (! array_key_exists($key, $types)) {
|
||||
$types[$key] = 'string';
|
||||
}
|
||||
if (! array_key_exists($key, $defaults)) {
|
||||
$defaults[$key] = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (! empty($types)) {
|
||||
$meta['custom_default_types'] = $types;
|
||||
}
|
||||
if (! empty($defaults)) {
|
||||
$meta['custom_defaults'] = $defaults;
|
||||
}
|
||||
if ($meta !== ($payload['meta'] ?? [])) {
|
||||
$payload['meta'] = $meta;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// ignore meta typing/defaults failures
|
||||
}
|
||||
$template = DocumentTemplate::create($payload);
|
||||
|
||||
return redirect()->back()->with('success', 'Predloga uspešno shranjena. (v'.$template->version.')')->with('template_id', $template->id);
|
||||
@@ -235,4 +374,29 @@ private function ensurePermission(): void
|
||||
abort(403);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapse common Word run boundaries and proofing spans so tokens like {{client.person.full_name}}
|
||||
* appear contiguous in XML for scanning.
|
||||
*/
|
||||
private static function normalizeDocxXmlTokens(string $xml): string
|
||||
{
|
||||
// Remove proofing error markers
|
||||
$xml = preg_replace('#<w:proofErr[^>]*/>#i', '', $xml) ?? $xml;
|
||||
// Iteratively collapse boundaries between text runs, even if w:rPr is present
|
||||
$patterns = [
|
||||
'#</w:t>\s*</w:r>\s*(?:<w:proofErr[^>]*/>\s*)*(?:<w:r[^>]*>\s*(?:<w:rPr>.*?</w:rPr>\s*)*)?<w:t[^>]*>#is',
|
||||
];
|
||||
$prev = null;
|
||||
while ($prev !== $xml) {
|
||||
$prev = $xml;
|
||||
foreach ($patterns as $pat) {
|
||||
$xml = preg_replace($pat, '', $xml) ?? $xml;
|
||||
}
|
||||
}
|
||||
// Remove zero-width and soft hyphen characters
|
||||
$xml = str_replace(["\xE2\x80\x8B", "\xC2\xAD"], '', $xml);
|
||||
|
||||
return $xml;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user