Fatal Error: Unexpected BLOG

主に自分用の備忘録として

【EC-CUBE】問合せフォームに画像をアップロードできるようにする

問合せフォームから画像をアップロードさせて、メールで送信する方法です。
環境は以下の通り。

 

以下のサイトを参考にしました。

 

一からゴリゴリ書いてもいいんですが、どうせならEC-CUBEのメールテンプレート機能を使いたいので、EC-CUBEをカスタマイズします。

メールテンプレート追加

まずEC-CUBE管理画面の「システム設定>マスターデータ管理」でメールテンプレートを追加します。

    mtb_mail_template → メールテンプレート名
    mtb_mail_tpl_path → テンプレートファイルのパス

 

テンプレートのパスはテンプレートディレクトリ配下であれば何でもよさそうですが、今回は素直に以下のように設定しました。

    メールテンプレート名 → 「ファイル添付メール」
    テンプレートファイルのパス → mail_templates/attach_mail.tpl

この時点で、管理画面の「基本情報設定>メール設定」で、「ファイル添付メール」が選択できるようになっているので、適宜設定してください。
この画面の「動的データ挿入部分」に上記で設定したテンプレートファイルが読み込まれます。

 

ここではまだテンプレートファイル自体が存在しないので、新たに作成します。
FTPクライアントソフトなどでサーバにログインして手動で作成します。

    data/Smarty/templates/default/mail_templates/attach_mail.tpl

 

これで、「ファイル添付メール」テンプレートを使用するメールの「動的データ挿入部分」に「attach_mail.tpl」がインクルードされるようになりました。

フォームをカスタマイズ

デフォルトのフォームではファイルをアップロード出来ないので、以下のようにカスタマイズします。

    <form name="form1" id="form1" method="post" enctype="multipart/form-data" action="?">
        ...
        <input type="hidden" name="sample_image_realpath" value="<!--{$arrFile.sample_image.real_filepath}-->" />
        <input type="hidden" name="sample_image_urlpath" value="<!--{$arrFile.sample_image.filepath}-->" />
        
        ...
        
        <span class="entry-error"><!--{$arrErr.sample_image}--></span>
        <!--{if $arrFile.sample_image.filepath != ""}-->
            <img src="<!--{$arrFile.sample_image.filepath}-->" alt="">
            <a href="" onclick="eccube.setModeAndSubmit('delete_image', 'image_key', 'sample_image'); return false;">
                [画像の取り消し]
            </a>
        <!--{else}-->
            <input type="file" name="sample_image" style="<!--{$arrErr.sample_image|sfGetErrorColor}-->" />
            <a class="btn-normal" href="javascript:;" name="btn" onclick="eccube.setModeAndSubmit('upload_image', 'image_key', 'sample_image'); return false;">
                アップロード
            </a>
        <p class="mini entry-error">画像を添付出来ます。(jpg/gif/png)</p>
        
        ...
        
    

商品登録フォームを参考にしています。
「アップロード」をクリックすると一旦フォームがmode=upload_imageでサブミットされ、画像がアップロードされます。
同時に$arrFileに値がアサインされ、hiddenインプットのvalueにセットされます。
具体的にはファイルのサーバ上での絶対パス(real_filepath)とドキュメントルートからのURL(filepath)をセットしています。
今回はメールでの送信のみを想定している為、パスは一時保存用のものです。

クラスファイルのカスタマイズ

今回は、管理画面の「デザイン設定>ページ詳細設定」からuser_data配下に新規ページ「/attach_mail」を作成し、そこにフォームを設置しています。
/user_data/attach_mail.phpにクラス( LC_Page_User_xxx )が自動生成されているので、それをカスタマイズします。

action()メソッドカスタマイズ

「LC_Page_Contact.php」「LC_Page_Admin_Products_Product.php」を参考に。

    function action()
    {
        $objFormParam = new SC_FormParam_Ex();

        $this->arrData = isset($_SESSION['customer']) ? $_SESSION['customer'] : '';

        // アップロードファイル情報の初期化
        $objUpFile = new SC_UploadFile_Ex(IMAGE_TEMP_REALDIR, IMAGE_SAVE_REALDIR);
        $this->lfInitFile($objUpFile);
        $objUpFile->setHiddenFileList($_POST);

        $mode = $this->getMode();
        switch ($mode) {
            case 'confirm':
                // エラーチェック
                $this->lfInitParam($objFormParam);
                $objFormParam->setParam($_POST);
                $objFormParam->convParam();
                $objFormParam->toLower('email');
                $objFormParam->toLower('email02');
                $this->arrErr = $this->lfCheckError($objFormParam);
                // 入力値の取得
                $this->arrForm = $objFormParam->getFormParamList();

                if (SC_Utils_Ex::isBlank($this->arrErr)) {
                    // エラー無しで完了画面
                    $this->tpl_mainpage = 'user_data/attach_mail_confirm.tpl';
                    $this->tpl_title = '添付ファイルメール(確認ページ)';
                }

                break;

            case 'return':
                $this->lfInitParam($objFormParam);
                $objFormParam->setParam($_POST);
                $this->arrForm = $objFormParam->getFormParamList();

                break;

            case 'complete':
                $this->lfInitParam($objFormParam);
                $objFormParam->setParam($_POST);
                $this->arrErr = $objFormParam->checkError();
                $this->arrForm = $objFormParam->getFormParamList();
                if (SC_Utils_Ex::isBlank($this->arrErr)) {
                    $this->lfSendMail($this);

                    // 完了ページへ移動する
                    SC_Response_Ex::sendRedirect('complete.php');
                    SC_Response_Ex::actionExit();
                } else {
                    SC_Utils_Ex::sfDispSiteError(CUSTOMER_ERROR);
                    SC_Response_Ex::actionExit();
                }
                break;

            // 画像のアップロード
            case 'upload_image':
            case 'delete_image':
                // パラメーター初期化
                $this->lfInitParam($objFormParam);
                $objFormParam->setParam($_POST);
                $this->arrErr = $objFormParam->checkError();
                $this->arrForm = $objFormParam->getFormParamList();

                switch ($mode) {
                    case 'upload_image':
                        // ファイルを一時ディレクトリにアップロード
                        $this->arrErr[$this->arrForm['image_key']['value']] = $objUpFile->makeTempFile($this->arrForm['image_key']['value'], IMAGE_RENAME);
                        if ($this->arrErr[$this->arrForm['image_key']['value']] == '') {
                            $img_temp_fullname = $objUpFile->temp_dir.'/'.$objUpFile->temp_file;
                        }
                        break;
                    case 'delete_image':
                        // ファイル削除
                        $this->lfDeleteTempFile($objUpFile, $this->arrForm['image_key']['value']);
                        break;
                    default:
                        break;
                }

                // 入力画面表示設定
                // アップロードファイル情報取得(Hidden用)
                $this->arrHidden = $objUpFile->getHiddenFileList();
                // 画像ファイル表示用データ取得
                $this->arrFile = $objUpFile->getFormFileList(IMAGE_TEMP_URLPATH, IMAGE_SAVE_URLPATH);
                break;

            default:

                break;
        }
    }

lfInitParam()メソッド追加

入力パラメータをオブジェクトにセットします。

    public function lfInitParam(&$objFormParam)
    {
        $objFormParam->addParam('テキスト入力', 'some_input', STEXT_LEN, 'KVa', array('EXIST_CHECK','SPTAB_CHECK','MAX_LENGTH_CHECK'));
        
        ...
        
        $objFormParam->addParam('sample_image', 'sample_image', '', '', array());
        $objFormParam->addParam('image_key', 'image_key', '', '', array());
        $objFormParam->addParam('sample_image_realpath', 'sample_image_realpath', '', '', array());
        $objFormParam->addParam('sample_image_urlpath', 'sample_image_urlpath', '', '', array());
    }

lfInitFile()メソッド追加

アップロードされるファイルをオブジェクトにセットします。

    public function lfInitFile(&$objUpFile)
    {
        $objUpFile->addFile('画像', 'sample_image', array('jpg', 'gif', 'png'), IMAGE_SIZE, false, SMALL_IMAGE_WIDTH, SMALL_IMAGE_HEIGHT);
    }

lfDeleteTempFile()メソッド追加

アップロードされたファイルを削除します。
これは、「LC_Page_Admin_Products_Product.php」からまるまるコピーで動きました。

    public function lfDeleteTempFile(&$objUpFile, $image_key)
    {
        $arrTempFile = $objUpFile->temp_file;
        $arrKeyName = $objUpFile->keyname;

        foreach ($arrKeyName as $key => $keyname) {
            if ($keyname != $image_key) continue;

            if (!empty($arrTempFile[$key])) {
                $temp_file = $arrTempFile[$key];
                $arrTempFile[$key] = '';

                if (!in_array($temp_file, $arrTempFile)) {
                    $objUpFile->deleteFile($image_key);
                } else {
                    $objUpFile->temp_file[$key] = '';
                    $objUpFile->save_file[$key] = '';
                }
            } else {
                $objUpFile->temp_file[$key] = '';
                $objUpFile->save_file[$key] = '';
            }
        }
    }

lfCheckError()メソッド追加

フォームの入力内容をチェックします。
これも「LC_Page_Contact.php」からまんまコピーでOK。

    public function lfCheckError(&$objFormParam)
    {
        // 入力データを渡す。
        $arrForm =  $objFormParam->getHashArray();
        $objErr = new SC_CheckError_Ex($arrForm);
        $objErr->arrErr = $objFormParam->checkError();
        $objErr->doFunc(array('メールアドレス', 'メールアドレス(確認)', 'email', 'email02') ,array('EQUAL_CHECK'));

        return $objErr->arrErr;
    }

lfSendMail()メソッド追加

メール送信処理。
基本的にはLC_Page_Contact.phpのものを流用しますが、少しカスタマイズします。

    public function lfSendMail(&$objPage)
    {
        $CONF = SC_Helper_DB_Ex::sfGetBasisData();
        $objPage->tpl_shopname = $CONF['shop_name'];
        $objPage->tpl_infoemail = $CONF['email02'];
        $attachment = $objPage->arrForm['sample_image_realpath']['value'];
        $helperMail = new SC_Helper_Mail_Ex();
        $helperMail->setPage($this);
        $helperMail->sfSendTemplateMail(
            $objPage->arrForm['email']['value'],            // to
            $objPage->arrForm['name01']['value'] .' 様',    // to_name
            6,                                              // template_id
            $objPage,                                       // objPage
            $CONF['email03'],                               // from_address
            $CONF['shop_name'],                             // from_name
            $CONF['email02'],                               // reply_to
            $CONF['email02'],                               // bcc
            $attachment                                     // attachment
        );
    }

$attachment変数を追加し、sfSendTemplateMail()へ渡しています。

コアファイルのカスタマイズ

コアファイルをカスタマイズします。

SC_Helper_Mail.php

上述のlfSendMail()から呼び出されるsfSendTemplateMail()をカスタマイズします。
80行目あたりにあります。

    public function sfSendTemplateMail($to, $to_name, $template_id, &$objPage, $from_address = '', $from_name = '', $reply_to = '', $bcc = '', $attachment = '')
    {
        // メールテンプレート情報の取得
        $objMailtemplate = new SC_Helper_Mailtemplate_Ex();
        $mailtemplate = $objMailtemplate->get($template_id);
        $objPage->tpl_header = $mailtemplate['header'];
        $objPage->tpl_footer = $mailtemplate['footer'];
        $tmp_subject = $mailtemplate['subject'];

        $arrInfo = SC_Helper_DB_Ex::sfGetBasisData();

        $objMailView = new SC_SiteView_Ex();
        $objMailView->setPage($this->getPage());
        // メール本文の取得
        $objMailView->assignobj($objPage);
        $body = $objMailView->fetch($this->arrMAILTPLPATH[$template_id]);

        // メール送信処理
        $objSendMail = new SC_SendMail_Ex();
        if ($from_address == '') $from_address = $arrInfo['email03'];
        if ($from_name == '') $from_name = $arrInfo['shop_name'];
        if ($reply_to == '') $reply_to = $arrInfo['email03'];
        $error = $arrInfo['email04'];
        $tosubject = $this->sfMakeSubject($tmp_subject, $objMailView);

        $objSendMail->setItem('', $tosubject, $body, $from_address, $from_name, $reply_to, $error, $error, $bcc, '', $attachment);
        $objSendMail->setTo($to, $to_name);
        if ($attachment == '') {
            $objSendMail->sendMail();    // メール送信
        } else {
            $objSendMail->sendmail(false, true);    // 添付ファイルつきテキストメール送信
        }
    }

オプション引数として$attachmentを追加し、$SC_SendMail::setItem()へ渡しています。

SC_SendMail.php

ということで、SC_SendMail::setItem()をカスタマイズします。
setItem()へのカスタマイズに伴って、さらにいくつかのカスタマイズが必要になります。

SC_SendMail::setItem()

$attachment引数を追加。

    public function setItem($to, $subject, $body, $fromaddress, $from_name, $reply_to='', $return_path='', $errors_to='', $bcc='', $cc ='', $attachment = '')
    {
        $this->setBase($to, $subject, $body, $fromaddress, $from_name, $reply_to, $return_path, $errors_to, $bcc, $cc, $attachment);
    }
SC_SendMail::setBase()

同じく$attachment引数を追加し、さらにメソッド呼び出しを追加。

    public function setBase($to, $subject, $body, $fromaddress, $from_name, $reply_to='', $return_path='', $errors_to='', $bcc='', $cc ='', $attachment = '')
    {
        // 宛先設定
        $this->setTo($to);
        // 件名設定
        $this->setSubject($subject);
        // 本文設定
        $this->setBody($body);
        // 送信元設定
        $this->setFrom($fromaddress, $from_name);
        // 返信先設定
        $this->setReplyTo($reply_to);
        // CC設定
        $this->setCc($cc);
        // BCC設定
        $this->setBcc($bcc);
        // 添付ファイル設定
        $this->setAttachment($attachment);

        ...
        
    }
SC_SendMail::setAttachment()

新たにsetAttachment()メソッドを追加します。

    public function setAttachment($attachment) {
        $this->attachment = $attachment;
    }
SC_SendMail::sendMail()

オプション引数と分岐、メソッド呼び出しを追加。

    public function sendMail($isHtml = false, $isAttached = false)
    {
        if (!$isAttached) {
            $header = $isHtml ? $this->getHTMLHeader() : $this->getTEXTHeader();
        } else {
            $header = $this->getAttachedHeader();
            $this->setMultipartBody();
        }
        $recip = $this->getRecip();
        
        ...
        
    }

オプション引数として$isAttachedを追加しました。
この引数にTrueが渡って来ると、添付ファイルつきのメールとしてメールヘッダと本文を再設定し、送信します。

SC_SendMail::getAttachedHeader()

メールヘッダ設定用のメソッドを新たに追加します。

    public function getAttachedHeader() {
        $arrHeader = $this->getBaseHeader();
        $this->boundary = '__BOUNDARY__'.md5(rand());
        $arrHeader['Content-Type'] = 'multipart/related;boundary='.$this->boundary;

        return $arrHeader;
    }

プレーンテキストも併せて送信することを前提に、「multipart/related」を設定しています。
$boundaryは、マルチパートの各パートを区切るためのランダムな文字列です。

SC_SendMail::setMultipartBody()

「Content-type: multipart/related」を前提に、メール本文を再設定します。
この時点でSC_SendMail::bodyにはテンプレートの内容をインクルードしたプレーンテキストのメッセージが設定されているので、これを活かしつつ最終的に上書きします。

    public function setMultipartBody() {
        $body = $this->body;
        $attachment = $this->attachment;
        $boundary = $this->boundary;
        $filebase = basename($attachment);

        $multi_body = "--{$boundary}\n";
        $multi_body .= "Content-Type: text/plain; charset=ISO-2022-JP\n";
        $multi_body .= "Content-Transfer-Encoding: 7bit\n";
        $multi_body .= "\n$body\n";

        $multi_body .= "--{$boundary}\n";
        $multi_body .= "Content-Type: application/octet-stream; name=\"{$filebase}\"\n";
        $multi_body .= "Content-Disposition: attachment; filename=\"{$filebase}\"\n";
        $multi_body .= "Content-Transfer-Encoding: base64\n";
        $multi_body .= "\n";
        $multi_body .= chunk_split(base64_encode(file_get_contents($attachment)))."\n";

        $multi_body .= "--{$boundary}--";

        $this->body = $multi_body;
    }

フロー

これで、基本的には問合せと同じフローで添付ファイルつきのメールが送信できます。

  • 1. index.tplで入力。mode=confirmでサブミット。
  • 2. confirm.tplで内容確認。mode=completeでサブミット。
  • -- メール送信処理 --
  • 3. complete.tplで完了画面表示

データベース、コアファイル、テンプレートとカスタマイズ対象が多岐に渡るのでちょっと大変。
いつかプラグインか何かにしてしまいたい。