Содержание
- 1 Загрузка изображений — фукнции.
- 2 Необходимые проверки
- 3 §1. Общие принципы
- 4 §2. Правила безопасности
- 5 §3. Конфигурация php.ini
- 6 §4. Загрузка картинок из формы
- 7 §5. Загрузка изображения по ссылке
- 8 §6. Настройка выбора нескольких файлов
- 9 Немного теории по загрузке изображений на сервер средствами PHP
- 10 Подготовка
- 11 Проверки
- 12 Изменение размеров изображений PHP
- 13 Вызов функции
- 14 Конечный результат
- 15 Послесловие
- 16 Домашнее задание
Итак, мы продолжаем обсуждать тему загрузки файлов на сервер. Если вы ещё не читали статью «Что необходимо учитывать при загрузке файлов на сервер», то рекомендую начать именно с неё.
В вышеуказанной статье мы обсудили общие нюансы загрузки файлов на сервер. А теперь пришло время программировать! В данном примере мы реализуем:
- Корректную загрузку картинки на сервер.
- Проверку, выбран файл или нет.
- Проверку на размер файла.
- Грамотную проверку расширения файла.
Предупреждаю сразу, рассматриваемый код будет без архитектурных изысков, поскольку он должен быть максимально понятным для начинающих.
Для начала определим, какие файлы и папки будут в нашем проекте:
- index.php – обработчик отправки формы
- functions.php – библиотека с двумя функциями
- img – папка, в которую будут загружаться изображения
Посмотрим на содержимое индекса.
Кода много, но, пожалуйста, не пугайтесь! Большая часть знакома вам из предыдущей статьи.
Главное — просто понять схему работы данного скрипта. У нас есть две функции:
- can_upload – производит все проверки: возвращает true либо строку с сообщением об ошибке
- make_upload – производит загрузку файла на сервер
Соответственно, если форма была отправлена, мы сначала вызываем функцию can_upload. Если она подтвердила, что файл подходит нам по всем параметрам, то мы вызываем функцию make_upload. Иначе просто распечатываем сообщение об ошибке.
Здесь всё достаточно коротко и логично. Схема стандартная. Поэтому нас больше интересует, а что именно делают эти загадочные функции can_upload и make_upload!
Загрузка изображений — фукнции.
Рассмотрим файл functions.php
Сначала всего пару слов про функцию make_upload, поскольку она проще. Обратите внимание на то, что мы перед именем файла вставляем mt_rand(0, 10000), т.е, случайное число от 0 до 10000. Делается это для того, чтобы у файла было уникальное имя. В противном случае, при загрузке двух картинок с одинаковыми именами, вторая заменит первую.
Кстати, если вы задались резонным вопросом, а где же закрывающий тег php в данном файле, значит, вы явно не читали статью «Ошибка headers already sent»!
Необходимые проверки
Основной же интерес для нас представляет функция can_upload.
($file[‘name’] == ») — мелочь, знакомая вам по предыдущей статье.
А вот ($file[‘size’] == 0) — это забавно! Особенно с учётом того, что мы говорим при этом, что файл слишком большой! Разгадка кроется в том, что если файл был больше, чем разрешено в настройках сервера, то он не будет загружен вообще. А если файл не загружен, то вполне логично, что его размер равен нулю.
Ну и наконец, проверка расширения. Здесь мы используем так называемую технику белого листа. Таким листом у нас является массив $types, в котором мы перечисляем все допустимые расширения. Если расширение загружаемого файла не найдено в массиве, значит нам загружают что-то не то.
Вот собственно говоря и всё! Скрипт надёжный, аккуратный и лаконичный.
А в следующей статье мы поговорим о том, как можно наложить на загружаемую картинку водяной знак. Рекомендую прочесть!
В этой статье подробно разберём механизм загрузки изображений на сервер с помощью PHP не прибегая к сторонним компонентам и фреймворкам. Научимся безопасно загружать изображения не только с локальной машины пользователя, но и удалённые файлы по ссылке. Все примеры кода я буду писать в процедурном стиле, дабы вы быстрее могли читать код, а не перескакивать с одного метода на другой. Руководство полностью авторское и не претендует на какую-либо академичность изложения.
§1. Общие принципы
Всю последовательность загрузки изображения на сервер можно отобразить следующим образом: настройка php.ini → получение файла → проверка безопасности → валидация данных → сохранение на диск. Процесс загрузки картинки с компьютера пользователя или по URL ничем не отличаются, за исключением способа получения изображения и его сохранения. Общая схема загрузки картинки на сервер выглядит следующим образом:
Для валидации картинки по URL мы будем использовать функцию getimagesizefromstring(), т. к. cURL скачает её в переменную для дальнейших манипуляций.
Поскольку мы загружаем изображения на сервер, то хорошо было бы проверять их определённые параметры: ширину, высоту, тип картинки, размер файла в байтах. Это зависит от логики вашего приложения, но для наглядности в этом руководстве мы проверим все вышеописанные параметры.
§2. Правила безопасности
Безопасность загрузки изображений сводится к недопущению попадания на сервер чужеродного кода и его выполнения. На практике загрузка картинок наиболее уязвимое место в PHP-приложениях: попадание shell-скриптов, запись вредоносного кода в бинарные файлы, подмена EXIF-данных. Для того, чтобы избежать большинства методов взлома нужно придерживаться следующих правил:
Если есть чем дополнить «Правила безопасности», тогда оставляйте свои замечания или ссылки на статьи по безопасности в комментариях к этому руководству, а я опубликую их в этом параграфе.
§3. Конфигурация php.ini
PHP позволяет внести определённые конфигурационные значения в процесс загрузки любых файлов. Для этого необходимо в файле php.ini найти блоки «Resource Limits», «Data Handling» и «File Uploads», а затем отредактировать, по необходимости, следующие значения:
Исходя из указанных значений, пользователь не сможет за один раз загрузить больше десяти файлов, причём каждый файл не должен превышать 5 Мбайт. Параметры из блока «Resource Limits» больше нужны для загрузки удалённого файла, т. к. с помощью cURL мы будем скачивать содержимое в переменную и проверять её по нужным нам критериям, а для этого необходимо дополнительное время и память.
50 Мбайт памяти. Кроме того, нам нужно знать максимальное время загрузки одного файла с локальной машины и по ссылке, дабы установить достаточное время выполнения скрипта в max_execution_time и не пугать пользователей ошибками.
§4. Загрузка картинок из формы
Сейчас мы не будем рассматривать загрузку нескольких файлов на сервер, а разберём лишь саму механику загрузки на примере одного файла. Итак, для загрузки картинки с компьютера пользователя необходимо с помощью HTML-формы отправить файл PHP-скрипту методом POST и указать способ кодирования данных enctype="multipart/form-data" (в данном случае данные не кодируются и это значение применяется только для отправки бинарных файлов). С формой ниже мы будем работать дальше:
Для поля выбора файла мы используем имя name="upload" в нашей HTML-форме, хотя оно может быть любым. После отправки файла PHP-скрипту file-handler.php его можно перехватить с помощью суперглобальной переменной $_FILES[‘upload’] с таким же именем, которая в массиве содержит информацию о файле:
Не всем данным из $_FILES можно доверять: MIME-тип и размер файла можно подделать, т. к. они формируются из HTTP-ответа, а расширению в имени файла не стоит доверять в силу того, что за ним может скрываться совершенно другой файл. Тем не менее, дальше нам нужно проверить корректно ли загрузился наш файл и загрузился ли он вообще. Для этого необходимо проверить ошибки в $_FILES[‘upload’][‘error’] и удостовериться, что файл загружен методом POST с помощью функции is_uploaded_file(). Если что-то идёт не по плану, значит выводим ошибку на экран.
Для того, чтобы злоумышленник не загрузил вредоносный код встроенный в изображение, нельзя доверять функции getimagesize(), которая также возвращает MIME-тип. Функция ожидает, что первый аргумент является ссылкой на корректный файл изображения. Определить настоящий MIME-тип картинки можно через расширение FileInfo. Код ниже проверит наличие ключевого слова image в типе нашего загружаемого файла и если его не окажется, выдаст ошибку:
На данном этапе мы уже можем загружать абсолютно любые картинки на наш сервер, прошедшие проверку на MIME-тип, но для загрузки изображений по определённым характеристикам нам необходимо валидировать их с помощью функции getimagesize(), которой скормим сам бинарный файл $_FILES[‘upload’][‘tmp_name’]. В результате мы получим массив максимум из 7 элементов:
Для дальнейшей валидации изображения и работы над ним нам необходиом знать только 3 значения: ширину, высоту и размер файла (для вычисления размера применим функцию filesize() для бинарного файла из временной папки).
После всех проверок мы можем с уверенностью переместить наш загружаемый файл в какую-нибудь папку с картинками. Делать лучше это через функцию move_uploaded_file(), которая работает в безопасном режиме. Перед перемещением файла нельзя забыть сгенерировать случайное имя и расширение из типа изображения для нашего файла. Вот так это выглядит:
На этом загрузка изображения завершена. Для более удобной загрузки файлов можете использовать класс UploadedFile из пакета Symfony HttpFoundation, который является обёрткой для $_FILES и также сохраняет файл через move_uploaded_file().
§5. Загрузка изображения по ссылке
Для загрузки изображения по ссылке нам понадобиться библиотека cURL, которая работает с удалёнными ресурсами. С помощью неё мы скачаем контент в переменную. С одной стороны может показаться, что для этих целей подойдёт file_get_contents(), но на самом деле мы не сможем контролировать объём скачиваемых данных и нормально обрабатывать все возникшие ошибки. Для того, чтобы cURL корректно скачал данные нам нужно: разрешить следовать перенаправлениям, включить проверку сертификата, указать максимальное время работы cURL (формируется за счёт объёма скачиваемых данных и средней скорости работы с ресурсом). Как правильно скачать файл в переменную показано ниже с необходимыми параметрами:
Если всё прошло успешно и cURL уложился в 60 секунд, тогда содержимое по ссылке будет скачано в переменную $raw. Кроме того, функция curl_getinfo() вернёт информацию о проделанном запросе, откуда мы можем получить дополнительную информацию для анализа работы с удалёнными ресурсами:
Дальше нам нужно проверить нет ли ошибок в curl_errno() и удостовериться, что ресурс отдаёт равный 200, иначе мы скажем, что по такому-то URL ничего не найдено. После всех проверок переменную $raw передаём в getimagesizefromstring() и работаем уже по отработанной схеме как в случае с загрузкой картинок из формы.
Для сохранения изображения на диск можно воспользоваться file_put_contents(), которая запишет контент в файл. Новое имя файла мы создадим через функцию md5(), а расширение сделаем из image_type_to_extension(). Теперь мы можем загружать любые картинки по ссылке.
§6. Настройка выбора нескольких файлов
В этом параграфе разберём способы загрузки нескольких изображений за один раз с локальной машины пользователя и по удалённым ссылкам. Для отправки ссылок мы задействуем $_POST и передадим ей все данные с помощью тега textarea. Для загрузки файлов из формы мы продолжим дальше работать с $_FILES. Наша новая HTML-форма будет немного отличаться от старой.
В конец имени поля выбора файла name="upload[]" добавились фигурные скобки и аттрибут multiple, который разрешает браузеру выбрать несколько файлов. Все файлы снова загрузятся во временную папку, если не будет никаких ошибок в php.ini . Перехватить их можно в $_FILES, но на этот раз суперглобальная переменная будет иметь неудобную структуру для обработки данных в массиве. Решается эта задача небольшими манипуляциями с массивом:
Для загрузки нескольких картинок по URL передадим наши ссылки через textarea с именем name="upload", где их можно указать через пробел или с новой строки. Функция preg_split разберёт все данные из $_POST[‘upload’] и сформирует массив, по которому нужно пройтись циклом и каждый валидный URL отправить в обработчик.
Вы можете улучшить форму для загрузки изображений, например, воспользоваться библиотекой FineUploader или jQuery FileUpload, чтобы настроить выбор картинок с определённым расширением.
Дата публикации: 2010-10-19
Сегодня я хочу рассказать о реализации довольно популярной задачи. Во-первых, это загрузка изображения на сервер. А во-вторых, это изменение размера изображения. Также рассмотрим поворот и изменение качества.
Немного теории по загрузке изображений на сервер средствами PHP
Вы не можете сразу загрузить файл в свою папку. Вначале он загружается во временную директорию сервера, а затем обрабатывается с помощью PHP интерпритатора. По окончанию сессии временный файл автоматически удаляется. То есть, мы вначале забрасываем файл во временную папку, а затем перекладываем в нужную.
$_FILES это массив загруженных файлов. Он имеет параметры (на примере файла picture):
$_FILES[‘ picture ‘][‘name’] – настоящее имя файла. Например: image.jpg.
Бесплатный курс по PHP программированию
Освойте курс и узнайте, как создать динамичный сайт на PHP и MySQL с полного нуля, используя модель MVC
В курсе 39 уроков | 15 часов видео | исходники для каждого урока
$_FILES[‘ picture ‘][‘size’] – размер файла в байтах.
$_FILES[‘ picture ‘][‘type’] – MIME-тип загруженного файла. Например: image/gif, image/png, image/jpeg.
$_FILES[‘ picture ‘][‘tmp_name’] – содержит имя файла во временном каталоге, например: /tmp/phpV3b3qY. Именно этот параметр и используется для перемещения файлов после загрузки.
$_FILES[‘ picture ‘][‘error’] – код ошибки.
Подготовка
Для начала нам нужна форма для загрузки. Возьмём простейшую форму.
Параметр enctype="multipart/form-data" обязателен для такой формы. Тег отвечает за поле для ввода имени файла, который загружается на сервер.
Также нам потребуется обработчик события – загрузки файла. Вначале у нас будет одна настройка – путь сохранения изображения. Можно указывать как прямой, так и относительный путь. В случае POST запроса обработчик попробует осуществить загрузку файла по указанному пути. Скрипт сообщит о результате загрузки – удачна она или нет.
Функция copy, как вы наверно догадались, отвечает за копирование файла из одного место в другое. Мы копируем файл из временной папки сервера в нужную, сохранив имя файла.
Договоримся, что и форма и её обработчик будут находиться в одном файле – upload.php.
Итого имеем простой, но рабочий скрипт. Его можно забросить на хостинг, создать папку i и потренироваться с загрузкой файлов.
Проверки
Любая форма представляет для сайта опасность. И особенно форма загрузки файлов. Злоумышленник может загрузить скрипт и выполнить его на сервере. Поэтому необходимо озаботиться безопасностью.
Самые простые и обязательные проверки – на размер и тип файла. Для этого укажем допустимые типы и размер.
Тип файла укажем в виде массива:
а размер файла в байтах:
Проверяем тип файла. В случае недопустимого типа прекращаем работу скрипта и выводим соответствующее уведомление. Функция in_array проверяет присутствие значения в массиве.
Проверяем размер файла. В случае недопустимого размера прекращаем работу скрипта и выводим соответствующее уведомление.
Итого получаем такой скрипт. Скрипт рабочий, можно баловаться. Немного забегая вперёд, добавим также параметр $tmp_path – путь к папке временных файлов.
Изменение размеров изображений PHP
Приступим к самому интересному, а именно изменению размеров изображения с помощью PHP. Для этого напишем функцию resize. Сделаем также возможным изменять качество изображения и поворачивать его.
Размер изображения будем подставлять исходя из параметра. Это будет либо эскиз ($type = 1), либо большое изображение ($type = 2).
Итак, шапка функции у нас получилась такая:
По умолчанию подставляем размеры эскиза, а поворот и качество по умолчанию не используются. Пойдём дальше.
Устанавливаем ограничение размера изображения по ширине. Функцию можно сделать более абстрактной, если задавать эти значения в параметрах, а также сделать возможным ограничение и по высоте. Однако в данном случае нам не нужна такая универсальность.
Устанавливаем качество изображения по умолчанию (при $quality = null) равным 75%.
Далее создаём изображение для дальнейших преобразований. Для создания используем функцию в зависимости от типа файла (jpg, png или gif). Функции создания называются очень лаконично imagecreatefrom + тип файла.
Если указан параметр $rotate, выполняем поворот изображения. Делаем это с помощью функции rotate(), параметрами которой являются: изображение, градусы, фон изображения для закрашивания пустых областей, образованных при повороте. Для того чтобы пустые области не возникали, поворачиваем изображение на угол в 90, 180, 270 градусов.
Бесплатный курс по PHP программированию
Освойте курс и узнайте, как создать динамичный сайт на PHP и MySQL с полного нуля, используя модель MVC
В курсе 39 уроков | 15 часов видео | исходники для каждого урока
Далее определяем высоту и ширину изображения с помощью функций imagesx и imagesy.
В зависимости от типа (эскиз или большое изображение) устанавливаем ограничение по ширине.
Далее, если ширина изображения больше максимальной, проводим преобразования. Иначе просто сохраняем изображение и очищаем память. Сохраняем изображение с помощью функции imagejpeg. В данном примере, рассмотрено сохранение только в формате jpg, однако функционал всегда можно расширить. Удаляем изображения из памяти с помощью функции imagedestroy.
В качестве результата работы функции возвращаем имя файла. Оно нам ещё понадобится.
Вернёмся к преобразованию. Вначале вычисляем пропорции изображения и размеры преобразованного изображения.
Далее создаём пустую картинку (функция imagecreatetruecolor) с шириной и высотой, полученными на прошлом шаге.
И копируем исходное изображение ($src) в только что созданное ($dest), изменяя его размеры. Функция imagecopyresampled делает это с пересэмплированием, что улучшает качество.
И наконец, сохраняем полученное изображение и очищаем память.
Итого, функция получает исходное изображение и параметры преобразования, выполняет преобразования, сохраняет полученный файл во временную папку и возвращает имя изображения. Теперь нам осталось только переложить файл в конечную папку.
Отвечу заранее на вопрос «Почему мы не можем сразу положить изменённый файл в конечную папку?». Можем. Однако не делаем для увеличения глубины абстракции, то есть, чтобы придать определённую универсальность функции. Вы же сможете её использовать на разных сайтах.
Совсем забыл. Добавляем в начало функции строку:
Она обозначает, что в функции будет использована глобальная переменная $tmp_path – путь к временной папке.
Вызов функции
Функцию нужно вызывать сразу после проверок. А также следует изменить ту часть скрипта, где мы копируем изображение в конечную папку. Вы ведь теперь работаем с новым изображением. Теперь схема загрузки такова: компьютер → временная папка сервера → наша временная папка → конечная папка. То есть, добавился ещё один промежуточный пункт.
Для пущего веселья добавим в нашу форму выпадающий список, чтобы мы устанавливать тип загрузки и поля для ввода градуса поворота.
В таком случае вызов функции будет такой:
Конечный результат
И наконец, конечный результат.
Слишком большой размер файла. Попробовать другой файл?
Что-то пошло не так.
Послесловие
Естественно, рассмотренный пример учебный. Однако он вполне рабочий. Что вы можете попробовать, забросив скрипт на сервер и создав папки для изображений и временных файлов. Скрипт можно бесконечно дорабатывать, изменять уровень абстракции, добавлять условия и параметры, преобразования, проверки, накладывать «водяной знак».
Идеальный вариант – осмыслить и допилить до своих требований.
Домашнее задание
Конечно, задание проверять никто не будет. Однако я рекомендую его выполнить для себя. Ведь программирование – это, прежде всего, практика. Итак:
Вынесите размеры эскиза ($max_thumb_size) и большого изображения ($max_size) из функции в настройки файла.
Попробуйте задавать их в параметрах функции. Это значительно повысит уровень абстракции.
Проработайте вариант, когда изображение ограничивается по большей стороне, а не только по ширине.
Попробуйте получить на выходе файл того же типа, что и исходный. То есть, например, если загружаете gif, то и уменьшенная копия будет gif.
Попробуйте генерировать и эскиз и большой файл за одну загрузку.
Попробуйте наложить «водяной знак».
Используя бонусный код, добавьте в функцию возможность создания квадратных файлов. Добавьте в форму выбор типа обрезки – квадратная или пропорциональная.