Цель лекции: Ознакомиться со способом программного добавления пунктов меню в Joomla, а также с некоторыми методами классов JMail, JEditor, JURI, JError, JDate.

Организация иерархии пунктов меню в Joomla

Для хранения иерархии пунктов меню в Joomla используются вложенные множества (http://en.wikipedia.org/wiki/Nested_set_model). Например, на рис. 4.1 представлена эта иерархия для случая, если бы в ней было всего восемь пунктов: корневой пункт меню Menu_Item_Root, com_banners с подпунктами com_banners, com_banners_categories, com_banners_clients и com_messages с подпунктами com_messages_add, com_messages_read:

Пример использования вложенных множеств для организации иерархии пунктов меню


увеличить изображение
Рис. 4.1.  Пример использования вложенных множеств для организации иерархии пунктов менюКаждому элементу иерархии соответствуют два числа - левый и правый ключи.

Для каждого пункта в таблице #__menu хранится id родителя parent_id, уровень level и левый и правый ключи lft и rgt. На рис. 4.2 оранжевым цветом показаны id, синим - уровни, красным - левые и правые ключи.

Данные, хранящиеся для каждого пункта меню как элемента иерархии


увеличить изображение
Рис. 4.2.  Данные, хранящиеся для каждого пункта меню как элемента иерархии

Отправка писем (класс JMail)

Класс JMail предназначен для создания и отправки электронных писем. JMail поддерживает три механизма отправки почты: функцию PHP mail(), Sendmail и SMTP.

Доступ к глобальному объекту JMail можно получить через метод getMailer() класса JFactory:

$mailer = JFactory::getMailer();

Отправитель письма

Для задания отправителя письма используется метод

JMail setSender(mixed $from)

где $from - либо строка, содержащая e-mail отправителя, либо массив, который содержит e-mail и имя отправителя.

Примеры:

// Первый вариант
$mailer = JFactory::getMailer();
$mailer->setSender('vasya@mysite.ru');
// Второй вариант
$sender = array('vasya@mysite.ru', 'Вася')
$mailer->setSender($sender);

Правильнее будет не задавать эти значения вручную, а получать их из настроек отправки почты, заданных в панели управления ("Сайт" - "Общие настройки" - "Сервер"):

$config = JFactory::getConfig();
$sender = array(
    $config->get('config.mailfrom'),
    $config->get('config.fromname'));
$mailer->setSender($sender);

Адреса для ответа

Адреса для ответа задаются методом

JMail addReplyTo(array $replyto)

где $replyto - пара e-mail-имя или массив таких пар.

Например:

// Первый вариант
$reply = array('vasya@mysite.ru', 'Вася');
$mailer->addReplyTo($reply);
// Второй вариант
$reply0 = array('vasya@mysite.ru', 'Вася');
$reply1 = array('vanya@mysite.ru', 'Ваня');
$replies = array($reply0, $reply1);
$mailer->addReplyTo($replies);

Получатель

Для задания получателя, получателя копии (Carbon Copy) и получателя скрытой копии (Blind Carbon Copy), используются методы

JMail addRecipient(mixed $recipient, mixed name = '')
JMail addCC(mixed $cc, mixed name = '')
JMail addBCC(mixed $bcc, mixed name = '')

где

$recipient, $cc, $bcc - e-mail или массив, состоящий из e-mail;
$name - имя получателя или массив, состоящий из имен.

Для примера зададим в качестве адреса получателя адрес текущего пользователя:

$user = JFactory::getUser();
$mailer->addRecipient($user->email);
Задание нескольких получателей:
$recipient = array('vasya@mysite.ru', 'vanya@mysite.ru');
$mailer->addRecipient($recipient);

Тема, текст, вложения

Тема, текст и вложения письма задаются методами

JMail setSubject(string $subject)
JMail setBody(string $content)
JMail addAttachment(mixed $attachment, mixed $name = '', mixed $encoding = 'base64', mixed $type = 'application/octet-stream')

где:

$subject - строка, содержащая тему письма;
$content - строка, содержащая текст письма;
$attachment - строка, содержащая путь к файлу, или массив таких строк;
$name - строка, содержащая имя файла, или массив таких строк;
$encoding - кодировка сообщения: 8bit, 7bit, binary, base64, quoted-printable;
$type - MIME-тип файла.

Например:

$mailer->setSubject('Тема письма');
$mailer->setBody("Текст письма");
$mailer->addAttachment(JPATH_COMPONENT.DS.'files'.DS.'document.pdf');
По умолчанию содержимое письма имеет формат plain text. Если вам нужно отправить письмо в формате HTML, используйте метод IsHTML(), задающий MIME-тип содержимого письма как text/html:
$mailer->IsHTML(true);
$mailer->setBody("Текст письма, содержащий <b>HTML-теги</b>");

В этом случае желательно указать кодировку письма - base64, чтобы в нем не появились нежелательные символы:

$mailer->Encoding = 'base64';

Отправка

Наконец, для отправки письма используется метод Send(), возвращающий true при успешной отправке или объект JError в случае ошибки:

if ($mailer->Send() !== true)
    die('Ошибка отправки письма');

Проверка корректности e-mail

Помимо класса JMail, существует статический класс JMailHelper. Большинство его методов предназначены для очистки данных перед добавлением к электронному письму и используются классом JMail. Интерес для разработчика представляет метод isEmailAddress(), который проверяет, является ли заданная строка корректным адресом электронной почты:

if (!JMailHelper::isEmailAddress($str))
    die('Недопустимый формат e-mail');

Перед использованием класса JMailHelper необходимо подключить библиотеку JMail:

jimport('joomla.utilities.mail');

WYSIWYG-редактор (класс JEditor)

Класс JEditor используется для работы с WYSIWYG-редактором.

Получить ссылку на глобальный объект JEditor можно так:

$editor = JFactory::getEditor();

Отображение редактора

Для отображения выбранного администратором сайта редактора используется метод display(). Если не выбран ни один редактор, то будет отображено поле <textarea>.

string display(string $name, string $html, string $width, string $height, int $col, int $row, bool $buttons = true, 
string $id = null, string $asset = null, object $author = null, array $params = array())

где

$name - имя элемента управления формы;
$html - содержимое поля;
$width - ширина текстового поля в процентах или пикселях;
$height - высота текстового поля;
$col - количество столбцов в поле <textarea> (применяется, если администратор выбрал не использовать HTML-редактор);
$row - количество строк в <textarea>;
$buttons - отображать ли кнопки редактора ("Материал", "Изображение", "Разрыв страницы", "Подробнее…");
$id - id элемента управления формы;
$asset и $author - используются для подтверждения прав доступа пользователя к некоторым функциям плагина редактора. Более подробная информация об этих параметрах в справочной документации отсутствует;
$params - ассоциативный массив параметров редактора.

Пример:

echo $editor->display('question',  $row->question,'100%', '250', '40', '10');

Если по умолчанию задан редактор TinyMCE, то этот код выведет на экран следующее (рис. 4.3).

Отображение WYSIWYG-редактора

увеличить изображение
Рис. 4.3.  Отображение WYSIWYG-редактора

URI (класс JURI)

Чтобы создать новый объект класса JURI или получить ссылку на глобальный объект, используется метод

JURI getInstance(string $uri='SERVER')

где $uri - URI страницы. При $uri='SERVER' метод вернет ссылку на глобальный объект, который представляет URI текущей страницы. В противном случае будет создан новый объект.

Например, так можно получить URI текущей страницы:

$u = JURI::getInstance();
echo $u->toString();

Методы класса JURI

Методы класса JURI хорошо документированы (http://docs.joomla.org/JURI), поэтому здесь мы только перечислим некоторые из них.

Получение базового URI сайта:

string base(bool $pathonly)

где $pathonly - возвращать только путь или также вернуть хост и порт.

Построение запроса (т.е. компонента URI):

string buildQuery(array $params)

где $params - ассоциативный массив пар "имя-значение".

Получение текущего URI без запроса и фрагмента:

string current()

Получение отдельных компонентов URI:

фрагмента - string getFragment();
хоста - string getHost();
пароля - string getPass();
пути - string getPath();
порта - string getPort();
запроса - string getQuery(bool $toArray), где $toArray задает, возвращать ли элементы запроса в виде ассоциативного массива или в виде строки;
схемы - string getScheme();
имени пользователя - string getUser().

Получение заданной переменной из запроса:

string getVar(string $name, string $default)

где

$name - имя переменной;
$default - значение по умолчанию.

Заполнение полей класса значениями, полученными из заданного URI ($uri):

boolean parse(string $uri)

Получение корневого URI сайта:

string root(bool $pathonly, string $path)

где $path - задает новый путь.

Ошибки (класс JError)

Методы raiseError(), raiseNotice() и raiseWarning() класса JError вызываются при возникновении каких-либо ошибок. Все они возвращают объект JException, содержащий сведения об ошибке, например, в каких файлах она возникла. raiseError() добавляет заданное сообщение в очередь сообщений и останавливает выполнение расширения, а остальные два метода только добавляют сообщение в очередь, но работа расширения продолжается.

JException raiseError(string $code, string $msg, mixed $info = null)
JException raiseWarning(string $code, string $msg, mixed $info = null)
JException raiseNotice(string $code, string $msg, mixed $info = null)

где

$code - внутренний код ошибки, задается по усмотрению программиста;
$msg - сообщение об ошибке для пользователя;
$info - дополнительная информация об ошибке для программиста.

Пример:

if ($this->published == 0)
  JError::raiseError (404, "Нет такой страницы");

Перечисленные методы создавались в то время, когда Joomla работала с PHP4, где отсутствовал механизм обработки исключений. Если на хостинге стоит PHP5, предпочтительнее использовать класс Exception.

Даты (класс JDate)

JDate - класс для работы с датами. Для создания объекта JDate используется метод getDate() класса JFactory:

JDate JFactory::getDate(mixed $time = 'now', mixed $tzOffset = null)

где

$time - дата и время в формате, годном для функции PHP strtotime();
$tzOffset - временная зона. Может задаваться числом от -12 до 14 (смещение относительно всемирного координированного времени - UTC) или строкой из числа временных зон, поддерживаемых PHP (их список можно найти на странице http://www.php.net/manual/ru/timezones.php).

В отличие от многих других методов JFactory, getDate() не возвращает глобальный объект, а создает новый. Поэтому перед вызовом этого метода не нужно ставить амперсанд:

$now = JFactory::getDate(); // текущие дата и время
$some_date = JFactory::getDate('2012-01-01 00:00:00','Europe/Moscow');

Получение даты в каком-либо формате

string format(string $format, bool $local = false, bool $translate = true)

где

$format - формат;
$local - возвращать дату в локальной временной зоне или в GMT;
$translate - переводить ли текст.

Чтобы не задавать формат даты вручную, можно использовать один из определенных в Joomla форматов: DATE_FORMAT_LC, DATE_FORMAT_LC1 и т.д. Их список находится в языковом файле language/<ln-LN>/<ln-LN>.ini, где <ln-LN> - код языка. Как и для всякого языкового ключа, значение такого формата можно получить с помощью функции JText::_(). Например, выведем дату в формате DATE_FORMAT_LC3:

echo $some_date->format(JText::_('DATE_FORMAT_LC3'));

Получение даты в формате, пригодном для вставки в запрос SQL

string toMySQL(bool $local=false)

Например, для получения материалов, созданных ранее даты $some_date, можно выполнить запрос:

$query = "SELECT * FROM #__content WHERE created < '{$some_date->toMySQL()}'";

Практика

Отправка уведомлений по электронной почте

Сейчас в форме для ответа на вопрос две кнопки на панели инструментов (рис. 4.4) - нерабочие.

Кнопки для отправки уведомлений по электронной почте

Рис. 4.4.  Кнопки для отправки уведомлений по электронной почтеНапишем код для их обработки.

Откройте файл admin.myquestions.php и добавьте в переключатель switch обработку двух задач:

case 'sendToExpert':
case 'sendAnswer':
    send($option,$task);
    break;

Добавьте также в этот же файл функцию send():

function send($option,$task)
{
    $row_new = save();
    $q = $row_new->question;
    $a = $row_new->answer;
    
    $mailer = JFactory::getMailer();
    $mailer->setSender('test@mysite.ru');
    if ($task == 'sendToExpert')
    {
        $mailer->addRecipient('expert@mysite.ru');
        $mailer->setSubject(JText::_('COM_MYQUESTIONS_NEW_QUESTION'));
        $mailer->setBody(JText::sprintf('COM_MYQUESTIONS_EMAIL_EXPERT_BODY',$q));
    }
    else
    {
        $mailer->addRecipient($row_new->email);
        $mailer->setSubject(JText::_('COM_MYQUESTIONS_NEW_ANSWER'));
        $mailer->setBody(JText::sprintf('COM_MYQUESTIONS_EMAIL_USER_BODY',$q,$a));
    }
    $mailer->IsHTML(true);
    
    if ($mailer->Send() !== true)
        $message = 'COM_MYQUESTIONS_EMAIL_ERROR';
    else
    {
        $message = 'COM_MYQUESTIONS_EMAIL_SUCCESS';
        
        $db = JFactory::getDbo();
        if ($task == 'sendToExpert')
            $query = "UPDATE #__myquestions SET senttoexpert=1 WHERE id={$row_new->id}";
        else
            $query = "UPDATE #__myquestions SET senttoauthor=1 WHERE id={$row_new->id}";
        
        $db->setQuery($query);
        $db->query();
        if ($db->getErrorNum())
        {
            echo $db->stderr();
            return false;
        }
    }
    global $app;
    $app->redirect('index.php?option='.$option.'&task=reply&cid[]='.$row_new->id, JText::_($message));
}
Листинг .

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

При задании тела письма используется метод JText::sprintf(), пропускающий заданную ему строку через PHP-функцию sprintf(). В выражении JText::sprintf('COM_MYQUESTIONS_EMAIL_USER_BODY',$q,$a) 'COM_MYQUESTIONS_EMAIL_USER_BODY' - это строка формата, которую мы сейчас зададим в языковом файле, а $q и $a - это аргументы (соответственно текст вопроса и текст ответа).

Тип содержимого письма задается как text/html, так как оно содержит html-теги.

Если письмо успешно отправлено, то в соответствующей вопросу записи в базе данных значение senttoexpert или senttoauthor устанавливается равным 1.

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

Добавьте в файл /administrator/language/ru-RU/ru-RU.com_myquestions.ini следующий код:

COM_MYQUESTIONS_EMAIL_SUCCESS="e-mail отправлен"
COM_MYQUESTIONS_EMAIL_ERROR="Не удалось отправить e-mail"
COM_MYQUESTIONS_NEW_QUESTION="Новый вопрос"
COM_MYQUESTIONS_EMAIL_EXPERT_BODY="<p>Добрый день!</p><p>На сайте появился новый вопрос:
</p><p><i>%s</i></p>"
COM_MYQUESTIONS_NEW_ANSWER="Ответ на ваш вопрос"
COM_MYQUESTIONS_EMAIL_USER_BODY="<p>Добрый день!</p><p>На сайте появился ответ на ваш вопрос:
</p><p><i>%s</i></p><p>Ответ был таким:</p><p><i>%s</i></p>"

Как видите, строки COM_MYQUESTIONS_EMAIL_EXPERT_BODY и COM_MYQUESTIONS_EMAIL_USER_BODY содержат параметры. Так как их тип - строковый (тексты вопроса, ответа), то используется описатель типа %s.

Теперь перейдите в бэкенде на страницу ответа на вопрос и нажмите кнопку "Отправить эксперту". Вы должны увидеть сообщение о том, что письмо отправлено (рис. 4.5).

Сообщение об отправке уведомления по электронной почте

увеличить изображение
Рис. 4.5.  Сообщение об отправке уведомления по электронной почтеЕсли вы используете локальный сервер, то ваше письмо, скорее всего, на самом деле не отправилось. В Денвере срабатывает почтовая заглушка sendmail: все письма просто помещаются в папку <путь к Денверу>/tmp/!sendmail в виде файлов с расширением .eml. Если в вашем случае так и есть, то откройте эту папку и найдите в ней только что "отправленное" письмо (можно ориентироваться по названию файла - это дата и время "отправки"). Откройте файл в почтовой программе и убедитесь, что его содержимое соответствует заданным нами значениям (рис. 4.6).

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

Рис. 4.6.  Результат отправки уведомления на локальном сервере - файл .eml, открытый в почтовой программеПерейдите к списку вопросов и убедитесь, что значение в столбце "Отправлен ли вопрос эксперту" поменялось с "Нет" на "Да" (рис. 4.7).

Новое значение в столбце

увеличить изображение
Рис. 4.7.  Новое значение в столбце "Отправлен ли вопрос эксперту"Аналогично проверьте, как работает кнопка "Отправить ответ".

Добавление пункта меню

Добавим пункт меню для управления категориями.

Создавая первый пункт меню для нашего компонента, мы не указали значения левого и правого ключей. Определим их сейчас. Уровень данного пункта равен 1 (его предком является корень дерева с уровнем 0, а потомки могут иметь уровень 2 и более). Родительский узел - это самая первая строка в таблице #__menu, посмотрите значение его правого ключа в поле rgt.

Пусть $right_key - правый ключ родительского узла, $level - уровень родительского узла. Тогда для задания правильных значений левого и правого ключей выполним следующие запросы (не забудьте заменить $right_key и $level на значения из вашей таблицы):

  1. Обновление ключей узлов, стоящих за родительским узлом:
    UPDATE jos_menu SET lft=lft+2, rgt=rgt+2 WHERE lft>$right_key
  2. Обновление родительской ветки:
    UPDATE jos_menu SET rgt=rgt+2 WHERE rgt>=$right_key AND lft<$right_key
  3. Обновление нового узла:
    UPDATE jos_menu SET lft=$right_key, rgt=$right_key+1, level=$level+1 WHERE title='com_myquestions_menu'

Пусть теперь $right_key и $level - соответственно правый ключ и уровень этого только что обновленного нами узла, а $parent_id - его id. Создадим два подпункта этого пункта меню (не забудьте заменить 10006 на id вашего компонента из таблицы #__extensions).

Подпункт для управления списком вопросов:

UPDATE jos_menu SET lft=lft+2, rgt=rgt+2 WHERE lft>$right_key;
UPDATE jos_menu SET rgt=rgt+2 WHERE rgt>=$right_key AND lft<$right_key;
INSERT INTO `jos_menu` (`menutype`, `title`, `alias`, `path`, `link`, `type`, `parent_id`, `level`, 
`component_id`, `access`, `img`,  `params`, `lft`, `rgt`, `client_id`) VALUES ('menu', 'com_myquestions_menu_questions', 
'Questions', 'My Questions/Questions', 'index.php?option=com_myquestions', 'component',  $parent_id, $level+1, 10006, 1, 
'class:component', '', $right_key, $right_key+1, 1);

Обратите внимание, что значения alias и path соответствуют иерархии узлов.

Посмотрите в таблице #__menu новое значение $right_key главного пункта меню для нашего компонента - оно должно было увеличиться на 2. Теперь создадим подпункт для управления списком категорий:

UPDATE jos_menu SET lft=lft+2, rgt=rgt+2 WHERE lft>$right_key;
UPDATE jos_menu SET rgt=rgt+2 WHERE rgt>=$right_key AND lft<$right_key;
INSERT INTO `jos_menu` (`menutype`, `title`, `alias`, `path`, `link`, `type`, `parent_id`, `level`, 
`component_id`, `access`, `img`,  `params`, `lft`, `rgt`, `client_id`) 
VALUES ('menu', 'com_myquestions_menu_categories', 
'Categories', 'My Questions/Categories', 'index.php?option=com_myquestions&task=showcat', 'component', $parent_id, 
$level+1, 10006, 1, 'class:component', '', $right_key, $right_key+1, 1);

Наконец, добавьте в файл /administrator/language/ru-RU/ru-RU.com_myquestions.sys.ini код:

COM_MYQUESTIONS_MENU_QUESTIONS="Управление вопросами"
COM_MYQUESTIONS_MENU_CATEGORIES="Управление категориями"

Обновите любую страницу в бэкенде и убедитесь, что появились два новых подпункта меню "Моя система "вопрос - ответ"" (рис. 4.8).

Подпункты меню в бэкенде

увеличить изображение
Рис. 4.8.  Подпункты меню в бэкенде

Разработка фронтенда

Для удобства дальнейшего тестирования добавим еще несколько вопросов в различные категории:

INSERT INTO `jos_myquestions` (`id`, `name`, `date`, `question`, `city`, `email`, `IP`, `id_cat`)
VALUES (NULL, 'Аноним', '2012-01-02 09:01:00', 'Быть или не быть?', 'Москва',
 'somebody@mail.ru', '12.345.67.890', '1');
INSERT INTO `jos_myquestions` (`id`, `name`, `date`, `question`, `city`, `email`, `IP`, `id_cat`)
VALUES (NULL, 'Аноним', '2012-01-01 09:02:00', 'А судьи кто?', 'Москва', 
 'somebody@mail.ru', '12.345.67.890', '2');
INSERT INTO `jos_myquestions` (`id`, `name`, `date`, `question`, `city`, `email`, `IP`, `id_cat`) 
VALUES (NULL, 'Аноним', '2012-01-01 09:03:00', ' А был ли мальчик?', 'Москва',
 'somebody@mail.ru', '12.345.67.890', '2');

Для наглядности выключите SEF в настройках сайта: в бэкенде зайдите в меню "Сайт" - "Общие настройки" и установите переключатель "Включить SEF (ЧПУ)" в значение "Нет".

Вывод списка категорий

Замените содержимое файла /components/com_myquestions/myquestions.php (обратите внимание, что мы больше не работаем с папкой /administrator) на следующий код:

<?php
defined('_JEXEC') or die('Restricted access');
$option = JRequest::getVar('option'); $task = JRequest::getVar('task');
jimport('joomla.application.helper');
require_once(JApplicationHelper::getPath('html'));
JTable::addIncludePath(JPATH_ADMINISTRATOR.DS.'components'.
DS.$option.DS.'tables');
switch($task)
{
  default:
    showCategories($option);
    break;
}
function showCategories($option)
{
  $db = JFactory::getDbo();
  $query = "SELECT c.id, c.name, c.desc ".
        "FROM #__myquestions_categories c";
  $db->setQuery($query);
  $rows = $db->loadObjectlist();
  if ($db->getErrorNum())
  {
    echo $db->stderr();
    return false;
  }
  HTML_questions::showCategories($rows, $option);
}
?>

Мы подключаем файл myquestions.html.php с помощью require_once(JApplicationHelper::getPath('html')) и папку table бэкенда с помощью JTable::addIncludePath(). Обратите внимание, что мы обращаемся к классам таблиц бэкенда, хотя пишем фронтенд компонента.

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

Теперь добавим класс HTML_questions для фронтенда. Создайте файл /components/com_myquestions/myquestions.html.php:

<?php
class HTML_questions
{
  function showCategories($rows, $option)
  {
    ?>
    <p><a href='index.php?option=<?=$option?>&task=showlist'><?=
     JText::_('COM_MYQUESTIONS_ALL_QUESTIONS')?></a></p>
    <table>
    <?php
    foreach($rows as $row)
    {
      $link = 'index.php?option='.$option.'&id_cat='.$row->id.'&task=showlist';
      echo '<tr><td><p><a href="' . $link . '">'.$row->name
      .'</a></td><td>'.$row->desc.'</td></tr>';
    }
    ?>
    </table>
    <?php
  }
}
?>

Класс HTML_questions будет содержать все функции вывода во фронтенде. Функция showCategories() принимает в качестве параметров массив записей таблицы базы данных и название текущего компонента. В цикле осуществляется обход этого массива и каждая запись выводится на экран.

Создайте файл /language/ru-RU/ru-RU.com_myquestions.ini и скопируйте в него строку:

COM_MYQUESTIONS_ALL_QUESTIONS="Все вопросы"

Теперь на странице компонента myquestions во фронтенде по умолчанию отображается список категорий (рис. 4.9).

Вывод списка категорий во фронтенде

увеличить изображение
Рис. 4.9.  Вывод списка категорий во фронтенде

Просмотр списка вопросов

Измените код конструкции switch в файле myquestions.php, добавив обработку задачи showlist:

case 'showlist':
  showQuestions($option);
  break;

Добавьте в этот же файл функцию showQuestions():

function showQuestions($option)
{
  $db = JFactory::getDbo();
  $query = "SELECT q.id,q.name,q.date,q.question,q.city,q.email,q.answer,q.id_cat,c.name AS cname ".
        "FROM #__myquestions q, #__myquestions_categories c ".
        "WHERE q.id_cat=c.id AND q.answer <> '' AND (q.published = 1 OR 
        (q.expiration_date <> '0000-00-00 00:00:00' AND q.expiration_date > NOW()))";
  $id_cat = JRequest::getVar('id_cat', '0');
  
  if ($id_cat != 0)
    $query .= "  AND q.id_cat = $id_cat";
  $db->setQuery($query);
  $rows = $db->loadObjectlist();
  if ($db->getErrorNum())
  {
    echo $db->stderr();
    return false;
  }
  
  if ($id_cat != 0)
  {
    $query = "SELECT name FROM #__myquestions_categories WHERE id=$id_cat";
    $db->setQuery($query);
    if ($db->getErrorNum())
    {
      echo $db->stderr();
      return false;
    }
    $name_cat = $db->loadResult();
  }
  else
    $name_cat = '';
  
  HTML_questions::showQuestions($rows, $option, $name_cat);
}

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

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

Данный запрос выбирает вопросы, которые соответствуют этим условиям.

Проверяется значение переменной $id_cat (id категории), и если категория задана, то из базы выбираются только вопросы, отнесенные к этой категории. Затем извлекается название категории для отображения над списком вопросов. Если же категория не задана, то выбираются вопросы из всех категорий.

Добавьте в класс HTML_questions в файле myquestions.html.php следующую функцию:

function showQuestions($rows, $option, $name_cat)
{
  if ($name_cat !== '')
    echo "<h1>$name_cat</h1>";
  foreach($rows as $row)
  {
    $link = 'index.php?option='.$option.'&id='.$row->id.'&task=showquestion';
    $link_cat = 'index.php?option='.$option.'&id_cat='.$row->id_cat.'&task=showlist';
    ?>
    <table width="100%">
      <tr>
        <td><i><?=$row->name?></i></td>
        <td><i><u><?=$row->email?></u></i></td>
        <td><i><?=JHTML::_('date', $row->date,
        JText::_('DATE_FORMAT_LC3'))?></i></td>
        <td><i><?=$row->city?></i></td>
      </tr>
      <tr>
        <td colspan="4"><a href="/<?=$link_cat?>"><?=$row->cname?></a></td>
      </tr>
      <tr>
        <td colspan="4"><b><?=$row->question?></b></td>
      </tr>
      <tr>
        <td colspan="4"><?=$row->answer?></td>
      </tr>
      <tr>
        <td colspan="4"><a style="text-decoration: none;"
         title="<?=JText::_('COM_MYQUESTIONS_READMORE')?>" alt="<?=JText::_
         ('COM_MYQUESTIONS_READMORE')?>" href="/<?=$link?>">---></a></td>
      </tr>
    </table>
    <br/>
    <?
  }
}

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

Добавьте в файл language/ru-RU/ru-RU.com_myquestions.ini строку:

COM_MYQUESTIONS_READMORE="Подробнее"

Для проверки работоспособности нового кода нажмите на ссылку "Все вопросы" во фронтенде нашего компонента. Результат должен быть приблизительно таким, как на рис. 4.10.

Список вопросов во фронтенде

увеличить изображение
Рис. 4.10.  Список вопросов во фронтенде

Вывод одного вопроса

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

Измените код конструкции switch в файле myquestions.php, добавив обработку задачи showquestion:

case 'showquestion':
  showQuestion($option);
  break;

Добавьте в этот же файл функцию showQuestion():

function showQuestion($option)
{
  $id = JRequest::getVar('id', 0) ;
  $row = JTable::getInstance ('question', 'Table');
  $row->load($id);
  
  if ($row->answer == '' || ($row->published == 0 && ($row->expiration_date ==
   '0000-00-00 00:00:00' || strtotime($row->expiration_date) <= time())))
    JError::raiseError(404, JText::_(' COM_MYQUESTIONS_ERROR404'));
  
  $row_cat = JTable::getInstance('Category','Table');
  $row_cat->load($row->id_cat);
  
  HTML_questions::showQuestion($row, $option, $row_cat);
}

В данном коде мы получаем id из запроса к серверу с помощью функции getVar(). Если значение id отсутствует или неприемлемо, будет использовано значение по умолчанию, переданное во втором параметре (ноль). Затем мы получаем экземпляр класса таблицы из бэкенда и загружаем запись таблицы базы данных, соответствующую идентификатору id.

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

  • нет ответа;
  • вопрос помечен как скрытый и дата снятия вопроса с публикации не указана или меньше или равна текущей дате.

Если вопрос не должен отображаться, то используется функция raiseError() класса JError для вывода сообщения "404 - Вопрос не найден". Такое же сообщение появится при попытке обращения к несуществующей записи.

Добавьте в файл language/ru-RU/ru-RU.com_myquestions.ini строку:

COM_MYQUESTIONS_ERROR404="Вопрос не найден"

Проверьте, что при обращении к вопросу, который не должен отображаться, выводится сообщение (рис. 4.11).

Ошибка 404

увеличить изображение
Рис. 4.11.  Ошибка 404 "Вопрос не найден"Теперь создадим функцию в классе вывода. Добавьте функцию showQuestion() в класс HTML_questions в файле myquestions.html.php:

function showQuestion($row, $option, $row_cat)
{
  $link_cat = 'index.php?option='.$option.'&id_cat='.$row->id_cat.'&task=showlist';
  ?>
  <p><a href='index.php?option=<?=$option?>&task=showlist'><?=JText::_
  ('COM_MYQUESTIONS_ALL_QUESTIONS')?></a></p>
  <table width="100%">
    <tr>
      <td><i><?=$row->name?></i></td>
      <td><i><u><?=$row->email?></u></i></td>
      <td><i><?=JHTML::_('date', $row->date,JText::_
      ('DATE_FORMAT_LC3'))?></i></td>
      <td><i><?=$row->city?></i></td>
    </tr>
    <tr>
      <td colspan="4"><a href="/<?=
      $link_cat?>"><?=$row_cat->name?></a></td>
    </tr>
    <tr>
      <td colspan="4"><b><?=$row->question?></b></td>
    </tr>
    <tr>
      <td colspan="4"><?=$row->answer?></td>
    </tr>
  </table>
  <?
}

Данная функция аналогична функции showQuestions() за исключением того, что выводит не массив вопросов, а один вопрос.

Теперь в списке вопросов стрелка под каждым из них ("--->") ведет на страницу с выводом этого вопроса (рис. 4.12).

Просмотр вопроса

увеличить изображение
Рис. 4.12.  Просмотр вопроса

Ключевые термины

JDate - класс для работы с датами.
JEditor - класс для работы с WYSIWYG-редактором.
JError - класс для работы с ошибками.
JMail - класс для создания и отправки электронных писем.
JMailHelper - класс для очистки данных перед добавлением к электронному письму и проверки, является ли заданная строка корректным адресом электронной почты.
JURI - класс для работы с URI.
Иерархия пунктов меню - дерево, состоящее из пунктов меню и организованное с помощью вложенных множеств.

Краткие итоги

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

Создавать и отправлять электронные письма удобнее с помощью методов класса JMail.

Для работы с WYSIWYG-редактором используется класс JEditor.

Класс JURI позволяет работать с URI текущей или любой другой страницы.

Для работы с ошибками и предупреждениями может быть использован класс JError.

Для работы с датами в Joomla существует класс JDate.

Вопросы

  1. Каким образом в базе данных Joomla хранится иерархия пунктов меню?
  2. Какой класс позволяет создавать и отправлять электронные письма?
  3. Каким образом можно отобразить код выбранного администратором сайта WYSIWYG-редактора?
  4. Какой класс существует для работы с URI?
  5. Для чего может быть использован класс JError?
  6. Каким образом можно работать с датами в Joomla?