feat: Update MailToTaskImporter to handle attachment MIME types and improve filename handling; add CLAUDE.md for project guidance; create TODO.md for PDF attachment issue

This commit is contained in:
2026-02-18 21:50:42 +01:00
parent c165a94016
commit 203a45e199
6 changed files with 267 additions and 15 deletions

View File

@@ -204,7 +204,18 @@ class MailToTaskImporter
// Zapisz normalne załączniki
foreach ( $content['attachments'] as $attachment )
{
$this -> attachments -> uploadFromContent( $task_id, self::TASK_USER_ID, $attachment['name'], $attachment['content'] );
$att_name = $attachment['name'];
$att_mime = isset( $attachment['mime'] ) ? $attachment['mime'] : '';
// Jeśli nazwa nie ma rozszerzenia, dodaj je z MIME type
if ( pathinfo( $att_name, PATHINFO_EXTENSION ) === '' && $att_mime !== '' )
{
$mime_ext = $this -> extensionFromMime( $att_mime );
if ( $mime_ext !== '' )
$att_name .= '.' . $mime_ext;
}
$this -> attachments -> uploadFromContent( $task_id, self::TASK_USER_ID, $att_name, $attachment['content'] );
}
$import_status = 'imported';
@@ -466,7 +477,8 @@ class MailToTaskImporter
$attachments[] = [
'name' => $part['name'],
'content' => $part['body']
'content' => $part['body'],
'mime' => isset( $part['mime'] ) ? $part['mime'] : 'application/octet-stream'
];
}
@@ -535,6 +547,14 @@ class MailToTaskImporter
$name = $this -> decodeHeaderValue( $name );
// Fallback: jeśli brak nazwy lub brak rozszerzenia, spróbuj z surowych nagłówków MIME
if ( $part_number !== null && ( trim( $name ) === '' || pathinfo( $name, PATHINFO_EXTENSION ) === '' ) )
{
$mime_name = $this -> extractNameFromRawMime( $imap, $message_no, $part_number );
if ( $mime_name !== '' )
$name = $mime_name;
}
$disposition = isset( $part -> disposition ) ? strtoupper( trim( (string)$part -> disposition ) ) : '';
$content_id = '';
if ( isset( $part -> id ) and trim( (string)$part -> id ) !== '' )
@@ -578,7 +598,113 @@ class MailToTaskImporter
}
}
return $params;
return $this -> reassembleRfc2231Params( $params );
}
private function reassembleRfc2231Params( array $params )
{
$normal = [];
$rfc2231 = [];
foreach ( $params as $key => $value )
{
if ( preg_match( '/^([^*]+)\*(\d+)?\*?$/', $key, $m ) )
{
$base = $m[1];
$index = isset( $m[2] ) && $m[2] !== '' ? (int)$m[2] : 0;
$rfc2231[$base][$index] = $value;
}
else
$normal[$key] = $value;
}
foreach ( $rfc2231 as $base => $parts )
{
ksort( $parts, SORT_NUMERIC );
$combined = implode( '', $parts );
if ( preg_match( "/^([^']*)'([^']*)'(.+)$/s", $combined, $enc ) )
{
$charset = strtoupper( $enc[1] );
$decoded = rawurldecode( $enc[3] );
if ( $charset !== '' && $charset !== 'UTF-8' && function_exists( 'mb_convert_encoding' ) )
$decoded = @mb_convert_encoding( $decoded, 'UTF-8', $charset );
$combined = $decoded;
}
if ( $combined !== '' || !isset( $normal[$base] ) )
$normal[$base] = $combined;
}
return $normal;
}
private function extractNameFromRawMime( $imap, $message_no, $part_number )
{
$headers = (string)@imap_fetchmime( $imap, $message_no, $part_number, FT_PEEK );
if ( trim( $headers ) === '' )
return '';
// Szukaj filename w Content-Disposition, potem name w Content-Type
foreach ( [ 'filename', 'name' ] as $param_name )
{
// RFC 2231 z kontynuacją: param*0*=...; param*1*=...
$rfc2231_parts = [];
if ( preg_match_all( '/' . preg_quote( $param_name, '/' ) . '\*(\d+)\*?\s*=\s*([^\r\n;]+)/i', $headers, $matches, PREG_SET_ORDER ) )
{
foreach ( $matches as $m )
$rfc2231_parts[(int)$m[1]] = trim( $m[2], " \t\"'" );
if ( !empty( $rfc2231_parts ) )
{
ksort( $rfc2231_parts, SORT_NUMERIC );
$combined = implode( '', $rfc2231_parts );
if ( preg_match( "/^([^']*)'([^']*)'(.+)$/s", $combined, $enc ) )
{
$charset = strtoupper( $enc[1] );
$decoded = rawurldecode( $enc[3] );
if ( $charset !== '' && $charset !== 'UTF-8' && function_exists( 'mb_convert_encoding' ) )
$decoded = @mb_convert_encoding( $decoded, 'UTF-8', $charset );
$combined = $decoded;
}
if ( trim( $combined ) !== '' && pathinfo( $combined, PATHINFO_EXTENSION ) !== '' )
return trim( $combined );
}
}
// RFC 2231 prosty: param*=charset'lang'value
if ( preg_match( '/' . preg_quote( $param_name, '/' ) . '\*\s*=\s*([^\r\n;]+)/i', $headers, $m ) )
{
$val = trim( $m[1], " \t\"'" );
if ( preg_match( "/^([^']*)'([^']*)'(.+)$/s", $val, $enc ) )
{
$charset = strtoupper( $enc[1] );
$decoded = rawurldecode( $enc[3] );
if ( $charset !== '' && $charset !== 'UTF-8' && function_exists( 'mb_convert_encoding' ) )
$decoded = @mb_convert_encoding( $decoded, 'UTF-8', $charset );
$val = $decoded;
}
if ( trim( $val ) !== '' && pathinfo( $val, PATHINFO_EXTENSION ) !== '' )
return trim( $val );
}
// Standardowy: param="value" lub param=value
if ( preg_match( '/' . preg_quote( $param_name, '/' ) . '\s*=\s*"([^"]+)"/i', $headers, $m ) )
{
$val = $this -> decodeHeaderValue( trim( $m[1] ) );
if ( trim( $val ) !== '' && pathinfo( $val, PATHINFO_EXTENSION ) !== '' )
return trim( $val );
}
if ( preg_match( '/' . preg_quote( $param_name, '/' ) . '\s*=\s*([^\s;]+)/i', $headers, $m ) )
{
$val = $this -> decodeHeaderValue( trim( $m[1], " \t\"'" ) );
if ( trim( $val ) !== '' && pathinfo( $val, PATHINFO_EXTENSION ) !== '' )
return trim( $val );
}
}
return '';
}
private function decodePartBody( $raw, $encoding )
@@ -1048,8 +1174,32 @@ class MailToTaskImporter
}
private function guessExtensionFromMime( $mime )
{
$ext = $this -> extensionFromMime( $mime );
return $ext !== '' ? $ext : 'png';
}
private function extensionFromMime( $mime )
{
$map = [
'application/pdf' => 'pdf',
'application/zip' => 'zip',
'application/x-rar-compressed' => 'rar',
'application/vnd.rar' => 'rar',
'application/x-7z-compressed' => '7z',
'application/msword' => 'doc',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx',
'application/vnd.ms-excel' => 'xls',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx',
'application/vnd.ms-powerpoint' => 'ppt',
'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx',
'application/xml' => 'xml',
'application/json' => 'json',
'application/rtf' => 'rtf',
'application/octet-stream' => '',
'text/plain' => 'txt',
'text/html' => 'html',
'text/csv' => 'csv',
'image/jpeg' => 'jpg',
'image/png' => 'png',
'image/gif' => 'gif',
@@ -1060,7 +1210,7 @@ class MailToTaskImporter
];
$mime = strtolower( trim( (string)$mime ) );
return isset( $map[$mime] ) ? $map[$mime] : 'png';
return isset( $map[$mime] ) ? $map[$mime] : '';
}
private function replaceCidReferences( $html, array $cid_to_url )