Загрузка изображений с превью AJAX + PHP + MySQL
В данной статье представлена упрощенная реализация загрузки изображений с превью через AJAX с сохранением в базу данных MySQL, а также дальнейший их вывод на примере модуля отзывов.
В примерах используется следующая структура файлов и директорий, находящихся в корне сайта:
index.php
upload_image.php
save_reviews.php
reviews.php
jquery.min.js
uploads/
├── tmp/
Первое что понадобится: HTML форма и JS скрипт, который после выбора одного или несколькольких файлов отправит их на upload_image.php через AJAX.
index.php
<form method="post" action="/save_reviews.php"> <h3>Отправить отзыв:</h3> <div class="form-row"> <label>Ваше имя:</label> <input type="text" name="name" required> </div> <div class="form-row"> <label>Комментарий:</label> <input type="text" name="text" required> </div> <div class="form-row"> <label>Изображения:</label> <div class="img-list" id="js-file-list"></div> <input id="js-file" type="file" name="file[]" multiple accept=".jpg,.jpeg,.png,.gif"> </div> <div class="form-submit"> <input type="submit" name="send" value="Отправить"> </div> </form> <script src="/jquery.min.js"></script> <script> $("#js-file").change(function(){ if (window.FormData === undefined) { alert('В вашем браузере загрузка файлов не поддерживается'); } else { var formData = new FormData(); formData.append('file[]', input); }); $.ajax({ type: 'POST', url: '/upload_image.php', cache: false, contentType: false, processData: false, data: formData, dataType : 'json', success: function(msg){ msg.forEach(function(row) { if (row.error == '') { $('#js-file-list').append(row.data); } else { alert(row.error); } }); $("#js-file").val(''); } }); } }); /* Удаление загруженной картинки */ function remove_img(target){ $(target).parent().remove(); } </script>
CSS-стили для формы и вывода загруженных файлов:
.form-row { margin-bottom: 15px; } .form-row label { display: block; color: #777; margin-bottom: 5px; } .form-row input[type="text"] { width: 100%; padding: 5px; box-sizing: border-box; } /* Стили для вывода превью */ .img-item { display: inline-block; margin: 0 20px 20px 0; position: relative; user-select: none; } .img-item img { border: 1px solid #767676; } .img-item a { display: inline-block; background: url(/remove.png) 0 0 no-repeat; position: absolute; top: -5px; right: -9px; width: 20px; height: 20px; cursor: pointer; }
В целях безопасности, во временной директории /uploads/ должно быть отключено выполнение PHP-скриптов и выключен листинг каталогов.
Тек же временную директорию /uploads/tmp/ будет нужно периодически очищать от старых файлов.
Скрипт upload_image.php
<?php // Локаль. //ini_set('display_errors', 1); // Название <input type="file"> $input_name = 'file'; exit; } // Разрешенные расширения файлов. // URL до временной директории. $url_path = '/uploads/tmp/'; // Полный путь до временной директории. $tmp_path = $_SERVER['DOCUMENT_ROOT'] . $url_path; } // Преобразуем массив $_FILES в удобный вид для перебора в foreach. if ($diff == 0) { } else { foreach($_FILES[$input_name] as $k => $l) { foreach($l as $i => $v) { $files[$i][$k] = $v; } } } foreach ($files as $file) { $error = $data = ''; // Проверим на ошибки загрузки. $error = 'Не удалось загрузить файл.'; $error = 'Не удалось загрузить файл.'; $error = 'Недопустимый тип файла'; } else { $error = 'Недопустимый тип файла'; } else { // Перемещаем файл в директорию с новым именем. $src = $tmp_path . $name . '.' . $ext; $thumb = $tmp_path . $name . '-thumb.' . $ext; // Создание миниатюры. switch ($info[2]) { case 1: break; case 2: break; case 3: break; } $width = $info[0]; $height = $info[1]; // Высота превью 100px, ширина рассчитывается автоматически. $h = 100; if ($info[2] == 1 || $info[2] == 3) { } if ($w >= $width && $h >= $height) { } elseif ($w >= $width) { } elseif ($h >= $height) { } elseif ($tw < $w) { } else { } // Сохранение. switch ($info[2]) { } // Вывод в форму: превью, кнопка для удаления и скрытое поле. $data = ' <div class="img-item"> <img src="' . $url_path . $name . '-thumb.' . $ext . '"> <a herf="#" onclick="remove_img(this); return false;"></a> <input type="hidden" name="images[]" value="' . $name . '.' . $ext . '"> </div>'; } else { $error = 'Не удалось загрузить файл.'; } } } } // Ответ в JSON.
Пример ответа AJAX запроса в случаи успешной загрузки файла:
[{ "error": "", "data": " <div class="img-item"> <img src="/uploads/tmp/1610809179-108359805-thumb.jpg"> <a herf="#" onclick="remove_img(this); return false;"></a> <input type="hidden" name="images[]" value="1610809179-108359805.jpg"> </div>" }]
Полученный из AJAX запроса контент вставляется в конец дива id="js-file-list" с помощью jQuery метода append().
Скрытое поле «images» передает названия загруженных файлов следующему скрипту для сохранения в базе данных.
Сохранение формы в базе данных
После нажатия на кнопку «Отправить», форма отправляется методом POST на обработчик save_reviews.php. В нём полученные данные сохраняются в базе данных, а файлы переносятся в постоянную директорию хранения.
Понадобятся две таблицы, `reviews`:
CREATE TABLE `reviews` ( `id` int(11) UNSIGNED NOT NULL, `name` varchar(255) NOT NULL, `text` text NOT NULL, `date_add` int(11) UNSIGNED NOT NULL DEFAULT '0' ) ENGINE=MyISAM DEFAULT CHARSET=utf8; ALTER TABLE `reviews` MODIFY `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT;
И таблица `reviews_images` для хранения названий файлов:
CREATE TABLE `reviews_images` ( `id` int(11) UNSIGNED NOT NULL, `reviews_id` int(11) NOT NULL DEFAULT '0', `filename` varchar(255) NOT NULL ) ENGINE=MyISAM DEFAULT CHARSET=utf8; ALTER TABLE `reviews_images` MODIFY `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT;
Скрипт save_reviews.php
<?php //ini_set('display_errors', 1); // Временная директория. $tmp_path = $_SERVER['DOCUMENT_ROOT'] . '/uploads/tmp/'; // Постоянная директория. $path = $_SERVER['DOCUMENT_ROOT'] . '/uploads/'; // Подключение к БД. $dbh = new PDO('mysql:dbname=db_name;host=localhost', 'логин', 'пароль'); $sth = $dbh->prepare("INSERT INTO `reviews` SET `name` = ?, `text` = ?, `date_add` = UNIX_TIMESTAMP()"); // Получаем id вставленной записи. $insert_id = $dbh->lastInsertId(); // Сохранение изображений в БД и перенос в постоянную папку. foreach ($_POST['images'] as $row) { $sth = $dbh->prepare("INSERT INTO `reviews_images` SET `reviews_id` = ?, `filename` = ?"); // Перенос оригинального файла // Перенос превью $thumb = $file_name . '-thumb.' . $file_ext; } } } } // Редирект, чтобы предотвратить повторную отправку по F5.
И последнее, PHP-скрипт для вывода отзывов с фотографиями из базы данных.
Скрипт reviews.php
<?php // Подключение к БД. $dbh = new PDO('mysql:dbname=db_name;host=localhost', 'логин', 'пароль'); $sth = $dbh->prepare("SELECT * FROM `reviews` ORDER BY `date_add` DESC"); $sth->execute(); $items = $sth->fetchAll(PDO::FETCH_ASSOC); ?> <h2>Отзывы</h2> <div class="reviews"> <?php foreach ($items as $row) { $sth = $dbh->prepare("SELECT * FROM `reviews_images` WHERE `reviews_id` = ?"); $images = $sth->fetchAll(PDO::FETCH_ASSOC); ?> <div class="reviews_item"> <div class="reviews_item-name"><?php echo $row['name']; ?></div> <div class="reviews_item-text"><?php echo $row['text']; ?></div> <div class="reviews_item-images"> <?php foreach($images as $img): ?> <div class="reviews_item-img"> <?php ?> <a href="/uploads/<?php echo $img['filename']; ?>" target="_blank"> <img src="/uploads/<?php echo $name . '-thumb.' . $ext; ?>"> </a> </div> <?php endforeach; ?> </div> <?php endif; ?> </div> <?php } ?> </div> <?php } ?>
CSS-стили списка:
.reviews_item { background: #efefef; padding: 15px 30px 0px 30px; margin-bottom: 20px; } .reviews_item-name { font-weight: 900; font-size: 18px; margin-bottom: 5px; } .reviews_item-text { margin-bottom: 15px; font-size: 15px; line-height: 1.5; } .reviews_item-img { display: inline-block; margin: 0 15px 15px 0; }