feat: Enhance MailToTaskImporter with inline image support and update task content handling

This commit is contained in:
2026-02-10 15:37:27 +01:00
parent 3adc50618d
commit 67ed085b38
8 changed files with 169 additions and 13 deletions

View File

@@ -1,7 +1,13 @@
{
"permissions": {
"allow": [
"Bash(git log:*)"
"Bash(git log:*)",
"Bash(git checkout:*)",
"Bash(mysql:*)",
"Bash(if [ -f logs.txt ])",
"Bash(then rm logs.txt)",
"Bash(else echo \"Plik logs.txt nie istnieje\")",
"Bash(fi)"
]
}
}

View File

@@ -151,9 +151,9 @@
},
"config.php": {
"type": "-",
"size": 1249,
"size": 1230,
"lmtime": 1770587027872,
"modified": false
"modified": true
},
"cron.php": {
"type": "-",
@@ -204,9 +204,9 @@
},
"main_view.php": {
"type": "-",
"size": 42341,
"size": 42781,
"lmtime": 1770583657535,
"modified": false
"modified": true
},
"task_edit.php": {
"type": "-",
@@ -216,8 +216,8 @@
},
"task_popup.php": {
"type": "-",
"size": 11071,
"lmtime": 0,
"size": 12356,
"lmtime": 1770711646996,
"modified": false
},
"task_single.php": {

View File

@@ -154,7 +154,7 @@ class MailToTaskImporter
null,
false,
'off',
false,
true,
self::TASK_STATUS_ID,
'on',
0
@@ -172,6 +172,30 @@ class MailToTaskImporter
continue;
}
// Konwertuj inline images do Base64 i osadź w treści
$cid_to_data_uri = [];
if ( isset( $content['inline_images'] ) && is_array( $content['inline_images'] ) )
{
foreach ( $content['inline_images'] as $cid => $inline_image )
{
// Konwertuj obraz do Base64 data URI
$data_uri = $this -> convertToDataUri( $inline_image['content'], $inline_image['mime'] );
if ( $data_uri !== '' )
$cid_to_data_uri[$cid] = $data_uri;
}
}
// Jeśli są inline images i HTML, zastąp CIDy na Base64 data URI i użyj HTML jako treść
if ( !empty( $cid_to_data_uri ) && isset( $content['html'] ) && trim( $content['html'] ) !== '' )
{
$html_with_images = $this -> replaceCidReferences( $content['html'], $cid_to_data_uri );
$task_text = $this -> prepareImportedTaskTextFromHtml( $html_with_images );
// Zaktualizuj treść zadania
$this -> mdb -> update( 'tasks', [ 'text' => $task_text ], [ 'id' => $task_id ] );
}
// Zapisz normalne załączniki
foreach ( $content['attachments'] as $attachment )
{
$this -> attachments -> uploadFromContent( $task_id, self::TASK_USER_ID, $attachment['name'], $attachment['content'] );
@@ -412,9 +436,25 @@ class MailToTaskImporter
}
$cid_refs = $this -> extractReferencedCidValues( $html );
$attachments = [];
$inline_images = [];
foreach ( $attachment_candidates as $part )
{
$content_id = isset( $part['content_id'] ) ? (string)$part['content_id'] : '';
// Sprawdź czy to inline image używany w HTML
if ( $content_id !== '' && isset( $cid_refs[$content_id] ) )
{
$inline_images[$content_id] = [
'name' => $part['name'],
'content' => $part['body'],
'mime' => isset( $part['mime'] ) ? $part['mime'] : 'application/octet-stream'
];
continue;
}
if ( !$this -> shouldImportAttachment( $part, $cid_refs ) )
continue;
@@ -429,6 +469,8 @@ class MailToTaskImporter
return [
'text' => $text,
'html' => $html,
'inline_images' => $inline_images,
'attachments' => $attachments
];
}
@@ -616,6 +658,22 @@ class MailToTaskImporter
if ( preg_match( '/^\s*--\s*$/', $line_trim ) )
break;
// Polskie stopki
if ( preg_match( '/^\s*(Pozdrawiam|Pozdrowienia|Z\s+powa[zż]aniem|Dzi[eę]kuj[eę]|Serdecznie|Miłego\s+dnia)/iu', $line_trim ) )
break;
// Angielskie stopki
if ( preg_match( '/^\s*(Best\s+regards|Kind\s+regards|Regards|Sincerely|Thanks|Thank\s+you|Cheers)/i', $line_trim ) )
break;
// Mobilne sygnatury
if ( preg_match( '/^\s*Sent\s+from\s+(my\s+)?(iPhone|iPad|Android|Samsung|mobile)/i', $line_trim ) )
break;
// Separatory z podkreślników
if ( preg_match( '/^\s*_{3,}\s*$/', $line_trim ) )
break;
$clean[] = $line_trim;
}
@@ -982,4 +1040,90 @@ class MailToTaskImporter
return '';
}
private function convertToDataUri( $content, $mime_type )
{
$content = (string)$content;
if ( $content === '' )
return '';
$mime_type = trim( (string)$mime_type );
if ( $mime_type === '' || $mime_type === 'application/octet-stream' )
$mime_type = 'image/png'; // Domyślny typ dla obrazów
$base64 = base64_encode( $content );
return 'data:' . $mime_type . ';base64,' . $base64;
}
private function replaceCidReferences( $html, array $cid_to_url )
{
if ( trim( (string)$html ) === '' || empty( $cid_to_url ) )
return $html;
foreach ( $cid_to_url as $cid => $url )
{
// Zastąp wszystkie odniesienia do tego CID
$html = preg_replace(
'/(<img[^>]+src=["\'])cid:' . preg_quote( $cid, '/' ) . '(["\'][^>]*>)/i',
'$1' . $url . '$2',
$html
);
}
return $html;
}
private function prepareImportedTaskTextFromHtml( $html )
{
$html = trim( (string)$html );
if ( $html === '' )
return '(brak tresci)';
// Usuń niepotrzebne elementy (style, script, blockquote)
$html = preg_replace( '/<style\b[^>]*>.*?<\/style>/is', ' ', $html );
$html = preg_replace( '/<script\b[^>]*>.*?<\/script>/is', ' ', $html );
$html = preg_replace( '/<blockquote\b[^>]*>.*?<\/blockquote>/is', ' ', $html );
$html = preg_replace( '/<div[^>]*class="[^"]*(gmail_quote|gmail_signature)[^"]*"[^>]*>.*?<\/div>/is', ' ', $html );
// Usuń niepotrzebne atrybuty z obrazów (zostaw tylko src, alt, width, height)
$html = preg_replace_callback(
'/<img([^>]*)>/i',
function( $matches ) {
$attrs = $matches[1];
$new_attrs = [];
// Wyciągnij ważne atrybuty
if ( preg_match( '/\bsrc=["\']([^"\']+)["\']/i', $attrs, $src ) )
{
// Nie używaj htmlspecialchars dla data URI (Base64), tylko dla zwykłych URLi
$src_value = $src[1];
if ( strpos( $src_value, 'data:' ) !== 0 )
$src_value = htmlspecialchars( $src_value, ENT_QUOTES );
$new_attrs[] = 'src="' . $src_value . '"';
}
if ( preg_match( '/\balt=["\']([^"\']+)["\']/i', $attrs, $alt ) )
$new_attrs[] = 'alt="' . htmlspecialchars( $alt[1], ENT_QUOTES ) . '"';
if ( preg_match( '/\bwidth=["\']?(\d+)["\']?/i', $attrs, $width ) )
$new_attrs[] = 'width="' . (int)$width[1] . '"';
if ( preg_match( '/\bheight=["\']?(\d+)["\']?/i', $attrs, $height ) )
$new_attrs[] = 'height="' . (int)$height[1] . '"';
return '<img ' . implode( ' ', $new_attrs ) . '>';
},
$html
);
// Wyczyść HTML z nadmiarowych białych znaków
$html = preg_replace( '/\s+/', ' ', $html );
$html = trim( $html );
if ( $html === '' )
return '(brak tresci)';
return $html;
}
}

View File

@@ -224,7 +224,8 @@ class TaskAttachmentRepository
if ( !$insert )
return [ 'status' => 'error', 'msg' => 'Nie udalo sie zapisac zalacznika w bazie.' ];
return [ 'status' => 'success' ];
$attachment_id = $this -> mdb -> id();
return [ 'status' => 'success', 'id' => (int)$attachment_id, 'relative_path' => $relative_path, 'stored_name' => $stored_name ];
}
private function buildPublicUrl( $relative_path, $stored_name )

View File

@@ -21,5 +21,5 @@ $settings['tasks_auto_start_timer'] = false;
// OpenAI ChatGPT API configuration for email task parsing
$settings['openai_api_key'] = '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'; // Wklej tutaj swój klucz API OpenAI
$settings['openai_parse_emails'] = true; // true = użyj AI do parsowania emaili, false = normalne parsowanie
$settings['openai_parse_emails'] = false; // true = użyj AI do parsowania emaili, false = normalne parsowanie
$settings['openai_model'] = 'gpt-4o-mini'; // Model: gpt-4o-mini, gpt-4o, gpt-5-nano, itp.

View File

@@ -27,7 +27,10 @@ if ( $this -> params['label'] )
if ( $this -> params['placeholder'] )
$out .= 'placeholder="' . $this -> params['placeholder'] . '" ';
$out .= ' rows="' . $this -> params['rows'] . '">' . $this -> secureHTML( $this -> params['value'] ) . '</textarea>';
if ( isset( $this -> params['skip_encoding'] ) && $this -> params['skip_encoding'] )
$out .= ' rows="' . $this -> params['rows'] . '">' . $this -> params['value'] . '</textarea>';
else
$out .= ' rows="' . $this -> params['rows'] . '">' . $this -> secureHTML( $this -> params['value'] ) . '</textarea>';
if ( $this -> params['label'] )
{

View File

@@ -22,7 +22,8 @@ ob_start();
'name' => 'text',
'id' => 'text',
'value' => $this -> task[ 'text' ],
'rows' => 10
'rows' => 10,
'skip_encoding' => true
]
);
?>

View File

@@ -22,7 +22,8 @@ ob_start();
'name' => 'text',
'id' => 'text',
'value' => $this -> task[ 'text' ],
'rows' => 10
'rows' => 10,
'skip_encoding' => true
]
);
?>