<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>SimpleCoding.org &#187; CodeIgniter</title>
	<atom:link href="http://www.simplecoding.org/category/code-igniter/feed" rel="self" type="application/rss+xml" />
	<link>http://www.simplecoding.org</link>
	<description>Блог о программировании</description>
	<lastBuildDate>Fri, 27 Jan 2012 18:27:45 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=</generator>
		<item>
		<title>Неожиданное обновление CircleTasks</title>
		<link>http://www.simplecoding.org/circlre-tasks-update.html</link>
		<comments>http://www.simplecoding.org/circlre-tasks-update.html#comments</comments>
		<pubDate>Tue, 13 Jul 2010 18:50:06 +0000</pubDate>
		<dc:creator>Владимир</dc:creator>
				<category><![CDATA[CodeIgniter]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Web разработка]]></category>
		<category><![CDATA[Разное]]></category>

		<guid isPermaLink="false">http://www.simplecoding.org/?p=1098</guid>
		<description><![CDATA[Постоянные читатели этого блога, наверное, помнят, что некоторое время назад (прошло почти 10 месяцев ) я опубликовал несколько постов о разработке TODO скрипта под названием CircleTasks. Там же были выложены исходники и ссылка на демо версию. Тогда пришло очень много пожеланий и советов по развитию системы. Какие-то я реализовал, какие-то – нет. Ключевой идеей была [...]]]></description>
			<content:encoded><![CDATA[<div id="attachment_909" class="wp-caption alignnone" style="width: 238px"><img src="http://www.simplecoding.org/wp-content/uploads/2009/09/circle_tasks_logo.png" alt="" title="circle tasks logo" width="228" height="145" class="size-full wp-image-909" style="float:left" /><p class="wp-caption-text"> </p></div>
<p>Постоянные читатели этого блога, наверное, помнят, что некоторое время назад (прошло почти 10 месяцев <img src='http://www.simplecoding.org/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' />  ) я опубликовал несколько постов о разработке <strong>TODO скрипта</strong> под названием <a href="http://www.simplecoding.org/circle-tasks-2-novaya-versiya-moego-todo-skripta.html">CircleTasks</a>. Там же были выложены исходники и ссылка на демо версию.</p>
<p>Тогда пришло очень много пожеланий и советов по развитию системы. Какие-то я реализовал, какие-то – нет. Ключевой идеей была максимальная простота интерфейса, поэтому вводил новые функции я очень аккуратно.</p>
<p>Но, вынужден признаться, что со временем интерес к этому проекту у меня ослабел. Текущий функционал лично меня устраивал, и мотивации что-то менять не было.</p>
<p>И, совсем недавно, я узнал, что оказывается не я один пользуюсь этой системой <img src='http://www.simplecoding.org/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /><br />
<span id="more-1098"></span><br />
Читатель этого блога <a href="http://www.vldcrowd.com">Radioact</a> предложил не только очень полезные изменения, но и реализовал их.</p>
<p>Я специально хочу отметить, что эти изменения основаны на опыте регулярного использования и экономят время при повседневной работе.</p>
<p>Кроме того, он поделился исходниками и демо версией.</p>
<p><a href="http://www.vldcrowd.com/todo/"><img src="http://www.simplecoding.org/wp-content/themes/three_cols/images/demo_btn_green.png" alt="демонстрационный пример" /></a></p>
<p>Логин: <code>demo@demo.com</code><br />
Пароль: <code>demodemo</code></p>
<p><a href='http://www.simplecoding.org/wp-content/uploads/2010/07/CircleTasks2.zip'><img src="http://www.simplecoding.org/wp-content/themes/three_cols/images/download_btn_blue.png" alt="архив с исходным кодом" /></a></p>
<p>Перечислю <strong>реализованные изменения</strong></p>
<p>1. Добавлен список статусов и при создании новой задачи. В моей версии этот список появлялся только при изменении задачи. Полезно если вам приходится добавлять начатые или не завершенные задачи.</p>
<p>2. Обнуление прошлых значений полей &#034;Создать задачу&#034;, при неоднократном добавлении задач. В оригинальном варианте данные, введенные в форму, сохранялись и отображались при создании новой задачи. Удобно, если нужно добавлять много задач на разные даты. Но не очень удобно если вам нужно добавить несколько задач на следующий месяц.</p>
<p>3. Сортировка по дате по умолчанию выполняется в убывающем порядке &#8211; новые вверху. Вообще, если вы правильно организовываете свою работу, то список невыполненных задач не должен быть большим <img src='http://www.simplecoding.org/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> </p>
<p>4. Повторный клик по статусу задачи закрывает список. Это важный момент, на который я не обращал внимания. Дело в том, что я отлично помню все иконки и статусы (т.к. сам их рисовал). Но новый пользователь сразу все не запомнит и будет открывать список только для того, чтобы посмотреть, что в нём есть.</p>
<p>Кроме того, <a href="http://www.vldcrowd.com">Radioact</a> сделал несколько предложений по <strong>дальнейшему развитию проекта</strong>. Все желающие могут присоединиться к обсуждению.</p>
<p>1. Добавить поддержку групповых операций вроде удаления записей и/или изменения их статусов. Возле каждой записи будет чекбокс.</p>
<p>2. Добавить пагинацию. Я сам стараюсь, чтобы список задач был максимально коротким и удаляю выполненные задачи. Но это мой стиль работы, описания к задачам у меня очень сжатые и через пару месяцев по ним сложно вспомнить, о чем шла речь. Но, возможно, для кого-то история окажется полезной.</p>
<p>3. Доработать оформление списка записей. Например, добавить в шапку треугольники для сортировки.</p>
<p>Все пожелания и замечания пишите в комментариях! Обсудим!</p>
<p><strong>Интересно почитать.</strong></p>
<p><a href="http://inetmarketing.ws">Интернет маркетинг &#8211; покер и видео сайты</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.simplecoding.org/circlre-tasks-update.html/feed</wfw:commentRss>
		<slash:comments>12</slash:comments>
		</item>
		<item>
		<title>XML-RPC, CodeIgniter, LiveJournal и куча проблем :)</title>
		<link>http://www.simplecoding.org/xml-rpc-codeigniter-livejournal-i-kucha-problem.html</link>
		<comments>http://www.simplecoding.org/xml-rpc-codeigniter-livejournal-i-kucha-problem.html#comments</comments>
		<pubDate>Fri, 13 Nov 2009 13:42:47 +0000</pubDate>
		<dc:creator>Владимир</dc:creator>
				<category><![CDATA[CodeIgniter]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Web разработка]]></category>

		<guid isPermaLink="false">http://www.simplecoding.org/?p=941</guid>
		<description><![CDATA[Недавно ко мне обратился читатель с просьбой помочь отправить сообщение в LiveJournal через XML-RPC протокол. При этом использовать нужно было библиотеку фреймворка CodeIgniter. Честно говоря, когда я вижу такие вопросы, то сразу пробую найти готовое решение. Этот случай исключением не был и подходящая инструкция быстро нашлась. XML-RPC и кросспостинг в ЖЖ. Константин Лихачев в ней [...]]]></description>
			<content:encoded><![CDATA[<div id="attachment_942" class="wp-caption alignnone" style="width: 310px"><img src="http://www.simplecoding.org/wp-content/uploads/2009/11/codeigniter-livejournal-xml-rpc.png" alt="codeigniter-livejournal-xml-rpc" title="codeigniter-livejournal-xml-rpc" width="300" height="178" class="size-full wp-image-942" style="float:left" /><p class="wp-caption-text"> </p></div>
<p>Недавно ко мне обратился читатель с просьбой помочь отправить сообщение в <strong>LiveJournal</strong> через <strong>XML-RPC</strong> протокол. При этом использовать нужно было библиотеку фреймворка <a href="http://codeigniter.com/">CodeIgniter</a>.</p>
<p>Честно говоря, когда я вижу такие вопросы, то сразу пробую найти готовое решение. Этот случай исключением не был и подходящая инструкция быстро нашлась. <a href="http://www.likhachev.net/2008/03/01/xml-rpc-crossposting/">XML-RPC и кросспостинг в ЖЖ</a>. Константин Лихачев в ней подробно рассказывает об отправке сообщений, единственное но – используется <a href="http://scripts.incutio.com/xmlrpc/">Incutio XML-RPC Library</a>, а не встроенная библиотека CI.</p>
<p>Раз работает код с использованием Incutio XML-RPC, то сервис 100% рабочий и нужно просто правильно передать параметры в библиотеке CodeIgniter&#039;а.</p>
<p>Думаю: «Почему бы не помочь человеку? Опыт работы с CI у меня есть, с документацией JiveJournal разбираться не нужно, т.к. список всех нужных параметров есть в статье Константина. Минут за 20 сделаю…» <img src='http://www.simplecoding.org/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<p>Мне пора бы привыкнуть, что когда я так думаю, эти 20 минут часто превращаются в 2-3 часа. <img src='http://www.simplecoding.org/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /><br />
<span id="more-941"></span><br />
Проблема была такая. Встроенная библиотека CodeIgniter требует, чтобы все параметры запроса были <strong>заданы в виде массива</strong>. При этом если нужно передавать структуры данных, получается куча вложенных друг в друга массивов.</p>
<p>И пропустить один из них (что я и сделал) или вставить лишний очень легко.</p>
<p>По-идее, именно для таких ситуаций предусмотрен режим отладки. Но в этом режиме библиотека CI выводит <strong>только XML ответ сервера</strong>, а мне нужно было посмотреть на XML строку, которая отправляется в запросе.</p>
<p><em>Примечание</em>. Ответ сервера:</p>
<pre class="brush: text">Can't use string ("Array") as a HASH ref while "strict refs" in use at /home/lj/cgi-bin/Apache/LiveJournal.pm line 1779.</pre>
<p>лично мне ни о чем не говорит (на perl я не программирую). Кстати, тот же <strong>WordPress</strong> в таких случаях возвращает «parse error. not well formed» &#8211; на мой взгляд, более понятный ответ, хоть и не очень информативный.</p>
<p>В документации LiveJournal есть примеры XML запросов, которые нужно отправить для добавления записи, т.е. достаточно просто сравнить их со своими и все проблемы будут видны.</p>
<p>После нескольких неудачных попыток узнать что все-таки отправляется серверу, пришлось лезть в исходники библиотеки.</p>
<p>Чтобы вывести запрос я добавил строку</p>
<pre class="brush: php">echo '&lt;pre&gt;'.htmlspecialchars($op).'&lt;/pre&gt;';</pre>
<p>в методе <code>sendPayload</code> перед</p>
<pre class="brush: php">if ( ! fputs($fp, $op, strlen($op)))</pre>
<p>Не очень элегантное решение, но зато сразу стало ясно, в чем проблема.</p>
<p>Привожу рабочий код отправки сообщения в livejournal.</p>
<pre class="brush: php">class LiveJournal extends Controller {

	private $LJ_url = 'http://www.livejournal.com/interface/xmlrpc';
	private $LJ_login = 'your login';
	private $LJ_pass = 'your pass';

	function LiveJournal()
	{
		parent::Controller();

		//подключаем XML-RPC библиотеку
		$this-&gt;load-&gt;library('xmlrpc');
	}

	function index()
	{
		$this-&gt;xmlrpc-&gt;server($this-&gt;LJ_url);

		//отправляем challange-запрос
		$this-&gt;xmlrpc-&gt;method('LJ.XMLRPC.getchallenge');

		if (!$this-&gt;xmlrpc-&gt;send_request()) {
			echo $this-&gt;xmlrpc-&gt;display_error();
		}
		else {
			//если получен ответ с challenge-строкой, читаем его и отправляем запись
			$challenge_response = $this-&gt;xmlrpc-&gt;display_response();

			//формируем массив с данными для создания записи
			$request = array(
			array(
				array(
					'username'=&gt;array($this-&gt;LJ_login,'string')
					,'auth_method'=&gt;array('challenge','string')
					,'auth_challenge'=&gt;array($challenge_response['challenge'],'string')
					,'auth_response'=&gt;array(md5($challenge_response['challenge'].md5($this-&gt;LJ_pass)),'string')
					,'ver'=&gt;array('1','string')
					,'event'=&gt;array('Заголовок записи','string')
					,'subject'=&gt;array('Текст записи ...','string')
					,'year'=&gt;array(2009,'int')
					,'mon'=&gt;array(11,'int')
					,'day'=&gt;array(12,'int')
					,'hour'=&gt;array(5,'int')
					,'min'=&gt;array(27,'int')
					,'props'=&gt;array(
						array(
							'opt_backdated'=&gt;array(true,'boolean')
							,'taglist'=&gt;array('tag 1, tag 2, tag 3','string')
						)
						,'struct')
					,'security'=&gt;array('public','string')
				)
				,'struct'
			)
			);

			//отправляем запрос серверу
			$this-&gt;xmlrpc-&gt;method('LJ.XMLRPC.postevent');
			$this-&gt;xmlrpc-&gt;request($request);
			if (!$this-&gt;xmlrpc-&gt;send_request()) {
				echo $this-&gt;xmlrpc-&gt;display_error();
			}
			else {
				$postData = $this-&gt;xmlrpc-&gt;display_response();
				//$postData['url'] - содержит адрес созданной записи
			}
		}
	}
}</pre>
<p>Несколько пояснений. Публикация сообщения выполняется в <strong>2</strong> этапа.</p>
<p>1) Нужно получить challenge строку, которая хешируется (строка 36) вместе с вашим паролем и используется вместо него. Это сделано для того, чтобы не передавать пароль в открытом виде.</p>
<p>2) Отправка самого сообщения. Как раз тут и нужно формировать структуру с данными. Как видите, мы везде используем массивы с двумя элементами. Первый элемент содержит передаваемое значение, второй – тип этого значения. Если нужно передать структуру, то первый элемент должен быть массивом.</p>
<p>Выглядит такая конструкция громоздкой и <strong>заполнять ее нужно внимательно</strong>, особенно если вы привыкли к библиотеке вроде Incutio XML-RPC. Кстати, эту библиотеку не сложно подключить и использовать вместо стандартной CodeIgniter’а.</p>
<p>Вообще в последнее время складывается впечатление, что разработчики забросили фреймворк. Хотя, возможно, я не прав, а Ellislab просто все силы тратит на свою CMS, вторую версию ExpressionEngine, которая вроде бы будет работать на основе CodeIgniter.</p>
<p>Очень не хочется, чтобы они забросили CI.</p>
<p><strong>Постовой</strong></p>
<p>Что выгоднее, <a href="http://busins.ru/prihodjawij_buhgalter_v_moskve">услуги приходящего бухгалтера</a> или ведение бухгалтерии у профессионалов? Ответ здесь</p>
]]></content:encoded>
			<wfw:commentRss>http://www.simplecoding.org/xml-rpc-codeigniter-livejournal-i-kucha-problem.html/feed</wfw:commentRss>
		<slash:comments>59</slash:comments>
		</item>
		<item>
		<title>PHP скрипт: ToDo с картинками</title>
		<link>http://www.simplecoding.org/php-skript-todo-s-kartinkami.html</link>
		<comments>http://www.simplecoding.org/php-skript-todo-s-kartinkami.html#comments</comments>
		<pubDate>Thu, 13 Aug 2009 19:01:21 +0000</pubDate>
		<dc:creator>Владимир</dc:creator>
				<category><![CDATA[Ajax]]></category>
		<category><![CDATA[CodeIgniter]]></category>
		<category><![CDATA[CSS]]></category>
		<category><![CDATA[htaccess]]></category>
		<category><![CDATA[HTML]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Web разработка]]></category>

		<guid isPermaLink="false">http://www.simplecoding.org/?p=885</guid>
		<description><![CDATA[Уделяете ли вы внимание организации своей работы? Много ли у вас &#034;мелких&#034; дел, о которых вы регулярно забываете? Вроде бы простые вопросы, но для многих людей (и я не исключение) организация работы – это актуальная проблема. Хуже всего, когда нужно сделать много «мелких» дел, которые не занимают много времени, но обязательно должны быть выполнены вовремя&#8230; [...]]]></description>
			<content:encoded><![CDATA[<div id="attachment_886" class="wp-caption alignnone" style="width: 320px"><img src="http://www.simplecoding.org/wp-content/uploads/2009/08/simple_tasks.png" alt="simple tasks" title="simple tasks" width="310" height="129" class="size-full wp-image-886" style="float:left" /><p class="wp-caption-text"> </p></div>
<p>Уделяете ли вы внимание организации своей работы?<br />
Много ли у вас &#034;мелких&#034; дел, о которых вы регулярно забываете?</p>
<p>Вроде бы простые вопросы, но для многих людей (и я не исключение) <strong>организация работы</strong> – это актуальная проблема.</p>
<p>Хуже всего, когда нужно сделать много «мелких» дел, которые не занимают много времени, но обязательно должны быть выполнены вовремя&#8230; и держать в голове их все просто невозможно.</p>
<p>Естественно, разработчики реагируют на потребности рынка, и на сегодняшний день создано множество программ-органайзеров, различных напоминалок и т.п.<br />
Примеры создания ToDo списков часто приводят в учебниках по программированию.</p>
<p>Я решил не оставаться в стороне и сделал <strong>собственный вариант такого ToDo списка, естественно, с некоторыми дополнительными возможностями</strong>.</p>
<p>Кстати, скрипт называется <strong>SimpleTasks</strong>.</p>
<p>Главная особенность – <strong>возможность указывать состояние выполнения задач</strong>. При этом используется специальная система обозначений.<br />
<span id="more-885"></span></p>
<div id="attachment_887" class="wp-caption alignnone" style="width: 490px"><img src="http://www.simplecoding.org/wp-content/uploads/2009/08/symbols_for_paper_notebook_with_description.png" alt="symbols for paper notebook with description" title="symbols for paper notebook with description" width="480" height="192" class="size-full wp-image-887" /><p class="wp-caption-text"> </p></div>
<p>Систему обозначений придумал не я. <del datetime="2009-08-14T13:47:28+00:00">Но, к сожалению, не могу найти первоисточник</del> (UPD. Огромное спасибо <a href="http://i-smarty.com">Smarty</a> за ссылку на <a href="http://font.is/?p=790">оригинал</a>). Предназначена она для использования с бумажным блокнотом и довольно удобная. Во всяком случае, читать такой список задач становится намного легче. Взгляд сразу отбрасывает выполненные задачи.</p>
<p>Но <strong>возможности web приложений намного шире</strong>, чем у обычного ежедневника. Например, можно сделать систему фильтров и легко работать с большими списками задач.</p>
<p>В общем, я написал небольшое web приложение, использующее эту систему обозначений.</p>
<p>Если есть желание, можете поиграться с ним (логин admin@todo.loc, пароль password).</p>
<p><a href="http://demosites.simplecoding.org/simpletasks/"><img src="http://www.simplecoding.org/wp-content/themes/three_cols/images/demo_btn_green.png" alt="демонстрационный пример" /></a></p>
<p>Или скачать архив и установить на своем сервере (инструкция в архиве).</p>
<p><a href='http://www.simplecoding.org/wp-content/uploads/2009/08/simpletasks.zip'><img src="http://www.simplecoding.org/wp-content/themes/three_cols/images/download_btn_blue.png" alt="архив с исходным кодом" /></a></p>
<p><strong>Принцип работы.</strong></p>
<p><strong>Создание новой записи.</strong><br />
Кликаем по ссылке &#034;Новая запись&#034; (справа в верхней части страницы) и в открывшемся окне вводим данные записи.<br />
При клике по полю &#034;Дата&#034; будет открыт календарь. Т.е. ввести дату в неправильном формате довольно проблематично.</p>
<p><strong>Редактирование.</strong><br />
Клик или двойной клик на соответствующем поле позволяет изменить его. Открываетеся либо диалог, либо inline редактор.</p>
<p><strong>Удаление.</strong><br />
Кликните по картинке с изображением крестика в соответствующей строке и подтвердите удаление.</p>
<p><strong>Фильтры.</strong><br />
Позволяют выбрать задачи с определенным состоянием в указанном диапазоне дат.</p>
<p>Ссылки в верхнем меню дублируют наиболее распространенные (на мой взгляд) фильтры.</p>
<p><strong>Состояние работ.</strong><br />
На данный момент это только бета версия.<br />
Еще не доделана локализация. Доступен только один вариант &#8211; русский.<br />
Возможно, отсутствуют некоторые сообщения об ошибках.</p>
<p>Скорее всего, некоторые функции будут дорабатываться.</p>
<p><strong>И я очень хочу услышать ваше мнение!<br />
</strong><br />
Прежде всего, меня интересуют такие вопросы.</p>
<p>1) Есть ли смысл делать подобное приложение многопользовательским? Т.е. технически это совсем не сложно, вопрос в том будет ли кто-то в рамках небольшой организации использовать такое приложение для хранения заметок сотрудников?</p>
<p>2) Какие еще функии вы бы добавили?</p>
<p>3) Может кто-то видел хороший тьюториал о локализации JS приложений? (Сейчас я ориентируюсь на то, как сделана локализация в jQuery UI).</p>
<p><strong>P.S.</strong> На этих выходных я уезжаю догуливать остатки отпуска, поэтому прошу прощения, если не сразу отвечу на ваши комментарии. Я обязательно их прочитаю&#8230; даже спаммерские <img src='http://www.simplecoding.org/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>http://www.simplecoding.org/php-skript-todo-s-kartinkami.html/feed</wfw:commentRss>
		<slash:comments>135</slash:comments>
		</item>
		<item>
		<title>Bug Tracker: ответы на комментарии (часть десятая)</title>
		<link>http://www.simplecoding.org/bug-tracker-otvety-na-kommentarii-chast-desyataya.html</link>
		<comments>http://www.simplecoding.org/bug-tracker-otvety-na-kommentarii-chast-desyataya.html#comments</comments>
		<pubDate>Sat, 28 Mar 2009 11:25:19 +0000</pubDate>
		<dc:creator>Владимир</dc:creator>
				<category><![CDATA[CodeIgniter]]></category>
		<category><![CDATA[HTML]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Web разработка]]></category>

		<guid isPermaLink="false">http://www.simplecoding.org/?p=797</guid>
		<description><![CDATA[В предыдущих частях (1, 2, 3, 4, 5, 6, 7, 8, 9) мы создали практически работоспособную систему отслеживания ошибок. «Практически» в данном случае означает, что на данный момент у пользователя отсутствует возможность отвечать на комментарии. Т.е. можно оставить только комментарий 1-ого уровня (комментарий к багу). Сегодня мы исправим этот недостаток. Напомню, что на стороне сервера [...]]]></description>
			<content:encoded><![CDATA[<div id="attachment_798" class="wp-caption alignnone" style="width: 310px"><img src="http://www.simplecoding.org/wp-content/uploads/2009/03/bug_tracker_logo_part10.png" alt="bug_tracker_logo_part10" title="bug_tracker_logo_part10" width="300" height="154" class="size-full wp-image-798" style="float:left" /><p class="wp-caption-text"> </p></div>
<p>В предыдущих частях (<a href="http://www.simplecoding.org/sozdaem-sobstvennuyu-sistemu-otslezhivaniya-oshibok-na-php.html">1</a>, <a href="http://www.simplecoding.org/bug-tracker-ustanovka-frejmvorka-i-sozdanie-bazy-dannyx-chast-vtoraya.html">2</a>, <a href="http://www.simplecoding.org/bug-tracker-izmeneniya-v-proekte-chast-tretya.html">3</a>, <a href="http://www.simplecoding.org/bug-tracker-preobrazuem-tablicu-v-html-spisok-chast-4.html">4</a>, <a href="http://www.simplecoding.org/bug-tracker-model-i-stranicy-prilozheniya-chast-pyataya.html">5</a>, <a href="http://www.simplecoding.org/bug-tracker-sozdanie-stranic-chast-shestaya.html">6</a>, <a href="http://www.simplecoding.org/bug-tracker-dobavlenie-zapisej-i-kommentariev-chast-sedmaya.html">7</a>, <a href="http://www.simplecoding.org/bugtracker-avtorizaciya-i-autentifikaciya-chast-vosmaya.html">8</a>, <a href="http://www.simplecoding.org/bugtracker-lokalizaciya-chast-devyataya.html">9</a>) мы создали практически работоспособную <strong>систему отслеживания ошибок</strong>.</p>
<p>«Практически» в данном случае означает, что на данный момент у пользователя отсутствует возможность отвечать на комментарии. Т.е. можно оставить только комментарий 1-ого уровня (комментарий к багу).</p>
<p><strong>Сегодня мы исправим этот недостаток.</strong></p>
<p>Напомню, что на стороне сервера поддержка вложенных комментариев уже реализована. В базе данных (таблица <code>comments</code>) есть поле <code>parent_id</code> в котором хранится <code>id</code> родительского комментария.</p>
<p>Кроме того, метод <code>addComment</code> (модели <code>mcomments</code>) принимает массив с данными комментария, одним из полей которого является <code>parent_id</code>.</p>
<p>И, наконец, метод <code>addcomment</code> контроллера читает этот параметр из массива <code>$_POST</code> и передает его модели.</p>
<p>Таким образом, <strong>нам остается обеспечить возможность посетителю указывать на какой комментарий он хочет ответить</strong>.<br />
<span id="more-797"></span><br />
В принципе, реализовать эту возможность можно без <strong>JavaScript</strong>. Но при этом пользоваться ей будет очень <strong>не</strong> удобно. Нам придется сформировать выпадающий список с перечнем <code>id</code> всех комментариев к данному багу, а посетитель вручную будет выбирать на какой он хочет ответить. Не думаю, что кому-то понравиться работать с такой формой.</p>
<p>Ситуация совершенно меняется если использовать <strong>JavaScript</strong>. Под каждым комментарием мы создадим ссылку «Ответить». При клике по ней <strong>форма будет перемещаться под выбранный комментарий</strong>. <code>Id</code> этого комментария мы сохраним в скрытом поле формы.</p>
<p>Таким образом, детали механизма работы со вложенными комментариями будут совершенно незаметны пользователю. Кстати, комментарии в этом блоге работают именно таким образом.</p>
<p><strong>Переходим к реализации</strong>.</p>
<p>Прежде всего, рассмотрим разметку формы.</p>
<pre class="brush: html">&lt;form id=&quot;fAddComment&quot; method=&quot;post&quot; action=&quot;http://www.bugtracker.l/bugtracker/addcomment&quot;&gt;
	...
	&lt;div&gt;
		&lt;input type=&quot;hidden&quot; value=&quot;&quot; id=&quot;parent_id&quot; name=&quot;parent_id&quot;/&gt;
	&lt;/div&gt;
	&lt;p&gt;
		&lt;input type=&quot;submit&quot; value=&quot;Отправить&quot; class=&quot;addcomment&quot; id=&quot;addcomment&quot; name=&quot;addcomment&quot;/&gt;
	&lt;/p&gt;
&lt;/form&gt;</pre>
<p>Чтобы сделать её немного понятнее я опустил поля, которые не имеют отношения ко вложенным комментариями. В данном случае интерес представляет только одно поле &#8211; <code>parent_id</code>. В нём хранится <code>id</code> комментария, на который отвечает посетитель. Пустое значение в этом поле соответствует комментарию к багу (первый уровень).</p>
<p>Теперь подключим два JavaScript файла. Первый – библиотека <code>jQuery</code>, второй – файл с нашими функциями.</p>
<pre class="brush: php">&lt;script type=&quot;text/javascript&quot; src=&quot;&lt;?php echo base_url(); ?&gt;js/jquery-1.3.2.min.js&quot;&gt;&lt;/script&gt;
&lt;script type=&quot;text/javascript&quot; src=&quot;&lt;?php echo base_url(); ?&gt;js/main.js&quot;&gt;&lt;/script&gt;</pre>
<p><em>Примечание</em>. Я разместил этот код в конце страницы (в представлении <code>footer.php</code>).</p>
<p><strong>Добавляем ссылку «Ответить»</strong> внизу каждого комментария. Для этого открываем шаблон комментариев (<code>\application\views\comment_tpl.php</code>)</p>
<pre class="brush: php">&lt;div class=&quot;comment depth-{depth}&quot; id=&quot;comment-{id}&quot;&gt;
	&lt;p class=&quot;commentInfo&quot;&gt;
		&lt;span class=&quot;commentAuthor&quot;&gt;Автор: {uname}.&lt;/span&gt;
		&lt;span class=&quot;commentDate&quot;&gt;Дата: {comment_date}.&lt;/span&gt;
	&lt;/p&gt;
	&lt;p class=&quot;commentDescription&quot;&gt;{description}&lt;/p&gt;
	&lt;p class=&quot;replyComment&quot;&gt;&lt;a href=&quot;#&quot;&gt;Ответить&lt;/a&gt;&lt;/p&gt;
&lt;?php
if ($this-&gt;redux_auth-&gt;logged_in()) {
	echo '&lt;p class=&quot;deleteComment&quot;&gt;'.anchor('bugtracker/deletecomment/{id}', 'Удалить').'&lt;/p&gt;';
}
?&gt;
&lt;/div&gt;</pre>
<p>Ссылка создается в строке 7. Тут может возникнуть вопрос: «Почему в параметрах ссылки мы не указываем <code>id</code> комментария?». Дело в том, что с помощью <a href="http://jquery.com/">jQuery</a> мы можем легко получить родительский <code>div</code> (строка 1) и прочесть его атрибут <code>id</code>, в котором записан <code>id</code> комментария.</p>
<p>Теперь рассмотрим <strong>функцию, которая перемещает форму</strong> (файл <code>main.js</code>).</p>
<pre class="brush: javascript">$(function() {
	//перемещаем форму
	$(&quot;.replyComment a&quot;).click(function() {
		var comment = $(this).parent().parent();
		var commentId = comment.attr(&quot;id&quot;);
		var id = Array();
		//в id[1] будет сохранен id комментария на который нужно ответить
		id = commentId.split('-');

		//перемещаем форму
		$(&quot;#fAddComment&quot;).appendTo(comment);

		//устанавливаем значение в поле parent_id
		$(&quot;#parent_id&quot;).val(id[1]);

		//добавляем ссылку &quot;Отменить комментарий&quot;, клик по ней возвращает форму вниз страницы
		cancelComment = $(&quot;&lt;p class=\&quot;cancelComment\&quot;&gt;&lt;a href=\&quot;#\&quot;&gt;Отменить комментарий&lt;/a&gt;&lt;/p&gt;&quot;);
		cancelComment.prependTo($(&quot;#fAddComment&quot;));
		var cancelLink = cancelComment.children(&quot;a&quot;).get(0);
		$(cancelLink).click(function() {
			$(&quot;#parent_id&quot;).val(&quot;&quot;);
			$(this).parent().remove();
			$(&quot;#fAddComment&quot;).insertBefore(&quot;#footer&quot;);

			return false;
		});

		//предотвращает &quot;скачки&quot; страницы
		return false;
	});

	//подтверждение удаления бага
	$(&quot;.deleteBug a&quot;).click(function() {
		return confirm(&quot;Точно удалить?&quot;);
	});

	//подтверждение удаления комментария
	$(&quot;.deleteComment a&quot;).click(function() {
		return confirm(&quot;Будет удалена ветка комментариев начиная с данного. Удалить?&quot;);
	});
});</pre>
<p>Рассмотрим как она работает.</p>
<p>Прежде всего, мы находим все ссылки, которые находятся внутри элементов с классом «<code>replyComment</code>» (т.е. наши ссылки «Ответить»), и назначаем им обработчик события <code>onclick</code> (строка 3).</p>
<p>При клике по ссылке начинает выполняться функция (строки 3-30). <strong>Алгоритм</strong> тут следующий.</p>
<p>1) Получаем родительский <code>div</code> и читаем его атрибут <code>id</code> (строки 4, 5).</p>
<p>2) Вырезаем из него номер комментария (с помощью функции <code>split</code>, строка 8).</p>
<p>3) Перемещаем форму. Для этого мы используем функцию <code>appendTo</code> из библиотеки jQuery. Элемент, к которому нужно добавить форму мы получили на 1-ом шаге.</p>
<p>4) Записываем значение в поле <code>parent_id</code> (строка 14).</p>
<p>5) Создаём ссылку «Отменить комментарий» и размещаем её в начале формы. Клик по ней вернет форму в исходное положение.</p>
<p>Без этой ссылки вернуть форму в исходное положение можно будет, только обновив страницу, а сделать это догадаются не все <img src='http://www.simplecoding.org/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' />  .</p>
<p>6) Находим ссыку «Отменить комментарий» и назначем ей обработчик события onclick (строки 19-26).</p>
<p>7) В этом обработчике мы сбрасываем значение скрытого поля parent_id, удаляем ссылку «Отменить комментарий» и возвращаем форму в исходное положение.</p>
<p>Как видите, принцип работы достаточно простой. А благодаря jQuery вся функция занимает меньше 20 строк (если убрать комментарии и пустые строки).</p>
<p>В завершение мы <strong>выполняем ещё два действия</strong>.</p>
<p>Назначаем обработчик события <code>onclick</code> для всех ссылок «Удалить» (строки 33-40). Теперь при попытке удаления бага или комментария (в администраторском режиме) будет появляться диалог с просьбой подтвердить действие.</p>
<p>На этом я закончу эту часть.</p>
<p><strong>Демо версия баг трекера</strong>.</p>
<p>Если у вас есть желание поэкспериментировать я выложил <a href="http://www.simplecoding.org/bugtracker/">демо версию баг трекера</a>. Логин: <code>vvv@ddd.sss</code>, пароль: <code>111</code>.<br />
Можете не задумываясь вставлять или удалять любые данные, все равно у меня есть бекап базы <img src='http://www.simplecoding.org/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> </p>
<p><strong>Скачать</strong></p>
<p>Также можно скачать <a href='http://www.simplecoding.org/wp-content/uploads/2009/03/bugtracker.zip'>архив с исходниками</a>.</p>
<p>И, конечно, вы можете поделиться своими впечатлениями в комментариях <img src='http://www.simplecoding.org/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>http://www.simplecoding.org/bug-tracker-otvety-na-kommentarii-chast-desyataya.html/feed</wfw:commentRss>
		<slash:comments>74</slash:comments>
		</item>
		<item>
		<title>BugTracker: локализация (часть девятая)</title>
		<link>http://www.simplecoding.org/bugtracker-lokalizaciya-chast-devyataya.html</link>
		<comments>http://www.simplecoding.org/bugtracker-lokalizaciya-chast-devyataya.html#comments</comments>
		<pubDate>Tue, 24 Mar 2009 07:36:44 +0000</pubDate>
		<dc:creator>Владимир</dc:creator>
				<category><![CDATA[CodeIgniter]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Web разработка]]></category>

		<guid isPermaLink="false">http://www.simplecoding.org/?p=795</guid>
		<description><![CDATA[Сегодня мы продолжаем разработку собственной системы отслеживания ошибок. В предыдущих частях мы рассмотрели добавление записей о багах и комментариев к ним. Но при этом не решенным остался вопрос локализации. По-умолчанию CodeIgniter выводит сообщения об ошибках на английском языке. Прежде всего, это касается описаний ошибок, которые возникают при некорректном заполнении форм. В этой части я расскажу, [...]]]></description>
			<content:encoded><![CDATA[<div id="attachment_796" class="wp-caption alignnone" style="width: 213px"><img src="http://www.simplecoding.org/wp-content/uploads/2009/03/bug_tracker_logo_part9.jpg" alt="bug_tracker_logo_part9" title="bug_tracker_logo_part9" width="203" height="152" class="size-full wp-image-796" style="float:left; padding:0 10px 10px 0" /><p class="wp-caption-text"> </p></div>
<p>Сегодня мы продолжаем разработку собственной системы отслеживания ошибок. В предыдущих частях мы рассмотрели <a href="http://www.simplecoding.org/bug-tracker-dobavlenie-zapisej-i-kommentariev-chast-sedmaya.html">добавление записей о багах и комментариев к ним</a>.</p>
<p>Но при этом не решенным остался вопрос <strong>локализации</strong>.</p>
<p>По-умолчанию <a href="http://codeigniter.com/">CodeIgniter</a> выводит сообщения об ошибках на английском языке. Прежде всего, это касается описаний ошибок, которые возникают при некорректном заполнении форм.</p>
<p>В этой части я расскажу, как <strong>перевести эти сообщения на русский язык</strong>.</p>
<p>Самое интересное, что часть работы мы уже сделали <strong>при создании правил для проверки полей форм</strong>.</p>
<p>Например, правила для проверки <strong>email</strong> выглядят следующим образом.<br />
<span id="more-795"></span></p>
<pre class="brush: php">$this-&gt;form_validation-&gt;set_rules('email', 'lang:email', 'required|valid_email');</pre>
<p>Здесь мы вызываем метод <code>set_rules</code> из библиотеки <code>form_validation</code>. В первом параметре указываем имя поля в форме (атрибут <code>name</code>), во втором – его описание, в третьем – правила.</p>
<p>Сейчас нас интересует <strong>второй параметр</strong>. Нам нужно, чтобы <strong>CodeIgniter</strong> подставил вместо него правильное описание поля.</p>
<p>Для этого нужно выполнить 4 шага.</p>
<p>1) Открываем файл <code>\application\config\config.php</code> и устанавливаем язык по-умолчанию.</p>
<pre class="brush: php">$config['language']	= "russian";</pre>
<p>Теперь CodeIgniter будет прежде всего искать файлы с описаниями в папке <code>\application\language\russian</code>.</p>
<p>2) Создаем папку <code>\application\language\russian</code> и в ней файл <code>form_fields_lang.php</code>. Обратите внимание: окончание <code>_lang</code> обязательно.</p>
<p>3) В этом файле создаем массив с описаниями полей:</p>
<pre class="brush: php">&lt;?php
$lang['title'] = '"Заголовок"';
$lang['uname'] = '"Ваше имя"';
$lang['category_id'] = '"Категория ошибки"';
$lang['description'] = '"Описание ошибки"';
$lang['email'] = '"eMail"';
$lang['password'] = '"Пароль"';
?&gt;</pre>
<p>Ключи элементов этого массива должны совпадать с именами, которые указаны после <code>lang:</code> (во втором параметре метода <code>set_rules</code>).</p>
<p>4) Загружаем файл с описаниями полей. Для этого в конструктор контроллера добавляем строку.</p>
<pre class="brush: php">$this-&gt;load-&gt;language('form_fields');</pre>
<p><em>Обратите внимание</em>. Окончание <code>_lang.php</code> не указываем.</p>
<p>После этих манипуляций описания полей будут правильно отображаться. Но сами ошибки будут на английском.</p>
<p>Для их руссификации нужно скачать <a href="http://rmcreative.ru/blog/post/obnovljon-perevod-codeigniter-1.7.0">файлы с переводами</a>.</p>
<p>Архив распаковываем в папку <code>\application\language\russian</code>.</p>
<p>Всё. На этом работа заканчивается. Названия этих файлов совпадают с названиями их английских версий, поэтому <strong>CodeIgniter загрузит их автоматически</strong>.</p>
<p><strong>Заключение</strong>.</p>
<p>В данном случае мы просто русифицировали баг трекер. Ничего сложного, но проблема в том, что выполнить обратную операцию, т.е. перевести приложение на английский, <strong>намного</strong> сложнее.</p>
<p>Для этого нужно найти и исправить весь русский текст в исходниках баг трекера. А их довольно много. Это и файлы шаблонов, и сообщения в контроллере, и текст в представлениях.</p>
<p>Я не ставил задачи сделать мультиязычное приложение, но если этот пункт для вас важен, в CodeIgniter входит <strong>Language Helper</strong> для этих целей. Использовать его не сложно, самый трудоёмкий этап – создание файлов с переводами. И нужно внимательно следить, чтобы в исходниках проекта напрямую не был жестко прописан текст.</p>
<p>До встречи!</p>
<p><strong>P.S.</strong> Думаю, к следующему выпуску будет готова демонстрационная версия баг трекера. И безусловно выложу архив файлами проекта.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.simplecoding.org/bugtracker-lokalizaciya-chast-devyataya.html/feed</wfw:commentRss>
		<slash:comments>14</slash:comments>
		</item>
		<item>
		<title>BugTracker: авторизация и аутентификация (часть восьмая)</title>
		<link>http://www.simplecoding.org/bugtracker-avtorizaciya-i-autentifikaciya-chast-vosmaya.html</link>
		<comments>http://www.simplecoding.org/bugtracker-avtorizaciya-i-autentifikaciya-chast-vosmaya.html#comments</comments>
		<pubDate>Sun, 22 Mar 2009 12:46:38 +0000</pubDate>
		<dc:creator>Владимир</dc:creator>
				<category><![CDATA[CodeIgniter]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Web разработка]]></category>

		<guid isPermaLink="false">http://www.simplecoding.org/?p=793</guid>
		<description><![CDATA[Приветствую всех читателей! В этой части мы рассмотрим создание не сложной системы авторизации пользователей нашего баг трекера. Для начала сформулируем задачу. Как вы, наверное, помните, оставить сообщение о найденном баге или комментарий к нему может кто угодно. Но удалять записи и комментарии могут только администраторы. На практике это будет выглядеть следующим образом. Администратор при входе [...]]]></description>
			<content:encoded><![CDATA[<div id="attachment_794" class="wp-caption alignnone" style="width: 273px"><img src="http://www.simplecoding.org/wp-content/uploads/2009/03/bug_tracker_logo_part8.png" alt="bug_tracker_logo_part8" title="bug_tracker_logo_part8" width="263" height="158" class="size-full wp-image-794" style="float:left" /><p class="wp-caption-text"> </p></div>
<p><strong>Приветствую всех читателей!</strong></p>
<p>В этой части мы рассмотрим создание не сложной <strong>системы авторизации пользователей</strong> нашего баг трекера.</p>
<p>Для начала <strong>сформулируем задачу</strong>.</p>
<p>Как вы, наверное, помните, оставить сообщение о найденном баге или комментарий к нему может кто угодно. Но удалять записи и комментарии могут только администраторы.</p>
<p>На практике это будет выглядеть следующим образом. Администратор при входе вводит имя и пароль. Баг трекер их проверяет и, если они совпадают с записанными в базе данных, добавляет к каждому багу и комментарию ссылку «Удалить».</p>
<p>Кроме того, при выполнении каждой операции удаления необходимо проверить является ли текущий пользователь администратором.</p>
<p>Для справки. Эти проверки называются <strong>аутентификация</strong> и <strong>авторизация</strong>.<br />
<span id="more-793"></span></p>
<blockquote><p><strong>Авторизация</strong> (англ. authorization) — процесс предоставления определенному лицу прав на выполнение некоторых действий.</p>
<p><strong>Аутентификация</strong> (англ. Authentication) — процедура проверки соответствия некоего лица и его учетной записи в компьютерной системе.</p></blockquote>
<p>(цитаты из <a href="http://ru.wikipedia.org/">википедии</a>)</p>
<p>Таким образом, при входе в систему нам нужно аутентифицировать администратора, а при выполнении им операции – авторизовать.</p>
<p>Задача, в общем-то, стандартная и для её решения написано множество библиотек разной степени сложности.</p>
<p>Прежде чем переходить к выбору библиотеки, <strong>перечислим функции</strong>, которые нужны для разделения прав в нашем баг трекере.</p>
<p>1) Создание учетной записи в базе данных.</p>
<p>2) Проверка имени и пароля при входе в систему (аутентификация).</p>
<p>3) Проверка прав пользователя на выполнение операции (авторизация).</p>
<p>Требования минимальные и не сложно было бы реализовать такую систему самостоятельно. Но зачем «изобретать велосипед» если есть куча готовых решений?</p>
<p>Для этого проекта я решил использовать библиотеку под названием <a href="http://code.google.com/p/reduxauth/">Redux</a>. Каких-то особых причин, по которым я на ней остановился, нет. Просто видел несколько хороших отзывов, и захотелось поэкспериментировать.</p>
<p>На данный момент есть две версии библиотеки: 1.4а (стабильная) и 2 (бета). Вторая по своим возможностям мне понравилась больше, поэтому и использовал я именно её. Но бета есть бета, и сразу обнаружилось несколько недостатков.</p>
<p>Самое главное, в архиве 2-ой версии нет документации, есть только демонстрационный пример. Полноценная справка написана для версии 1.4, причем очень хорошая, сделана в том же стиле, что и тьюториал <strong>CodeIgniter</strong>.</p>
<p>Используя пример и справку из версии 1.4, разобраться не сложно.</p>
<p>Теперь рассмотрим, что нужно сделать для <strong>подключения этой библиотеки к нашему приложению</strong>.</p>
<p>1) Скачать и распаковать <a href="http://code.google.com/p/reduxauth/">архив</a>.</p>
<p>2) Скопировать 3 файла:<br />
<code>\application\config\redux_auth.php<br />
\application\models\redux_auth_model.php<br />
\application\libraries\redux_auth.php</code></p>
<p>3) Импортировать файл <code>database.sql</code> в базу данных. При этом будут созданы несколько таблиц.</p>
<p>4) Настроить библиотеку. Для этого, открываем файл \application\config\redux_auth.php и изменяем соответствующие значения. Здесь я только отключил активацию по email.</p>
<pre class="brush: php">$config['email_activation'] = false;</pre>
<p>5) Создаем группу, которая используется по-умолчанию. В файле <code>\application\config\redux_auth.php</code> есть параметр</p>
<pre class="brush: php">$config['default_group'] = 'member';</pre>
<p>Чтобы библиотека нормально заработала нужно создать в таблице groups запись с название этой группы. Т.е. <code>name='member'</code>, <code>description='member group description'</code>.</p>
<p>6) В таблице <code>sessions</code> изменяем сравнение для поля <code>user_data</code> на <code>utf8_general_ci</code>, иначе будут проблемы с кириллицей.</p>
<p>7) Открываем файл <code>\application\config\config.php</code> и находим раздел с настройками сессий. Я изменил</p>
<pre class="brush: php">$config['sess_encrypt_cookie']	= TRUE;
$config['sess_use_database']	= TRUE;
$config['sess_table_name']		= 'sessions';
$config['sess_match_ip']		= TRUE;
$config['sess_match_useragent']	= TRUE;</pre>
<p>8 ) Добавляем библиотеку в автозагрузку (файл <code>\application\config\autoload.php</code>).</p>
<pre class="brush: php">$autoload['libraries'] = array('database', 'session', 'redux_auth');
$autoload['model'] = array('redux_auth_model');</pre>
<p>Теперь нужно <strong>создать учетную запись администратора</strong>.</p>
<p>Т.к. в базе данных храниться не сам пароль, а его хеш, то удобнее всего добавить запись средствами библиотеки, а не вручную.</p>
<p>Для этого, добавим в контроллер (<code>bugtracker</code>) метод <code>register</code>.</p>
<pre class="brush: php">function register() {
	$this-&gt;redux_auth-&gt;register('admin', 'password', 'admin@bugtracker.local');
}</pre>
<p>Заходим один раз на страницу <code>bugtracker.local/bugtracker/register</code> и в базе данных появляется новая запись.</p>
<p>После этого метод можно удалить.</p>
<p><em>Обратите внимание!</em> Для аутентификации <strong>Redux</strong> использует не имя (admin), а email.</p>
<p><strong>Создаем страницу с формой входа.</strong></p>
<p>Для этого добавляем в контроллер метод <code>login</code>.</p>
<pre class="brush: php">function login() {
	if ($this-&gt;redux_auth-&gt;logged_in()) {
		redirect('bugtracker/page');
	}
	$pageData['title'] = 'Login';
	$this-&gt;load-&gt;view('header', $pageData);
	$this-&gt;load-&gt;view('login');
	$this-&gt;load-&gt;view('footer');
}</pre>
<p>Этот метод просто создает страницу с формой. Сама форма находится в представлении <code>\views\login.php</code>.</p>
<p>Перед формированием страницы мы с помощью метода <code>logged_in()</code> проверяем, выполнил ли вход данный пользователь (строки 2-4). И если да, то отправляем его на главную страницу багтрекера.</p>
<p>Теперь рассмотрим <strong>представление</strong> (<code>\views\login.php</code>)</p>
<pre class="brush: php">&lt;?php
$mes = $this-&gt;session-&gt;flashdata('message');
if (!empty($mes)) {
	echo '&lt;div id=&quot;infoMessage&quot;&gt;'.$mes.'&lt;/div&gt;';
}
?&gt;
&lt;?php echo form_open('bugtracker/checklogin', array('id'=&gt;'fLogin')); ?&gt;
	&lt;p&gt;
	&lt;label for=&quot;email&quot;&gt;eMail&lt;/label&gt;
	&lt;input type=&quot;text&quot; name=&quot;email&quot; id=&quot;email&quot; value=&quot;&lt;?php echo set_value('email'); ?&gt;&quot; /&gt;
	&lt;/p&gt;
	&lt;?php echo form_error('eMail'); ?&gt;
	&lt;p class=&quot;even&quot;&gt;
	&lt;label for=&quot;password&quot;&gt;Пароль&lt;/label&gt;
	&lt;input type=&quot;password&quot; name=&quot;password&quot; id=&quot;password&quot; value=&quot;&quot; /&gt;
	&lt;/p&gt;
	&lt;?php echo form_error('password'); ?&gt;
	&lt;p&gt;&lt;input type=&quot;submit&quot; name=&quot;bLogin&quot; id=&quot;bLogin&quot; class=&quot;bLogin&quot; value=&quot;Войти&quot; /&gt;
	&lt;/p&gt;
&lt;/form&gt;</pre>
<p>Тут все просто. Мы создаем обычную форму с двумя полями: «eMail» и «Пароль». После нажатия на кнопку «Войти» данные будут отправлены методу <code>checklogin</code> контроллера (строка 7).</p>
<p>Этот метод выполняет аутентификацию пользователя. Проще говоря проверяет соответствие email&#039;а паролю.</p>
<pre class="brush: php">function checklogin() {
	$this-&gt;load-&gt;library('form_validation');
	$this-&gt;form_validation-&gt;set_rules('email', 'lang:email', 'required|valid_email');
	$this-&gt;form_validation-&gt;set_rules('password', 'lang:password', 'required');
	$this-&gt;form_validation-&gt;set_error_delimiters('&lt;div class=&quot;fErrMessage&quot;&gt;', '&lt;/div&gt;');

	if ($this-&gt;form_validation-&gt;run() == false)
	{
		$pageData['title'] = 'Login error';
		$this-&gt;load-&gt;view('header', $pageData);
		$this-&gt;load-&gt;view('login');
		$this-&gt;load-&gt;view('footer');
	}
	else
	{
		$login    = $this-&gt;input-&gt;post('email');
		$password = $this-&gt;input-&gt;post('password');

		if ($this-&gt;redux_auth-&gt;login($login, $password)) {
			$this-&gt;session-&gt;set_flashdata('message', 'Привет, '.$login);
			redirect('bugtracker/page');
		}
		else {
			$this-&gt;session-&gt;set_flashdata('message', 'Неверная пара логин/пароль, попробуйте ещё раз');
			redirect('bugtracker/login');
		}
	}
}</pre>
<p>Прежде всего, мы поверяем корректность заполнения формы. Для этого создаем правила (строки 3, 4) и выполняем проверку (сторока 7). Если форма заполнена правильно (данные введены), то с помощью метода <code>login($login, $password)</code> (строка 19) ищем посетителя в базе данных.</p>
<p>Если соответствующая запись в БД будет найдена, отправляем его на главную (строка 21). При этом библиотека сама сохранит данные этого пользователя в сессии. И в дальнейшем мы сможем использовать метод <code>logged_in()</code> для авторизации пользователя.</p>
<p>Если email или пароль введены не правильно, то показываем посетителю форму ещё раз с предложением попробовать ещё раз.</p>
<p>Теперь рассмотрим <strong>метод удаления записи о баге</strong>.</p>
<pre class="brush: php">function deletebug($bugId) {
	if ($this-&gt;redux_auth-&gt;logged_in()) {
		if ($this-&gt;mbug-&gt;delete($bugId)) {
			$this-&gt;session-&gt;set_flashdata('message', 'Баг удален');
			redirect($this-&gt;session-&gt;userdata('prev_page'));
		}
		else {
			$this-&gt;session-&gt;set_flashdata('message', 'При удалении бага возникла ошибка');
			redirect($this-&gt;session-&gt;userdata('prev_page'));
		}
	}
	else {
		$this-&gt;session-&gt;set_flashdata('message', 'Вы должны войти для того, чтобы выполнить это действие');
		redirect('bugtracker/login');
	}
}</pre>
<p>Во второй строке мы <strong>выполняем авторизацию</strong> пользователя. И в зависимости от её результатов либо удаляем баг (строки 3-10), либо отправляем посетителя на страницу с формой входа (строки 13, 14).</p>
<p>Т.е. если кто-то, не выполнив вход, попробует отправить запрос <code>bugtracker.local/bugtracker/deletebug/1</code>, то он увидит сообщение &#039;Вы должны войти для того, чтобы выполнить это действие&#039; и форму ввода email и пароля.</p>
<p>В шаблонах багов и комментариев (bug_tpl.php и comment_tpl.php) также добавим несколько строк:</p>
<pre class="brush: php">if ($this-&gt;redux_auth-&gt;logged_in()) {
	echo '&lt;p class=&quot;deleteBug&quot;&gt;'.anchor('bugtracker/deletebug/{id}', 'Удалить').'&lt;/p&gt;';
}</pre>
<p>Таким образом, администраторы увидят ссылку «Удалить» рядом с каждым багом и комментарием.</p>
<p><em>Примечание</em>. Подробнее об этих шаблонах можно почитать в <a href="http://www.simplecoding.org/bug-tracker-preobrazuem-tablicu-v-html-spisok-chast-4.html/2">четвертой части</a>.</p>
<p>И последний момент. Нужно дать возможность администратору <strong>выйти из приложения</strong>.</p>
<p>Для этого создаем метод <code>logout</code>.</p>
<pre class="brush: php">function logout()
{
	$this-&gt;redux_auth-&gt;logout();
	$this-&gt;session-&gt;set_flashdata('message', 'Вы вышли из администраторского режима');
	redirect('bugtracker/page');
}</pre>
<p>После выхода (строка 2) мы сохраняем в сессии сообщение и отправляем редирект на главную страницу.</p>
<p>Как видите, библиотека довольно удобная и работать с ней не сложно. Кроме того, при необходимости с её помощью можно легко добавить возможность регистрации в баг трекере или разделить пользователей на группы с разными правами.</p>
<p><strong>P.S.</strong> Если возникли вопросы или есть замечания, пишите, постараюсь ответить <img src='http://www.simplecoding.org/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>http://www.simplecoding.org/bugtracker-avtorizaciya-i-autentifikaciya-chast-vosmaya.html/feed</wfw:commentRss>
		<slash:comments>46</slash:comments>
		</item>
		<item>
		<title>Bug Tracker: добавление записей и комментариев (часть седьмая)</title>
		<link>http://www.simplecoding.org/bug-tracker-dobavlenie-zapisej-i-kommentariev-chast-sedmaya.html</link>
		<comments>http://www.simplecoding.org/bug-tracker-dobavlenie-zapisej-i-kommentariev-chast-sedmaya.html#comments</comments>
		<pubDate>Wed, 18 Mar 2009 17:07:19 +0000</pubDate>
		<dc:creator>Владимир</dc:creator>
				<category><![CDATA[CodeIgniter]]></category>
		<category><![CDATA[HTML]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Web разработка]]></category>

		<guid isPermaLink="false">http://www.simplecoding.org/?p=789</guid>
		<description><![CDATA[В этой части цикла статей о разработке баг трекера мы рассмотрим добавление записей о багах и комментариев к ним. Вообще-то обе эти операции сводятся к добавлению записей в таблицы bugs и comments, т.е. их можно выполнить с помощью всего пары строк кода. Но, как несложно догадаться, основная часть работы будет заключаться в обработке данных. Кроме [...]]]></description>
			<content:encoded><![CDATA[<div id="attachment_791" class="wp-caption alignnone" style="width: 275px"><img src="http://www.simplecoding.org/wp-content/uploads/2009/03/bug_tracker_logo_part7.png" alt="bug_tracker_logo_part7" title="bug_tracker_logo_part7" width="265" height="160" class="size-full wp-image-791" style="float:left" /><p class="wp-caption-text"> </p></div>
<p>В этой части цикла статей о разработке баг трекера мы рассмотрим <strong>добавление записей о багах и комментариев к ним</strong>.</p>
<p>Вообще-то обе эти операции сводятся к добавлению записей в <a href="http://www.simplecoding.org/bug-tracker-izmeneniya-v-proekte-chast-tretya.html">таблицы bugs и comments</a>, т.е. их можно выполнить с помощью всего пары строк кода.</p>
<p>Но, как несложно догадаться, <strong>основная часть работы будет заключаться в обработке данных</strong>.</p>
<p>Кроме того, нужно сразу решить вопрос с тегами и <strong>защитой от XSS атак</strong>.</p>
<p>Проблема следующая. Если разрешить пользователям вставлять в текст описания багов любые HTML теги и не выполнять никаких проверок, то кто угодно сможет провести любую XSS атаку. Например, вставить скрипт с редиректом на свой ресурс.</p>
<p>С другой стороны, если фильтровать все теги, то посетители не смогут использовать жирный шрифт, курсив и т.п.</p>
<p>Решить проблему можно с помощью <strong>bbCodes</strong> или <strong>фильтрацией части тегов</strong>. Например, теги <code>script</code> удаляем, а <code>strong</code> – не трогаем.</p>
<p>Я решил выбрать второй вариант. Тем более что его не сложно реализовать с помощью библиотеки <a href="http://htmlpurifier.org/">HTML Purifier</a>. Кроме фильтрации опасных тегов, библиотека исправляет ошибки в разметке. Например, добавляет закрывающие теги.</p>
<p><strong>Подключение HTML Purifier к CodeIgniter</strong>.<br />
<span id="more-789"></span><br />
Я использовал вот <a href="http://blog.ortz.org/2008/12/30/making-html-purifier-work-with-codeigniter/">эту инструкцию</a>. Правда, я создал для библиотеки отдельную папку (\application\libraries\htmlpurifier).</p>
<p>После этого нужно закомментировать строку<br />
<code>require 'HTMLPurifier.php';</code><br />
в файле HTMLPurifier.includes.php.</p>
<p>А в файл HTMLPurifier.php (сразу после <code>&lt;?php</code>) добавить<br />
<code>require_once('HTMLPurifier.includes.php');</code></p>
<p>Подключается библиотека так:</p>
<pre class="brush: php">$this-&gt;load-&gt;library('htmlpurifier/HTMLPurifier');</pre>
<p>Теперь рассмотрим <strong>алгоритм добавления записи о баге</strong>.</p>
<div id="attachment_792" class="wp-caption alignnone" style="width: 422px"><img src="http://www.simplecoding.org/wp-content/uploads/2009/03/addbug.jpg" alt="add bug" title="add bug" width="412" height="504" class="size-full wp-image-792" /><p class="wp-caption-text"> </p></div>
<p>Сначала мы проверяем данные с помощью встроенной библиотеки <code>form_validation</code>. Для её работы нужно <strong>установить правила</strong> для каждого поля формы.</p>
<p>Полей у нас 5: заголовок, имя пользователя, eMail пользователя, категория и описание бага.</p>
<p>Если проверка прошла, мы обрабатываем данные с помощью <strong>HTML Purifier</strong> и добавляем их в базу. Читаем из сессии адрес последней страницы, на которой находился посетитель. И отправляем ему редирект с адресом этой страницы. Т.к. у нас есть несколько страниц, на которых находится форма добавления бага (главная, страницы категорий), то пользователь, скорее всего, захочет остаться на той странице, с которой он отправлял сообщение.</p>
<p>Если во время проверки возникли ошибки – формируем страницу с этой же формой и описаниями ошибок и отправляем её посетителю.</p>
<p>Теперь посмотрите на код метода <code>addbug</code>.</p>
<pre class="brush: php">function addbug() {
	$this-&gt;load-&gt;library('form_validation');
	$this-&gt;form_validation-&gt;set_error_delimiters('&lt;div class=&quot;fErrMessage&quot;&gt;', '&lt;/div&gt;');
	$this-&gt;form_validation-&gt;set_rules('title', 'lang:title', 'required');
	$this-&gt;form_validation-&gt;set_rules('uname', 'lang:uname', 'required');
	$this-&gt;form_validation-&gt;set_rules('category_id', 'lang:category_id', 'required|integer');
	$this-&gt;form_validation-&gt;set_rules('description', 'lang:description', 'required');
	$this-&gt;form_validation-&gt;set_rules('email', 'lang:email', 'required|valid_email');
	if ($this-&gt;form_validation-&gt;run() === FALSE) {
		//ошибка
		$pageData['message'] = 'Форма заполнена неправильно';

		//показываем форму с описаниями ошибок
		$pageData['title'] = 'Bug Tracker';
		$pageData['categories'] = $this-&gt;mcategory-&gt;getAllCategories();

		$this-&gt;load-&gt;view('header', $pageData);
		$this-&gt;load-&gt;view('categories');
		$this-&gt;load-&gt;view('addbugform');
		$this-&gt;load-&gt;view('footer');
	}
	else {
		//форма заполнена правильно
		$this-&gt;load-&gt;library('htmlpurifier/HTMLPurifier');
		$config = HTMLPurifier_Config::createDefault();

		$bugData['title'] = $this-&gt;htmlpurifier-&gt;purify($this-&gt;input-&gt;post('title'));
		$bugData['uname'] = $this-&gt;htmlpurifier-&gt;purify($this-&gt;input-&gt;post('uname'));
		$bugData['category_id'] = $this-&gt;input-&gt;post('category_id');
		$bugData['uemail'] = $this-&gt;input-&gt;post('email');
		$bugData['description'] = $this-&gt;htmlpurifier-&gt;purify($this-&gt;input-&gt;post('description'));

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

		redirect($this-&gt;session-&gt;userdata('prev_page'));
	}
}</pre>
<p>Как видите, метод работает точно в соответствии с описанным алгоритмом.</p>
<p>Тут стоит обратить внимание на строки 6 и 8. В них мы установили правила (<code>integer</code>, <code>valid_email</code>), которые обеспечивают проверку номера категории и email адреса.</p>
<p>Кроме того, использовать библиотеку <strong>HTML Purifier</strong> имеет смысл только после обычной проверки. Причин тут две. Во-первых, если пользователь не заполнил какое-то поле формы (а в данном случае они все обязательные), то пытаться удалить теги из него бессмысленно. Во-вторых, если вы все-таки попытаетесь это сделать, то HTML Purifier начинает потреблять кучу ресурсов. Например, использование памяти увеличивается с ~2,6 до 7 МБ.</p>
<p>Ещё один интересный момент связан с использованием сессий. Для вставки сообщений о результатах выполнения операции мы используем метод <code>set_flashdata</code>. Особенность flash данных в том, что они автоматически удаляются после считывания.</p>
<p>Таким образом, посетитель увидит сообщение о добавлении бага только один раз. Если он обновит страницу, то сообщение исчезнет.</p>
<p>Рассмотрим представление, которое создает форму (файл application\views\addbugform.php)</p>
<pre class="brush: php">&lt;?php
if (!empty($errMessage)) {
	echo '&lt;div id=&quot;errMessage&quot;&gt;'.$errMessage.'&lt;/div&gt;';
}
$mes = $this-&gt;session-&gt;flashdata('message');
if (!empty($mes)) {
	echo '&lt;div id=&quot;infoMessage&quot;&gt;'.$mes.'&lt;/div&gt;';
}
?&gt;
&lt;?php echo form_open('bugtracker/addbug', array('id'=&gt;'fAddBug')); ?&gt;
	&lt;?php echo form_error('title'); ?&gt;
	&lt;p&gt;
	&lt;label for=&quot;title&quot;&gt;Заголовок&lt;/label&gt;
	&lt;input type=&quot;text&quot; name=&quot;title&quot; id=&quot;title&quot; value=&quot;&lt;?php echo set_value('title'); ?&gt;&quot; /&gt;
	&lt;/p&gt;
	&lt;?php echo form_error('uname'); ?&gt;
	&lt;p&gt;
	&lt;label for=&quot;uname&quot;&gt;Ваше имя&lt;/label&gt;
	&lt;input type=&quot;text&quot; name=&quot;uname&quot; id=&quot;uname&quot; value=&quot;&lt;?php echo set_value('uname'); ?&gt;&quot; /&gt;
	&lt;/p&gt;
	&lt;?php echo form_error('category_id'); ?&gt;
	&lt;p&gt;
	&lt;label for=&quot;category_id&quot;&gt;Категория ошибки&lt;/label&gt;
	&lt;select name=&quot;category_id&quot; id=&quot;category_id&quot; size=&quot;1&quot;&gt;
	&lt;?php
		foreach ($categories as $category) {
			echo '&lt;option value=&quot;'.$category['id'].'&quot;';
			echo set_select('category_id', $category['id']);
			echo '&gt;'.$category['name'].'&lt;/option&gt;';
		}
	?&gt;
	&lt;/select&gt;
	&lt;/p&gt;
	&lt;?php echo form_error('email'); ?&gt;
	&lt;p&gt;
	&lt;label for=&quot;email&quot;&gt;eMail&lt;/label&gt;
	&lt;input type=&quot;text&quot; name=&quot;email&quot; id=&quot;email&quot; value=&quot;&lt;?php echo set_value('email'); ?&gt;&quot; /&gt;
	&lt;/p&gt;
	&lt;?php echo form_error('description'); ?&gt;
	&lt;p&gt;
	&lt;label for=&quot;description&quot;&gt;Описание ошибки&lt;/label&gt;
	&lt;textarea name=&quot;description&quot; id=&quot;description&quot; cols=&quot;30&quot; rows=&quot;5&quot;&gt;&lt;?php echo set_value('description'); ?&gt;&lt;/textarea&gt;
	&lt;/p&gt;
	&lt;p&gt;&lt;input type=&quot;submit&quot; name=&quot;addbug&quot; id=&quot;addbug&quot; value=&quot;Отправить&quot; /&gt;
	&lt;/p&gt;
&lt;/form&gt;</pre>
<p>В начале представления мы выводим сообщения, если они есть. После этого создаем форму.</p>
<p>Форма в общем-то обычная. Обратить внимание стоит на функции вывода ошибок <code>form_error</code> и автоматического заполнения формы <code>set_value</code>. Обратите внимание на восстановление предыдущего значения Select Box (строка 28). В первом параметре метода <code>set_select</code> нужно указать значение атрибута <code>name</code> Select Box.</p>
<p>Теперь <strong>переходим к модели</strong> (application\models\ mbug.php).</p>
<pre class="brush: php">function addNewBug($bugData) {
	$qAddBug = 'INSERT INTO bugs (title, uname, category_id, description, bug_date, uemail)'
		.' VALUES (?, ?, ?, ?, NOW(), ?)';
	$res = $this-&gt;db-&gt;query($qAddBug, array(
					$bugData['title'],
					$bugData['uname'],
					$bugData['category_id'],
					$bugData['description'],
					$bugData['uemail']
	));
	if ($res) {
		return $this-&gt;db-&gt;insert_id();
	}
	return $res;
}</pre>
<p>Здесь всё просто. В параметре <code>$bugData</code> передается массив с данными из формы. После этого выполняется вставка этих данных в таблицу <code>bugs</code>.</p>
<p>При выполнении запроса все знаки вопроса заменяются исходными данными. При этом автоматически происходит экранирование спецсимволов, что обеспечивает защиту от <strong>SQL Injection</strong>.</p>
<p><strong>Добавление комментарие</strong>в.</p>
<p>Этот метод практически не отличается от предыдущего. Тот же алгоритм, те же библиотеки.</p>
<p>Поэтому приводить код для этих методов я не буду.</p>
<p>Конечно в форме только 3 поля: имя, email и текст комментария. Правил проверки меньше. И, конечно, данные вставляются в таблицу <code>comments</code>, а не <code>bugs</code>.</p>
<p>Но на один момент хочу обратить ваше внимание. На данный момент (отсутствует поддержка JavaScript) можно оставить комментарий только первого уровня. Т.е. нельзя указать, что ваш комментарий является ответом на предыдущий.</p>
<p>Когда будет добавлена поддержка JavaScript, форма отправки комментария будет работать также как и в этом блоге, т.е. перемещаться под выбранный комментарий. При этом будет добавляться скрытое поле, содержащее <code>id</code> комментария на который вы отвечаете.</p>
<p>Сделать ответы на предыдущие комментарии <strong>без JavaScript</strong> можно, но при этом придется добавлять ещё один select box, содержащий перечень предыдущих комментариев, и пользователь сам должен будет выбрать, на какой комментарий он отвечает. Естественно, это очень неудобно. Поэтому поддержка ответов на предыдущие комментарии будет только при поддержке JavaScript.</p>
<p>До встречи!</p>
<p><strong>P.S.</strong> Если возникли вопросы, пишите, попробую ответить <img src='http://www.simplecoding.org/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>http://www.simplecoding.org/bug-tracker-dobavlenie-zapisej-i-kommentariev-chast-sedmaya.html/feed</wfw:commentRss>
		<slash:comments>28</slash:comments>
		</item>
		<item>
		<title>Bug Tracker: создание страниц (часть шестая)</title>
		<link>http://www.simplecoding.org/bug-tracker-sozdanie-stranic-chast-shestaya.html</link>
		<comments>http://www.simplecoding.org/bug-tracker-sozdanie-stranic-chast-shestaya.html#comments</comments>
		<pubDate>Mon, 16 Mar 2009 06:53:33 +0000</pubDate>
		<dc:creator>Владимир</dc:creator>
				<category><![CDATA[CodeIgniter]]></category>
		<category><![CDATA[CSS]]></category>
		<category><![CDATA[HTML]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Web разработка]]></category>

		<guid isPermaLink="false">http://www.simplecoding.org/?p=787</guid>
		<description><![CDATA[Мы продолжаем разработку собственной системы отслеживания ошибок. И сегодня подробно рассмотрим создание страниц нашего приложения. В предыдущих частях мы определились с типами страниц. Напомню, их всего два: страницы с общим перечнем багов (главная и страницы категорий) и страницы отдельных багов с комментариями. Т.к. общее количество записей о багах может быть большим, мы будем использовать библиотеку [...]]]></description>
			<content:encoded><![CDATA[<div id="attachment_788" class="wp-caption alignnone" style="width: 301px"><img src="http://www.simplecoding.org/wp-content/uploads/2009/03/bug_tracker_logo_part6.png" alt="bug_tracker_logo_part6" title="bug_tracker_logo_part6" width="291" height="204" class="size-full wp-image-788" style="float:left" /><p class="wp-caption-text"> </p></div>
<p>Мы продолжаем разработку собственной системы отслеживания ошибок. И сегодня подробно рассмотрим <strong>создание страниц</strong> нашего приложения.</p>
<p>В предыдущих частях мы определились с типами страниц. Напомню, их всего два: страницы с общим перечнем багов (главная и страницы категорий) и страницы отдельных багов с комментариями.</p>
<p>Т.к. общее количество записей о багах может быть большим, мы будем использовать библиотеку <a href="http://codeigniter.com/user_guide/libraries/pagination.html">pagination</a>, входящую в состав фреймворка <strong>CodeIgniter</strong>, для вывода этого списка по частям.</p>
<p>Количество записей на одной странице мы задаем в <strong>файле конфигурации</strong> (application\config\config.php).</p>
<pre class="brush: php">$config['bugs_per_page'] = 5;</pre>
<p>Получить значение этого параметра можно так:</p>
<pre class="brush: php">$this-&gt;config-&gt;item('bugs_per_page');</pre>
<p>Теперь определимся с <strong>названиями методов контроллера и структурой URL</strong>.</p>
<p>По-умолчанию, URL в CodeIgniter имеют такой вид:</p>
<p><code>sitename.domen/index.php/имя_контроллера/имя_метода/параметр1/параметр2/…</code></p>
<p>Контроллер у нас будет называться <code>bugtracker</code>, а метод, показывающий страницы с багами – <code>page</code>. В результате получим URL с такой структурой:</p>
<p><code>…/bugtracker/page/номер_записи</code></p>
<p>В последнем сегменте адреса указываем номер первой записи на текущей странице. Кстати, это не <code>id</code> бага в базе данных, это его индекс в массиве с результатами поиска по БД.</p>
<p><strong>Создаём контроллер</strong> (application\controllers\bugtracker.php)<br />
<span id="more-787"></span></p>
<pre class="brush: php">class BugTracker extends Controller {

	//настройки отображения списка багов
	private $listConf = array(
		'commentOpen'=&gt;'&lt;li class=&quot;depth-{depth}&quot;&gt;'
	);

	function BugTracker() {
		parent::Controller();

		$this-&gt;load-&gt;model('mbug');
		$this-&gt;load-&gt;model('mcategory');
		$this-&gt;load-&gt;model('mcomment');
		$this-&gt;load-&gt;library('Table2Tree');
		$this-&gt;load-&gt;library('session');
		$this-&gt;load-&gt;helper('form');
	}

	function index() {
		$this-&gt;page();
	}

	function page($firstBug = 0) {
	...
	}
}</pre>
<p>Как видите, на данный момент он содержит конструктор, в котором мы загрузили модели и несколько библиотек, а также два метода.</p>
<p>1) <code>index</code> – вызывается CodeIgniter’ом если имя метода явно не указано в URL;</p>
<p>2) <code>page($firstBug = 0)</code> – показывает записи о багах начиная с $firstBug.</p>
<p>Т.к. на главной странице у нас будет отображаться список багов начиная с первого, то из метода <code>index</code> мы просто вызываем <code>page</code> без параметров.</p>
<p>Теперь подробно рассмотрим метод <code>page</code>.</p>
<pre class="brush: php">function page($firstBug = 0) {
	if (!is_integer((int)$firstBug)) {
		$firstBug = 0;
	}

	//сохраняем адрес этой страницы в сессии
	$this-&gt;session-&gt;set_userdata(array('prev_page'=&gt;current_url()));

	$pageData['title'] = 'Bug Tracker';

	//настраиваем разбивку на страницы
	$this-&gt;load-&gt;library('pagination');
	$pconf['base_url'] = $this-&gt;config-&gt;item('base_url').'bugtracker/page';
	$pconf['total_rows'] = $this-&gt;mbug-&gt;getBugsCount();
	$pconf['per_page'] = $this-&gt;config-&gt;item('bugs_per_page');

	$this-&gt;pagination-&gt;initialize($pconf);
	$pageData['paginationLinks'] = $this-&gt;pagination-&gt;create_links();

	//загружаем общий список ошибок и комментариев к ним
	$bugs = $this-&gt;mbug-&gt;getAllBugs($firstBug, $this-&gt;config-&gt;item('bugs_per_page'));

	if ($bugs !== false) {
		$bugsTree = $this-&gt;table2tree-&gt;getTree($bugs, $firstBug, $this-&gt;config-&gt;item('bugs_per_page'));

		$pageData['bugsList'] = $this-&gt;table2tree-&gt;getHTMLList($bugsTree, $this-&gt;listConf);

	}

	$pageData['categories'] = $this-&gt;mcategory-&gt;getAllCategories();

	$this-&gt;load-&gt;view('header', $pageData);
	$this-&gt;load-&gt;view('categories');
	//размещаем форму отправки сообщений о багах
	$this-&gt;load-&gt;view('addbugform');
	$this-&gt;load-&gt;view('allbugs');
	$this-&gt;load-&gt;view('footer');
}</pre>
<p>Его работу можно разделить на следующие <strong>этапы</strong>.</p>
<p>1) Проверяем параметр и сохраняем адрес текущей страницы в сессии (зачем это нужно я расскажу немного позже).</p>
<p>2) Настраиваем разбивку на страницы (строки 11-18). Для этого загружаем библиотеку <code>pagination</code>, передаём ей массив с параметрами и создаем ссылки. В качестве параметров мы указываем: общее количество записей (получаем с помощью метода <code>getBugsCount()</code>), количество записей, которые нужно показать на странице (читаем из конфига) и первую часть URL ссылок (к нему последним параметром библиотека будет добавлять номер первой записи на очередной странице).</p>
<p>Т.е. если мы показываем по 5 записей на странице, библиотека создаст такие URL:<br />
<code>.../bugtracker/page/<br />
.../bugtracker/page/5<br />
.../bugtracker/page/10<br />
…</code></p>
<p>После этого мы загружаем список багов для данной страницы (строка 21). Запрос, который формирует этот список мы рассматривали в <a href="http://www.simplecoding.org/bug-tracker-model-i-stranicy-prilozheniya-chast-pyataya.html">прошлый раз</a>.</p>
<p>3) С помощью библиотеки <code>table2tree</code> мы преобразуем таблицу с данными о багах в <strong>HTML список</strong> (строки 23-28).</p>
<p>4) Загружаем список категорий (строка 30). Он используется для создания навигации по баг трекеру.</p>
<p>5) Показываем страницы (строки 32-37). Для этого загружаем представления и передаем им параметры.</p>
<p>Подробно рассматривать все представления смысла я не вижу. В любом случае оформлением я не занимался, поэтому, скорее всего, их придется немного изменить. Например, добавить CSS классы. Но на принцип работы и передаваемые данные это влиять не будет.</p>
<p>Поэтому сейчас я просто приведу краткое описание всех представлений. Все они находятся в папке (application\views):</p>
<p><code>header.php</code> – формирует заголовок страницы, загружает JavaScript и CSS файлы;<br />
<code>categories.php</code> – формирует список с перечнем категорий (используется для навигации);<br />
<code>addbugform.php</code> – форма добавления записи о найденном баге;<br />
<code>allbugs.php</code> – показывает список багов и строку со ссылками на другие страницы;<br />
<code>footer.php</code> – «хвостовик» страницы.</p>
<p><strong>Создание страницы выбранной категории.</strong></p>
<p>Приводить код этого метода я не буду, т.к. он практически полностью повторяет метод <code>page</code>. Тем не менее, есть несколько нюансов на которых стоит остановиться.</p>
<p>Во-первых, метод контроллера, создающий страницу отдельной категории, называется category и принимает 2 параметра: название категории и номер первой записи на странице.</p>
<p>Т.е. URL имеет такой вид:</p>
<p><code>…/bugtracker/category/имя_категоии/номер_записи</code></p>
<p>Во-вторых, при настройке библиотеки pagination необходимо явно указать в каком сегменте URL находится номер первой записи (по-умолчанию, это значение равно 3).</p>
<pre class="brush: php">$pconf['uri_segment'] = 4;</pre>
<p>В третьих, список багов получаем с помощью метода <code>getBugsByCategory</code> (модель <code>mbugs</code>). В <a href="http://www.simplecoding.org/bug-tracker-model-i-stranicy-prilozheniya-chast-pyataya.html">прошлый раз</a> мы рассматривали <strong>SQL запрос</strong>, который возвращает список багов для указанной категории.</p>
<p>Остальной код полностью повторяет метод page.</p>
<p><strong>Создание страницы отдельного бага</strong> (метод <code>bug</code>).</p>
<pre class="brush: php">function bug($id = 1) {
	if (!is_integer((int)$id) || (int)$id &lt;= 0) {
		redirect('bugtracker/index');
		return;
	}

	//сохраняем адрес этой страницы в сессии
	$this-&gt;session-&gt;set_userdata(array('prev_page'=&gt;current_url()));

	//ищем указанный баг
	$bug = $this-&gt;mbug-&gt;getBug($id);
	if ($bug !== false) {
		$bugTree = $this-&gt;table2tree-&gt;getTree($bug);

		$pageData['bugList'] = $this-&gt;table2tree-&gt;getHTMLList($bugTree, $this-&gt;listConf);
		$pageData['bug_id'] = $bugTree[0]['id'];
	}

	$pageData['categories'] = $this-&gt;mcategory-&gt;getAllCategories();

	$this-&gt;load-&gt;view('header', $pageData);
	$this-&gt;load-&gt;view('categories');
	$this-&gt;load-&gt;view('onebug');
	$this-&gt;load-&gt;view('addcommentform');
	$this-&gt;load-&gt;view('footer');
}</pre>
<p>Этот метод очень похож на предыдущие. Но есть несколько существенных различий.</p>
<p>1) Здесь мы не используем библиотеку <code>pagination</code>. В ней просто нет смысла. На странице отображается запись только об одном баге и все комментарии к нему.</p>
<p>2) Данные бага загружаем с помощью метода <code>getHTMLList</code> (строка 15). Кстати, для создания дерева комментариев мы используем собственную библиотеку <code>table2tree</code>. Её методы <code>getTree</code> и <code>getHTMLList</code> автоматически определяют есть ли в исходных данных комментарии и глубину их вложенности.</p>
<p>3) Вместо формы добавления бага вставляем форму добавления комментария (строка 24).</p>
<p>На этом мы закончим эту часть.<br />
В следующий раз рассмотрим добавление записей и комментариев.</p>
<p>До встречи!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.simplecoding.org/bug-tracker-sozdanie-stranic-chast-shestaya.html/feed</wfw:commentRss>
		<slash:comments>28</slash:comments>
		</item>
		<item>
		<title>Bug Tracker: модель и страницы приложения (часть пятая)</title>
		<link>http://www.simplecoding.org/bug-tracker-model-i-stranicy-prilozheniya-chast-pyataya.html</link>
		<comments>http://www.simplecoding.org/bug-tracker-model-i-stranicy-prilozheniya-chast-pyataya.html#comments</comments>
		<pubDate>Fri, 13 Mar 2009 21:13:39 +0000</pubDate>
		<dc:creator>Владимир</dc:creator>
				<category><![CDATA[Ajax]]></category>
		<category><![CDATA[CodeIgniter]]></category>
		<category><![CDATA[HTML]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Web разработка]]></category>

		<guid isPermaLink="false">http://www.simplecoding.org/?p=784</guid>
		<description><![CDATA[В комментариях к прошлой части я получил много советов о том, в каком виде лучше получать данные из базы. Я очень признателен всем за эти замечания, т.к. они действительно помогли найти недостатки в прошлой части. Основная проблема была в том, что я не продумал до конца, что именно будет отображаться на страницах баг трекера, но [...]]]></description>
			<content:encoded><![CDATA[<div id="attachment_785" class="wp-caption alignnone" style="width: 265px"><img src="http://www.simplecoding.org/wp-content/uploads/2009/03/bug_tracker_logo_part5.png" alt="bug_tracker_logo_part5" title="bug_tracker_logo_part5" width="255" height="153" class="size-full wp-image-785" style="float:left" /><p class="wp-caption-text"> </p></div>
<p>В комментариях к прошлой части я получил много советов о том, в каком виде лучше получать данные из базы. Я очень признателен всем за эти замечания, т.к. они действительно помогли найти недостатки в <a href="http://www.simplecoding.org/bug-tracker-preobrazuem-tablicu-v-html-spisok-chast-4.html">прошлой части</a>.</p>
<p>Основная проблема была в том, что я не продумал до конца, <strong>что именно будет отображаться на страницах баг трекера</strong>, но взялся за работу с базой.</p>
<p>В общем-то, страниц у баг трекера не много. Первоначально я вообще хотел сделать одностраничное приложение, но передумал из-за <strong>проблем с индексацией поисковиками</strong>.</p>
<p>Поэтому сегодня постараюсь максимально подробно осветить этот вопрос.</p>
<p>Т.к. индексация страниц баг трекера поисковиками очень желательна, то при разработке имеет смысл придерживаться <a href="http://googlewebmastercentral.blogspot.com/2007/11/spiders-view-of-web-20.html">рекомендаций от Google</a>, а именно методики <strong>Progressive Enhancement</strong> (постепенного улучшения).</p>
<p><strong>Идея следующая</strong>. Нужно сделать приложения так, чтобы <strong>без</strong> поддержки <strong>JavaScript</strong> (только с помощью обычных ссылок) можно было получить доступ к любой информации (багам и комментариям).</p>
<p>После этого, с помощью <strong>JavaScript</strong> вносятся различные улучшения. Например, без JavaScript при клике на заголовке бага посетитель попадет на страницу с его описанием и комментариями.</p>
<p>Если JavaScript включен, то после загрузки страницы обычные ссылки будут преобразованы в <strong>ajax-ссылки</strong> и вместо перехода на другую страницу будет подгужен список комментариев.</p>
<p>Кроме того, не правильно показывать на одной странице одновременно несколько багов вместе с комментариями (как я планировал раньше). Во-первых, если комментарии содержат рисунки, то такая страница будет долго грузиться. Во-вторых, получится, что отдельные страницы багов будут дублировать главную, а, насколько я знаю, поисковики это не любят (дублирование контента).</p>
<p>В результате получается, что для работы баг трекера нужны <strong>страницы двух типов</strong>.<br />
<span id="more-784"></span><br />
1) <strong>Страницы с общим перечнем багов</strong>. Они будут содержать заголовок и описание бага (без комментариев). Сюда относятся: главная и страницы категорий. Тут же будет форма для добавления сообщения о новом баге.</p>
<p>2) <strong>Страницы отдельных багов</strong>. Тут будет полная информация о баге и дерево комментариев. Плюс форма добавления комментария.</p>
<p>Кстати, из страниц первого типа можно будет сформировать RSS ленты (как предлагал <a href="http://www.kachayev.ru/">Алексей Качаев</a>).</p>
<p>Теперь посмотрим, что нам нужно сделать для того, чтобы сформировать содержимое этих страниц.</p>
<p>Как ни странно, но <strong>практически вся работа сделана в прошлой части</strong> <img src='http://www.simplecoding.org/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' />  .</p>
<p>У нас есть библиотека, которая позволяет преобразовывать таблицу с багами и комментариями в <strong>html</strong> списки. При этом совершенно не важно, сколько багов и комментариев находится в этой таблице.</p>
<p>Например, если мы получим из базы таблицу только с информацией о багах (без комментариев), то сможем точно также сформировать html список.</p>
<p>Кроме того, у нас есть шаблоны для отображения багов и комментариев. И совершенно неважно как их использовать, для создания списка багов с комментариями или без них.</p>
<p>Тем не менее, в библиотеку нужно внести несколько мелких исправлений, чтобы сделать её более универсальной.</p>
<p>1) Убрать <code>array_slice</code>. Дело в том, что я сразу не разобрался как ограничить количество результатов, которые возвращает <strong>SQL</strong> запрос при использовании объединений (JOINS), но потом исправил эту ошибку.</p>
<p>2) Добавить метод <code>getHTMLCommentsList</code>, который будет использоваться для преобразования массива с комментариями (без данных о баге) в HTML список. Этот метод потребуется при загрузке списка комментариев с помощью AJAX.</p>
<pre class="brush: php">function getHTMLCommentsList($comments, $config = null) {
	if (!empty($config)) {
		foreach ($config as $key => $value) {
			$this->config[$key] = $value;
		}
	}
	$this->CI->load->library('parser');
	$html = $this->_convert2HTMLList($comments, 0);
	return $html;
}</pre>
<p>Этот метод по сути представляет собой оболочку, для <code>_convert2HTMLList</code>, описанного в <a href="http://www.simplecoding.org/bug-tracker-preobrazuem-tablicu-v-html-spisok-chast-4.html">прошлой части</a>.</p>
<p>3) Добавить проверку наличия полей с данными о комментариях (в методе <code>_convert2SimpleTree</code>). Т.к. теперь этот метод может получить данные багов как с комментариями так и без них.</p>
<p>Исходники библиотеки можно скачать в виде архива. Ссылка в конце страницы.</p>
<p>Кроме того, я думаю, не стоит отказываться от возможности получения полного списка багов с комментариями. По крайней мере, есть один случай, в котором это может быть полезно. Речь о том, что кроме RSS можно организовать отправку сообщений о новых багах на eMail, например, раз в день. И в таких письмах имеет смысл отправить не только описания багов, но и комментарии к ним. Ведь если между добавлением бага и отправкой письма может пройти несколько часов, то, скорее всего и комментарии появятся.</p>
<p>Также, думаю, стоит рассмотреть основные SQL запросы, с помощью которых можно будет получить информацию для перечисленных выше страниц. Диаграмма со структурой таблиц приведена в <a href="http://www.simplecoding.org/bug-tracker-izmeneniya-v-proekte-chast-tretya.html">третьей части</a>.</p>
<p>1) Получение списка багов (используется для создания главной страницы и страниц категорий).</p>
<pre class="brush: sql">SELECT b.* , c.link, c.name AS category
	FROM bugs AS b
	LEFT JOIN categories AS c ON b.category_id = c.id
	ORDER BY b.bug_date DESC LIMIT ?, ?</pre>
<p>Обратите внимание на LIMIT в конце запроса. С его помощью мы ограничиваем число багов на странице. Естественно, при этом будет использоваться библиотека пагинации из <strong>CodeIgniter</strong>.</p>
<p>2) Получение списка багов для выбранной категории.</p>
<pre class="brush: sql">SELECT COUNT(b.id) AS catsnum FROM bugs AS b
	LEFT JOIN categories AS c ON b.category_id = c.id
	WHERE c.link=?</pre>
<p>3) Получение подробной информации о баге и всех его комментариев.</p>
<pre class="brush: sql">SELECT b.*, c.link, c.name AS category,
	cm.id AS c_id, cm.uname AS c_uname, cm.description AS c_description,
	cm.comment_date, cm.parent_id, cm.bug_id, cm.uemail AS c_uemail
	FROM bugs AS b
	LEFT JOIN categories AS c ON b.category_id=c.id
	LEFT JOIN comments AS cm ON b.id=cm.bug_id WHERE b.id=?</pre>
<p>4) Получение отдельно списка комментариев выбранного бага (для работы в ajax-режиме). По-моему, не имеет смысла использовать отдельный запрос для решения этой задачи. Ведь все необходимые данные можно получить предыдущим запросом.</p>
<p>5) Получение списка багов и комментариев (я приводил этот запрос в прошлый раз, но сейчас немного изменил его и ввел ограничение на количество полей).</p>
<pre class="brush: sql">SELECT b.* , c.link, c.name AS category, cm.id AS c_id,
	cm.uname AS c_uname, cm.description AS c_description,
	cm.comment_date, cm.parent_id, cm.bug_id, cm.uemail AS c_uemail
	FROM comments AS cm
	RIGHT JOIN (
		SELECT * FROM bugs ORDER BY bugs.bug_date DESC
		LIMIT ?, ?
	) b ON b.id = cm.bug_id
	LEFT JOIN categories AS c ON b.category_id = c.id</pre>
<p>Тут стоит обратить внимание на строку 7.</p>
<p>6) Получение общего количества багов (используется для пагинации)</p>
<pre class="brush: php">function getBugsCount() {
	return $this->db->count_all('bugs');
}</pre>
<p>Это не SQL запрос <img src='http://www.simplecoding.org/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' />  Здесь я использовал встроенную функцию CodeIgniter. На самом деле выполняется </p>
<pre class="brush: sql">SELECT COUNT(*) AS 'numrows' FROM 'bugs'</pre>
<p>7) Получение общего количества багов в выбранной категории</p>
<pre class="brush: sql">SELECT COUNT(b.id) AS catsnum FROM bugs AS b
	LEFT JOIN categories AS c ON b.category_id = c.id
	WHERE c.link=?</pre>
<p>Я не буду подробно описывать принцип работы каждого запроса. В <a href="http://www.simplecoding.org/bug-tracker-preobrazuem-tablicu-v-html-spisok-chast-4.html">прошлой части</a> я довольно подробно останавливался на одном из самых сложных (пятый запрос в этом списке). Самое главное четко представлять как работают объединения таблиц (JOINS).</p>
<p>Вместо вопросительно знака в запросы будут подставлены конкретные данные.</p>
<p>В завершение приведу ссылку на <strong>ахив с исходниками библиотеки</strong> <a href='http://www.simplecoding.org/wp-content/uploads/2009/03/table2tree.zip'>table2tree</a>.</p>
<p>Как видите, изменений минимум. На этом примере очень хорошо видно одно из основных преимуществ архитектуры MVC. За счет того, что вся работа с базой данных вынесена в модель можно расширять возможности приложения практически без изменений в остальной части кода.</p>
<p>До встречи!</p>
<p><strong>P.S.</strong> Как всегда я рад выслушать любые замечания и предложения.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.simplecoding.org/bug-tracker-model-i-stranicy-prilozheniya-chast-pyataya.html/feed</wfw:commentRss>
		<slash:comments>47</slash:comments>
		</item>
		<item>
		<title>Bug tracker. Преобразуем таблицу в html список (часть 4).</title>
		<link>http://www.simplecoding.org/bug-tracker-preobrazuem-tablicu-v-html-spisok-chast-4.html</link>
		<comments>http://www.simplecoding.org/bug-tracker-preobrazuem-tablicu-v-html-spisok-chast-4.html#comments</comments>
		<pubDate>Mon, 09 Mar 2009 20:23:50 +0000</pubDate>
		<dc:creator>Владимир</dc:creator>
				<category><![CDATA[CodeIgniter]]></category>
		<category><![CDATA[HTML]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Web разработка]]></category>

		<guid isPermaLink="false">http://www.simplecoding.org/?p=781</guid>
		<description><![CDATA[Сегодня я продолжаю рассказывать о разработке системы отслеживания ошибок. В этой части речь пойдет о создании списка багов и комментариев. Напомню, в прошлый раз мы решили, что все для хранения записей о багах и комментариях к ним будем использовать две таблицы: bugs и comments. Там же мы обсудили список полей и их назначение, и создали [...]]]></description>
			<content:encoded><![CDATA[<div id="attachment_782" class="wp-caption alignnone" style="width: 263px"><img src="http://www.simplecoding.org/wp-content/uploads/2009/03/bug_tracker_logo_part4.png" alt="bug_tracker_logo_part4" title="bug_tracker_logo_part4" width="253" height="192" class="size-full wp-image-782" style="float:left" /><p class="wp-caption-text"> </p></div>
<p>Сегодня я продолжаю рассказывать о разработке <strong>системы отслеживания ошибок</strong>. В этой части речь пойдет о <strong>создании списка багов и комментариев</strong>.</p>
<p>Напомню, в <a href="http://www.simplecoding.org/bug-tracker-izmeneniya-v-proekte-chast-tretya.html">прошлый раз</a> мы решили, что все для хранения записей о багах и комментариях к ним будем использовать две таблицы: <code>bugs</code> и <code>comments</code>.</p>
<p>Там же мы обсудили список полей и их назначение, и создали несколько внешних ключей для связи между таблицами.</p>
<p>Теперь <strong>переходим к созданию списка</strong>.</p>
<p>Чтобы лучше понять задачу рассмотрим html разметку, которую нам нужно получить.<br />
<span id="more-781"></span></p>
<pre>&lt;ul&gt;
	&lt;li&gt;
		Описание бага №1
		&lt;ul&gt;
			&lt;li&gt;Комментарий 1&lt;/li&gt;
			&lt;li&gt;
				Комментарий 2
				&lt;ul&gt;
					&lt;li&gt;Ответ на комментарий 2&lt;/li&gt;
				&lt;/ul&gt;
			&lt;/li&gt;
		&lt;/ul&gt;
	&lt;/li&gt;
	&lt;li&gt;
		Описание бага №2
		&lt;ul&gt;
			&lt;li&gt;Комментарий 1 к багу 2&lt;/li&gt;
			...
		&lt;/ul&gt;
	&lt;/li&gt;
&lt;/ul&gt;</pre>
<p>Как видите, разметка достаточно простая. Мы создаем список, каждый элемент которого соответствует записи о баге. Если к этому багу оставлены комментарии, то они размещаются во вложенном списке. Вкладывая такие списки друг в друга, мы формируем дерево комментариев.</p>
<p>Таким образом, нам нужно <strong>преобразовать данные из таблиц в дерево</strong>.</p>
<p>Примечание. Если вы интересуетесь хранением древовидных структур в базе данных, то советую почитать статью «<a href="http://leopard.in.ua/2009/02/07/postroenie-derevev/">Построение деревьев</a>».</p>
<p>Теперь посмотрим, каким образом можно сформировать такой список. Есть несколько вариантов.</p>
<p>Можно решать задачу «в лоб». Т.е. первым запросом найти все записи о багах (напомню, у всех таких записей поле <code>parent_id = NULL</code>). А после этого в цикле отправлять запросы поиска комментариев к каждому из найденных багов. Недостаток такого подхода – большое количество запросов к БД.</p>
<p>Второй вариант. Мы можем получить всю необходимую информацию с помощью  <strong>одного</strong> запроса. Но при этом усложняется алгоритм получения дерева с багами и комментариями.</p>
<p>Остановимся мы, конечно, на втором варианте.</p>
<p>И, прежде всего, <strong>нам нужно получить данные</strong>. Для этого создаем модель, назовем её <code>mbug</code> (application\models\mbugs.php), и метод <code>getAllBugsWithComments</code>.</p>
<pre class="brush: php">class MBug extends Model {

	function MBug() {
		parent::Model();
	}

	/**
	 * Ищет все баги и комментарии к ним. Баги возвращаются начиная с последнего добавленного.
	 *
	 * @return массив со всеми багами и комментариями к ним, FALSE - если ничего не найдено
	 */
	function getAllBugsWithComments() {
		$qGetAll = 'SELECT b.*, c.link, c.name AS category,'
			.' cm.id AS c_id, cm.uname AS c_uname, cm.description AS c_description,'
			.' cm.comment_date, cm.parent_id, cm.bug_id, cm.uemail AS c_uemail'
			.' FROM bugs AS b'
			.' LEFT JOIN categories AS c ON b.category_id=c.id'
			.' LEFT JOIN comments AS cm ON b.id=cm.bug_id'
			.' ORDER BY b.bug_date DESC';
		$res = $this-&gt;db-&gt;query($qGetAll);
		if ($res-&gt;num_rows() &lt;= 0) {
			return false;
		}
		return $res-&gt;result_array();
	}
}</pre>
<p>Работает метод <code>getAllBugsWithComments</code> предельно просто. Он выполняет запрос к БД и возвращает результат его выполнения в виде массива.</p>
<p>Самая интересная часть – это, конечно, сам <strong>запрос</strong>. Для того, чтобы сформировать дерево, нам нужны данные о багах, сведения о категориях к которым они относятся и комментарии к ним.</p>
<p>Поэтому в запросе мы выполняем объединение трех таблиц (<code>bugs</code>, <code>categories</code>, <code>comments</code>). Рассмотрим, <strong>как выполняется это объединение</strong>.</p>
<p>Прежде всего, обратите внимание на то, что таблицы объединяются слева направо (<code>LEFT JOIN</code>). На первом этапе объединяются таблицы <code>bugs</code> и <code>categories</code>. Т.к. каждому багу соответствует только одна категория, то в результате мы получим исходную таблицу <code>bugs</code> с двумя новыми столбцами <code>link</code> и <code>name</code> из таблицы <code>categories</code>.</p>
<p>Обратите внимание, что т.к. таблица <code>bugs</code> расположена слева, то в результат войдут только те записи из таблицы <code>categories</code>, для которых есть соответствующая запись в таблице <code>bugs</code>. Другими словами, если у нас есть категория, в которой нет ни одного бага, то сведений о ней в результирующей таблице не будет.</p>
<p>На следующем этапе происходит <strong>объединение с таблицей comments</strong>. Тут возможны два варианта.</p>
<p>Первый – очередной баг не имеет комментариев. В этом случае в результирующей таблице будет одна строка со сведениями из таблиц <code>bugs</code> и <code>categories</code>, а все поля, соответствующие таблице <code>comments</code> будут иметь значения <code>NULL</code>.</p>
<p>Второй – очередной баг имеет один или более комментариев. Тогда в результирующей таблице будет одна или более строк с данными из таблиц <code>bugs</code>, <code>categories</code> и <code>comments</code>. Причем количество таких строк определяется количеством комментариев.</p>
<p>Посмотрите на пример такой таблицы (я опустил часть полей).</p>
<table style="border-collapse:collapse; font-size:80%">
<tr>
<td style="border:1px solid #000000">id</td>
<td style="border:1px solid #000000">title</td>
<td style="border:1px solid #000000">&#8230;</td>
<td style="border:1px solid #000000">name</td>
<td style="border:1px solid #000000">&#8230;</td>
<td style="border:1px solid #000000">c_description</td>
</tr>
<tr>
<td style="border:1px solid #000000">1</td>
<td style="border:1px solid #000000">Баг 1</td>
<td style="border:1px solid #000000">&#8230;</td>
<td style="border:1px solid #000000">Критические ошибки</td>
<td style="border:1px solid #000000">&#8230;</td>
<td style="border:1px solid #000000">Комментарий к багу 1</td>
</tr>
<tr>
<td style="border:1px solid #000000">2</td>
<td style="border:1px solid #000000">Баг 2</td>
<td style="border:1px solid #000000">&#8230;</td>
<td style="border:1px solid #000000">Пожелания</td>
<td style="border:1px solid #000000">&#8230;</td>
<td style="border:1px solid #000000">NULL</td>
</tr>
<tr>
<td style="border:1px solid #000000">3</td>
<td style="border:1px solid #000000">Баг 3</td>
<td style="border:1px solid #000000">&#8230;</td>
<td style="border:1px solid #000000">Критические ошибки</td>
<td style="border:1px solid #000000">&#8230;</td>
<td style="border:1px solid #000000">Первый комментарий к багу 3</td>
</tr>
<tr>
<td style="border:1px solid #000000">3</td>
<td style="border:1px solid #000000">Баг 3</td>
<td style="border:1px solid #000000">&#8230;</td>
<td style="border:1px solid #000000">Критические ошибки</td>
<td style="border:1px solid #000000">&#8230;</td>
<td style="border:1px solid #000000">Второй комментарий к багу 3</td>
</tr>
</table>
<p><strong>Переходим к преобразованию этой таблицы в HTML список.</strong></p>
<p>Т.к. операция довольно сложная, то будет удобно написать для неё небольшую <strong>библиотеку </strong>– <code>Table2Tree</code>.</p>
<p>Для этого создаём файл <code>application\libraries\table2tree.php</code> (название файла совпадает с названием библиотеки).</p>
<p>Но, прежде чем переходить к описанию библиотеки, рассмотрим пример её использования. Допустим, у нас есть контроллер (<code>bugtracker</code>) с методом <code>page</code> (формирует страницу со списком багов и комментариев).</p>
<pre class="brush: php">class BugTracker extends Controller {

	//настройки отображения списка багов
	private $listConf = array(
		'commentOpen'=&gt;'&lt;li class=&quot;depth-{depth}&quot;&gt;'
	);

	function BugTracker() {
		parent::Controller();

		$this-&gt;load-&gt;model('mbug');
		$this-&gt;load-&gt;library('Table2Tree');
	}

	function page($firstBug = 0) {
		...
		//загружаем общий список ошибок и комментариев к ним
		$bugs = $this-&gt;mbug-&gt;getAllBugsWithComments();

		if ($bugs !== false) {
			$bugsTree = $this-&gt;table2tree-&gt;getTree($bugs, $firstBug, $this-&gt;config-&gt;item('bugs_per_page'));

			$pageData['bugsList'] = $this-&gt;table2tree-&gt;getHTMLList($bugsTree, $this-&gt;listConf);

		}
		//формируем страницу
	}
}</pre>
<p>Как видите, в конструкторе мы загрузили нашу библиотеку (<code>Table2Tree</code>) и модель (<code>mbug</code>).</p>
<p>Метод <code>page()</code> работает следующим образом.</p>
<p>1) Получаем список багов с комментариями (с помощью метода <code>getAllBugsWithComments</code> модели).</p>
<p>2) Преобразовываем полученную таблицу в многомерный массив (метод <code>getTree</code> нашей библиотеки).</p>
<p>3) Преобразовываем многомерный массив в HTML список (метод <code>getHTMLList</code>).</p>
<p>На втором и третьем этапе я хочу остановиться подробнее. Основной вопрос тут: что представляет собой многомерный массив и зачем он вообще нужен.</p>
<p>Дело в том, что мы формируем этот массив таким образом, что <strong>его структура полностью совпадает со структурой HTML списка который мы хотим получить</strong>.</p>
<p>Например, для списка, показанного в начале статьи, этот массив будет выглядеть примерно так.</p>
<pre>[0]=&gt;
	‘title’=&gt;’Описание бага №1’
	...
	‘comments’=&gt;
		[0]=&gt;
			‘description’=&gt;’Комментарий 1’
		[1]=&gt;
			‘description’=&gt;’Комментарий 2’
			‘comments’=&gt;
				[0]=&gt;
					‘description’=&gt;’Ответ на комментарий 2’
[1]=&gt;
	‘title’=&gt;’Описание бага №2’
	...
	‘comments’=&gt;
		[0]=&gt;
			‘description’=&gt;’Комментарий 1 к багу 2’</pre>
<p>Используя такой массив, получить HTML список будет значительно проще.</p>
<p>Теперь рассмотрим, каким образом можно <strong>преобразовать результаты поиска по БД в такой массив</strong>.</p>
<p>Я решил разбить задачу на 2 этапа.</p>
<p>На первом – <strong>формируем массив с багами</strong>. В нем каждый элемент будет содержать описание бага и одномерный массив со всеми комментариями к нему.</p>
<p>На втором – <strong>преобразуем массивы с комментариями в многомерные</strong> (на основании значения в поле <code>parent_id</code>), т.е. формируем дерево комментариев.</p>
<p>Рассмотрим метод <code>getTree</code></p>
<pre class="brush: php">function getTree($data, $firstBug = 0, $count = 1) {
	$sTree = $this-&gt;_convert2SimpleTree($data);
	//запоминаем общее количество багов
	$this-&gt;bugsCount = count($sTree);
	//оставляем в массиве только те элементы, которые будут отображаться на странице
	$sTree = array_slice($sTree, $firstBug, $count, TRUE);
	foreach ($sTree as $i =&gt; $row) {
		if (!empty($row['comments'])) {
			$sTree[$i]['comments'] = $this-&gt;_nestedComments($row['comments'], null, 0);
		}
	}
	return $sTree;
}</pre>
<p>Он имеет 3 входных параметра:<br />
<code>data</code> – массив с результатами поиска в БД;<br />
<code>firstBug</code> – номер первого бага на странице (используется для пагинации, нет никакого смысла строить дерево комментариев для багов, которые не будут отображаться на странице);<br />
<code>count</code> – количество багов на странице.</p>
<p>Первый этап работы (формирование массива с багами) осуществляется с помощью метода <code>_convert2SimpleTree</code> (по соглашению, принятому в CodeIgniter, имена приватных методов должны начинаться с подчеркивания).</p>
<p>После этого мы оставляем в массиве только те записи о багах, которые будут отображаться на странице (строка 6).</p>
<p>И <strong>формируем дерево комментариев</strong>. Для этого мы проходим в цикле по всем элементам массива и с помощью метода <code>_nestedComments</code> преобразовываем массив <code>comments</code> в многомерный.</p>
<p>Рассмотрим метод <code>_convert2SimpleTree</code></p>
<pre class="brush: php">function _convert2SimpleTree($bugs) {
	$bugRes = array();
	$currentBugId = 0;
	$i = -1;
	$j = 0;
	foreach ($bugs as $bug) {
		if ($bug['id'] != $currentBugId) {
			$currentBugId = $bug['id'];
			$i++;
			//заполняем массив данными о баге
			$bugRes[$i]['id'] = $bug['id'];
			$bugRes[$i]['title'] = $bug['title'];
			$bugRes[$i]['uname'] = $bug['uname'];
			$bugRes[$i]['category_id'] = $bug['category_id'];
			$bugRes[$i]['description'] = $bug['description'];
			$bugRes[$i]['status'] = $bug['status'];
			$bugRes[$i]['bug_date'] = $bug['bug_date'];
			$bugRes[$i]['uemail'] = $bug['uemail'];

			//данные о категории
			$bugRes[$i]['link'] = $bug['link'];
			$bugRes[$i]['category'] = $bug['category'];

			//создаем массив с комментариями
			if ($bug['c_id'] != NULL) {
				$bugRes[$i]['comments'] = array();
				$j = 0;
				$bugRes[$i]['comments'][$j]['id'] = $bug['c_id'];
				$bugRes[$i]['comments'][$j]['uname'] = $bug['c_uname'];
				$bugRes[$i]['comments'][$j]['description'] = $bug['c_description'];
				$bugRes[$i]['comments'][$j]['comment_date'] = $bug['comment_date'];
				$bugRes[$i]['comments'][$j]['parent_id'] = $bug['parent_id'];
				$bugRes[$i]['comments'][$j]['bug_id'] = $bug['bug_id'];
				$bugRes[$i]['comments'][$j]['uemail'] = $bug['c_uemail'];
				$j++;
			}
		}
		else {
			//добавляем новую запись в массив с комментариями
			$bugRes[$i]['comments'][$j]['id'] = $bug['c_id'];
			$bugRes[$i]['comments'][$j]['uname'] = $bug['c_uname'];
			$bugRes[$i]['comments'][$j]['description'] = $bug['c_description'];
			$bugRes[$i]['comments'][$j]['comment_date'] = $bug['comment_date'];
			$bugRes[$i]['comments'][$j]['parent_id'] = $bug['parent_id'];
			$bugRes[$i]['comments'][$j]['bug_id'] = $bug['bug_id'];
			$bugRes[$i]['comments'][$j]['uemail'] = $bug['c_uemail'];
			$j++;
		}
	}
	return $bugRes;
}</pre>
<p>Выглядит он довольно объемным, но большую часть его составляют операторы присвоения. А <strong>принцип работы</strong> достаточно простой.</p>
<p>В исходной таблице (с результатами из БД) каждому багу может соответствовать несколько строк (в зависимости от количества комментариев).</p>
<p>Поэтому мы перебираем в цикле все строки этой таблицы и если встречаем новый баг (его <code>id</code> отличается от предыдущего), то создаём новый элемент в результирующем массиве и копируем в него данные бага, категории и комментария (если он есть).</p>
<p>Если очередная строка относится к той же записи о баге, что и предыдущая, то мы копируем только данные комментария.</p>
<p>Продолжение на <a href="http://www.simplecoding.org/bug-tracker-preobrazuem-tablicu-v-html-spisok-chast-4.html/2">следующей странице &gt;&gt;</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.simplecoding.org/bug-tracker-preobrazuem-tablicu-v-html-spisok-chast-4.html/feed</wfw:commentRss>
		<slash:comments>40</slash:comments>
		</item>
	</channel>
</rss>

