Простая авторизация и аутентификация на php в связке с mysql

Постоянные cookie-наборы в аутентификации с помощью форм

В продемонстрированных до сих пор примерах поддержка билетов аутентификации между запросами осуществлялась с помощью непостоянных cookie-наборов аутентификации. Непостоянство означает, что после закрытия браузера пользователем такие cookie-наборы немедленно удалялись. Это разумный шаг для обеспечения безопасности. И особенно он важен на компьютерах общего пользования, поскольку он предотвращает использование следующим пользователем билета, выданного предыдущему. Непостоянные cookie-наборы затрудняют и ограничивают возможности атак перехватом сеанса (когда злоумышленник получает доступ к сети и похищает cookie-набор другого пользователя).

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

Если вы решили применять постоянные cookie-наборы, реализовать их достаточно просто. Для этого понадобится передать значение true вместо false во втором параметре метода RedirectFromLoginPage() или SetAuthCookie() класса FormsAuthentication. Вот пример:

Постоянные cookie-наборы не устаревают по закрытии браузера. Подобно непостоянным cookie-наборам, они устаревают, когда вызывается метод FormsAuthentication.SignOut(), или же когда достигнут лимит времени, установленного в атрибуте timeout элемента <forms> (по умолчанию 30 минут). Это порождает потенциальную проблему. В некоторых приложениях пользователи должны иметь выбор — применять кратковременный непостоянный cookie-набор или же хранить долгосрочный постоянный. Тем не менее, для атрибута timeout допускается указывать только одно значение. Решение состоит в использовании метода GetAuthCookie() класса FormsAuthentication для создания постоянного cookie-набора, установки вручную даты и времени, когда cookie-набор должен стать недействительным, и самостоятельном включении постоянного cookie-набора в ответ HTTP.

В следующем примере переписан код аутентификации пользователя при щелчке на кнопке входа. Он создает постоянный cookie-набор, но выполняет также дополнительные шаги, чтобы ограничить время его действия 10 днями:

Код проверки удостоверения в этом сценарии прежний. Единственное отличие состоит в том, что cookie-набор аутентификации не добавляется автоматически. Вместо этого он создается вызовом GetAuthCookie(), который возвращает новый экземпляр HttpCookie. После того, как cookie-набор аутентификации создан, можно получить текущее время и дату (с помощью статического свойства DateTime.Now), добавить к нему 10 дней (методом DateTime.AddDays()) и установить полученное значение в качестве даты устаревания данного cookie-набора. Далее cookie-набор добавляется в HTTP-ответ и, наконец, можно перенаправить пользователя на исходный запрошенный URL-адрес, который получается с помощью метода GetRedirectUrl().

В результате получаем cookie-набор, который сохраняется при закрытии браузера, но время действия которого истекает через 10 дней, когда пользователю придется ввести заново свое удостоверение, чтобы снова аутентифицироваться на веб-сайте.

Запомнить пользователя

Теперь нам надо запомнить что пользователь авторизован и точно знать кто это. Первое что приходит в голову — использовать для этого куки. Действительно — запихать в куки логин и id пользователя и всегда знать кто запрашивает страницу в данный момент.

Но это порочная практика. Почему? Потому что куки — это файл, который хранится у пользователя в браузере и данные из этого файла передаются серверу в каждом запросе. Во-первых, они передаются как текст, а это значит — их легко перехватить. Во-вторых, это простой текст, посылаемый пользователем. Поэтому его можно буквально переписать. Например, если мы решили хранить в куках логин пользователя «Вася» он может открыть управление куками в своем браузере, найти нужную куку и исправить ее на, скажем, «Admin». И все. Теперь при каждом запросе мы будем получать куку, которая нам будет сообщять юзернейм пользователя — «Admin».

Поэтому безопаснее хранить все данные на сервере в недоступном из веба месте. В какой-то папке, к которой нельзя получить доступ из браузера. В эту папку нужно писать все данные о пользователе и читать их оттуда каждый раз, когда он запрашивает документ. Чтобы узнать какому пользователю принадлежит какой файл данных — надо обзывать файл уникальным именем и вот это имя кидать уже пользователю в куки. Таким образом, пользователь не сможет узнать как называется файл для пользователя Admin — этот файл генерирует система на сервере. И это позволяет таскать от документа к документу хоть пароли в открытом виде.

То что я описал — это механизм сессии
. В Perl, например, для использования сессий нужно загружать модули. А в php сессии поддерживаются из коробки. Фактически, все что вам нужно знать — это функция session_start() и массив $_SESSION. Это все. Сейчас расскажу.

В каждом скрипте, где вы будете писать в сессию или читать из нее, вам надо до того, как вы выведете какую-то информацию
вызвать функцию session_start(). Это запустит сессию. Эта функция создаст файл сессии если его нет или прочитает его если скрипту была передана специальная кука.

Чтобы записать в сессию данные нужно просто записать их в массив $_SESSION. Сейчас нам надо помнить id юзера.

$_SESSION = $userinfo;

Все. Теперь каждый раз когда пользователь будет запрашивать скрипт, который использует сессии — вам будет доступно значение элемента $_SESSION.

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

CSS-стили для оформления форм

Для улучшения внешнего вида форм примените к ним следующие CSS-стили:

* {
    padding: 0;
    margin: 0;
    box-sizing: border-box;
}
body {
    margin: 50px auto;
    text-align: center;
    width: 800px;
}
h1 {
    font-family: 'Passion One';
    font-size: 2rem;
    text-transform: uppercase;
}
label {
    width: 150px;
    display: inline-block;
    text-align: left;
    font-size: 1.5rem;
    font-family: 'Lato';
}
input {
    border: 2px solid #ccc;
    font-size: 1.5rem;
    font-weight: 100;
    font-family: 'Lato';
    padding: 10px;
}
form {
    margin: 25px auto;
    padding: 20px;
    border: 5px solid #ccc;
    width: 500px;
    background: #eee;
}
div.form-element {
    margin: 20px 0;
}
button {
    padding: 10px;
    font-size: 1.5rem;
    font-family: 'Lato';
    font-weight: 100;
    background: yellowgreen;
    color: white;
    border: none;
}
p.success,
p.error {
    color: white;
    font-family: lato;
    background: yellowgreen;
    display: inline-block;
    padding: 2px 10px;
}
p.error {
    background: orangered;
}

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

Отправляем PHP-скрипт на сервер

Последнее, что осталось сделать — загрузить файл скрипта на сервер. Для этого сохраним его как post.php и загрузим по адресу mihailmaximov.ru/projects/mail/post.php. Если у вас ещё нет своего сервера, можете использовать этот скрипт для тестирования формы обратной связи.

Как загружать файлы, мы рассказывали в статье про публикацию сайта в Сети, поэтому просто сделаем всё по той инструкции:

Теперь, когда мы обновим HTML-страницу, заполним все поля и нажмём «Отправить», на указанную почту придёт письмо с нашим сообщением. Это значит, что форма работает, а мы с вами сделали очередной полезный проект!

Регистрация пользователя

Форму мы отправляем на обработку файлу register.php, через метод POST. Название данного файла обработчика, указано в значение атрибута action. А метод отправки указано в значение атрибута method.

Открываем этот файл register.php и первое что нам нужно сделать, это написать функцию запуска сессии и подключить созданный нами ранее файл dbconnect.php (В этом файле мы сделали подключение к БД). И ещё, сразу объявим ячейки error_messages и success_messages в глобальном массиве сессии. В error_mesages будем записывать все сообщения об ошибках возникающие при обработке формы, а в succes_messages, будем записывать радостные сообщения.

Перед тем как продолжить, мы должны проверить, была ли вообще отправлена форма. Злоумышленник, может посмотреть на значение атрибута action из формы, и узнать какой файл занимается обработкой данной формы. И ему может прийти в голову мысль перейти напрямую в этот файл, набирая в адресной строке браузера такой адрес: http://арес_сайта/register.php

Поэтому нам нужно проверить наличие ячейки в глобальном массиве POST, имя которой соответствует имени нашей кнопки «Зарегистрироваться» из формы. Таким образом мы проверяем была ли нажата кнопка «Зарегистрироваться» или нет.

Если злоумышленник попытается перейти напрямую в этот файл, то он получить сообщение об ошибке. Напоминаю, что переменная $address_site содержит название сайта и оно было объявлено в файле dbconnect.php.

Далее, нам необходимо проверить введённую капчу. То есть сравнивать полученное значение от пользователя со значением которая есть в сессии.

Значение капчи в сессии было добавлено при её генерации, в файле captcha.php. Для напоминания покажу ещё раз этот кусок кода из файла captcha.php, где добавляется значение капчи в сессию:

Теперь приступим к самой проверке. В файле register.php, внутри блока if, где проверяем была ли нажата кнопка «Зарегистрироваться», а точнее где указан комментарий » // (1) Место для следующего куска кода» пишем:

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

Если ячейка существует, то обрезаем пробелы с начала и с конца строки из этой ячейки, иначе, перенаправляем пользователя обратно на страницу с формой регистрации.

Далее, после того как обрезали пробелы, добавляем строку в переменную и проверяем эту переменную на пустоту, если она не является пустой, то идём дальше, иначе перенаправляем пользователя обратно на страницу с формой регистрации.

Этот код вставляем в указанное место «// (2) Место для следующего куска кода«.

В указанном месте «// (3) Место кода для проверки формата почтового адреса и его уникальности» добавляем следующий код:

И так, мы закончили со всеми проверками, пора добавить пользователя в БД. В указанном месте » // (4) Место для кода добавления пользователя в БД » добавляем следующий код:

Если в запросе на добавления пользователя в БД произошла ошибка, мы добавляем сообщение об этой ошибке в сессию и возвращаем пользователя на страницу регистрации.

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

С регистрацией мы закончили. Двигаемся дальше.

Структура сайта

Шапку и подвал сайта вынесем в отдельные файлы, header.php и footer.php. Их будем подключать на всех страницах. А именно на главной (файл index.php), на страницу с формой регистрации (файл form_register.php) и на страницу с формой авторизации (файл form_auth.php).

Блок с нашими ссылками, регистрация и авторизация, добавим в шапку сайта, чтобы они отображались на всех страницах. Одна ссылка будет ввести на страницу с формой регистрации (файл form_register.php) а другая на страницу с формой авторизации (файл form_auth.php).

Содержимое файла header.php:

Содержимое файла footer.php:

Подвал сайта

</body>
</html>

Подключение файлов header.php и footer.php будем делать с помощью функции require_once(«путь_к_файлу»).

        <?php
            //Подключение шапки
            require_once("header.php");
        ?>

        
            

Контент главной страницы

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

<?php
//Подключение подвала
require_once(«footer.php»);
?>

Для оформления вида страницы, в шапке (файл header.php) подключили файл стилей css/styles.css. Код данного файла сейчас нас не особо интересует, поэтому нет смысла его здесь показывать. Вы сможете увидеть его открыв этот файл css/styles.css, из архива с материалами данной статьи.

В итоге, главная страница, у нас выглядит так:

Сервис push-уведомлений для 1С (Push Notification Service For 1C — PNS4OneS)

Сервис предназначен для передачи сообщений от сервера 1С клиентским сеансам. В простом случае полученное сообщение отображается у клиента в виде всплывающего окна (процедура 1С ПоказатьОповещениеПользователя). С использованием данного сервиса можно оповещать пользователей о новых задачах, полученных электронных письмах, менеджеров о новых заказах, загруженных с сайта и т. п. Дополнительно имеется возможность передавать произвольные данные и обрабатывать самостоятельно полученные сообщения на клиенте, что позволяет реализовать более сложные сценарии (чаты, отображение прогресса длительной операции на сервере и т. п.).

1 стартмани

Исходный код для регистрации пользователей

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

Сохраните приведенный далее код в начале файла registration.php:

<?php
    session_start();
    include('config.php');
    if (isset($_POST)) {
        $username = $_POST;
        $email = $_POST;
        $password = $_POST;
        $password_hash = password_hash($password, PASSWORD_BCRYPT);
        $query = $connection->prepare("SELECT * FROM users WHERE email=:email");
        $query->bindParam("email", $email, PDO::PARAM_STR);
        $query->execute();
        if ($query->rowCount() > 0) {
            echo '<p class="error">Этот адрес уже зарегистрирован!</p>';
        }
        if ($query->rowCount() == 0) {
            $query = $connection->prepare("INSERT INTO users(username,password,email) VALUES (:username,:password_hash,:email)");
            $query->bindParam("username", $username, PDO::PARAM_STR);
            $query->bindParam("password_hash", $password_hash, PDO::PARAM_STR);
            $query->bindParam("email", $email, PDO::PARAM_STR);
            $result = $query->execute();
            if ($result) {
                echo '<p class="success">Регистрация прошла успешно!</p>';
            } else {
                echo '<p class="error">Неверные данные!</p>';
            }
        }
    }
?>

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

Далее, с помощью $_POST мы проверяем, нажал ли пользователь кнопку «Регистрация». Следует помнить, что пароли нельзя сохранять в виде незашифрованного текста. Поэтому наш код использует функцию password_hash() и сохраняет пароль в хэшированном виде. Эта функция записывает пароль в базу данных в виде хэш-строки, состоящей из 60 случайных символов.

Алгоритм регистрации

Для создания нового пользователя используется метод , который принимает логин и пароль в качестве аргументов.

Если пользователь существует — выбрасываем исключение. Иначе, генерируем новую соль и хешируем ей пароль. После этого выполняем запрос на добавление данных в базу. Если при выполнении запроса происходит ошибка, печатаем соответствуюее сообщение и завершаем работу скрипта. Такая ситуация может произойти при отключении сервера MySQL или его внутренней ошибки.

Если пользователь был усшешно создан, функция возвращает его уникальный идентификатор. Это обычное числовое поле, которое автоматически увеличивается при добавлении записей в таблицу.

Создаём регистрацию на PHP:

Для этого пишем скрипт который будет ниже:

PHP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77

// Проверяем нажата ли кнопка отправки формы

if(isset($_REQUEST’doGo’)){

// Все последующие проверки, проверяют форму и выводят ошибку

// Проверка на совпадение паролей

if($_REQUEST’pass’!==$_REQUEST’pass_rep’){

$error=’Пароль не совпадает’;

}

// Проверка есть ли вообще повторный пароль

if(!$_REQUEST’pass_rep’){

$error=’Введите повторный пароль’;

}

// Проверка есть ли пароль

if(!$_REQUEST’pass’){

$error=’Введите пароль’;

}

// Проверка есть ли email

if(!$_REQUEST’email’){

$error=’Введите email’;

}

// Проверка есть ли логин

if(!$_REQUEST’login’){

$error=’Введите login’;

}

// Если ошибок нет, то происходит регистрация

if(!$error){

$login=$_REQUEST’login’;

$email=$_REQUEST’email’;

// Пароль хешируется

$pass=password_hash($_REQUEST’pass’,PASSWORD_DEFAULT);

// Если день рождения не был указан, то будет самый последний год из доступных

$DOB=$_REQUEST’year_of_birth’;

// Добавление пользователя

mysqli_query($db,»INSERT INTO `users` (`login`, `email`, `password`, `DOB`) VALUES (‘».$login.»‘,'».$email.»‘,'».$pass.»‘, ‘».$DOB.»‘)»);

// Подтверждение что всё хорошо

echo’Регистрация прошла успешна’;

}else{

// Если ошибка есть, то выводить её

echo$error;

}

}

?>

<!DOCTYPE html>

<html lang=»ru»>

<head>

<meta charset=»UTF-8″>

<meta name=»viewport»content=»width=device-width, initial-scale=1.0″>

<meta http-equiv=»X-UA-Compatible»content=»ie=edge»>

<title>Зарегистрироваться<title>

<head>

<body>

<form action=»<?= $_SERVER ?>»>

<p>Логин<input type=»text»name=»login»id=»»><samp style=»color:red»>*<samp><p>

<p>EMail<input type=»email»name=»email»id=»»><samp style=»color:red»>*<samp><p>

<p>Пароль<input type=»password»name=»pass»id=»»><samp style=»color:red»>*<samp><p>

<p>Повторитепароль<input type=»password»name=»pass_rep»id=»»><samp style=»color:red»>*<samp><p>

<?php$year=date(‘Y’);?>

Годрождения

<select name=»year_of_birth»id=»»>

<option value=»»>—-<option>

<?phpfor($i=$year-14;$i>$year-14-100;$i—){?>

<option value=»<?= $i ?>»><?=$i?><option>

<?php}?>

<select>

<p><input type=»submit»value=»Зарегистрироваться»name=»doGo»><p>

<form>

<body>

<html>

HTML я не стал рассказывать, так как, там всё понятно, да и вообще программист должен разберется в коде.

Создание таблицы с учетными данными и подключение к базе данных

Следующий шаг – создание таблицы базы данных, содержащей учетные данные пользователей. В нашем случае таблица состоит всего из четырех столбцов:

  1. Порядковый номер ID, который для каждого нового пользователя увеличивается автоматически.
  2. Уникальное имя пользователя.
  3. Адрес электронной почты.
  4. Пароль.

Для быстрого создания таблицы базы данных можно использовать следующий SQL-запрос:

CREATE TABLE `users` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
    `username` varchar(25) NOT NULL,
    `password` varchar(255) NOT NULL,
    `email` varchar(100) NOT NULL,
    PRIMARY KEY (`id`),
    UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf-8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1;

Теперь создайте файл config.php и сохраните в нем приведенный далее код для подключения к базе данных:

<?php
    define('USER', 'root');
    define('PASSWORD', '');
    define('HOST', 'localhost');
    define('DATABASE', 'test');
    try {
        $connection = new PDO("mysql:host=".HOST.";dbname=".DATABASE, USER, PASSWORD);
    } catch (PDOException $e) {
        exit("Error: " . $e->getMessage());
    }
?>

В приведенном выше коде замените название базы данных на то, которое вы используете для своего собственного сайта.

Создание объектов в базе данных

Переходим к практике. Для начала создадим таблицу хранения данных о пользователях в базе MySQL. Я предлагаю использовать простую структуру таблицы (Вы ее, конечно, можете дополнить чем-нибудь, у меня база называется test, а таблица users):

   
   CREATE TABLE test.users(
     user_id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
     user_login VARCHAR(30) NOT NULL,
     user_password VARCHAR(32) NOT NULL,
     PRIMARY KEY (user_id)
   )
   ENGINE = MYISAM
   CHARACTER SET utf8
   COLLATE utf8_general_ci;

И давайте сразу же добавим одну запись в эту таблицу:

   
   insert into test.users (user_login, user_password)
                values ('mylogin','202cb962ac59075b964b07152d234b70')

Итого у нас получилось:

  • Логин – mylogin;
  • Пароль — 202cb962ac59075b964b07152d234b70;

Мы пароль, конечно же, будем хранить в хешированном виде, так как хранить пароль в открытом виде, мягко сказать, не безопасно. У нас Выше хеш пароля 123, поэтому, когда будем вводить пароль в форму, мы будем забивать именно 123, а не 202cb962ac59075b964b07152d234b70.

Готовим страницу с формой

Возьмём стандартный шаблон страницы и наполним его стилями и кодом для формы.

Пропишем CSS-стили, чтобы наша страница выглядела опрятно. Забежим немного вперёд и используем в стилях разделы и :

Чтобы сделать форму на странице, мы будем использовать такие теги:

— для ввода имени, почты для связи и темы письма. Они занимают одну строку, нам этого достаточно.

— здесь будут писать само сообщение, поэтому нужно будет сделать это поле побольше и пошире.

Ещё мы воспользуемся тегом — он мысленно собирает наши поля в одну форму и помогает управлять ими из одного места. У каждой формы есть свой метод, по которому она работает с данными. Форма может или отправлять данные (post), или получать их (get). Так как нам надо отправить сообщение в PHP-скрипт, будем использовать метод post. Сразу пропишем путь к скрипту на сервере — по этому адресу мы зальём нужный файл на следующем этапе. Этот скрипт, который мы позже напишем, и есть обработчик формы.

Оформим всё в виде кода:

У нас уже есть форма, но она пока не работает. Сейчас это исправим.

Изображения

Слайд-шоуГалерея слайд-шоуМодальные изображенияЛайтбоксАдаптивная Сетка изображенияСетка изображенияГалерея вкладокОверлей изображенияСлайд с наложенным изображениемМасштабирование наложения изображенияНазвание наложения изображенияЗначок наложения изображенияЭффекты изображенияЧерно-белое изображениеТекст изображенияТекстовые блоки изображенийПрозрачный текст изображенияПолное изображение страницыФорма на картинкеГерой изображениеПараллельные изображенияОкругленные изображенияАватар изображенияАдаптивные образыЦентрировать изображенияМиниатюрыПознакомьтесь с командойЛипкое изображениеОтражение изображенияВстряхните изображениеПортфолио галереяПортфолио с фильтрациейМасштабирование изображенияИзображение увеличительное стеклоПолзунок сравнения изображений

Рейтинг
( Пока оценок нет )
Editor
Editor/ автор статьи

Давно интересуюсь темой. Мне нравится писать о том, в чём разбираюсь.

Понравилась статья? Поделиться с друзьями:
Люкс-хост
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: