0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

(覚書)メールフォーム確認画面まで

Posted at
<?php
/*
==============================================
ファイルアップロード機能(確認画面まで)
==============================================
*/

// Controller部分(Contact.php)
class Contact extends CI_Controller {
    
    public function __construct() {
        parent::__construct();
        $this->load->database();
        $this->load->helper(['form', 'url', 'file']);
        $this->load->library(['form_validation', 'upload', 'session', 'smarty']);
    }
    
    /**
     * メイン処理
     */
    public function index() {
        $data = [
            'errors' => [],
            'form_data' => [],
            'success' => false
        ];
        
        if ($this->input->method() === 'post') {
            $action = $this->input->post('action');
            
            if ($action === 'confirm') {
                $data = $this->_process_confirm();
                if (empty($data['errors'])) {
                    $this->_show_confirm($data);
                    return;
                }
            }
        }
        
        $this->_show_form($data);
    }
    
    /**
     * 確認処理(ファイル名表示対応)
     */
    private function _process_confirm() {
        $data = [
            'errors' => [],
            'form_data' => [],
            'uploaded_files' => [],
            'file_info' => []
        ];
        
        // バリデーション設定
        $this->form_validation->set_rules('name', '名前', 'required|trim|max_length[100]');
        $this->form_validation->set_rules('email', 'メールアドレス', 'required|valid_email|max_length[255]');
        $this->form_validation->set_rules('message', 'メッセージ', 'required|trim|max_length[1000]');
        
        // バリデーション実行
        if (!$this->form_validation->run()) {
            $data['errors'] = $this->form_validation->error_array();
        }
        
        // フォームデータ取得
        $data['form_data'] = [
            'name' => trim($this->input->post('name')),
            'email' => trim($this->input->post('email')),
            'message' => trim($this->input->post('message'))
        ];
        
        // ファイルアップロード処理(一時保存)
        $upload_result = $this->_handle_file_upload_temp();
        
        if (!empty($upload_result['errors'])) {
            $data['errors'] = array_merge($data['errors'], $upload_result['errors']);
        } else {
            $data['uploaded_files'] = $upload_result['files'];
            $data['file_info'] = $upload_result['file_info'];
            
            // セッションに保存(確認画面→送信で使用)
            $this->session->set_userdata('temp_uploaded_files', $upload_result['files']);
            $this->session->set_userdata('temp_file_info', $upload_result['file_info']);
        }
        
        return $data;
    }
    
    /**
     * ファイルアップロード処理(一時保存)
     * 投稿者名+input名でファイル名を生成
     */
    private function _handle_file_upload_temp() {
        $result = [
            'files' => [],
            'file_info' => [],
            'errors' => []
        ];
        
        // 投稿者名取得(ファイル名用)
        $user_name = trim($this->input->post('name'));
        $safe_user_name = $this->_sanitize_filename($user_name);
        
        // 複数のファイル入力フィールドに対応
        $file_inputs = [
            'application_form' => '申請書',
            'resume' => '履歴書', 
            'portfolio' => 'ポートフォリオ',
            'certificate' => '証明書',
            'other_document' => 'その他資料'
        ];
        
        // 単一ファイルの場合(従来互換)
        if (isset($_FILES['upload_file']) && !empty($_FILES['upload_file']['name'])) {
            $file_inputs['upload_file'] = '添付ファイル';
        }
        
        foreach ($file_inputs as $input_name => $display_name) {
            if (!isset($_FILES[$input_name]) || empty($_FILES[$input_name]['name'])) {
                continue; // ファイルが選択されていない場合はスキップ
            }
            
            // 一時保存用フォルダ
            $temp_path = './uploads/temp/';
            if (!is_dir($temp_path)) {
                mkdir($temp_path, 0755, true);
            }
            
            // ファイル情報を保存
            $original_name = $_FILES[$input_name]['name'];
            $file_size = $_FILES[$input_name]['size'];
            $file_type = $_FILES[$input_name]['type'];
            $extension = pathinfo($original_name, PATHINFO_EXTENSION);
            
            // 投稿者名+input名でファイル名生成
            // 例:田中太郎_申請書_20241209143022.xlsx
            $timestamp = date('YmdHis');
            $safe_filename = "{$safe_user_name}_{$display_name}_{$timestamp}." . strtolower($extension);
            
            // アップロード設定(エクセルファイル対応)
            $config = [
                'upload_path' => $temp_path,
                'allowed_types' => 'jpg|jpeg|png|gif|pdf|doc|docx|xls|xlsx|txt|zip|csv',
                'max_size' => 10240, // 10MB(エクセルファイルは大きくなりがち)
                'max_width' => 3000,
                'max_height' => 3000,
                'file_name' => $safe_filename,  // カスタムファイル名
                'remove_spaces' => TRUE
            ];
            
            $this->upload->initialize($config);
            
            if ($this->upload->do_upload($input_name)) {
                $upload_data = $this->upload->data();
                
                // アップロードされたファイル情報を保存
                $result['files'][] = $upload_data['file_name'];
                $result['file_info'][] = [
                    'original_name' => $original_name,
                    'saved_name' => $upload_data['file_name'],
                    'input_name' => $input_name,
                    'input_display_name' => $display_name,
                    'file_size' => $file_size,
                    'file_type' => $file_type,
                    'extension' => $extension,
                    'upload_time' => date('Y-m-d H:i:s'),
                    'user_name' => $user_name
                ];
                
            } else {
                $result['errors'][$input_name] = strip_tags($this->upload->display_errors());
            }
        }
        
        return $result;
    }
    
    /**
     * ファイル名用の文字列サニタイズ
     */
    private function _sanitize_filename($name) {
        // 日本語をローマ字に変換(簡易版)
        $name = trim($name);
        
        // 特殊文字を除去
        $name = preg_replace('/[^\p{L}\p{N}\s]/u', '', $name);
        
        // スペースをアンダースコアに変換
        $name = preg_replace('/\s+/', '_', $name);
        
        // 長すぎる場合は切り詰め
        if (mb_strlen($name) > 20) {
            $name = mb_substr($name, 0, 20);
        }
        
        // 空の場合はデフォルト名
        if (empty($name)) {
            $name = 'user_' . date('mdHis');
        }
        
        return $name;
    }
    
    /**
     * フォーム表示
     */
    private function _show_form($data) {
        $this->smarty->assign('errors', $data['errors']);
        $this->smarty->assign('form_data', $data['form_data']);
        $this->smarty->display('contact/form.tpl');
    }
    
    /**
     * 確認画面表示(ファイル名表示対応)
     */
    private function _show_confirm($data) {
        $this->smarty->assign('form_data', $data['form_data']);
        $this->smarty->assign('uploaded_files', $data['uploaded_files']);
        $this->smarty->assign('file_info', $data['file_info']);
        $this->smarty->display('contact/confirm.tpl');
    }
}
?>

<!-- 
==============================================
フォームテンプレート: contact/form.tpl
============================================== 
-->
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>お問い合わせフォーム</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; background-color: #f5f5f5; }
        .container { max-width: 800px; margin: 0 auto; background: white; padding: 30px; border-radius: 8px; }
        .form-group { margin-bottom: 20px; }
        .error { color: red; font-size: 14px; margin-top: 5px; }
        label { display: block; margin-bottom: 8px; font-weight: bold; color: #555; }
        input[type="text"], input[type="email"], textarea { 
            width: 100%; padding: 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; 
        }
        textarea { resize: vertical; min-height: 100px; }
        .file-input-group {
            border: 1px solid #ddd; border-radius: 4px; padding: 15px; margin-bottom: 15px; background: #fafafa;
        }
        .file-input-title {
            font-weight: bold; color: #495057; margin-bottom: 10px; 
            border-bottom: 1px solid #dee2e6; padding-bottom: 5px;
        }
        .file-info-text { font-size: 12px; color: #666; margin-top: 5px; line-height: 1.4; }
        .file-preview { 
            background: #f5f5f5; padding: 10px; margin-top: 10px; border-radius: 4px; display: none;
        }
        .selected-file {
            margin: 5px 0; padding: 10px; background: white; border: 1px solid #ddd; 
            border-radius: 3px; border-left: 4px solid #007bff;
        }
        .excel-icon::before { content: "📊 "; }
        .pdf-icon::before { content: "📄 "; }
        .doc-icon::before { content: "📝 "; }
        .image-icon::before { content: "🖼️ "; }
        .btn { 
            background: #007bff; color: white; padding: 12px 30px; border: none; 
            border-radius: 4px; font-size: 16px; cursor: pointer; 
        }
        .btn:hover { background: #0056b3; }
    </style>
</head>
<body>
    <div class="container">
        <h2>お問い合わせフォーム</h2>
        
        <form method="post" enctype="multipart/form-data" action="{site_url('contact')}">
            
            <!-- 名前 -->
            <div class="form-group">
                <label for="name">お名前 *</label>
                <input type="text" id="name" name="name" value="{$form_data.name|default:''|escape}" placeholder="山田太郎">
                <div class="file-info-text">※ファイル名に使用されます</div>
                {if isset($errors.name)}
                    <div class="error">{$errors.name}</div>
                {/if}
            </div>
            
            <!-- メールアドレス -->
            <div class="form-group">
                <label for="email">メールアドレス *</label>
                <input type="email" id="email" name="email" value="{$form_data.email|default:''|escape}" placeholder="example@example.com">
                {if isset($errors.email)}
                    <div class="error">{$errors.email}</div>
                {/if}
            </div>
            
            <!-- メッセージ -->
            <div class="form-group">
                <label for="message">お問い合わせ内容 *</label>
                <textarea id="message" name="message" rows="6" placeholder="お問い合わせ内容をご記入ください">{$form_data.message|default:''|escape}</textarea>
                {if isset($errors.message)}
                    <div class="error">{$errors.message}</div>
                {/if}
            </div>
            
            <!-- ファイルアップロード -->
            <div class="form-group">
                <h3>ファイル添付(任意)</h3>
                
                <!-- 申請書 -->
                <div class="file-input-group">
                    <div class="file-input-title excel-icon">申請書(Excelファイル推奨)</div>
                    <input type="file" id="application_form" name="application_form" 
                           accept=".xlsx,.xls,.pdf,.doc,.docx"
                           onchange="showSelectedFile(this, 'application_form_preview')">
                    <div class="file-info-text">保存名例: 山田太郎_申請書_20241209143022.xlsx</div>
                    <div id="application_form_preview" class="file-preview"></div>
                    {if isset($errors.application_form)}
                        <div class="error">{$errors.application_form}</div>
                    {/if}
                </div>
                
                <!-- 履歴書 -->
                <div class="file-input-group">
                    <div class="file-input-title doc-icon">履歴書</div>
                    <input type="file" id="resume" name="resume" 
                           accept=".pdf,.doc,.docx,.xlsx,.xls"
                           onchange="showSelectedFile(this, 'resume_preview')">
                    <div class="file-info-text">保存名例: 山田太郎_履歴書_20241209143022.pdf</div>
                    <div id="resume_preview" class="file-preview"></div>
                    {if isset($errors.resume)}
                        <div class="error">{$errors.resume}</div>
                    {/if}
                </div>
                
                <!-- ポートフォリオ -->
                <div class="file-input-group">
                    <div class="file-input-title image-icon">ポートフォリオ</div>
                    <input type="file" id="portfolio" name="portfolio" 
                           accept=".pdf,.jpg,.jpeg,.png,.zip,.xlsx,.xls"
                           onchange="showSelectedFile(this, 'portfolio_preview')">
                    <div class="file-info-text">保存名例: 山田太郎_ポートフォリオ_20241209143022.pdf</div>
                    <div id="portfolio_preview" class="file-preview"></div>
                    {if isset($errors.portfolio)}
                        <div class="error">{$errors.portfolio}</div>
                    {/if}
                </div>
                
                <!-- 証明書 -->
                <div class="file-input-group">
                    <div class="file-input-title pdf-icon">証明書・資格証</div>
                    <input type="file" id="certificate" name="certificate" 
                           accept=".pdf,.jpg,.jpeg,.png"
                           onchange="showSelectedFile(this, 'certificate_preview')">
                    <div class="file-info-text">保存名例: 山田太郎_証明書_20241209143022.pdf</div>
                    <div id="certificate_preview" class="file-preview"></div>
                    {if isset($errors.certificate)}
                        <div class="error">{$errors.certificate}</div>
                    {/if}
                </div>
                
                <!-- その他資料 -->
                <div class="file-input-group">
                    <div class="file-input-title">その他資料</div>
                    <input type="file" id="other_document" name="other_document" 
                           accept=".pdf,.doc,.docx,.xlsx,.xls,.jpg,.jpeg,.png,.zip,.txt"
                           onchange="showSelectedFile(this, 'other_document_preview')">
                    <div class="file-info-text">保存名例: 山田太郎_その他資料_20241209143022.zip</div>
                    <div id="other_document_preview" class="file-preview"></div>
                    {if isset($errors.other_document)}
                        <div class="error">{$errors.other_document}</div>
                    {/if}
                </div>
                
                <!-- アップロード仕様説明 -->
                <div style="background: #e7f3ff; padding: 15px; border-radius: 4px; margin-top: 15px;">
                    <h4 style="margin-top: 0;">📋 ファイルアップロード仕様</h4>
                    <ul style="margin: 0; padding-left: 20px;">
                        <li><strong>最大ファイルサイズ:</strong> 10MB</li>
                        <li><strong>対応形式:</strong> Excel(.xlsx/.xls), PDF, Word(.doc/.docx), 画像(JPG/PNG), ZIP, CSV, TXT</li>
                        <li><strong>ファイル名:</strong> 自動的に「お名前_種類_日時」の形式で保存されます</li>
                        <li><strong>注意:</strong> Excelファイルは互換性のため.xlsx形式を推奨します</li>
                    </ul>
                </div>
            </div>
            
            <!-- 送信ボタン -->
            <div class="form-group">
                <input type="hidden" name="action" value="confirm">
                <button type="submit" class="btn">確認画面へ</button>
            </div>
        </form>
    </div>
    
    <script>
        // 選択されたファイル名を表示する関数
        function showSelectedFile(input, previewId) {
            const preview = document.getElementById(previewId);
            
            if (input.files && input.files.length > 0) {
                const file = input.files[0];
                const userName = document.getElementById('name').value || 'ユーザー名未入力';
                const inputDisplayName = getInputDisplayName(input.name);
                
                // 予想されるファイル名を生成
                const extension = file.name.split('.').pop();
                const timestamp = new Date().toISOString().replace(/[-:T]/g, '').slice(0, 14);
                const predictedName = `${userName}_${inputDisplayName}_${timestamp}.${extension}`;
                
                // ファイル情報を表示
                preview.innerHTML = `
                    <div class="selected-file">
                        <strong>📎 選択されたファイル:</strong> ${file.name}<br>
                        <strong>💾 保存予定名:</strong> ${predictedName}<br>
                        <strong>📏 サイズ:</strong> ${formatFileSize(file.size)}<br>
                        <strong>📋 種類:</strong> ${file.type || '不明'} (${getFileIcon(extension)} ${extension.toUpperCase()})
                    </div>
                `;
                
                preview.style.display = 'block';
            } else {
                preview.style.display = 'none';
            }
        }
        
        // input名から表示名を取得
        function getInputDisplayName(inputName) {
            const names = {
                'application_form': '申請書',
                'resume': '履歴書',
                'portfolio': 'ポートフォリオ',
                'certificate': '証明書',
                'other_document': 'その他資料'
            };
            return names[inputName] || '添付ファイル';
        }
        
        // ファイル拡張子からアイコンを取得
        function getFileIcon(extension) {
            const ext = extension.toLowerCase();
            if (['xlsx', 'xls', 'csv'].includes(ext)) return '📊';
            if (['pdf'].includes(ext)) return '📄';
            if (['doc', 'docx'].includes(ext)) return '📝';
            if (['jpg', 'jpeg', 'png', 'gif'].includes(ext)) return '🖼️';
            if (['zip', 'rar'].includes(ext)) return '📦';
            return '📄';
        }
        
        // ファイルサイズを読みやすい形式に変換
        function formatFileSize(bytes) {
            if (bytes === 0) return '0 Bytes';
            const k = 1024;
            const sizes = ['Bytes', 'KB', 'MB', 'GB'];
            const i = Math.floor(Math.log(bytes) / Math.log(k));
            return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
        }
        
        // 名前が変更されたときにファイル名プレビューを更新
        document.getElementById('name').addEventListener('input', function() {
            const fileInputs = document.querySelectorAll('input[type="file"]');
            fileInputs.forEach(input => {
                if (input.files && input.files.length > 0) {
                    const previewId = input.id + '_preview';
                    showSelectedFile(input, previewId);
                }
            });
        });
    </script>
</body>
</html>

<!-- 
==============================================
確認画面テンプレート: contact/confirm.tpl
============================================== 
-->
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>入力内容確認</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; background-color: #f5f5f5; }
        .container { max-width: 600px; margin: 0 auto; background: white; padding: 30px; border-radius: 8px; }
        .confirm-box { background: #f8f9fa; padding: 20px; border-radius: 8px; margin-bottom: 30px; }
        .confirm-item { margin-bottom: 20px; padding-bottom: 15px; border-bottom: 1px solid #dee2e6; }
        .confirm-item:last-child { margin-bottom: 0; padding-bottom: 0; border-bottom: none; }
        .confirm-label { font-weight: bold; margin-bottom: 8px; color: #495057; font-size: 14px; }
        .confirm-value { padding: 10px; background: white; border-radius: 4px; border: 1px solid #ced4da; min-height: 40px; line-height: 1.5; }
        .file-list { margin-top: 10px; }
        .file-item { 
            background: #e7f3ff; padding: 10px; margin: 5px 0; border-radius: 4px; 
            border-left: 4px solid #007bff;
        }
        .btn { padding: 12px 30px; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; text-decoration: none; display: inline-block; margin: 5px; }
        .btn-primary { background: #007bff; color: white; }
        .btn-secondary { background: #6c757d; color: white; }
        .btn:hover { opacity: 0.8; }
    </style>
</head>
<body>
    <div class="container">
        <h2>入力内容確認</h2>
        
        <div class="confirm-box">
            <!-- 名前 -->
            <div class="confirm-item">
                <div class="confirm-label">お名前</div>
                <div class="confirm-value">{$form_data.name|escape}</div>
            </div>
            
            <!-- メールアドレス -->
            <div class="confirm-item">
                <div class="confirm-label">メールアドレス</div>
                <div class="confirm-value">{$form_data.email|escape}</div>
            </div>
            
            <!-- メッセージ -->
            <div class="confirm-item">
                <div class="confirm-label">お問い合わせ内容</div>
                <div class="confirm-value">{$form_data.message|nl2br|escape}</div>
            </div>
            
            <!-- ファイル情報 -->
            <div class="confirm-item">
                <div class="confirm-label">添付ファイル</div>
                <div class="confirm-value">
                    {if $file_info && count($file_info) > 0}
                        <div class="file-list">
                            {foreach $file_info as $file}
                                <div class="file-item">
                                    <div style="display: flex; align-items: center; gap: 10px;">
                                        <span class="file-icon">
                                            {if $file.extension == 'xlsx' || $file.extension == 'xls'}📊
                                            {elseif $file.extension == 'pdf'}📄
                                            {elseif $file.extension == 'doc' || $file.extension == 'docx'}📝
                                            {elseif $file.extension == 'jpg' || $file.extension == 'jpeg' || $file.extension == 'png'}🖼️
                                            {else}📄{/if}
                                        </span>
                                        <div style="flex: 1;">
                                            <strong>{$file.input_display_name}</strong><br>
                                            <div style="font-size: 14px; color: #666;">
                                                元ファイル名: {$file.original_name|escape}<br>
                                                保存ファイル名: {$file.saved_name|escape}
                                            </div>
                                        </div>
                                        <div style="text-align: right; font-size: 12px; color: #999;">
                                            {$file.file_size|number_format} bytes<br>
                                            {$file.upload_time}
                                        </div>
                                    </div>
                                </div>
                            {/foreach}
                            
                            <div style="margin-top: 15px; padding: 10px; background: #e7f3ff; border-radius: 4px;">
                                <strong>📦 ZIPファイル保存先:</strong><br>
                                <code>uploads/{$smarty.now|date_format:'Y/m/d'}/{$form_data.name|escape}様_{$smarty.now|date_format:'YmdHis'}_xxxxxx/</code>
                            </div>
                        </div>
                    {else}
                        <div style="color: #6c757d; font-style: italic;">ファイルの添付はありません</div>
                    {/if}
                </div>
            </div>
        </div>
        
        <!-- 送信フォーム -->
        <form method="post" action="{site_url('contact')}">
            <!-- 入力データを隠しフィールドで保持 -->
            <input type="hidden" name="name" value="{$form_data.name|escape}">
            <input type="hidden" name="email" value="{$form_data.email|escape}">
            <input type="hidden" name="message" value="{$form_data.message|escape}">
            <input type="hidden" name="action" value="submit">
            
            <div style="text-align: center;">
                <button type="submit" class="btn btn-primary">送信する</button>
                <button type="button" class="btn btn-secondary" onclick="history.back()">戻って修正</button>
            </div>
        </form>
    </div>
</body>
</html>
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?