В разделе "Первые папки и файлы. Добавление пунктов меню" предлагается создать две файла: - myquestions.php; - admin.myquestions.php с соответствуюшими адресами: - /components/com_myquestions/myquestions.php; - /administrator/components/com_myquestions/admin.myquestions.php; Так вот, при создании файла "admin.myquestions.php" В админке выдает ошибку - "Компонент не найден", а при переименовании его на "myquestions.php" в последующем шаге, в админке не выводятся кнопки редактирования. |
Работа с базой данных
Создание формы для ответа на вопрос
Как и ранее, отделим HTML-вывод от логики обработки. PHP-код, необходимый для загрузки значений элементов формы, будет храниться в файле admin.myquestions.php, а код формы - в файле admin.myquestions.html.php. Откройте admin.myquestions.php и замените его содержимое следующим кодом:
<?php defined('_JEXEC') or die('Restricted access'); $option = JRequest::getVar('option'); $task = JRequest::getVar('task'); require_once (JApplicationHelper::getPath('admin_html')); JTable::addIncludePath(JPATH_COMPONENT.DS.'tables') ; switch($task) { case 'reply': replyToQuestion($option); break; default: break; } function replyToQuestion($option) { $row =& JTable::getInstance('Question','Table'); $cid = JRequest::getVar('cid', array(0), '', 'array'); $id = $cid[0]; $row->load($id); HTML_questions::replyToQuestion($row, $option); } ?>
Проверив, что код вызван из Joomla, мы используем выражение require_once(JApplicationHelper::getPath('admin_html')) для подключения файла admin.myquestions.html.php.
Затем с помощью JTable::addIncludePath() папка tables добавляется к списку директорий, в которых следует искать классы таблиц.
Переключатель switch() вызывает функцию, соответствующую значению переменной $task.
В функции replyToQuestion() создается экземпляр класса TableQuestion для управления записью таблицы. С помощью JRequest::getVar() из переменных запроса извлекается массив cid, хранящий идентификаторы записей. Так как эксперт будет отвечать только на один вопрос за раз, то мы выбираем первый идентификатор и загружаем соответствующую запись. Затем она передается в функцию вывода HTML_questions::replyToQuestion().
Теперь создайте файл /administrator/components/com_myquestions/admin.myquestions.html.php:
<?php defined ('_JEXEC') or die ('Restricted access'); class HTML_questions { function replyToQuestion($row, $option) { $editor =& JFactory::getEditor(); ?> <form action = "index.php" method="post" name="adminForm" id="adminForm"> <fieldset class="adminform"> <table class="admintable" width=100%> <tr> <td width="100" class="key"> <?php echo JText::_('COM_MYQUESTIONS_AUTHOR');?>: </td> <td> <input class="text_area" type="text" name="name" id="name" size="50" maxlength="255" value="<?php echo $row->name;?>"/> </td> </tr> <tr> <td width="100" class="key"> <?php echo JText::_('COM_MYQUESTIONS_DATE');?>: </td> <td> <span class="text_area" type="text" name="date" id="date"><?php echo JHTML::_('date', $row->date,JText::_('DATE_FORMAT_LC3'));?></span> </td> </tr> <tr> <td width="100" class="key"> <?php echo JText::_('COM_MYQUESTIONS_QUESTION');?>: </td> <td> <?php echo $editor->display('question', $row->question, '100%', '250', '40', '10');?> </td> </tr> <tr> <td width="100" class="key"> <?php echo JText::_('COM_MYQUESTIONS_CITY');?>: </td> <td> <input class="text_area" type="text" name="city" id="city" size="50" maxlength="50" value="<?php echo $row->city;?>"/> </td> </tr> <tr> <td width="100" class="key"> <?php echo JText::_('COM_MYQUESTIONS_EMAIL');?>: </td> <td> <input class="text_area" type="text" name="email" id="email" size="50" maxlength="50" value="<?php echo $row->email;?>"/> </td> </tr> <tr> <td width="100" class="key"> <?php echo JText::_('COM_MYQUESTIONS_IP');?>: </td> <td> <span class="text_area" type="text" name="IP" id="IP"><?php echo $row->IP;?></span> </td> </tr> <tr> <tr> <td width="100" class="key"> <?php echo JText::_('COM_MYQUESTIONS_CATEGORY');?>: </td> <td> <input class="text_area" type="text" name="id_cat" id="id_cat" size="50" maxlength="250" value="<?php echo $row->id_cat;?>"/> </td> </tr> <tr> <td width="100" class="key"> <?php echo JText::_('COM_MYQUESTIONS_PUBLISHED');?>: </td> <td valign="top"> <?php if ($row->published == '1') echo JText::_('JYES'); else echo JText::_('JNO');?> </td> </tr> <tr> <td width="100" class="key"> <?php echo JText::_('COM_MYQUESTIONS_EXPIRATION_DATE');?>: </td> <td> <?php echo JHTML::_('calendar', $row->expiration_date, 'expiration_date', 'expiration_date', '%Y-%m-%d');?> </td> </tr> <tr> <td width="100" class="key"> <?php echo JText::_('COM_MYQUESTIONS_SENTTOEXPERT');?>: </td> <td valign="top"> <?php if ($row->senttoexpert == '1') echo JText::_('JYES'); else echo JText::_('JNO');?> </td> </tr> <tr> <td width="100" class="key"> <?php echo JText::_('COM_MYQUESTIONS_ANSWER');?>: </td> <td> <?php echo $editor->display('answer', $row->answer,'100%', '250', '40', '10');?> </td> </tr> <tr> <td width="100" class="key"> <?php echo JText::_('COM_MYQUESTIONS_SENTTOAUTHOR');?>: </td> <td valign="top"> <?php if ($row->senttoauthor == '1') echo JText::_('JYES'); else echo JText::_('JNO');?> </td> </tr> </table> </fieldset> <input type="hidden" name="id" value="<?php echo $row->id;?>"/> <input type="hidden" name="option" value="<?php echo $option;?>"/> <input type="hidden" name="task" value=""/> </form> <?php } } ?>Листинг .
Функция HTML_questions::replyToQuestion() выводит на экран уже заполненную форму, значения элементов которой берутся из объекта $row. Форме присвоено название adminForm, чтобы к ней можно было обращаться из JavaScript.
Классы JHTML и JEditor будут рассмотрены позже. Сейчас поясним только те выражения, в которых используются методы этих классов:
echo JHTML::_('date', $row->date,JText::_('DATE_FORMAT_LC3')); |
выводит дату $row->date в формате DATE_FORMAT_LC3 (один из стандартных форматов, заданных в Joomla). |
$editor =& JFactory::getEditor(); echo $editor->display('question', $row->question, '100%', '250', '40', '10'); |
отображает выбранный администратором HTML-редактор. Если не выбран ни один редактор, то будет отображено поле <textarea>. В редакторе или поле <textarea> будет выведено значение $row->question. |
echo JHTML::_('calendar', $row->expiration_date, 'expiration_date', 'expiration_date', '%Y-%m-%d'); |
выводит текстовое поле со значением $row->expiration_date и пиктограмму календаря, при нажатии на которую появляется календарь для выбора даты. |
Перед закрывающим тегом </form> выводятся три скрытые элемента. Первый из них хранит значение id записи, т.к. оно необходимо для дальнейшего сохранения отредактированного вопроса. Элемент option хранит название текущего компонента для правильного редиректа в дальнейшем. Третьему скрытому элементу, task, не присвоено значения, чтобы JavaScript-код панели инструментов мог изменять его до отправки формы.
Осталось добавить перевод ключей COM_MYQUESTIONS_AUTHOR, COM_MYQUESTIONS_DATE и др. Откройте файл /administrator/language/ru-RU/ru-RU.com_myquestions.ini и добавьте к его содержимому следующий код:
COM_MYQUESTIONS_AUTHOR="Автор" COM_MYQUESTIONS_DATE="Дата вопроса" COM_MYQUESTIONS_QUESTION="Текст вопроса" COM_MYQUESTIONS_CITY="Город" COM_MYQUESTIONS_EMAIL="e-mail" COM_MYQUESTIONS_IP="IP-адрес" COM_MYQUESTIONS_CATEGORY="Категория" COM_MYQUESTIONS_PUBLISHED="Отображать ли вопрос на сайте" COM_MYQUESTIONS_EXPIRATION_DATE="Дата снятия вопроса с публикации" COM_MYQUESTIONS_SENTTOEXPERT="Отправлен ли вопрос эксперту" COM_MYQUESTIONS_ANSWER="Ответ" COM_MYQUESTIONS_SENTTOAUTHOR="Отправлен ли ответ автору вопроса"
Обратите внимание, что мы не задали перевод для слов "Да" и "Нет", а использовали ключи JYES и JNO, т.к. подобные распространенные слова уже переведены в файле /administrator/language/ru-RU/ru-RU.ini.
Наберите в адресной строке браузера ссылку http://localhost/joomla/administrator/index.php?option=com_myquestions&task=reply&cid[]=1. Должна появиться следующая страница ( рис. 2.1).
Сохранение введенных данных
После того, как эксперт напечатал ответ на заданный вопрос и нажал кнопку "Сохранить", необходимо сохранить информацию в базе данных. Прежде всего, создайте две функции - save() и saveQuestion() - в файле admin.myquestions.php:
function save() { $row =& JTable::getInstance('question', 'Table'); if (!$row->bind(JRequest::get('post'))) { echo "<script> alert('".$row->getError()."'); window.history.go(-1); </script>\n"; exit(); } $row->question = JRequest::getVar('question', '', 'post', 'string', JREQUEST_ALLOWRAW); $row->answer = JRequest::getVar('answer', '', 'post', 'string', JREQUEST_ALLOWRAW); if (!$row->store()) { echo "<script> alert('".$row->getError()."'); window.history.go(-1); </script>\n"; exit(); } return $row; } function saveQuestion($option, $task) { $row = save(); global $app; if ($task == 'save') $app->redirect('index.php?option='.$option, JText::_('COM_MYQUESTIONS_REPLY_SAVED')); else if ($task == 'apply') $app->redirect('index.php?option='.$option.'&task= reply&cid[]='.$row->id, JText::_('COM_MYQUESTIONS_REPLY_SAVED')); }
Переменной $row присваивается значение экземпляра класса TableQuestion и вызывается функция bind() для связывания переменных, полученных из формы, с полями этого класса.
Для тех значений, которые вводились с помощью редактора Joomla, стандартный способ получения значений из массива JRequest::get('post') не подходит, т.к. функция bind() автоматически удаляет HTML-код, что приведет, в частности, к потере разрывов строк и тегов абзаца. Поэтому для получения значений question и answer в том виде, в котором они были введены в редакторе, используется функция getVar() класса JRequest. Данной функции передается имя переменной формы, значение по умолчанию, метод запроса, с помощью которого мы хотим получить данные (get/post), ожидаемый формат и флаг JREQUEST_ALLOWRAW, означающий, что данные не должны быть отфильтрованы.
Наконец, вызывается функция store() для сохранения вопроса в базе данных.
В функции saveQuestion() происходит вызов функции save(), а затем в зависимости от задачи, т.е. от того, какая кнопка была нажата, - "Сохранить" или "Сохранить и закрыть", - мы перенаправляем пользователя либо к той же странице редактирования вопроса, на которой он находится, но уже с сохраненными данными, либо к главной странице нашего компонента. В обоих случаях выводится сообщение о том, что данные были сохранены. Для перенаправления и вывода сообщения используется функция redirect() глобального объекта JApplication.
Добавьте задачу сохранения записи в переключатель switch() в файле admin.myquestions.php (выделенный код):
Добавьте перевод для ключа COM_MYQUESTIONS_REPLY_SAVED в файл /administrator/language/ru-RU/ru-RU.com_myquestions.ini:
COM_MYQUESTIONS_REPLY_SAVED="Данные сохранены"
Сохраните все ваши файлы и перейдите по ссылке http://localhost/joomla/administrator/index.php?option=com_myquestions&task=reply&cid[]=1. Напишите что-нибудь в поле для ответа и нажмите кнопку "Сохранить и закрыть". Вы должны увидеть следующую страницу ( рис. 2.2).
С помощью phpMyAdmin вы можете проверить, что данные были сохранены в таблице базы данных jos_myquestions ( рис. 2.3).
Вывод списка записей
Прежде всего, добавьте в файл admin.myquestions.php следующую функцию:
function showQuestions($option) { $db =& JFactory::getDbo(); $query = "SELECT * FROM #__myquestions"; $db->setQuery($query); $rows = $db->loadObjectList(); if ($db->getErrorNum()) { echo $db->stderr(); return false; } HTML_questions::showQuestions($option, $rows); }
Эта функция загружает все записи из таблицы #__myquestions и передает их в виде массива $rows в следующую функцию, которую необходимо добавить в файл admin.myquestions.html.php в класс HTML_questions:
function showQuestions($option, &$rows) { $maxlen = 100; ?> <form action="index.php" method="post" name="adminForm"> <table class="adminlist"> <thead> <tr> <th width="20"> <input type="checkbox" name="toggle" value="" onclick="checkAll(<?php echo count($rows);?>);"/> </th> <th class="title"><?php echo JText::_('COM_MYQUESTIONS_AUTHOR');?></th> <th><?php echo JText::_('COM_MYQUESTIONS_DATE');?></th> <th><?php echo JText::_('COM_MYQUESTIONS_QUESTION');?></th> <th><?php echo JText::_('COM_MYQUESTIONS_EMAIL');?></th> <th><?php echo JText::_('COM_MYQUESTIONS_PUBLISHED');?></th> <th><?php echo JText::_('COM_MYQUESTIONS_EXPIRATION_DATE');?></th> <th><?php echo JText::_('COM_MYQUESTIONS_SENTTOEXPERT');?></th> <th><?php echo JText::_('COM_MYQUESTIONS_ANSWER');?></th> <th><?php echo JText::_('COM_MYQUESTIONS_SENTTOAUTHOR');?></th> </tr> </thead> <?php jimport('joomla.filter.output'); $k = 0; for ($i = 0, $n = count($rows); $i < $n; $i ++) { $row = &$rows[$i]; $checked = JHTML::_('grid.id', $i, $row->id); $link = JFilterOutput::ampReplace('index.php?option=' .$option . '&task=reply&cid[]='. $row->id); ?> <tr class="<?php echo "row$k";?>"> <td><?=$checked?></td> <td><?=$row->name?></td> <td><?=JHTML::_('date', $row->date, JText::_('DATE_FORMAT_LC3'))?></td> <td><?='<a href="'.$link.'">'.substr(strip_tags($row->question), 0,$maxlen-1).'</a>'?></td> <td><?=$row->email?></td> <td align="center"> <?php if ($row->published == '1') echo JText::_('JYES'); else echo JText::_('JNO');?> </td> <td> <?php if ($row->expiration_date == '0000-00-00 00:00:00') echo JText::_('COM_MYQUESTIONS_DATE_NOT_DEFINED'); else echo JHTML::_('date', $row->expiration_date, JText::_('DATE_FORMAT_LC3'));?> </td> <td align="center"> <?php if ($row->senttoexpert == '1') echo JText::_('JYES'); else echo JText::_('JNO');?> </td> <td><?=substr(strip_tags($row->answer),0,$maxlen-1)?></td> <td align="center"> <?php if ($row->senttoauthor == '1') echo JText::_('JYES'); else echo JText::_('JNO'); ?> </td> </tr> <?php $k = 1 - $k; } ?> </table> <input type="hidden" name="option" value="<?php echo $option;?>"/> <input type="hidden" name="task" value=""/> <input type= "hidden" name="boxchecked" value="0"/> </form> <?php }Листинг .
Записи выводятся в таблице, для которой задан CSS-класс adminlist. Все заголовки таблицы, кроме первого, - это обычный текст. Первый заголовок является чекбоксом и используется для одновременного выделения всех отображенных записей.
Затем начинается цикл для вывода самих записей. Значение переменной $k меняется с 0 на 1 и обратно для того, чтобы переключаться между различными классами CSS для четных и нечетных строк, имеющими немного различающиеся свойства фона. С помощью вызова функции JHTML::_('grid.id') мы получаем HTML-код для чекбокса, который будет обрабатываться с помощью JavaScript.
Для каждого вопроса и ответа выводятся первые maxlen символов вместо его текста целиком. При этом с помощью функции strip_tags() отбрасываются теги, чтобы предотвратить ситуацию, когда граница обрезки текста может оказаться внутри тега.
Для перехода к форме ответа на вопрос для каждой записи выводится гиперссылка, которая пропускается через функцию JFilterOutput::ampReplace(), заменяющую амперсанды "&" на коды "&" в соответствии со спецификацией XHTML. Для подключения класса JFilterOutput в код вставлена строка jimport('joomla.filter.output').
Перед закрывающим тегом </form> расположены три скрытых элемента. Option и task были рассмотрены при анализе формы для ответа на вопрос. Значение boxchecked заключается в следующем. Когда пользователь ставит флажок в каком-либо из чекбоксов, значение boxchecked меняется на 1. Значение boxchecked, равное 0, возвращается, когда ни один из чекбоксов не отмечен. С помощью этого значения JavaScript обрабатывает список.
Для обработки случая, когда не выбрано никакой задачи, измените код переключателя switch в файле admin.myquestions.php следующим образом:
switch($task) { case 'reply': replyToQuestion($option); break; case 'save': case 'apply': saveQuestion($option, $task); break; default: showQuestions($option); break; }
Добавьте в файл /administrator/language/ru-RU/ru-RU.com_myquestions.ini строку:
COM_MYQUESTIONS_DATE_NOT_DEFINED="Дата не задана"
Теперь при загрузке http://localhost/joomla/administrator/index.php?option=com_myquestions должна появиться страница, как на рис. 2.4.
Удаление записей
Добавьте следующий оператор case в переключатель switch() в файле admin.myquestions.php:
case 'remove': removeQuestions($option); break;
Также добавьте функцию removeQuestions():
function removeQuestions($option) { global $app; $cid = JRequest::getVar('cid', array(), '', 'array'); $db =& JFactory::getDbo(); if(count($cid)) { $cids = implode(',', $cid); $query = "DELETE FROM #__myquestions WHERE id IN ($cids)"; $db->setQuery($query); if (!$db->query()) { echo "<script> alert('".$db->getErrorMsg()."'); window.history.go(-1); </script>\n"; } } $app->redirect('index.php?option=' . $option, JText::_('COM_MYQUESTIONS_QUESTION_DELETED')); }
Если в массиве cid есть элементы, то составляется строка из идентификаторов, разделенных запятыми, которая затем используется для построения запроса удаления соответствующих записей. В данном случае нельзя использовать метод JTable::delete(), т.к. он предназначен для удаления одной записи, а не нескольких.
Добавьте в файл /administrator/language/ru-RU/ru-RU.com_myquestions.ini строку:
COM_MYQUESTIONS_QUESTION_DELETED="Вопрос(ы) успешно удален(ы)"
Ключевые термины
Краткие итоги
При работе с базой данных различают реальный и символический префиксы. Реальный префикс используется в названиях таблиц базы данных, а символический префикс ("#__") используется в запросах вместо реального префикса. При обработке запроса вместо символического префикса будет автоматически подставлен реальный.
Чтобы выполнить запрос к базе данных Joomla, необходимо осуществить пять операций: получение ссылки на объект JDatabase (абстрактный класс, предоставляющий доступ к соединению с базой данных), формирование запроса, задание запроса, выполнение запроса, загрузка результата.
Запрос может быть сформулирован в виде строки либо разбит на составляющие и построен с помощью методов класса JDatabaseQuery.
Запрос задается для последующего выполнения методом setQuery(), а выполняется либо методом query(), либо, если нам необходимо получить результат, одним из методов для получения форматированного результата: loadResult(), loadRow() и т.д.
Для каждой таблицы, использующейся расширением, необходимо создать класс, производный от JTable. Для каждого поля таблицы необходимо создать одноименное поле этого класса. Производный от JTable класс наследует в числе прочих методы bind(), store(), load() и delete(), позволяющие управлять записями таблицы без единой строки SQL-кода. Когда компонент получает массив переменных запроса, он осуществляет связывание, то есть присваивает каждому полю этого класса значение элемента массива, ключ которого совпадает с названием данного поля.
Существуют методы класса JTable для управления часто используемыми полями ordering, checked_out/checked_out_time, published и hits.
Вопросы
- Что такое реальный и символический префиксы?
- Какие операции необходимо осуществить для выполнения запроса к базе данных Joomla?
- Каким образом может быть сформулирован SQL-запрос?
- Какие методы задают и выполняют запрос?
- Для чего создается производный от JTable класс?
- В чем заключается связывание?
- Каким образом осуществляется управления часто используемыми полями?
Упражнения
Адаптируйте код из раздела "Практика" для своего варианта (см. список вариантов в "Варианты заданий для лабораторных работ" ).