На прошлой неделе я получил от клиента требование предоставить пользователю возможность загружать файлы из формы обратной связи, и эти файлы необходимо отправить по почте вместе с другими данными в службу поддержки. После недолгих копаний я пришел к пониманию, что в SFCC из коробки этого не предусмотрено. Если у вас есть аналогичные требования, эти шаги могут вам помочь.

Первый шаг — добавить поле ввода файла, чтобы пользователь мог загружать файлы.

<input type="file" id="" name="" accept="image/png, image/jpeg, image/jpg, application/pdf" multiple />

Атрибут несколько позволяет загружать несколько файлов.

Обратите внимание, что для того, чтобы разрешить отправку файлов вместе с формой, нам нужно добавить enctype=”multipart/form-data” в форму, как показано ниже.

<form action="" method="POST" enctype="multipart/form-data">

После добавления входного поля пользователь может загружать файлы. Теперь нам нужно закодировать эти файлы в base64. Для кодирования я использовал API FileReader.

function encodeToBase64(file) {
    const reader = new FileReader();
    reader.onloadend = () => {
        file.src = reader.result;
    };
  
    reader.readAsDataURL(file);
}

const fileInput = $('your selector');
// Encode files when uploaded
fileInput.on('change', (e) => {
    const files = e.target.files;
    if (files) {
        Array.prototype.forEach.call(files, encodeToBase64);
    }
});

После этого нам нужно передать эти файлы контроллеру, когда форма отправляется через вызов ajax. Обратите внимание, что если форма также содержит файлы, мы не можем передать их как json. Нам нужно создать для него объект FormData.

$('form').submit(function (e) {
    e.preventDefault();
    var form = new FormData($(this)[0])
    var inputFileField =$('your selector');
    var files = inputFileField.length ? inputFileField.prop('files') : null;

    if (files) {
        var fileObj = {};
        Array.prototype.forEach.call(files, function addToMap(file) {
            fileObj[file.name] = file.src;
        })

        if(fileObj) {
            form.append('your selector', JSON.stringify(fileObj));
        }
    }

     $.ajax({
        url: url,
        type: 'post',
        contentType: false,
        processData: false,
        data: form,
        success: function (data) {
            // do something
        },
        error: function (err) {
            // error message
        }
      });
});

Как только у нас будет все в контроллере, включая объект json для файлов с именем файла в качестве ключа, мы создадим карту файлов. Теперь мы создадим карту для всего, что необходимо для построения почты, и создадим содержимое, которое является MimeEncodedText, используя метод рендеринга dw.util.Template. Затем мы устанавливаем этот контент в почту и отправляем почту.

base.sendWithAttachments = function sendWithAttachments(emailObj, template, context) {
    var Map = require('dw/util/HashMap');
    var Template = require('dw/util/Template');
    var Mail = require('dw/net/Mail');

    // Create the template that we will use to send the email.
    var template = new Template('template');

    if(context.files){
        context.files = processFiles(JSON.parse(context.files));
    }
    
    var mailAttributes = new Map();
    Object.keys(context).forEach(function (key) {
        mailAttributes[key] = context[key];
    });

    var mail = new Mail();
    // Render the template with the data in the Hash
    var content = template.render(mailAttributes);

    mail.addTo(emailObj.to);
    mail.setFrom(emailObj.from);
    mail.setSubject(emailObj.subject);
    
    if ('cc' in emailObj && emailObj.cc) {
        mail.setCc(new ArrayList(emailObj.cc));
    }
    if ('bcc' in emailObj && emailObj.bcc) {
        mail.setBcc(new ArrayList(emailObj.bcc));
    }

    mail.setContent(content);
    mail.send();
}
function processFiles(files) {
    var Map = require('dw/util/HashMap');
    
    var filesMap = new Map();
    Object.keys(files).forEach(function (key) {
        filesMap.put(key, files[key]);  
    });

    return filesMap;
}

Структура шаблона зависит от того, какие вещи у нас есть в почте. В моем случае я должен показать некоторые данные, заполненные пользователем, в хорошо структурированном формате и прикрепить несколько файлов. Поэтому мой шаблон выглядит примерно так.

<iscontent type="multipart/mixed; boundary=content-divider" compact="false" charset="ISO-8859-1">--content-divider
Content-Type: multipart/alternative; boundary=mail-body-divider

--mail-body-divider
Content-Type: text/html; charset=utf-8
Content-Transfer-Encoding: quoted-printable

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
   <head>
      <!-- Add styles or anything required here -->
   </head>
   <body>
      <!-- Display all the content here -->
   </body>
</html>

--mail-body-divider--
<isif condition="${ !empty(pdict.files) }"><isloop items="${ pdict.files.keySet() }" var="key">
<isset name="file" value="${ pdict.files.get(key) }" scope="page"/>
<isset name="contentType" value="${ file.substring(file.indexOf(':')+1, file.indexOf(";")) }" scope="page"/>
<isset name="fileContent" value="${ file.replace('data:', '').replace(/^.+,/, '') }" scope="page"/>
--content-divider
Content-Type: ${contentType}; name="${key}";
Content-Description: ${key}
Content-Disposition: attachment; filename="${key}"; size=${fileContent.length}; creation-date="${(new Date()).toISOString()}"; modification-date="${(new Date()).toISOString()}"
Content-Transfer-Encoding: base64

${fileContent}</isloop>
</isif>--content-divider--

В шаблоне я использовал цикл для прикрепления всех файлов, а внутри самого цикла я проверял тип содержимого каждого файла. Потому что в моем случае пользователь может прикреплять файлы pdf, png и jpeg.