Автор: Kuzya Заранее извиняюсь перед модераторами если я перепутал форум для публикации статьи. Я долго выбирал между форумом "Статьи" и "PHP, PERL...." и выбрал последний. Переместите пожалуйста тему если я ошибся файлы приложенные к статье: http://kuzya59.narod.ru/gb_sql.zip http://kuzya59.narod.ru/gb_files.zip http://kuzya59.narod.ru/gallery.zip Официальный сайт: http://codeigniter.com/ Сайт русскоязычной поддержки: http://code-igniter.ru/ Версия на время написания статьи: 1.7 (но документация использовалась для версии 1.6.3) Введение Для хорошего понимания этой статьи Вам желательно ознакомиться с документацией к данному фреймворку. Не обязательно со всей полностью, хотя бы с «Общими темами». Также вам нужно знать архитектуру MVC (Модель-Вид-контроллер) и что именно делает каждая из этих трёх частей. Я бы мог описать базовые этапы работы с CI, но мне кажется, что авторы документации и переводчики её русской версии отлично это сделали. Документация к рассматриваемому фреймворку действительно очень проста и понятна. Плюс ко всему – сам CI спроектирован с максимальным удобством для его будущего изучения, поэтому если у Вас будут возникать какие-либо вопросы по ходу прочтения статьи, то не ленитесь и обращайтесь к документации. Всё, что я здесь буду описывать в ней есть. Также я постарался подробно комментировать весь описываемый код и объяснять каждое действие, относящееся к приложению, дабы у читателя не возникло лишних вопросов. Ниже мы рассмотрим создание простейших приложений с помощью базовых возможностей CI. Приложений этих будет два — гостевая книга и фотогаллерея. В процессе их написания я постарался задействовать как можно больше классов, хэлперов и прочего инструментария CI для того чтобы Вы могли на практике увидеть как они используются и какую пользу могут принести. И ещё кое-что прежде чем приступить непосредственно к программированию. Установите CI на отдельный домен. Писать каждое новое приложение мы будем на «чистом» домене, который не содержит посторонних скриптов. У меня этот хост называется «ci», далее по тексту я буду использовать именно это имя, поэтому будьте внимательны, если Вы назовёте хост по-другому. Фреймворк мы тоже всегда будем использовать «чистый». Для каждого нового приложения мы будет устанавливать CI заново. Нужно это для того чтобы не возникло путаницы между контроллерами, моделями и прочими скриптами разных приложений. В корень хоста положите файл «.htaccess» со следующим текстом: Code: RewriteEngine on RewriteCond $1 !^(index\.php|images|styles\.css|robots\.txt) RewriteRule ^(.*)$ /index.php/$1 [L] Это позволит нам при обращениях к сайту убрать из адресной строки «index.php». А так же здесь записано игнорирование обращений к файлам «index.php»,«styles.css»,«robots.txt» и к директории «images». Запросы обращённые к ним переписываться не будут. И последнее. Откройте файл «application/config/config.php» и измените в нём параметр «base_uri» с «http://your-site.com/» на «http://ci/» (или на название Вашего хоста, если он у Вас назван по другому). 1. Гостевая книга Сейчас мы напишем гостевую книгу, обладающую минимальным функционалом. Она будет показывать отзывы, делить их на страницы и позволять посетителю оставлять сообщения. При написании этих трёх действий мы постараемся задействовать как можно больше инструментов встроенных в CI, чтобы рассмотреть их на практике. Начнём с дизайна. Шаблон гостевой книги Вы можете взять в файле «template.zip» приложенному к статье. Если Вы хотите посмотреть шаблон отдельно от приложения, то поместите его на какой-либо хост, потому что при открытии шаблона браузером локально не отобразятся изображения и не загрузятся стили, поскольку путь к ним начинается с «/». Скопируйте папку «images» и файл стилей в корень хоста с CI. Теперь откройте Вашим редактором файл index.html, содержащий код шаблона. Ниже я буду приводить куски html-кода для того, чтобы не возникло путаницы, и Вы случайно не вынесли в шаблоны ненужный код. Верхнюю часть, до тега «</head>», нужно вынести в файл header.php и сохранить в папке видов «application/views/» (информация по видам: http://code-igniter.ru/user_guide/general/views.html). Вот она: Code: <HTML> <HEAD> <TITLE>71</TITLE> <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8"> <link rel="stylesheet" href="/styles.css" type="text/css"> </HEAD> Нижней частью (footer.php) будет то что идёт после таблицы отзывов: Code: <table width="100%" border="0" cellspacing="0" cellpadding="0"> <tr> <td background="/images/bgd.gif" height="26"> </td> </tr> </table> <p><img src="/images/str.gif" width=26 height=27 alt="" hspace="20"></p> <p> </p> <a href="http://templatemonster.com"></a><a href="http://inverse-logic.com"></a> </BODY> </HTML> Всё что осталось, а именно центральную часть шаблона, сохраните в папке видов как «answers_list.php». По ходу разработки мы будем вносить изменения в основном в этот шаблон. Также нам нужно создать форму ввода сообщений. Вот её код: Code: <br /><br /> <form action='/guestbook/make_answer/' method='POST' name='answer_form'> <table width="100%" border="0" cellspacing="0" cellpadding="0"> <tr> <td width="81" valign="top"><img src="/images/rc.gif" width=55 height=24 alt=""></td> <td> <span class='stylesbold'>Ваше имя:</span> </td> <td> <input type='text' name='author' value=''/> </td> </tr> <tr> <td width="81" valign="top"><img src="/images/rc.gif" width=55 height=24 alt=""></td> <td> <span class='stylesbold'>E-mail:</span> </td> <td> <input type='text' name='email' value=''/> </td> </tr> <tr> <td width="81" valign="top"><img src="/images/rc.gif" width=55 height=24 alt=""></td> <td> <span class='stylesbold'>Ваш отзыв:</span> </td> <td> <textarea name='text' cols='20' rows='5'></textarea> </td> </tr> <tr> <td width="81" valign="top"><img src="/images/rc.gif" width=55 height=24 alt=""></td> <td colspan='2'> <input type='submit' value='Отправить' /> </td> </tr> </table> </form> Сохраните этот шаблон под именем «answer_form.php». С отображениями разобрались, начнём разработку кода приложения. Создадим основной контроллер (информация по контроллерам: http://code-igniter.ru/user_guide/general/controllers.html) «Guestbook». В нём объявим всего один метод - «index». Этот метод используется контроллерами по умолчанию. То есть если при обращении к приложению в URI не указан вызываемый метод то будет вызван именно «index». В конце всей разработки он будет выводить список имеющихся отзывов, но сейчас введём лишь отображение дизайна. По очереди загрузим шаблоны «header», «answers_list»,«answer_form» и «footer». PHP: class Guestbook extends Controller { function index() { // Обрабатываем шаблоны $this->load->view('header'); $this->load->view('answer_form'); $this->load->view('answers_list'); $this->load->view('footer'); } } Если всё правильно, то при открытии ссылки http://ci/guestbook/ Вы должны увидеть страницу с одним сообщением от пользователя Dmitriy и формой отправки отзывов: Теперь нам нужно сделать этот контроллер загружаемым по умолчанию для того чтобы не приходилось постоянно его явно указывать в URI. Для этого откройте файл «application/config/routes.php» и пропишите имя контроллера в ячейке «default_controller»: PHP: $route['default_controller'] = "Guestbook"; Сохранив изменения пройдите по ссылке http://ci/ и Вы увидите результат, который ранее получался при обращении к адресу http://ci/guestbook/. Сейчас нам нужно поработать с базой данных. Создайте отдельную БД для нашей гостевой книги (я назвал её аналогично хосту - «ci»). В этой базе выполните нижеприведённый SQL-код (он создаст таблицу «guestbook»): Code: CREATE TABLE `guestbook` ( `id` int(11) NOT NULL auto_increment, `author` varchar(50) character set utf8 NOT NULL, `email` varchar(50) character set utf8 NOT NULL, `pub_date` int(11) NOT NULL, `text` text character set utf8 NOT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM AUTO_INCREMENT=0 DEFAULT CHARSET=utf8 PACK_KEYS=0 AUTO_INCREMENT=0 ; Здесь «id» — номер записи в таблице, «author» — автор отзыва, «email» — электронный почтовый адрес автора, «pub_date» — время публикации комментария в формате «unix-time» и «text» — сам отзыв. Также выполните один запрос на вставку данных в таблицу. Это будет единственный отзыв. Code: INSERT INTO `guestbook` VALUES (NULL, 'Anna', '[email protected]', '1230443834', 'This is my first answer!'); Далее настроим подключение к MySQL в конфигурационном файле приложения. Откройте файл «application/config/database.php» и укажите в массиве «db» данные для соединения с БД. Пример: PHP: $db['default']['hostname'] = "localhost"; $db['default']['username'] = "root"; $db['default']['password'] = ""; $db['default']['database'] = "ci"; Остальные ячейки массива «db» нам не нужны. Затем нам нужно организовать автоматическое подключение к базе данных при запуске приложения. Можно несколько раз вызывать это подключение прямо из кода, но намного удобнее, когда оно происходить само при каждом обращении пользователя к приложению. Чтобы это сделать откройте файл «application/config/autoload.php» и в массив $autoload['libraries'], содержащий имена библиотек загружаемых при старте приложения, добавьте ячейку «database»: PHP: $autoload['libraries'] = array('database'); Сейчас обратитесь снова к корню нашего сайта. Если Вы где-то ошиблись, или какие-то данные были не верно указаны в конфигурационном файле, CI выведет ошибку о подключении к БД в тексте которой подробно опишет её причину. Для работы с MySQL у нас всё готово. С этой целью в CI можно использовать класс «Database» предназначенный специально для работы с базами данных (информация по нему: http://code-igniter.ru/user_guide/database/index.html). Сейчас мы создадим модель (информация по моделям: http://code-igniter.ru/user_guide/general/models.html) для обращения к БД. Вообще, нам нужны лишь 3 функции — функция, которая бы брала сообщения из базы (для их показа на главной странице), функция получения общего количества отзывов (понадобится при разбиении на страницы), ну и функция добавления записи в таблицу. Пока мы опишем одну функцию — получения всех сообщений из базы. Для этого создадим пустую модель Querys и сохраним её в папке моделей «application/models/»под именем Querys.php. PHP: class Querys extends Model { } Теперь добавим в неё метод «getLastAnswers()», который будет брать из базы все сообщения и возвращать их в виде массива. PHP: function getLastAnswers() { $sql = "SELECT * FROM guestbook ORDER BY id DESC"; $query = $this->db->query($sql); $answers = Array(); // Используем метод result_array() который возвращает строку результата в виде массива foreach($query->result_array() as $answer) $answers[] = $answer; return $answers; } Подключать модель мы будем при запуске контроллера. Для этого объявим в нём конструктор следующего содержания: PHP: function __construct() { // Вызываем конструктор предка parent::Controller(); // Подключаем модель "Querys" $this->load->model('Querys'); } Обратите внимание на то что мы вызвали конструктор класса-предка (Controller). Делается это из-за того что объявлением собственного конструктора мы перекрываем конструктор предка и поэтому он не выполнится самостоятельно. Для проверки работоспособности модели впишите в метод «index» контроллера дамп результатов работы метода «getLastAnswers()»: PHP: var_dump($this->Querys->getLastAnswers()); Теперь при обновлении страницы в самом верху должен появиться дамп массива содержащий информацию о единственном отзыве. Если всё правильно, то удалите строку дампа, если нет - еще раз проверьте все свои действия. Массив с отзывами мы должны передать только соответствующему шаблону («answers_list»). Для этого в метод «index» перед обработкой шаблонов добавим объявление массива «answers» с полученными ответами: PHP: $answers = $this->Querys->getLastAnswers(); Сформируем массив данных «data» для передачи в шаблон: PHP: $data = Array(); $data['answers'] = $answers; и при обработке шаблона с отзывами передадим эти данные обработчику «view» вторым параметром. PHP: $this->load->view('answers_list',$data); В итоге в методе «index» у Вас должен содержаться следующий код: PHP: // Получаем массив отзывов $answers = $this->Querys->getLastAnswers(); // Создаём массив данных для передачи в шаблон $data = Array(); $data['answers'] = $answers; // Обрабатываем шаблоны $this->load->view('header'); // Передаём в шаблон данные $this->load->view('answers_list',$data); $this->load->view('answer_form'); $this->load->view('footer'); Сделаем небольшое отступление от основной темы. Поскольку мы сейчас будем выводить дату каждого отзыва, а она у нас содержится в формате «unix-time», нужно все эти даты как-то преобразовать в понятный человеку вид. Для этого мы воспользуемся функцией «unix_to_human» хелпера «Date» (информация по хелперам: http://code-igniter.ru/user_guide/general/helpers.html, информация по хелперу «Date»: http://code-igniter.ru/user_guide/helpers/date_helper.html). Саму функцию преобразования мы вызовем в шаблоне, а подключаться к хелперу будем в самом начале метода «index». Для этого в начало кода этого метода добавьте строку: PHP: $this->load->helper('date'); Далее нам стоит изменить шаблон «answers_list» таким образом, чтобы он обрабатывал массив сообщений и выводил их на экран. Делать мы это будем с помощью альтернативного синтаксиса PHP (информация по нему: http://code-igniter.ru/user_guide/general/alternative_php.html). Найдите в шаблоне код самого отзыва: Code: <p> </p> <p align="left" class="stylesbold">Dmitriy/<a class='stylesbold' href='mailto:[email protected]'>email</a> (08:28:10-28.12.2008)</p> <p align="left">This is my answer!</p> Добавим в него обработку массива «answers» с помощью цикла «foreach» и заменим все части отзыва (автор, дата и т.д.) на соответствующие ячейки массива. Code: <? foreach($answers as $answer): ?> <p> </p> <p align="left" class="stylesbold"><?=$answer['author'];?>/<a href='mailto:<?=$answer['email'];?>'>email</a> (<?=unix_to_human($answer['pub_date']);?>)</p> <p align="left"><?=$answer['text'];?></p> <? endforeach; ?> В этом коде на самом деле нет ничего страшного. Здесь мы заменили имя «Dmitriy» на «<?=$answer['author'];?>», почтовый адрес «[email protected]» на «<?=$answer['email'];?>», дату отзыва на «<?=unix_to_human($answer['pub_date']);?>» и текст сообщения на «<?=$answer['text']?>». Да, с виду, конечно, выглядит не очень, но при тщательном рассмотрении Вы увидите что всё легко и понятно, особенно если у Вас редактор с подсветкой кода. Для проверки работоспособности нашего шаблона и метода обратитесь к главной странице. На ней Вы должны увидеть отзыв из базы данных за место того, который был в шаблоне с самого начала. Теперь приступим к написанию метода, добавляющего сообщения от пользователей. Если Вы обращали внимание на код формы отзыва, то наверное заметили что имя этого метода - «make_answer». В нём мы организуем все нужные данные и передадим методу «addAnswer» находящемуся в нашей модели, и его работа будет заключаться во вставке переданной ему информации в таблицу. Для получения внешних данных в CI имеется библиотека input (информация по ней: http://code-igniter.ru/user_guide/libraries/input.html). Для получения значений всех нужных полей мы будем использовать метод «post» этой библиотеки. Методу добавления записи мы будем передавать три параметра — имя автора, его e-mail, текст отзыва. PHP: $author = $this->input->post('author',true); $email = $this->input->post('email',true); $text = $this->input->post('text',true); $this->Querys->addAnswer($author,$email,$text); Вторым параметром методу «post» передаётся «true» для того, чтобы полученные данные сразу прошли фильтрацию от XSS-вставок. Согласитесь - это очень удобно. Какой-либо обработки для усечения попыток sql-инъекций мы пока не делаем, они будут произведены в модели встроенными в CI механизмами. Ну и под конец вызываем из модели функцию добавления записи в базу: PHP: $this->Querys->addAnswer($author,$email,$text); Вот сам код этой функции: PHP: function addAnswer($author,$email,$text) { $pub_date = time(); $sql = "INSERT INTO `guestbook` VALUES(NULL,?,?,?,?)"; $this->db->query($sql,Array($author,$email,$pub_date,$text)); } Как видите, внешние данные передаются методу «query» отдельным массивом. В этом случае все параметры автоматически проходят фильтрацию и обрамляются кавычками, что автоматически делает невозможным проведение sql-инъекций. Ну и добавим в конец кода «make_answer» - вызов метода «index», чтобы сразу после добавления информации вывелся весь список отзывов, включая последний.
PHP: self::index(); Попробуйте добавить ещё один отзыв в нашу гостевую книгу. Теперь стоит «подшлифуем» предыдущую функцию. Сделаем проверку вводимых данных, семантическую обработку текста отзыва и добавим смайлики. Для проверки правильности вводимых пользователем данных мы будем использовать класс валидации (информация по нему: http://code-igniter.ru/user_guide/libraries/validation.html). Для проверки мы будем использовать следующий правила. Для поля «author» - проверку на пустоту и максимальное ограничение в 50 символов. Для поля «email» - проверку на пустоту, ограничение в 50 символов и проверку на правильность введённого адреса. Текст отзыва проверим только на пустоту. Также в правилах для каждого поля укажем обработку их «trim»-функцией. Задаются правила следующим образом. Создаётся массив полей. В каждой ячейке этого массива указываются правила для конкретного поля. Если правил несколько то они перечисляются через знак «|». Вот как это будет выглядеть у нас: PHP: $rules = Array(); $rules['author'] = 'trim|required|max_length[50]'; $rules['email'] = 'trim|required|max_length[50]|valid_email'; $rules['text'] = 'trim|required'; Здесь trim — указывает что нужно обработать содержимое поля этой функцией (так же можно использовать такие функции как md5, htmlspecialchars, strtolower и т.д.) required — полу обязательно должно быть заполнено. max_length[50] — максимальная длинна строки в этом поле — 50 символов. valid_email — данные должны представлять из себя правильный e-mail адрес. Полный список правил и функций-обработчиков Вы можете найти в документации к классу «Validation». Вы наверное заметили что мы не проверяем имя автора на наличие только алфавитных или алфавитно-цифровых символов. Сделано это потому что CI не может работать с русскоязычными символами — он их воспринимает как неалфавитные. Установка правил производится методом «set_rules», ему нужно передать массив наших правил в качестве одиночного. Запуск проверки производится вызовом метода «run». Если этот метод вернёт «true», то все данные совпадают с установленными нами правилами. От этого и будем отталкиваться. Мы проведём валидацию данных и по её результатам будем выполнять добавление запроса. Код метода «make_answer» в итоге должен стать таким: PHP: // Загружаем библиотеку валидации $this->load->library('validation'); // Создаём массив правил $rules = Array(); $rules['author'] = 'trim|required|max_length[50]'; $rules['email'] = 'trim|required|max_length[50]|valid_email'; $rules['text'] = 'trim|required'; // Устанавливаем правила $this->validation->set_rules($rules); // Только если все указанные правила соблюдены добавляем запись if($this->validation->run()) { $author = $this->input->post('author',true); $email = $this->input->post('email',true); $text = $this->input->post('text',true); $this->Querys->addAnswer($author,$email,$text); } // Выводим все отзывы self::index(); Обратите внимание вот ещё на что: если вдруг данные не проходят проверку, то метод «run» не только возвращает «false», но и помещает сообщения с указанием ошибок в свойство «error_string», поэтому для того, чтобы пользователь мог увидеть сообщения о допущенных им ошибках, в начало шаблона «answer_form» добавим следующие строки: Code: <center> <?if(isset($this->validation->error_string)):?> <?=$this->validation->error_string?> <?endif;?> </center> Здесь мы специально проверяем заполненность свойства функцией «isset», потому что при его отсутствии PHP будет выводить ошибку типа «notice». Конечно же, вывод ошибок можно просто отключить (так стоит делать только в полностью рабочих приложениях), но так как у нас тренировочное приложение, то сообщения об ошибках, даже самых незначительных, должны выводиться. Проверим теперь работу наших правил. Отправьте скрипту пустую форму. Если Вы всё сделали правильно, то должны увидеть сообщения «The author field is required.», «The email field is required.» и «The text field is required.» Другие сообщения будут выводиться на иные ошибки. Например если Вы не правильно укажете e-mail, то сообщение будет таким: «The email field must contain a valid email address.». Воспользуемся ещё одной возможностью библиотеки «validation». Если информация из полей не прошла проверку, то при новом отображении формы имеется возможность ввести в её поля то, что вводил до этого пользователь. То есть, если в форме 7 полей, а ошибка лишь в одном, то пользователю не нужно будет заново всё заполнять, а лишь изменить то поле, в котором он допустил ошибку. Это встречается сейчас почти на каждом сайте. В классе валидации для этого используется метод «set_fileds», которому необходимо передать набор полей в виде массива. PHP: // Создаём массив полей $fields = Array(); $fields['author'] = 'author'; $fields['email'] = 'email'; $fields['text'] = 'text'; // Устанавливаем эти поля $this->validation->set_fields($fields); Вставим этот код до запуска проверки методом «run». После проверки данные из этих полей занесутся в свойства класса «validation», откуда могут быть считаны. Теперь нам нужно подредактировать шаблон формы таким образом, чтобы в наших полях отображались введённые данные, если конечно они есть. Для этого замените код поля «author» c Code: <input type='text' name='author' value=''/> на Code: <?if(isset($this->validation->author)):?> <input type='text' name='author' value='<?=$this->validation->author?>'/> <?else:?> <input type='text' name='author' value=''/> <?endif;?> код Code: <input type='text' name='email' value=''/> на Code: <?if(isset($this->validation->email)):?> <input type='text' name='email' value='<?=$this->validation->email?>'/> <?else:?> <input type='text' name='email' value=''/> <?endif;?> и Code: <textarea name='text' cols='20' rows='5'></textarea> на Code: <?if(isset($this->validation->text)):?> <textarea name='text' cols='20' rows='5'><?=$this->validation->text?></textarea> <?else:?> <textarea name='text' cols='20' rows='5'></textarea> <?endif;?> Здесь мы просто за место кода полей ввели выражение типа «если было значение в полях то выводить его». Можно обойтись и полностью без конструкции if, вводя например такой код Code: <input type='text' name='email' value='<?=$this->validation->email?>'/> Но опять же, при обычном обращении к главной странице будет выскакивать notice-ошибка PHP о том, что свойство «$this->validation->email» не установлено. В целях проверки того, что мы сейчас проделали введите любые данные в поля формы и специально допустите какую-нибудь ошибку (например не заполните поле имени). После отправки формы Вы должны увидеть сообщения об ошибке, и поля формы автоматически заполнятся тем, что Вы ввели ранее. Теперь займёмся семантической обработкой поступившего от пользователя текста. Под семантической обработкой подразумевается замена специальных символов (или их комбинаций) на их html-эквиваленты. Например перенос строки «\r\n» заменяется на «<br />», двойной перенос на «<p></p>» и так далее. Для подобных задач в CI имеется хэлпер «Typography» (информация по нему: http://code-igniter.ru/user_guide/helpers/typography_helper.html). Его функция «auto_typography» форматирует передаваемый ей текст для привидения его к семантически правильному виду. Добавим вызов этого хэлпера перед получением значений введённых полей, а сразу после определения переменной «text» вызовем нужную нам функцию: PHP: $text = auto_typography($text); На очереди смайлики. В CI есть очень удобный и полезный хелпер «Smiley» (http://code-igniter.ru/user_guide/helpers/smiley_helper.html). Он отвечает за преобразование текстовых смайликов в картинки. С ним придётся немного повозиться. Скачайте по этой ссылке архив со смайликами: http://codeigniter.com/download_files/smileys.zip и поместите изображения из него в папку «images/smileys». В код конструктора нашего контроллера добавьте загрузку этого хелпера. Мы помещаем его именно в конструктор потому что в методе добавления записи нам нужно будет им воспользоваться чтобы обработать полученный текст, и в методе показа списка ответов — чтобы сделать отображение списка смайлов около формы. Обработка текста производится функцией «parse_smileys». Ей нужно передать 2 параметра — текст, который надо обработать и путь к папке со смайликами. Добавим её вызов в метод «answer_add», после обработки текста функцией «auto_typography». PHP: $text = parse_smileys($text,config_item('base_url').'images/smileys/'); Для получения адреса хоста мы используем функцию «config_item», которая является общей функцией (http://code-igniter.ru/user_guide/general/common_functions.html). Можно было бы использовать для этого специальный класс «Config», но тогда бы строка кода получилась больше: PHP: $text = parse_smileys($text,$this->config->item('base_url').'images/smileys/'); Хотя существенной разницы нет. Вы можете использовать какой угодно вариант, разницы в результате работы от этого не появится. Наконец подключим список смайлов к форме ввода комментария для того чтобы пользователь смог кликом мышки добавить нужный ему смайл. Здесь всё проходит в 2 этапа. На первом эапе в заголовок страницы нужно добавить вызов функции «js_insert_smiley». Ей нужно передать 2 параметра — имя формы (у нас это «answer_form») и имя поля (у нас - «text») куда смайлики будут вставляться. Вызовем её в шаблоне «header» прямо перед закрывающим тегом «head»: Code: <?=js_insert_smiley('answer_form', 'text');?> Эта функция генерирует простой JS-код который отвечает за добавление смайлов в текстовое поле: Code: <script type="text/javascript"> function insert_smiley(smiley) { document.answer_form.text.value += " " + smiley; } </script> Теперь займёмся отображением смайликов около нашей формы. В этом нам поможет функция «get_clicable_ssmileys» которой нужно передать всего один параметр — путь к папке с картинками смайликов. PHP: $image_array = get_clickable_smileys(config_item('base_url').'images/smileys/'); Эта функция возвращает массив соответствий смайликов и кликабельных ссылок на них. После получения этого массива нам нужно как-то обработать его чтобы на выходе получить код таблицы со смайлами. Мы сделаем так чтобы смайлы выстраивались в 2 ряда. Здесь есть 2 варианта — вставить цикл обработки этого массива в шаблон или же воспользоваться библиотекой «HTML Table» (http://code-igniter.ru/user_guide/libraries/table.html) генерирующей HTML-таблицы. Второй вариант намного лучше потому что он проще в реализации. В первом случае нам пришлось бы описывать цикл в шаблоне, добавлять туда код который бы определил когда начать новую строку и т.д. А с вышеупомянутым классом всё намного проще, в чём Вы сами сейчас убедитесь. Впишите его подключение прямо в начале метода «index»: PHP: $this->load->library('table'); Для генерации нашей таблицы требуется вызвать всего 2 метода. Первый из требуемых методов - «make_columns». Он сгенерирует нужные колонки. Нужно передать ему 2 параметра — массив смайлов и число обозначающее их количество на строку. В нашем случае это число 20 потому что смайлов всего 40, а нам нужно чтобы они выстроились в 2 строки. И второй метод - «generate», ему нужно лишь передать результат действия прошлого метода. PHP: // Генерируем код колонок таблицы смайлов $col_array = $this->table->make_columns($image_array, 20); Код получившейся таблицы возвращается методом «generate» PHP: // Генерируем код таблицы смайлов и добавляем его в передающиеся шаблону данные $data['smiley_table'] = $this->table->generate($col_array); Из шаблонов сейчас стала доступна переменная «smiley_table» которая содержит код нужной нам таблицы. Вставим показ этой переменной между полями формы и кнопкой оправки отзыва новой строкой в таблице: Code: <tr> <td width="81" valign="top"><img src="/images/rc.gif" width=55 height=24 alt=""></td> <td colspan='2'><?=$smiley_table?></td> </tr> В итоге прямо под формой у Вас в 2 ряда должны отображаться смайлы. При клике на них в текстовое поле сообщения должны добавляться их текстовые эквиваленты. Попробуйте оставить отзыв со смайликом. Теперь приступим к разбиению отзывов на страницы, а именно — по десять штук на страницу. Для того, чтобы наше разбиение можно было увидеть, оставьте на сайте 11 или более сообщений. Для решения этой задачи воспользуемся библиотекой «Pagination» (информация по ней: http://code-igniter.ru/user_guide/libraries/pagination.html). Итак, чтобы сделать то, что нам нужно, требуется загрузить эту библиотеку, определить минимум три параметра — базовый URI, количество объектов на странице, общее количество объектов в базе, ну и под конец вызвать метод «initialize», которому следует все эти данные передать. После этого код страниц будет возвращаться методом «create_links». Его мы передадим вместе с массивом отзывов шаблону «answers_list» и запишем туда код отображения списка страниц (к слову о 11 отзывах - если страниц будет меньше чем 2, то метод «create_links» не вернёт ничего). Обратите внимание на то, что нам нужно будет передать общее количество данных в базе. Для этого мы сейчас добавим соответствующий метод в нашу модель. Он будет просто возвращать количество записей в таблице. PHP: function getAnswersCount() { $sql = "SELECT COUNT(id) as count FROM guestbook"; $query = $this->db->query($sql); $row = $query->result_array(); return $row[0]['count']; } Теперь займёмся кодом контроллера. Сначала создадим массив данных необходимых для работы библиотеки «Pagination»: PHP: $pagination_config = Array(); $pagination_config['base_url'] = config_item('base_url').'/guestbook/index/'; // Обозначаем общее количество отзывов $pagination_config['total_rows'] = $this->Querys->getAnswersCount(); // Число отзывов на страницу $pagination_config['per_page'] = 10; Обратите внимание на то что мы подставляем в основной URI строку «guestbook/index/». Мы это делаем, потому что в конец этой ссылки будут подставляться данные о странице. Если же мы будем использовать просто обращение к корню сайта (что с точки зрения запросов одинаково), то данные о странице будут восприниматься как имя контроллера, и сервер всегда будет возвращать ответ 404. Теперь нам нужно инициализировать страницы: PHP: $this->pagination->initialize($pagination_config); Ну и под конец добавим код страниц в массив «data», хранящий данные для шаблона: PHP: $data['pages_code'] = $this->pagination->create_links(); В итоге в методе «index» у нас должен получиться следующий код: PHP: // Загружаем библиотеку "Pagination" и библиотеку «HTML Table» $this->load->library(Array('pagination','table')); // Загружаем хелпер "Date" $this->load->helper('date'); // Формируем массив параметров для генерации страниц $pagination_config = Array(); $pagination_config['base_url'] = config_item('base_url').'/guestbook/index/'; // Обозначаем общее количество отзывов $pagination_config['total_rows'] = $this->Querys->getAnswersCount(); // Число отзывов на страницу $pagination_config['per_page'] = 10; // Инициализируем страницы $this->pagination->initialize($pagination_config); // Получаем массив отзывов $answers = $this->Querys->getLastAnswers(); $image_array = get_clickable_smileys(config_item('base_url').'images/smileys/'); // Генерируем код колонок таблицы смайлов $col_array = $this->table->make_columns($image_array, 20); // Создаём массив данных для передачи в шаблон $data = Array(); $data['answers'] = $answers; // Генерируем код таблицы смайлов и добавляем его в передающиеся шаблону данные $data['smiley_table'] = $this->table->generate($col_array); // Добавляем в данные для шаблона код страниц $data['pages_code'] = $this->pagination->create_links(); // Обрабатываем шаблоны $this->load->view('header'); // Передаём в шаблон данные $this->load->view('answers_list',$data); $this->load->view('answer_form'); $this->load->view('footer'); А в сам шаблон «answers_list» нужно добавить всего одну строку в самый конце кода: Code: <center><?=$pages_code;?></center> И всё. Теперь по центру экрана будет отображаться список страниц. Обратите внимание на ссылки, которые указывают на страницы: http://ci/guestbook/index/10 http://ci/guestbook/index/20 http://ci/guestbook/index/30 То есть каждая страница указывается в виде номера, с записи которого надо начать вывод. В соответствии с этим, мы модифицируем наши методы «index» в контроллере и метод «getLastAnswers» в модели. Будем передавать им число полученное из ссылки и с помощью «LIMIT» выводить ограниченное количество записей. Для того, чтобы метод «index» принимал данные из ссылки, укажем, что ему должен передаваться один параметр - «start_limit» (Как осуществляется передача данных в функции из ссылок Вы можете прочитать в разделе «URI в Code Igniter»: http://code-igniter.ru/user_guide/general/urls.html). PHP: function index($start_limit = 0) В самом начале кода мы пропустим это значение через функцию «intval», дабы избежать неприятностей, ведь эти данные пользователь может легко подделать, и доверять им нельзя. PHP: $start_limit = intval($start_limit); Передадим этот параметр методу «getLastAnswers» нашей модели для того, чтобы он поместился в запрос. Для этого заменим строку PHP: $answers = $this->Querys->getLastAnswers(); на PHP: $answers = $this->Querys->getLastAnswers($start_limit); Осталось лишь модифицировать метод «getLastAnswers». Добавим ему параметр «start_limit», который должен передаваться при вызове и заменим строки осуществления запроса с PHP: $sql = "SELECT * FROM guestbook ORDER BY id DESC"; $query = $this->db->query($sql); на PHP: $sql = "SELECT * FROM guestbook ORDER BY id DESC LIMIT ?,10"; $query = $this->db->query($sql,$start_limit); Теперь гостевая книга показывает отзывы в соответствии с номером страницы. Если у Вас возникли какие-то трудности, то готовый код приложения Вы можете взять в файле «gb_sql.zip» который приложен к статье.
2. Перевод модели с базы данных на файлы Отделение модели от контроллера в архитектуре MVC предоставляет разработчикам множество преимуществ. Одно из них — свободное изменение источника данных. То есть если приложение работает с БД, то чтобы перевести его на работу, например, с XML-базой нужно лишь изменить код модели. Главное чтобы её методы возвращали то что раньше, а как они реализованы внутри — приложению не важно. Сейчас мы рассмотрим перевод приложения с одного источника данных на другой на практике, а точнее — на нашей гостевой книге. Мы переведём её с использования MySQL на текстовые файлы. Храниться они у нас будут в папке «data» в корне хоста. Каждая запись будет находятся в отдельном файле. Каждый файл за место имени будет иметь номер записи, то есть это будут файлы без расширения с именами типа 1,2,3 и т.д. Внутри у них будут содержаться те же данные (автор, e-mail, дата публикации и текст), но в виде сериализованного массива. Приступим непосредственно к программированию. При работе с данными нам понадобятся 2 хэлпера - «Directory»(http://code-igniter.ru/user_guide/helpers/directory_helper.html) и «File»(http://code-igniter.ru/user_guide/helpers/file_helper.html). Их подключение мы организуем в конструкторе нашей модели: PHP: function __construct() { parent::Model(); $this->load->helper('Directory'); $this->load->helper('File'); } Рассмотрим первый метод - «getLastAnswers». Алгоритм его работы будет такой. Сначала мы получим массив всех файлов которые есть в директории. Затем мы с помощью цикла отберём 10 отзывов, которые нам нужны, и вернём массив с ними. Ниже я приведу код метода, а после него опишу каждую строку чтобы у Вас не возникло каких-либо вопросов. PHP: function getLastAnswers($start_limit) { // Получаем список файлов в директории $directory = directory_map('./data/',true); // Сортируем имена файлов в порядке убывания rsort($directory); $i = 0; $answers = Array(); foreach($directory as $file) { // Пропускаем столько отзывов сколько нужно $start_limit--; if($start_limit > 0) continue; // Ломаем цикл если счётчик насчитал 10 сообщений if($i == 10) break; // Считываем данные из файла и заносим в массив ответов $data = read_file('./data/'.$file); $answers[] = unserialize($data); // Увеличиваем на 1 счётчик сообщений $i++; } return $answers; } В самом начале с помощью функции «directory_map» helper`a «Directory» мы получаем всё содержимое папки ответов. Так как файлы у нас имеют цифровые имена то мы сортируем результат функцией «rsort» для того чтобы расположить их в массиве в порядке убывания. То есть файлы с последними ответами будут в начале, а с самыми первыми — в конце. Затем мы создаём счётчик сообщений «i». Он будет увеличиваться при обработке каждого следующего сообщения для того чтобы мы могли контролировать выводимое количество сообщений — как только обработано 10 штук сразу останавливаем цикл. Далее мы циклом «foreach» обрабатываем массив с содержимым директории. В начале цикла помещён код который уменьшает переменную «start_limit». Сделано это вот зачем. Например пользователь пройдёт на вторую страницу гостевой книги и тогда в «start_limit» будет лежать число 10, означающее что нам нужно пропустить 10 отзывов которые были на первой странице и только потом вывести следующий десяток. При начале цикла «start_limit» уменьшается на единицу, и если он больше нуля цикл начинается сначала. То есть пока цикл не сработает в холостую 10 раз выборка сообщений не начнётся. Так мы заменили SQL-конструкцию «LIMIT». Дальше происходит проверка счётчика сообщений — если он равен 10 (в массив помещено 10 сообщений) то цикл прекращает работу с помощью оператора «break» и функция возвращает массив результатов. Если же нет, то происходит считывание следующего файла с сообщением, его разсериализация и помещение в массив сообщений. Перейдём к функции «getAnswersCount». В ней мы просто будем получать количество файлов в директории. Это будет выполнять функция «directory_files_count», которую мы добавим в хелпер «Directory». Создайте файл «MY_Directory_helper.php» в папке «application/helpers/» и поместите в нём следующий текст: PHP: <?php function directory_files_count($source) { return count(directory_map($source)); } ?> Этим мы расширили функционал хэлпера «Directory». Функция «direactory_map» возвращает содержимое указанной директории в виде массива, а наша функция просто возвратит количество записей в этом массиве. Теперь, в функции получения количества ответов нам нужно просто вызвать «directory_files_count» и вернуть её результат: PHP: function getAnswersCount() { return directory_files_count('./data/'); } Возьмёмся за последнюю функцию — «addAnswer». Алгоритм её работы прост — сначала мы организуем массив данных сообщения (автор, текст и т.д.). Затем определяем номер последнего отзыва, увеличиваем его на единицу, тем самым получая имя следующего файла, и записываем в этот файл сериализованный массив с ранее сформированныи данными. PHP: function addAnswer($author,$email,$text) { $pub_date = time(); // Формируем массив пришедших к нам данных $answer = Array(); $answer['author'] = $author; $answer['email'] = $email; $answer['pub_date'] = $pub_date; $answer['text'] = $text; // Получаем список файлов из папки // А фактически - массив чисел $files = directory_map('./data/',true); // Получаем максимальное число из этого массива // (последний отзыв) $last_file = @max($files); // Прибавляем к нему единицу, чем получаем // номер следующего комментария $next_file = intval($last_file)+1; // Ну и записываем файл write_file('./data/'.$next_file,serialize($answer)); } Вас может удивить значёк «@» у функции «max» и пропуск переменной «last_file» через функцию «intval». Дело в том что если в гостевой книге нет ни одного отзыва то не будет и списка чисел, который мы должны передать функции «max». Следовательно она выдаст ошибку. чтобы этой ошибки не вывелось мы и подставили к ней знак «@». При тех же условиях функция «max» ничего не вернёт в переменную «last_file», поэтому для того чтобы пустоту преобразовать в 0 (нам ведь нужно число т.к. мы его потом увеличиваем на единицу) мы используем функцию «intval». Вот и всё. Модель нами перенесена полностью на текстовые файлы. Можете сами в этом убедится обратившись к приложению. Полностью рабочий код этой части статьи Вы можете взять в файле «gb_gile.zip»
3. Фотогаллерея. Следующая часть статьи будет посвящена написанию фотогаллереи. Функционал у неё будет тоже не очень большой, но по количеству рассмотренных инструментов, вместительный. В нашей фотогаллерее будет следующее. Пользовательская часть будет позволять просматривать списки фотоальбомов и их содержимое. Так же нами будет написана имитация рейтинговых оценок альбомов. В админ-части у нас будет авторизация, возможность добавления/удаления фотоальбомов. Возможность их редактирования, включающая в себя добавление в альбом фотографии как по одиночке так и пакетно (архивами). Так же будет возможность архивного бэкапа всех фотографий. В отличие от предыдущего примера мы будем использовать для обработки видов шаблонизатор (Templater parser — http://code-igniter.ru/user_guide/libraries/parser.html). Нужно сказать что его функционал очень скуден (по сравнению с использованием альтернативного синтаксиса PHP в отображениях) и рассмотреть его я решил только потому что это часть инструментария CI. Хотя здесь жаловаться не на что, разработчики в документации чётко написали - «Класс Template Parser не полнофункциональный шаблонизатор. Мы специально оставили его очень простым, чтобы сохранить максимум производительности.». Для обращений же к базе данных мы теперь будем использовать не прямые запросы и метод «query», а паттерн «Active Record» (http://code-igniter.ru/user_guide/database/active_record.html). Он очень прост и удобен в работе, а так же позволяет максимально сэкономить и время разработчика и количество кода. В добавок ко всему интерфейс AR интуитивно понятный и разобраться в чужом коде (работающем с AR) можно даже без документации. Так же, как я писал выше, мы поработаем с ZIP-архивами (создание/распаковка). Пока что в арсенале CI нет функций для извлечения ZIP-архивов, поэтому мы немного дополним его библиотекой PclZip, но всему своё время. Возьмёмся за начальные приготовления. Как я уже писал в начале статьи — для нового приложения мы будем использовать «чистую» копию CI. В корне приложения создайте папку «photos» - в ней будут храниться фотографии из наших альбомов. Шаблон для этого примера у нас останется тот же. Оставьте в корне сайта файл стилей и директорию «images». В папку видов скопируйте прошлые шаблоны «header» и «footer», только удалите вызов функции добавления смайлов в шаблоне «header». Так же оставьте в этой папке файл «.htaccеss» который мы рассматривали в самом начале статьи. В уже созданной в первом примере базе данных выполните следующий код. Code: CREATE TABLE `albums` ( `id` int(11) NOT NULL auto_increment, `name` varchar(100) character set utf8 NOT NULL, `t_name` varchar(100) character set utf8 NOT NULL, `about` text character set utf8 NOT NULL, `rating` int(11) NOT NULL, PRIMARY KEY (`id`), KEY `t_name` (`t_name`) ) ENGINE=MyISAM AUTO_INCREMENT=0 DEFAULT CHARSET=utf8 PACK_KEYS=0 AUTO_INCREMENT=0 ; INSERT INTO `albums` VALUES (NULL, 'First album', 'first_album', 'This is text about the first our album!', 0); INSERT INTO `albums` VALUES (NULL, 'Second album', 'second_album', 'This is text about the second album!', 0); INSERT INTO `albums` VALUES (NULL, 'Third album', 'third_album', 'This is text about the third album!', 0); Это таблица содержащая данные фотоальбомов. Имеет она 5 полей. Поле «id» содержит порядковый номер альбома, «name» - его название, поле «t_name» название альбома в транслите (оно будет использоваться нами для ЧПУ), «about» - описание альбома и «rating» - его рейтинг. Вторая таблица будет содержать информацию о фотографиях, содержащихся в этих альбомах. Code: CREATE TABLE `photos` ( `id` int(11) NOT NULL auto_increment, `album_id` int(11) NOT NULL, `file_name` varchar(50) character set utf8 default NULL, PRIMARY KEY (`id`), KEY `album_id` (`album_id`) ) ENGINE=MyISAM AUTO_INCREMENT=0 DEFAULT CHARSET=utf8 PACK_KEYS=0 AUTO_INCREMENT=0 ; Тут всё ещё проще. Поле «id» содержит порядковый номер фотографии, «album_id» - номер альбома которому эта фотография принадлежит и «file_name» - имя файла фотографии. Для настройки подключения к MySQL откройте файл «application/config/database.php» и впишите те данные которые обсуждались выше. Далее откройте файл «application/config/autoload.php» и добавьте библиотеку «database» в автозагрузку. После этого откройте файл «application/config/config.php» и замените содержимое ячейки «base_url» на имя вашего хоста. Перейдём к созданию администраторской панели. Все её контроллеры будут содержаться в директории «admin», находящейся в папке контроллеров. Никаких трудностей при доступе к ним это не создаст, нужно будет только в URI перед именем контроллера указывать имя этой директории. Подробнее о размещении контроллеров в поддиректориях Вы можете почитать в соответствующем разделе документации (http://code-igniter.ru/user_guide/general/controllers.html), под-раздел «Организация ваших контроллеров в подпапках». Первый контроллер который мы создадим - «Albums». Из названия видно что он будет отвечать за работу с альбомами. Установите его в файле «application/config/routes.php» контроллером по умолчанию как в предыдущем примере. И для пользовательской и для администраторской части главные контроллеры будут называться «Albums». Пусть он пока побудет пустой. Его метод «index» в будущем должен отображать список имеющихся у нас фотоальбомов. Что бы этот список получить нам нужно создать модель. Назовём её «Albums_model» и напишем в ней функцию «getAlbumsArray». Она просто будет выбирать все записи из таблицы «albums» и возвращать результат в виде массива. Для выборки данных в «Active Record» есть метод «get» который получает всё (если не указаны конкретные поля) из указанной таблицы. PHP: function getAlbumsArray() { $albums = Array(); // Берём все данные из таблицы `albums` $query = $this->db->get('albums'); // Для результата используем всё ту же функцию result_array // возвращающую запись в виде ассоциативного массива. foreach($query->result_array() as $album) $albums[] = $album; return $albums; } Добавим загрузку этой модели в конструктор контроллера «Albums». А функцию «getAlbumsArray» мы вызовем в методе «index» и присвоим возвращённое значение переменной «albums». Так же после вызова вышеупомянутой функции добавьте строку дампа переменной — так мы проверим всё ли правильно работает. PHP: class Albums extends Controller { function __construct() { parent::Controller(); $this->load->model('Albums_model'); } function index() { $albums = $this->Albums_model->getAlbumsArray(); var_dump($albums); } } Если при обращении к странице видно дамп пассива с тремя ячейками то всё нормально, удалите строку дампа и приступим к работе с видами. Как я уже писал ранее, для отображения шаблонов мы будем использовать встроенный парсер. Для того что бы не вызывать его каждый раз в ручную, откройте конфигурационный файл автозагрузки и в список загружаемых автоматически библиотек впишите класс «parser». Теперь нужно создать шаблон для отображения списка альбомов. Ему мы передадим наш массив полученный от функции «getAlbumsArray». Так как это массив двумерный то его придётся обработать с помощью парных переменных (про них можно прочитать в документации к парсеру). Code: <BODY BGCOLOR=#FFFFFF LEFTMARGIN=0 TOPMARGIN=0 MARGINWIDTH=0 MARGINHEIGHT=0> <table width="100%" border="0" cellspacing="0" cellpadding="0"> <tr> <td height="137" valign="top" background="/images/bgt.gif"> <table width="100" border="0" cellspacing="0" cellpadding="0"> <tr> <td><img src="/images/head.jpg" width=378 height=96 alt=""></td> <td><img src="/images/thead.gif" width=372 height=31 alt=""><br> <img src="/images/name.jpg" width=372 height=27 alt=""><br> <a href=" "><img src="/images/slogan.jpg" width=372 height=18 border=0 alt=""><br> </a><img src="/images/index_05.jpg" width=372 height=20 alt=""></td> </tr> </table> </td> </tr> </table> <table width="750" border="0" cellspacing="0" cellpadding="0" class="styles"> <tr> <td width="81" valign="top"><img src="/images/rc.gif" width=55 height=24 alt=""></td> <td width="669"> {albums} <p> </p> <p align="left"> <a class='stylesbold' href='/admin/albums/album_edit/{id}/'>{name}</a> / <a class='stylesbold' href='/admin/albums/album_del/{id}/'>Удалить</a> </p> <p align="left">{about}</p> {/albums} </td> </tr> </table> Для хранения шаблонов админ-панели создайте в директории видов папку «admin». В ней сохраните этот шаблон под именем «admin_albums_list.php». В контроллер, в код метода «index», впишем формирование массива «data» для передачи данных шаблону и вызов трёх отображений — «header», «footer» и «admin_albums_list». PHP: // Формируем данные для шаблона $data = Array(); $data['albums'] = $albums; // Обрабатываем шаблоны $this->parser->parse('header',Array()); $this->parser->parse('admin/admin_albums_list',$data); $this->parser->parse('footer',Array()); Обратимся снова по ссылке http://ci/admin/albums/ . Там должна отобразиться страница со списком альбомов из базы, и с ссылками на их редактирование/удаление. Вы, наверное, озадачены тем что шаблонам «header» и «footer» передаются пустые массивы вторым параметром. Дело в том что если второй параметр вообще не передавать то парсер начинает выдавать список ошибок. Конечно это неудобно, но легко исправляется. Если Вы в будущем будете использовать «Tempalte parser», то для того чтобы не указывать каждый раз за место данных пустой массив замените строку 45 в файле «libraries/Parser.php» c PHP: function parse($template, $data, $return = FALSE) на PHP: function parse($template, $data = Array(), $return = FALSE) и всё. Я же в этой статье буду передавать каждый раз пустой массив потому что планов по вторжению в код CI у меня не было с самого начала и из изменений инструментария CI я описываю только расширение функционала. Добавим к списку альбомов форму добавления нового, вот её код. Code: <br /><br /> <form action='/admin/albums/album_add/' method='POST'> <table width="100%" border="0" cellspacing="0" cellpadding="0"> <tr> <td width="81" valign="top"><img src="/images/rc.gif" width=55 height=24 alt=""></td> <td> <span class='stylesbold'>Название альбома:</span> </td> <td> <input type='text' name='name' value=''/> </td> </tr> <tr> <td width="81" valign="top"><img src="/images/rc.gif" width=55 height=24 alt=""></td> <td> <span class='stylesbold'>Его описание:</span> </td> <td> <textarea name='about' cols='20' rows='5'></textarea> </td> </tr> <tr> <td width="81" valign="top"><img src="/images/rc.gif" width=55 height=24 alt=""></td> <td colspan='2'> <input type='submit' value='Добавить' /> </td> </tr> </table> </form> Сохраните эту форму под именем «admin_album_add_form.php» в папке где хранятся шаблоны админ-части и добавьте её обработку перед обработкой вида «footer». Следующим шагом проработаем удаление. Из кода отображения списка альбомов видно что он называется «album_del» и передаётся ему через URI один параметр — номер альбома в базе. В этом методе мы просто вызовем запрос на удаление и после него вызовем метод «index», чтобы он отобразил список оставшихся альбомов. Сам запрос на удаление мы разместим в модели. Назовём его «delAlbumQuery», ему нужно будет передать лишь один параметр — номер альбома который требуется удалить. Запрос к базе мы совершим с помощью метода «delete», которому нужно первым параметром передать имя таблицы, из которой удаляется строка, а вторым массив условий в форме «поле => значение». PHP: function delAlbumQuery($album_id) { $this->db->delete('albums',Array('id'=>$album_id)); } Ну и сам код функции «album_del» в контроллере. PHP: function album_del($album_id) { $album_id = intval($album_id); // Вызываем функцию удаления $this->Albums_model->delAlbumQuery($album_id); self::index(); } Для проверки попробуйте теперь удалить один из существующих альбомов. Далее нам нужно заняться функцией добавления фотоальбомов. К ней будет отправлять данные наша форма. Метод который нам нужен называется album_add. В нём у нас будут формироваться данные нового альбома и передаваться в модель, методу который будет эти данные вставлять. На этапе контроллера нам нужно сформировать следующие данные — имя альбома, его транслитерационное имя для ЧПУ и описание. Первое и третье свойства можно свободно получить, а вот для того что бы получить имя альбома в транслите мы напишем отдельную функцию которую сделаем расширением хэлпера «String». В папке «application/helpers» создайте файл «MY_string_helper.php» и впишите в него одну функцию - «translit». Вот её код: PHP: function translit($str) { $upper = Array( '#А#','#Б#','#В#','#Г#','#Д#','#Е#','#Ё#','#Ж#','#З#','#И#', '#Й#','#К#','#Л#','#М#','#Н#','#О#','#П#','#Р#','#С#','#Т#', '#У#','#Ф#','#Х#','#Ц#','#Ч#','#Ш#','#Щ#','#Ъ#','#Ы#','#Ь#', '#Э#','#Ю#','#Я#'); $lower = Array( 'а','б','в','г','д','е','ё','ж','з','и','й','к','л','м','н','о','п','р','с','т', 'у','ф','х','ц','ч','ш','щ','ъ','ы','ь','э','ю','я'); $str = preg_replace($upper,$lower,$str); $from = Array( '#а#','#б#','#в#','#г#','#д#','#е#','#ё#','#ж#','#з#','#и#', '#й#','#к#','#л#','#м#','#н#','#о#','#п#','#р#','#с#','#т#', '#у#','#ф#','#х#','#ц#','#ч#','#ш#','#щ#','#ъ#','#ы#','#ь#', '#э#','#ю#','#я#'); $to = Array( "a", "b", "v", "g", "d","e", "yo", "zh", "z", "i","j", "k", "l", "m", "n", "o", "p", "r", "s", "t","u", "f", "h", "c", "ch", "sh", "sh", "", "y", "","je", "ju", "ja"); $str = preg_replace($from,$to,$str); $str = str_replace(' ','_',$str); $str = str_replace('+','_',$str); $str = str_replace('-','_',$str); $str = preg_replace('#[^0-9a-z_]#mi','',$str); $str = trim($str); return $str; } Думаю код в комментариях не нуждается, несмотря на размеры он очень простой. Вот через эту функцию мы и пропустим русскоязычное имя альбома что бы получить его в транслитерационном виде. Добавлением данных в базу будет заниматься метод «addAlbumQuery» модели. Ему мы передадим все 3 параметра которые формируются в контроллере. Для вставки мы используем метод «insert» которому требуется передать первым параметром имя таблицы, а вторым массив полей вставляемой записи и их значений. PHP: function addAlbumQuery($name,$t_name,$about) { // Формируем массив полей и их значений $fields_array = Array( 'id'=>'', 'name' =>$name, 't_name'=>$t_name, 'about' =>$about, 'rating'=>0 ); $this->db->insert('albums',$fields_array); } Сам же код функции album_add достаточно прост: PHP: function album_add() { // Загружаем расширенный нами хэлпер "string" $this->load->helper('string'); // Формируем массив данных для добавления $name = $this->input->post('name',true); $t_name = translit($name); $about = $this->input->post('about',true); // Вызываем метод добавления $this->Albums_model->addAlbumQuery($name,$t_name,$about); // Выводим список альбомов, там уже присутствует новый альбом self::index(); } Попробуйте добавить новый альбом и заглянув в БД убедитесь что все данные сформированы правильно. Следующая на очереди функция редактирования альбома. Как раз при редактировании мы сделаем добавление фотографий. Вообще на странице редактирования будут отображаться следующие данные — имя альбома, описание, количество фотографий в нём и сами фотографии. Каждую фотографию можно будет по отдельности удалить. Форма редактирования свойств альбомов будет точь-в-точь как форма добавления новых, а после неё уже будет отдельная форма с 2 полями для загрузки фотографий — по отдельности и архивом. Прежде всего напишем функции модели необходимые для редактирования. Первая из них должна получать данные указанного альбома. Она очень проста. PHP: function getAlbumData($album_id) { $this->db->where('id',$album_id); $query = $this->db->get('albums'); $row = $query->result_array(); return $row['0']; } В первой строке мы используем метод where для создания условия к SELECT-запросу. Ему нужно лишь передать имя поля и его значение, которое требуется использовать при выборке. Следующим шагом мы напишем метод «album_edit» в контроллере «Albums». Для начала он будет просто получать данные с помощью вышеописанной функции и выводить их в браузер. В коде этого метода нет ничего сложного, мы просто получим все данные требуемого альбома по его номеру и передадим их обработчику при отображении видов. PHP: function album_edit($album_id) { $album_id = intval($album_id); // Получаем данные альбома $album = $this->Albums_model->getAlbumData($album_id); // Обрабатываем шаблоны $this->parser->parse('header',Array()); $this->parser->parse('admin/admin_album_edit_form',$album); $this->parser->parse('footer',Array()); } Из кода видно что методу должен быть передан номер редактируемого альбома. Если Вы обращали внимание на код отображения их списка, то должны были заметить что `id` альбома передаётся этому методу при клике на ссылку. http://ci/admin/albums/album_edit/1/ http://ci/admin/albums/album_edit/2/ http://ci/admin/albums/album_edit/3/ Теперь нам осталось лишь создать вид формы редактирования. Code: <table width="100%" border="0" cellspacing="0" cellpadding="0"> <tr> <td height="137" valign="top" background="/images/bgt.gif"> <table width="100" border="0" cellspacing="0" cellpadding="0"> <tr> <td><img src="/images/head.jpg" width=378 height=96 alt=""></td> <td><img src="/images/thead.gif" width=372 height=31 alt=""><br> <img src="/images/name.jpg" width=372 height=27 alt=""><br> <a href=" "><img src="/images/slogan.jpg" width=372 height=18 border=0 alt=""><br> </a><img src="/images/index_05.jpg" width=372 height=20 alt=""></td> </tr> </table> </td> </tr> </table> <center>Редактирование альбома "{name}"</center> <br /><br /> <form action='/admin/albums/album_save/{id}/' method='POST'> <table width="100%" border="0" cellspacing="0" cellpadding="0"> <tr> <td width="81" valign="top"><img src="/images/rc.gif" width=55 height=24 alt=""></td> <td> <span class='stylesbold'>Название альбома:</span> </td> <td> <input type='text' name='name' value='{name}'/> </td> </tr> <tr> <td width="81" valign="top"><img src="/images/rc.gif" width=55 height=24 alt=""></td> <td> <span class='stylesbold'>Его описание:</span> </td> <td> <textarea name='about' cols='20' rows='5'>{about}</textarea> </td> </tr> <tr> <td width="81" valign="top"><img src="/images/rc.gif" width=55 height=24 alt=""></td> <td colspan='2'> <input type='submit' value='Сохранить' /> <br /><br /> </td> </tr> </form> </table> После этого при клике на имя альбома Вы должны попадать на форму его редактирования. Далее мы добавим к этой форме информацию о количестве фотографий в фотоальбоме. Для этого в модели создадим простой метод. Он будет просто производить подсчёт записей в таблице с помощью функции «COUNT» в SQL-запросе. PHP: function getPhotosCount($album_id) { $this->db->select('COUNT(id) as count'); $query = $this->db->get('photos'); $row = $query->result_array(); return $row[0]['count']; } Используемый здесь метод «select» нужен для указания списка полей используемых при выборке. Но указываем мы не одно поле а фуникцю с выражением «as», для того что бы извлекать результат не из обычного а из ассоциативного массива, что намного удобнее. Обратимся вновь к контроллеру. В метод редактирования мы просто добавим одну строку после получения всех данных о альбоме из базы. PHP: $album['photos_count'] = $this->Albums_model->getPhotosCount($album_id); А в шаблон добавим html-код который отобразит новый параметр «photos_count». Это будет новая строка таблицы, вставьте её между полем описания и кнопкой сохранения. Code: <tr> <td width="81" valign="top"><img src="/images/rc.gif" width=55 height=24 alt=""></td> <td> <span class='stylesbold'>Количество фотографий в альбоме:</span> </td> <td> <span class='stylesbold'>{photos_count}</span> </td> </tr>
Для сохранения данных из формы мы создадим метод «album_save» в контроллере и «saveAlbumQuery» в модели. Ничего сложного в них не будет. Первый получит 2 поля от пользователя и передаст второму, который в свою очередь вставит эти данные в БД. PHP: function album_save($album_id) { $album_id = intval($album_id); // Получаем нужные поля $name = $this->input->post('name',true); $about = $this->input->post('about',true); // Передаём их на изменение $this->Albums_model->saveAlbumQuery($album_id,$name,$about); // Вызываем функцию редактирования изменённого альбома self::album_edit($album_id); } PHP: function saveAlbumQuery($album_id,$name,$about) { // Формируем данные для обновления $data = Array( 'name'=>$name, 'about'=>$about ); // Добавляем условие "WHERE id = '$album_id'" $this->db->where('id',$album_id); // Обновляем таблицу `albums` $this->db->update('albums',$data); } Проверьте их работоспособность. Общая часть редактирования готова. Теперь стоит добавить в шаблон форму загрузки файлов с двумя полями- для единичных фотографий и для архивов с ними. Не закрывая таблицу вставьте туда ещё одну форму. Code: <form action='/admin/photos/photo_add/{id}/' method='POST' enctype='multipart/form-data'> <tr> <td width="81" valign="top"><img src="/images/rc.gif" width=55 height=24 alt=""></td> <td> <span class='stylesbold'>Добавить фотографию:</span> </td> <td> <span class='stylesbold'><input name='photo' type='file' /></span> </td> </tr> <tr> <td width="81" valign="top"><img src="/images/rc.gif" width=55 height=24 alt=""></td> <td> <span class='stylesbold'>Добавить архив фотографий:</span> </td> <td> <span class='stylesbold'><input name='archive' type='file' /></span> </td> </tr> <tr> <td width="81" valign="top"><img src="/images/rc.gif" width=55 height=24 alt=""></td> <td colspan='2'> <input type='submit' value='Добавить' /> </td> </tr> </form> Данные она будет передавать контроллеру «photos». Создадите его с пустым методом «photo_add». PHP: class Photos extends Controller { function photo_add($album_id) { } } В этом методе мы будем получать данные от формы, производить загрузку требуемых файлов и записывать в базу данные загруженных изображений. Сначала мы напишем загрузку фотографий по отдельности, а под конец и архивом. Но перед этим создадим модель «Photos_model» которая будет содержать метод регистрации фотографии в базе. В нем мы сначала сформируем нужные для вставки данные, а затем вставим с помощью метода «insert». Называться этот метод будет «insertPhoto» и передавать ему нужно будет 2 параметра — имя файла фотографии и номер альбома, которому она принадлежит. PHP: function insertPhoto($file_name,$album_id) { // Формируем массив данных для вставки $data = Array( 'id' => '', 'album_id' => $album_id, 'file_name'=> $file_name ); $this->db->insert('photos',$data); } Примемся за контроллер. Сначала добавим в него загрузку нашей модели в конструкторе. PHP: function __construct() { parent::Controller(); $this->load->model('Photos_model'); } В методе «photo_add» мы с помощью библиотеки «File upload» загрузим фотографию в папку «photos», зарегистрируем новое изображение в базе, и с помощью функции «redirect», хэлпера «URL», мы вернём пользователя обратно к редактированию альбома. Обратите внимание на то что для загрузки файлов библиотеке «File upload» нужно передать 2 обязательных парметра — путь загрузки и список допустимых расширений. Список расширений передаётся не в виде массива а в формате «расширение|расширение|расширение». В этот раз мы инициализируем настройки класса немного по другому. Возьмём прошлый пример с использованием класса «Pagination». Сначала мы загрузили библиотеку, потом сформировали данные конфигурации для неё и только потом, с помощью метода «initialize» загрузили в неё эти данные. Сейчас же мы передадим конфигурационные данные вторым возможным способом — при загрузке библиотеки. Для этого их нужно вторым параметром передать методу «library». При таком способе у нас не будет нужды писать строку с вызовом метода «initialize». PHP: // Формируем настройки для класса "File Upload" $upload_config['upload_path'] = './photos/'; $upload_config['allowed_types'] = 'gif|jpg|png'; // Передаём их при загрузке библиотеки $this->load->library('upload',$upload_config); Непосредственно загрузка осуществляется методом «do_upload» которому нужно передать имя поля для выбора файла. У нас их 2 — «photo» и «archive», но пока мы будем работать лишь с первым. Если загрузка пройдёт успешно то мы запишем информацию о загруженном файле в базу. В конце мы просто перенесём пользователя обратно на редактирование альбома. PHP: function photo_add($album_id) { $album_id = intval($album_id); $this->load->helper('url'); // Формируем настройки для класса "File Upload" $upload_config['upload_path'] = './photos/'; $upload_config['allowed_types'] = 'gif|jpg|png'; // Передаём их при загрузке библиотеки $this->load->library('upload',$upload_config); // Пробуем загрузить фото if($this->upload->do_upload('photo')) { // Получаем данные загруженного файла $file_data = $this->upload->data(); // Регистрируем фотографию в базе $this->Photos_model->insertPhoto($file_data['file_name'],$album_id); } // Возвращаем пользователя к редактированию альбома. redirect("/admin/albums/album_edit/{$album_id}/"); } Если Вы попытаетесь сейчас загрузить фотографию то заметите что после загрузки URL, на который Вас перенесло, содержит «index.php». Функция «redirect» сама его подставляет. Что бы он не заносился в URL при редиректах откройте файл «application/config.php» и сотрите «index.php» в параметре «index_page», оставив там пустую строку. Теперь нам нужно написать отображение всех, существующих в альбоме, фотографий. Исходными данными для этого будет массив номеров фотографий текущего альбома, полученный из БД. Его формированием будет заниматься метод «getPhotosArray». PHP: function getAlbumPhotosArray($album_id) { // Указываем что из всех колонок // таблицы нам нужна только одна - id $this->db->select('id'); // Устанавливаем WHERE-условие $this->db->where('album_id',$album_id); $query = $this->db->get('photos'); // Обрабатываем результат $photos_ids = Array(); foreach($query->result_array() as $row) $photos_ids[] = Array('photo_id'=>$row['id']); return $photos_ids; } Вас может удивить код PHP: $photos_ids[] = Array('photo_id'=>$row['id']); Формирование массива для каждого номера нужно для последующей правильной обработки основного массива(«photos_ids») в шаблоне. А в контроллер мы добавим всего одну строку которая будет создавать новую ячейку в массиве информации о альбоме: PHP: $album['photos'] = $this->Albums_model->getAlbumPhotosArray($album_id); Теперь стоит немного подправить шаблон редактирования и вставить туда код отображения картинок. Сделаем это сразу после всех форм. Code: <table width='100%' align='center'> <tr> {photos} <td> <img src='/admin/photos/photo_view/{photo_id}/150/' /><br /><br /> <a href='/admin/photos/photo_del/{photo_id}/{id}/'>Удалить</a> </td> {/photos} </tr> </table> Если Вы сейчас посмотрите на получившийся список фотографий то ничего не обнаружите потому что у нас нет метода обработки изображений. Для работы с изображениями в CI имеется библиотека «image_lib» (http://code-igniter.ru/user_guide/libraries/image_lib.html). Список возможностей у неё очень большой, но мы рассмотрим лишь изменение размера изображений. Из шаблона видно что метод, на который ссылается тег «img», называется «photo_view». В нём мы будем изменять размер требуемого изображения и выдавать его в браузер. Для нормальной работы методу будут передаваться 2 параметра — номер фотографии в базе и число 150. Это ширина будущего изображения. Высота же будет генерироваться относительно ширины. Так как методу изменения размера изображения нужно передавать имя файла, а у нас на входе имеется только её номер в базе, то придётся расширить функционал модели «Photos_model». В ней мы создадим функцию получающую данные из базы о фотографии по её номеру. Код её достаточно прост, и в комментариях врятли нуждается. PHP: function getPhotoData($photo_id) { // Установка условия $this->db->where('id',$photo_id); $query = $this->db->get('photos'); $row = $query->result_array(); return $row[0]; } В самом методе отображения мы сначала получим данные из базы, потом укажем нужные для ресайза данные и с помощью функции «resize» изменим размер изображения. При инициализации библиотеки «image_lib» нужно указать список параметров которые будут использоваться при работе с изображением. У нас этот список будет следующий: source_image — полный путь к фотографии. maintain_ratio — будет установлен в «true» для того что бы при изменении размеров соблюдались пропорции. width — ширина будущего изображения. height — высота будущего изображения. Если её не указать то в изображении изменится только ширина, невзирая на сохранении пропорций. dynamic_output — будет установлен в «true» для того что бы результат работы выводился в браузер сразу. Иначе изображение будет сохранено на жёстком диске. PHP: function photo_view($photo_id,$width = 100) { $photo_id = intval($photo_id); $width = intval($width); $this->load->library('Image_lib'); // Получение данных из БД $photo = $this->Photos_model->getPhotoData($photo_id); // Составляем полный путь к картинке из корня приложения $path = './photos/'.$photo['file_name']; // Формируем данные для изменения размера $config['source_image'] = $path; $config['maintain_ratio'] = true; $config['width'] = $width; $config['height'] = 100; $config['dynamic_output'] = TRUE; $this->image_lib->initialize($config); // Изменяем размер и выводим в браузер результат $this->image_lib->resize(); } Вот и всё. Теперь при просмотре формы редактирования фотоальбома Вы будете видеть уменьшенные копии изображений хранящихся в нём. Время поработать над пакетной закачкой. В базовом наборе CI есть библиотека «Zip Encoding», но она ориентирована лишь на создание архивов, а не на их распаковку. Для того что бы научить CI распаковывать архивы мы подключим к нему библиотеку PclZip. Вы можете скачать её последнюю версию здесь: http://www.phpconcept.net/pclzip/index.en.php . Сохраните скачанный файл «pclzip.lib.php» в папке пользовательских классов «application/libraries», но с именем «Pclzip.php». Затем откройте этот скрипт. В начале файла Вы увидите большое количество констант устанавливаемых для работы данной библиотеки. Что бы соответствовать стандартам перенесём всё объявление констант в файл «application/config/constants.php», тут содержаться константы объявляемые при старте CI. В файле библиотеки должен остаться только код класса (объявление единственной внеклассовой переменной «g_pclzip_version» можно просто удалить). Теперь переименуйте имя класса и конструктора из «PclZip» в «Pclzip». И последним нашим действием замените в конструкторе объявление свойства «zipname» с PHP: $this->zipname = $p_zipname; на PHP: $this->zipname = $p_zipname['p_zipname']; Это нужно вот зачем. Конструктор PclZip требует что бы при создании объекта (а он создастся сразу после загрузки библиотеки) был передан параметр «p_zipname», содержащий путь к архиву с которым мы будем работать. Данные конструктору любой библиотеки в CI передаются при её загрузке массивом (мы работали так с библиотекой «File upload» выше, передавая конструктору массив «upload_config»). Под массив параметров в конструкторе приспособлены в CI все библиотеки, а вот PclZip ждёт в конструкторе строку. Что бы всё нормально работало мы в конструктор передадим массив, а потом имя файла просто достанем из ячейки «p_zipname». Будьте внимательны с этим и в своих библиотеках — не указывайте в конструкторе передачу нескольких параметров, параметры должны передаваться одним массивом. Теперь данную библиотеку можно спокойно загружать и работать с ней. Маленькое отступление. Создайте в корне приложения папку «temp». В неё мы будем загружать архивы, распаковывать их и только потом распакованные фотографии будут копироваться в общую папку. Перейдём к коду самого метода. Для наших целей мы расширим метод «photo_add» контроллера «Photos». Сразу после части кода отвечающей за загрузку одной фотографии мы заново инициализируем библиотеку «File upload» уже с другими параметрами и попытаемся загрузить отправленный архив. PHP: // Составляем новые настройки $upload_config = Array(); // Меняем папку загрузки $upload_config['upload_path'] = './temp/'; // Меняем разрешённые расширения $upload_config['allowed_types'] = 'zip'; // Заново инициализируем библиотеку $this->upload->initialize($upload_config); // Пытаемся загрузить архив if($this->upload->do_upload('archive')) { //... Код обработки архива } Алгоритм обработки архива будет следующий. Сначала мы с помощью метода «data» получим информацию о загруженном файле. Затем загружаем библиотеку PclZip и с помощью метода «extract» распаковываем содержимое архива в папку «temp». Далее мы удалим архив для того чтобы в папке остались одни фотографии и перебирая их циклом по очереди скопируем в папку изображений фотоальбомов и зарегистрируем в базе. PHP: // Получаем данные о загруженом файле $file_data = $this->upload->data(); // Параметры для PclZip $pclzip_config = Array(); $pclzip_config['p_zipname'] = './temp/'.$file_data['file_name']; $this->load->library('Pclzip',$pclzip_config); // Распаковываем архив $this->pclzip->extract('./temp/'); // И тут же его удаляем unlink('./temp/'.$file_data['file_name']); // Получаем список фотографий $directory = directory_map('./temp/'); // и циклично их обрабатываем foreach($directory as $photo) { // Копируем в директорию фотоальбомов copy("./temp/{$photo}","./photos/{$photo}"); // Регистрируем в БД $this->Photos_model->insertPhoto($photo,$album_id); // И удаляем из временной директории unlink("./temp/{$photo}"); } Обратите внимание на то что список файлов формируется с использованием функции «directory_map» хэлпера «Directory», а в начале функции у нас подключается только хэлпер «url». Добавьте загрузку хэлпера «Directory». В итоге код метода «photo_add» должен стать таким. PHP: function photo_add($album_id) { $album_id = intval($album_id); $this->load->helper(Array('url','directory')); // Формируем настройки для класса "File Upload" $upload_config['upload_path'] = './photos/'; $upload_config['allowed_types'] = 'gif|jpg|png'; // Передаём их при загрузке библиотеки $this->load->library('upload',$upload_config); // Пробуем загрузить фото if($this->upload->do_upload('photo')) { // Получаем данные загруженного файла $file_data = $this->upload->data(); // Регистрируем фотографию в базе $this->Photos_model->insertPhoto($file_data['file_name'],$album_id); } // Составляем новые настройки $upload_config = Array(); // Меняем папку загрузки $upload_config['upload_path'] = './temp/'; // Меняем разрешённые расширения $upload_config['allowed_types'] = 'zip'; // Заново инициализируем библиотеку $this->upload->initialize($upload_config); // Пытаемся загрузить архив if($this->upload->do_upload('archive')) { // Получаем данные о загруженом файле $file_data = $this->upload->data(); // Параметры для PclZip $pclzip_config = Array(); $pclzip_config['p_zipname'] = './temp/'.$file_data['file_name']; $this->load->library('Pclzip',$pclzip_config); // Распаковываем архив $this->pclzip->extract('./temp/'); // И тут же его удаляем unlink('./temp/'.$file_data['file_name']); // Получаем список фотографий $directory = directory_map('./temp/'); // и циклично их обрабатываем foreach($directory as $photo) { // Копируем в директорию фотоальбомов copy("./temp/{$photo}","./photos/{$photo}"); // Регистрируем в БД $this->Photos_model->insertPhoto($photo,$album_id); // И удаляем из временной директории unlink("./temp/{$photo}"); } } // Возвращаем пользователя к редактированию альбома. redirect("/admin/albums/album_edit/{$album_id}/"); } Попытайтесь сейчас сформировать архив из 3-4 фотографий и загрузить его. На очереди удаление. Оно будет происходить так же просто как и у фотоальбомов. В контроллере «Photos» создайте метод «photo_del» с двумя параметрами передаваемыми по ссылке — номера фотографии и альбома. Номер альбома нам нужен для того что бы после выполнения удаления перенаправить пользователя обратно на редактирование. PHP: function photo_del($photo_id,$album_id) { // Получаем номер фотографии и номер её альбома $photo_id = intval($photo_id); $album_id = intval($album_id); // Получаем её данные что бы знать имя файла $photo = $this->Photos_model->getPhotoData($photo_id); $this->load->helper('url'); // Удаляем из базы запись о фотографии $this->Photos_model->photo_del($photo_id); // Удаляем её из папки 'photos' unlink('./photos/'.$photo['file_name']); // Переносим пользователя на редактирование альбома. redirect('/admin/albums/album_edit/'.$album_id); } Затем создадим функцию «photo_del», осуществляющую непосредственно удаление из базы, в модели фотографий. Она будет ещё проще: PHP: function photo_del($photo_id) { $this->db->delete('photos',Array('id'=>$photo_id)); } Это всё. Попробуйте для проверки удалить пару фотографий. Предпоследним действием в отношении админ-панели у нас будет создание бэкапа фотографий. Данная функция будет формировать ZIP-архив со всеми имеющимися фотографиями на сервере, и передавать его на закачку браузеру. Внимание! Никогда не делайте похожих вещей в своих проектах так как они просто не будут работать на большинстве серверов. Дело в том что на многих серверах (а тем более на различных хостингах) объём памяти, выделяемой для PHP, сильно ограничен. Как правило это не больше 25 мегабайт. Соответственно, если объём архива будет превышать это ограничение то будет вылазить ошибка гласящая о недостатке памяти. Ведь архив перед выдачей пользователю (или сохранением на жёсткий диск) формируется именно в памяти. Создание такой функции мы рассматриваем здесь только для того что бы познакомиться с некоторыми свойствами библиотеки «Zip Encoding»(http://code-igniter.ru/user_guide/libraries/zip.html), использование которой может быть Вам полезно при выполнении каких-либо других задач. Итак, начнём. Метод, ответственный за создание резервных копий, будет называться «photos_backup» и находиться в модели «Photos». В начале метода будут загружаться 2 компонента — хэлпер directory (с помощью его функции мы получим список файлов для архивирования) и библиотека «zip». Далее мы получим список файлов и по очереди добавим их в архив. В конце метода мы отдадим содержимое архива браузеру пользователя. Это может Вас удивить, но код этой функции крайне мал. PHP: function photos_backup() { $this->load->helper('directory'); $this->load->library('zip'); // Получаем список файлов в папке «photos» $files = directory_map('./photos/'); // Поочерёдно добавляем их в архив foreach($files as $file) $this->zip->read_file('./photos/'.$file); // Выдаём архив на закачку $this->zip->download('backup.zip'); } При прохождении через цикл мы используем метод «read_file». Он считывает указанный файл и сразу же помещает его в архив. С тем же успехом мы могли бы использовать и вторую подобную функцию - «add_data». Ей нужно передать 2 параметра — имя добавляемого файла и его содержимое. Перевести нашу функцию на использование «add_file» можно заменив строку PHP: $this->zip->read_file('./photos/'.$file); на PHP: $this->zip->add_data($file,file_get_contents('./photos/'.$file)); Согласитесь, первый вариант удобнее. Обратите внимание ещё на то что для добавления бинарных файлов методом «add_data» нужно использовать функции бинарно-безопасного чтения (или как их ещё называют — функции для безопасного чтения двоичных данных) их содержимого. В конце метода за место вызова закачки файла можно его сохранить на жёсткий диск методом archive, передав ему путь и имя будущего архива. PHP: $this->zip->archive('./backup.zip'); Как завершающую часть администраторской панели сделаем авторизацию. Алгоритм её работы будет таков. При каждом обращении к нашему приложению (не важно к какой части) будет происходить проверка — обращается пользователь в администраторскую часть сайта или нет. Если да то следующим шагом будет проверка данных авторизации. Мы их будем записывать в сессию. Если в сессии таких данных нет, то показываем форму ввода пароля, или же, если пароль введён сверяем его с настоящим (пароль у нас будет 123) и в случае совпадения записываем в сессию данные об успешной авторизации. Сделаем мы всё это с помощью класса для работы с сессиями (http://code-igniter.ru/user_guide/libraries/sessions.html) и хуков ядра (http://code-igniter.ru/user_guide/general/hooks.html). Хуки мы будем использовать для того чтобы запустить нашу проверку перед выполнением каких-либо действий контроллером. Активировать хуки можно в основном конфигурационном файле «application/config/config.php», установив значение ячейки «enable_hooks» в значение «true». Так же в этом файле нам нужно установить в «true» значение ячейки «sess_encrypt_cookie» для того что бы используемые сессией cookies шифровались. Дело в том что данные сессии CI хранит в cookies, что с моего взгляда большой промах. Всё-таки лучше лучше когда они хранятся на стороне сервера. Но раз уж так сложилось то будем пользоваться шифрованием данных для того что бы пользователь не имел возможности подделывать их. Далее откроем конфигурацию хуков («application/config/hooks.php») и объявим один: PHP: $hook['post_controller_constructor'] = Array( 'class' => '', 'function' => 'admin_auth', 'filename' => 'admin_auth.php', 'filepath' => 'hooks', 'params' => array() ); Мы используем именно хук активизирующийся после конструктора контроллера потому что более ранние хуки не дают доступа к библиотекам и прочим необходимым вещам. Как видно из параметров, файл с ним называется «admin_auth.php». Создадим его в папке хуков «application/hooks» и впишем туда следующую функцию. PHP: function admin_auth() { // Создаём ссылку на супер объект CI // Дальше можно работать с этой ссылкой // как с $this в контроллерах и моделях $CI = & get_instance(); // Получаем первый сегмент введённого URI $segment = $CI->uri->segment(1); // Если первый сегмент URL не "admin" то завершаем работу функции if($segment != 'admin') return ; // Если пользователь не авторизирован if($CI->session->userdata('logged_in') === false) { // Если введён верный пароль то записываем в сессию // данные о том что пользователь вошёл if($CI->input->post('password') == '123') { $CI->session->set_userdata('logged_in',true); } else { // Если нет то показываем форму $code = $CI->parser->parse('header',Array(),true); $code .= $CI->parser->parse('admin/admin_login_form',Array(),true); $code .= $CI->parser->parse('footer',Array(),true); echo $code; exit; } } }
В самом начале мы создаём ссылку на супер-объект CI для того что бы пользоваться всеми его возможностями, в том числе библиотеками. Мы ведь не можем пользоваться «$this» т.к. находимся не в объекте, а в обычной функции. Далее сравниваем первый сегмент и начинаем проверку только если содержимое этого сегмента «admin». То есть обычные пользователи под эту проверку никак не попадут. Ну и далее по алгоритму. В конце, при отображении формы авторизации (admin/admin_login_form.php) мы указываем методу «parse» третьим параметром «true» для того что бы он не скидывал код в буффер вывода а возвращал в нашу переменную «code». Код шаблона этой формы. Code: <table width="100%" border="0" cellspacing="0" cellpadding="0"> <tr> <td height="137" valign="top" background="/images/bgt.gif"> <table width="100" border="0" cellspacing="0" cellpadding="0"> <tr> <td><img src="/images/head.jpg" width=378 height=96 alt=""></td> <td><img src="/images/thead.gif" width=372 height=31 alt=""><br> <img src="/images/name.jpg" width=372 height=27 alt=""><br> <a href=" "><img src="/images/slogan.jpg" width=372 height=18 border=0 alt=""><br> </a><img src="/images/index_05.jpg" width=372 height=20 alt=""></td> </tr> </table> </td> </tr> </table> <form action='/admin/' method='POST'> <table width="100%" border="0" cellspacing="0" cellpadding="0"> <tr> <td width="81" valign="top"><img src="/images/rc.gif" width=55 height=24 alt=""></td> <td> <span class='stylesbold'>Пароль:</span> </td> <td> <input type='password' name='password' value=''/> </td> </tr> <tr> <td width="81" valign="top"><img src="/images/rc.gif" width=55 height=24 alt=""></td> <td colspan='2'> <input type='submit' value='Войти' /> </td> </tr> </table> </form> 4. Фотогалерея. Пользовательская часть. Сейчас мы напишем пользовательскую часть нашей галереи. В ней будет не так много функционала как в админ-панели. Мы сделаем следующее: 1.Просмотр списка фотоальбомов 2.Просмотр содержимого альбома 3.Голосование за фотоальбом. Начнём с первого пункта. Для пользовательской части мы будем использовать контроллеры с теми именами что и для администраторской. Создайте пустой контроллер «Albums» (если Вы не забыли, он является контроллером по умолчанию в нашем приложении) и в его конструкторе подключите модель работы с альбомами. Она будет общая для обоих частей сайта. PHP: function __construct() { parent::Controller(); $this->load->model('Albums_model'); } Затем напишем функцию «index» которая будет выводить список доступных альбомов. Она столь же проста как и её администраторский аналог. PHP: function index() { $albums = $this->Albums_model->getAlbumsArray(); $data = Array(); $data['albums'] = $albums; $this->parser->parse('header',Array()); $this->parser->parse('albums_list',$data); $this->parser->parse('footer',Array()); } Вот код шаблона который используется для отображения списка. Code: <BODY BGCOLOR=#FFFFFF LEFTMARGIN=0 TOPMARGIN=0 MARGINWIDTH=0 MARGINHEIGHT=0> <table width="100%" border="0" cellspacing="0" cellpadding="0"> <tr> <td height="137" valign="top" background="/images/bgt.gif"> <table width="100" border="0" cellspacing="0" cellpadding="0"> <tr> <td><img src="/images/head.jpg" width=378 height=96 alt=""></td> <td><img src="/images/thead.gif" width=372 height=31 alt=""><br> <img src="/images/name.jpg" width=372 height=27 alt=""><br> <a href=" "><img src="/images/slogan.jpg" width=372 height=18 border=0 alt=""><br> </a><img src="/images/index_05.jpg" width=372 height=20 alt=""></td> </tr> </table> </td> </tr> </table> <table width="750" border="0" cellspacing="0" cellpadding="0" class="styles"> <tr> <td width="81" valign="top"><img src="/images/rc.gif" width=55 height=24 alt=""></td> <td width="669"> {albums} <p> </p> <p align="left"><a class='stylesbold' href='/albums/album_view/{t_name}/'>{name}</a></p> <p align="left">{about}</p> {/albums} </td> </tr> </table> Всё практически одно и тоже по сравнению с администраторской панелью. Обратите внимание на ссылку просмотра фотоальбома. Она имеет следующий вид «/albums/album_view/транслитерационное_имя_альбома». Методу «album_view» будет передаваться содержимое поля «t_name». В самом же методе, для получения данных альбома, мы будем использовать функцию модели «getAlbumData», но так как ей нужен номер альбома, а не его транслитерационное имя, то мы добавим в модель ещё одну функцию - «getAlbumIdByTName». Как видно из названия - она должна получать номер альбома по указанному нами содержимому поля «t_name». PHP: function getAlbumIdByTName($t_name) { // Указываем какое поле нужно выбрать $this->db->select('id'); // Создаём условие $this->db->where('t_name',$t_name); $query = $this->db->get('albums'); if(!$query->num_rows()) return false; $row = $query->result_array(); return $row[0]['id']; } Теперь можно создать и метод «album_view». Он очень похож на метод редактирования альбома, за исключением того что в начале мы вызываем метод «getAlbumByTName». PHP: function album_view($t_name) { $album_id = $this->Albums_model->getAlbumIdByTName($t_name); $album = $this->Albums_model->getAlbumData($album_id); $album['photos_count'] = $this->Albums_model->getPhotosCount($album_id); $album['photos'] = $this->Albums_model->getAlbumPhotosArray($album_id); $this->parser->parse('header',Array()); $this->parser->parse('album_view',$album); $this->parser->parse('footer',Array()); } Вид «album_view» имеет следующий код. Code: <BODY BGCOLOR=#FFFFFF LEFTMARGIN=0 TOPMARGIN=0 MARGINWIDTH=0 MARGINHEIGHT=0> <table width="100%" border="0" cellspacing="0" cellpadding="0"> <tr> <td height="137" valign="top" background="/images/bgt.gif"> <table width="100" border="0" cellspacing="0" cellpadding="0"> <tr> <td><img src="/images/head.jpg" width=378 height=96 alt=""></td> <td><img src="/images/thead.gif" width=372 height=31 alt=""><br> <img src="/images/name.jpg" width=372 height=27 alt=""><br> <a href=" "><img src="/images/slogan.jpg" width=372 height=18 border=0 alt=""><br> </a><img src="/images/index_05.jpg" width=372 height=20 alt=""></td> </tr> </table> </td> </tr> </table> <table width="750" border="0" cellspacing="0" cellpadding="0" class="styles"> <tr> <td width="81" valign="top"><img src="/images/rc.gif" width=55 height=24 alt=""></td> <td width="669"> <p align="left"> <span class='stylesbold'>{name}</span> </p> <p align="left">{about}</p> <p align="left"> Рейтинг: {rating} (<a href='/albums/rating_inc/{id}/'>+</a> / <a href='/albums/rating_dec/{id}/'>-</a>) </p> <table width='100%' align='center'> <tr> {photos} <td> <img src='/photos/photo_view/{photo_id}/150/' /><br /><br /> </td> {/photos} </tr> </table> </td> </tr> </table> Ну а метод photo_view мы просто скопируем из контроллера админ-части, добавив в начало кода загрузку библиотеки «image_lib». Поместим его в контроллер «Photos» PHP: class Photos extends Controller { function photo_view($photo_id,$width = 100) { $photo_id = intval($photo_id); $width = intval($width); $this->load->model('Photos_model'); $this->load->library('Image_lib'); // Получение данных из БД $photo = $this->Photos_model->getPhotoData($photo_id); // Составляем полный путь к картинке из корня приложения $path = './photos/'.$photo['file_name']; // Формируем данные для изменения размера $config['source_image'] = $path; $config['maintain_ratio'] = true; $config['width'] = $width; $config['height'] = 100; $config['dynamic_output'] = TRUE; $this->image_lib->initialize($config); // Изменяем размер и выводим в браузер результат $this->image_lib->resize(); } } Займёмся теперь рейтингами альбомов. Как Вы уже заметили, в шаблоне есть 2 ссылки на увеличение и уменьшение рейтинга - «/albums/rating_inc/{id}/» и «/albums/rating_dec/{id}/» соответственно. Эти методы будут вызывать в модели функцию увеличения/уменьшения рейтинга и обратно переносить пользователя на просмотр альбома. Вот код который должен располагаться в модели. PHP: function rating_inc($album_id) { // Формируем запрос типа SET rating=rating+1 $this->db->set('rating', 'rating+1', false); // Устанавливаем условие $this->db->where('id',$album_id); // Вызываем запрос на обновление таблицы "albums" $this->db->update('albums'); } PHP: function rating_dec($album_id) { // Формируем запрос типа SET rating=rating-1 $this->db->set('rating', 'rating-1', false); // Устанавливаем условие $this->db->where('id',$album_id); // Вызываем запрос на обновление таблицы "albums" $this->db->update('albums'); } Обратите внимание на то что методу «set» третьим параметром передаётся «false», что запрещает обрамлять кавычками содержимое поля. Если бы этого параметра не было то код запроса стал таким «SET rating='rating+1'», а с ним - «SET rating=rating+1». Ну и займёмся функциями модели. Принцип работы у них следующий. Сначала мы вызываем увеличение или уменьшение рейтинга указанного альбома. Затем получаем его данные с помощью метода «getAlbumData». От этих данных нам нужно лишь содержимое ячейки «t_name» для того что бы перебросить пользователя обратно. Ну и с помощью функции «redirect» мы переносим пользователя снова на страницу просмотра фотографий. PHP: function rating_inc($album_id) { $album_id = intval($album_id); $this->load->helper('url'); // Увеличение рейтинга $this->Albums_model->rating_inc($album_id); // Получение данных альбома $album = $this->Albums_model->getAlbumData($album_id); // Редирект на просмотр альбома redirect('/albums/album_view/'.$album['t_name']); } function rating_dec($album_id) { $album_id = intval($album_id); $this->load->helper('url'); // Уменьшение рейтинга $this->Albums_model->rating_dec($album_id); // Получение данных альбома $album = $this->Albums_model->getAlbumData($album_id); // Редирект на просмотр альбома redirect('/albums/album_view/'.$album['t_name']); } Вот и всё. Пользовательская часть тоже закончена.
Перейдём к завершающей стадии этой статьи — интернационализации, или проще говоря — работе с языковыми файлами. Часто у распространяемых приложений или мульти-язычных проектов разработчики используют не разные дизайны (для каждого языка отдельный), а языковые файлы, содержащие в себе текст общего интерфейса сайта. Это позволяет не трогая ни кода ни дизайна приложения удобно и быстро изменять его язык. В CI для этих целей есть библиотека «Language» (http://code-igniter.ru/user_guide/libraries/language.html) и хэлпер с таким же названием (http://code-igniter.ru/user_guide/helpers/language_helper.html). Сейчас мы с помощью них попытаемся перевести часть нашего приложения на использование языковых файлов. Возьмёмся за локализацию системной части — это всевозможные сообщения и ошибки которые генерируются CI. Например сообщения класса валидации о том что не заполнено какое-то поле, или ошибка библиотеки «Image lib» о том что не верно указан путь к изображению. Языковые файлы этой части находятся в директории «system/language». В свою очередь каждый язык находится в отдельной папке. Создайте в «system/language» папку «russian» и скопируйте туда содержимое папки «english». Теперь откройте файл основной конфигурации («application/config/config.php») и установите значение ячейки «language» в «russian». Для проверки того что мы сделали в директории «russian» откройте файл «db_lang.php», и в ячейку «db_unable_to_connect», содержащую сообщение о невозможности подключения к базе данных, впишите «Не могу соединиться с указанной базой данных.». Теперь откройте файл конфигурации БД и измените имя хоста базы на что угодно. После этого, при обращении к любой странице сайта, Вы должны видеть русскоязычную ошибку о том что к базе невозможно подключиться. Так Вы можете совершить полный русскоязычный (или другой) перевод всего движка. Далее очередь языковой части приложения. Под использование языковых файлов мы переделаем лишь шаблон редактирования альбома. Принцип такой переделки везде одинаков, поэтому достаточно будет только одного примера. Пользовательские языковые файлы хранятся в другой директории - «application/language». Создайте в нём папку «russian», поместите туда пустой файл «albums_lang.php» и впишите в него одну ячейку: PHP: $lang['album_editing'] = 'Редактирование альбома'; Эта надпись отображается у нас в самом начале страницы редактирования. Отображение языковых данных происходит с помощью метода «line» PHP: $this->lang->line('ячейка_lang_массива'); Заменим надпись «Редактирование альбома» в шаблоне на код PHP: <?=$this->lang->line('album_editing');?> Теперь при обращении к странице Вы должны видеть данные из языкового файла. Можете поменять в нём текст, для пущей убедительности. Обсудим хэлпер. Вызовы метода «line» довольно большие по количеству символов. Куда приятнее работать с менее длинными вызовами, в чём нам поможет хэлпер «Language». Он содержит единственную функцию «lang» которой нужно передать один параметр — имя ячейки lang-массива. Поместите его в автозагрузку, а в шаблоне за место PHP: <?=$this->lang->line('album_editing');?> впишите PHP: <?=lang('album_editing');?> Если хэлпер подключился нормально то надпись о редактировании отобразится на том же месте. Давайте создадим другие ячейки с фразами из нашего шаблона. PHP: <?php $lang['album_editing'] = 'Редактирование альбома'; $lang['album_name'] = 'Название альбома'; $lang['album_about'] = 'Его описание'; $lang['album_photos_count'] = 'Количество фотографий в альбоме'; $lang['album_save'] = 'Сохранить альбом'; $lang['album_add_photo'] = 'Добавить фотографию'; $lang['album_add_photo_archive'] = 'Добавить архив фотографий'; $lang['album_photo_add'] = 'Добавить'; $lang['album_photo_del'] = 'Удалить'; ?> И вставим их в шаблон на свои места. Вот и всё. Как видите с локализацией, как и со всем остальным, всё очень просто. Если у Вас что-то не получается то готовый код галереи Вы можете взять в приложенных файлах, в архиве «gellery.zip» Надеюсь что эта статья поможет Вам в освоении столь лёгкого и отличного фреймворка. Code Igniter действительно заслуживает уважения так как является возможно самым наипростейшим инструментом для создания веб-приложений сохраняя, в то же время, огромную мощность и простоту изучения. Удачи Вам в Ваших проектах!
Kuzya, а где же ваши исходники на тему гостевой и галереи, к тому же у вас куча ошибок в html коде. Теги html не пишут в верхнем регистре, это не текст и не доктайп.