Bug Tracker: добавление записей и комментариев (часть седьмая)

Владимир | | CodeIgniter, HTML, MySQL, PHP, Web разработка.

bug_tracker_logo_part7

В этой части цикла статей о разработке баг трекера мы рассмотрим добавление записей о багах и комментариев к ним.

Вообще-то обе эти операции сводятся к добавлению записей в таблицы bugs и comments, т.е. их можно выполнить с помощью всего пары строк кода.

Но, как несложно догадаться, основная часть работы будет заключаться в обработке данных.

Кроме того, нужно сразу решить вопрос с тегами и защитой от XSS атак.

Проблема следующая. Если разрешить пользователям вставлять в текст описания багов любые HTML теги и не выполнять никаких проверок, то кто угодно сможет провести любую XSS атаку. Например, вставить скрипт с редиректом на свой ресурс.

С другой стороны, если фильтровать все теги, то посетители не смогут использовать жирный шрифт, курсив и т.п.

Решить проблему можно с помощью bbCodes или фильтрацией части тегов. Например, теги script удаляем, а strong – не трогаем.

Я решил выбрать второй вариант. Тем более что его не сложно реализовать с помощью библиотеки HTML Purifier. Кроме фильтрации опасных тегов, библиотека исправляет ошибки в разметке. Например, добавляет закрывающие теги.

Подключение HTML Purifier к CodeIgniter.

Я использовал вот эту инструкцию. Правда, я создал для библиотеки отдельную папку (\application\libraries\htmlpurifier).

После этого нужно закомментировать строку
require 'HTMLPurifier.php';
в файле HTMLPurifier.includes.php.

А в файл HTMLPurifier.php (сразу после <?php) добавить
require_once('HTMLPurifier.includes.php');

Подключается библиотека так:

$this->load->library('htmlpurifier/HTMLPurifier');

Теперь рассмотрим алгоритм добавления записи о баге.

add bug

Сначала мы проверяем данные с помощью встроенной библиотеки form_validation. Для её работы нужно установить правила для каждого поля формы.

Полей у нас 5: заголовок, имя пользователя, eMail пользователя, категория и описание бага.

Если проверка прошла, мы обрабатываем данные с помощью HTML Purifier и добавляем их в базу. Читаем из сессии адрес последней страницы, на которой находился посетитель. И отправляем ему редирект с адресом этой страницы. Т.к. у нас есть несколько страниц, на которых находится форма добавления бага (главная, страницы категорий), то пользователь, скорее всего, захочет остаться на той странице, с которой он отправлял сообщение.

Если во время проверки возникли ошибки – формируем страницу с этой же формой и описаниями ошибок и отправляем её посетителю.

Теперь посмотрите на код метода addbug.

function addbug() {
	$this->load->library('form_validation');
	$this->form_validation->set_error_delimiters('<div class="fErrMessage">', '</div>');
	$this->form_validation->set_rules('title', 'lang:title', 'required');
	$this->form_validation->set_rules('uname', 'lang:uname', 'required');
	$this->form_validation->set_rules('category_id', 'lang:category_id', 'required|integer');
	$this->form_validation->set_rules('description', 'lang:description', 'required');
	$this->form_validation->set_rules('email', 'lang:email', 'required|valid_email');
	if ($this->form_validation->run() === FALSE) {
		//ошибка
		$pageData['message'] = 'Форма заполнена неправильно';
		
		//показываем форму с описаниями ошибок
		$pageData['title'] = 'Bug Tracker';
		$pageData['categories'] = $this->mcategory->getAllCategories();
		
		$this->load->view('header', $pageData);
		$this->load->view('categories');
		$this->load->view('addbugform');
		$this->load->view('footer');
	}
	else {
		//форма заполнена правильно
		$this->load->library('htmlpurifier/HTMLPurifier');
		$config = HTMLPurifier_Config::createDefault();
		
		$bugData['title'] = $this->htmlpurifier->purify($this->input->post('title'));
		$bugData['uname'] = $this->htmlpurifier->purify($this->input->post('uname'));
		$bugData['category_id'] = $this->input->post('category_id');
		$bugData['uemail'] = $this->input->post('email');
		$bugData['description'] = $this->htmlpurifier->purify($this->input->post('description'));

		if (($res = $this->mbug->addNewBug($bugData)) !== FALSE) {
			//данные добавлены
			$this->session->set_flashdata('message', 'Сообщение добавлено');
		}
		else {
			//при добавлении данных возникла ошибка
			$this->session->set_flashdata('message', 'При добавлении данных возникла ошибка');
		}
		
		redirect($this->session->userdata('prev_page'));
	}
}

Как видите, метод работает точно в соответствии с описанным алгоритмом.

Тут стоит обратить внимание на строки 6 и 8. В них мы установили правила (integer, valid_email), которые обеспечивают проверку номера категории и email адреса.

Кроме того, использовать библиотеку HTML Purifier имеет смысл только после обычной проверки. Причин тут две. Во-первых, если пользователь не заполнил какое-то поле формы (а в данном случае они все обязательные), то пытаться удалить теги из него бессмысленно. Во-вторых, если вы все-таки попытаетесь это сделать, то HTML Purifier начинает потреблять кучу ресурсов. Например, использование памяти увеличивается с ~2,6 до 7 МБ.

Ещё один интересный момент связан с использованием сессий. Для вставки сообщений о результатах выполнения операции мы используем метод set_flashdata. Особенность flash данных в том, что они автоматически удаляются после считывания.

Таким образом, посетитель увидит сообщение о добавлении бага только один раз. Если он обновит страницу, то сообщение исчезнет.

Рассмотрим представление, которое создает форму (файл application\views\addbugform.php)

<?php
if (!empty($errMessage)) {
	echo '<div id="errMessage">'.$errMessage.'</div>';
}
$mes = $this->session->flashdata('message');
if (!empty($mes)) {
	echo '<div id="infoMessage">'.$mes.'</div>';
}
?>
<?php echo form_open('bugtracker/addbug', array('id'=>'fAddBug')); ?>
	<?php echo form_error('title'); ?>
	<p>
	<label for="title">Заголовок</label>
	<input type="text" name="title" id="title" value="<?php echo set_value('title'); ?>" />
	</p>
	<?php echo form_error('uname'); ?>
	<p>
	<label for="uname">Ваше имя</label>
	<input type="text" name="uname" id="uname" value="<?php echo set_value('uname'); ?>" />
	</p>
	<?php echo form_error('category_id'); ?>
	<p>
	<label for="category_id">Категория ошибки</label>
	<select name="category_id" id="category_id" size="1">
	<?php
		foreach ($categories as $category) {
			echo '<option value="'.$category['id'].'"';
			echo set_select('category_id', $category['id']);
			echo '>'.$category['name'].'</option>';
		}
	?>
	</select>
	</p>
	<?php echo form_error('email'); ?>
	<p>
	<label for="email">eMail</label>
	<input type="text" name="email" id="email" value="<?php echo set_value('email'); ?>" />
	</p>
	<?php echo form_error('description'); ?>
	<p>
	<label for="description">Описание ошибки</label>
	<textarea name="description" id="description" cols="30" rows="5"><?php echo set_value('description'); ?></textarea>
	</p>
	<p><input type="submit" name="addbug" id="addbug" value="Отправить" />
	</p>
</form>

В начале представления мы выводим сообщения, если они есть. После этого создаем форму.

Форма в общем-то обычная. Обратить внимание стоит на функции вывода ошибок form_error и автоматического заполнения формы set_value. Обратите внимание на восстановление предыдущего значения Select Box (строка 28). В первом параметре метода set_select нужно указать значение атрибута name Select Box.

Теперь переходим к модели (application\models\ mbug.php).

function addNewBug($bugData) {
	$qAddBug = 'INSERT INTO bugs (title, uname, category_id, description, bug_date, uemail)'
		.' VALUES (?, ?, ?, ?, NOW(), ?)';
	$res = $this->db->query($qAddBug, array(
					$bugData['title'],
					$bugData['uname'],
					$bugData['category_id'],
					$bugData['description'],
					$bugData['uemail']
	));
	if ($res) {
		return $this->db->insert_id();
	}
	return $res;
}

Здесь всё просто. В параметре $bugData передается массив с данными из формы. После этого выполняется вставка этих данных в таблицу bugs.

При выполнении запроса все знаки вопроса заменяются исходными данными. При этом автоматически происходит экранирование спецсимволов, что обеспечивает защиту от SQL Injection.

Добавление комментариев.

Этот метод практически не отличается от предыдущего. Тот же алгоритм, те же библиотеки.

Поэтому приводить код для этих методов я не буду.

Конечно в форме только 3 поля: имя, email и текст комментария. Правил проверки меньше. И, конечно, данные вставляются в таблицу comments, а не bugs.

Но на один момент хочу обратить ваше внимание. На данный момент (отсутствует поддержка JavaScript) можно оставить комментарий только первого уровня. Т.е. нельзя указать, что ваш комментарий является ответом на предыдущий.

Когда будет добавлена поддержка JavaScript, форма отправки комментария будет работать также как и в этом блоге, т.е. перемещаться под выбранный комментарий. При этом будет добавляться скрытое поле, содержащее id комментария на который вы отвечаете.

Сделать ответы на предыдущие комментарии без JavaScript можно, но при этом придется добавлять ещё один select box, содержащий перечень предыдущих комментариев, и пользователь сам должен будет выбрать, на какой комментарий он отвечает. Естественно, это очень неудобно. Поэтому поддержка ответов на предыдущие комментарии будет только при поддержке JavaScript.

До встречи!

P.S. Если возникли вопросы, пишите, попробую ответить 😉

  • Big_Shark

    if (!empty($errMessage)) {
    echo ».$errMessage.»;
    }

    $this->form_validation->set_error_delimiters(», »);
    Зачем 2 вложенность?
    В результате будет:

    err

    За место форича при выводе категорий лучшие за ранние в контролере подготовить массив и выводить его через form_dropdown

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

    $bugData['category_id'] = $this->input->post('category_id');
    Ее также очень легко подделать как и все остальное так что зря мы ничего XSS атаками не проверяем.

    $this->db->insert('bugs, $bugData);

    Неплохая статья рад что в ней описывается более менее как использовать set_flashdata потому что у многих возникает вопрос по его использованию.

  • Big_Shark

    if (!empty($errMessage)) {
    echo ».$errMessage.»;
    }

    $this->form_validation->set_error_delimiters(», »);
    Зачем 2 вложенность?
    В результате будет:

    err

    За место форича при выводе категорий лучшие за ранние в контролере подготовить массив и выводить его через form_dropdown

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

    $bugData['category_id'] = $this->input->post('category_id');
    Ее также очень легко подделать как и все остальное так что зря мы ничего XSS атаками не проверяем.

    $this->db->insert('bugs, $bugData);

    Неплохая статья рад что в ней описывается более менее как использовать set_flashdata потому что у многих возникает вопрос по его использованию.

  • Big_Shark

    Опять XSS фильтр сожрал мой пример(((

  • Big_Shark

    Опять XSS фильтр сожрал мой пример(((

  • В результате будет:

    err

    Этот момент я не понял. XSS фильтр вырезал этот кусок?

    выводить его через form_dropdown

    Согласен, просто забыл об этом хелпере 🙂

    зря мы ничего XSS атаками не проверяем

    Почему не проверяем? Для поля category_id установлено правило 'required|integer'. Т.е. проверка не пройдет если значение поля нельзя преобразовать в целое число. Как при этом провести XSS атаку?

    $this->db->insert('bugs, $bugData);

    Это на тему ActiveRecord? 🙂

    Неплохая статья

    Спасибо 🙂

  • В результате будет:

    err

    Этот момент я не понял. XSS фильтр вырезал этот кусок?

    выводить его через form_dropdown

    Согласен, просто забыл об этом хелпере 🙂

    зря мы ничего XSS атаками не проверяем

    Почему не проверяем? Для поля category_id установлено правило 'required|integer'. Т.е. проверка не пройдет если значение поля нельзя преобразовать в целое число. Как при этом провести XSS атаку?

    $this->db->insert('bugs, $bugData);

    Это на тему ActiveRecord? 🙂

    Неплохая статья

    Спасибо 🙂

  • Big_Shark

    зря мы ничего XSS атаками не проверяем

    Простит незаметил валидацию.

    Это на тему ActiveRecord? 🙂

    Да ) еще 1 преимущество актив рекорд. мы можем добавить 1 из полей в массив и вб и он автоматом заполнит и его.

    Все ошибки упакуются в
    $this->form_validation->set_error_delimiters(», '</ di v '); перед выводом
    а в выводе вы их еще раз упаковываете в echo » . $errMessage . »;

  • Big_Shark

    зря мы ничего XSS атаками не проверяем

    Простит незаметил валидацию.

    Это на тему ActiveRecord? 🙂

    Да ) еще 1 преимущество актив рекорд. мы можем добавить 1 из полей в массив и вб и он автоматом заполнит и его.

    Все ошибки упакуются в
    $this->form_validation->set_error_delimiters(», '</ di v '); перед выводом
    а в выводе вы их еще раз упаковываете в echo » . $errMessage . »;

  • Big_Shark

    Повторяю примеры
    1 строка. упаковка в дивы ошибки валидаторов
    $this->form_validation->set_error_delimiters(' ', ' ');
    2 строка. Упаковка в дивы в отображении.
    echo ' ' . $ e r r M e s s a g e . ' ';

    Таким образов на выходе будет 2 друг в друга вложеных дива и только уже там ошибки

    • Я понял о чем речь.
      Дело в том, что я не удачно скопипастил код. Переменная $errMessage не имеет отношения к проверке формы.

      Я собирался ее использовать для вывода сообщений об ошибках, которые могут возникнуть уже при добавлении записей в базу. Но потом заменил на flashdata('message') и забыл убрать из представления.

      Вложенные дивы в любом случае не появятся, т.к. ошибки заполнения формы выводятся отдельно с помощью form_error (строки 11, 16, 21, 34).

      В общем, строки 2-4 в представлении нужно вообще убрать.

      • Big_Shark

        Точно)
        Я не заметил что она не используется и подумал что она также будет служить для вывода ошибок.

  • Big_Shark

    Повторяю примеры
    1 строка. упаковка в дивы ошибки валидаторов
    $this->form_validation->set_error_delimiters(' ', ' ');
    2 строка. Упаковка в дивы в отображении.
    echo ' ' . $ e r r M e s s a g e . ' ';

    Таким образов на выходе будет 2 друг в друга вложеных дива и только уже там ошибки

    • Я понял о чем речь.
      Дело в том, что я не удачно скопипастил код. Переменная $errMessage не имеет отношения к проверке формы.

      Я собирался ее использовать для вывода сообщений об ошибках, которые могут возникнуть уже при добавлении записей в базу. Но потом заменил на flashdata('message') и забыл убрать из представления.

      Вложенные дивы в любом случае не появятся, т.к. ошибки заполнения формы выводятся отдельно с помощью form_error (строки 11, 16, 21, 34).

      В общем, строки 2-4 в представлении нужно вообще убрать.

      • Big_Shark

        Точно)
        Я не заметил что она не используется и подумал что она также будет служить для вывода ошибок.

  • увидел знакомые диаграммки на умл решил прочитать. )

  • увидел знакомые диаграммки на умл решил прочитать. )

  • Для XSS атак нужно было другое модуль перенаправления написать

  • Для XSS атак нужно было другое модуль перенаправления написать

  • Максим

    Интересная и полезная статья.

  • Максим

    Интересная и полезная статья.

  • Спасибо. Обработка данных действительно интересна. Часть информация будет полезна мне для работы.

  • Спасибо. Обработка данных действительно интересна. Часть информация будет полезна мне для работы.

  • Это уж точно, что знакомые диаграмммы, спасибо, работа стоит внимания!

  • Это уж точно, что знакомые диаграмммы, спасибо, работа стоит внимания!

  • Alibek_89_03_13

    я не могу создать php comments!! я хочу сделать так что бы при отправки комент модератор мог проверить его и после этого оно выявлялось на общий обозрение для всех пользователей

  • Tester_grep

    Пользуюсь этим фреймворком уже несколько лет. Удобная справка и вообще хорошо документирован.
    Кстати, появилось руководство пользователя на русском для версии 2.0.0
    http://codeigniter.su