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:
@@ -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 )
|
||||
|
||||
Reference in New Issue
Block a user