Опубликован: 02.10.2012 | Доступ: свободный | Студентов: 1916 / 85 | Длительность: 11:48:00
Теги: joomla, mvc, php, xhtml, xml
Лекция 5:

SEF-ссылки. Классы ядра JDocument, JUser

< Лекция 4 || Лекция 5: 12 || Лекция 6 >

Практика

Форма для написания вопроса

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

case 'showform':
  showForm($option);
  break;
        

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

function showForm($option)
{
  $user =&JFactory::getUser();
  if($user->name)
    $name = $user->name;
  else
    $name = '';
  
  HTML_questions::showForm($option, $name);
}
        

Перед вызовом функции вывода HTML-кода мы получаем имя залогиненного в настоящий момент пользователя, если таковой имеется. Код $user = &JFactory::getUser() присваивает переменной $user ссылку на объект-представитель залогиненного пользователя. Если удалось получить имя пользователя, то мы сохраняем это значение в переменной $name, а в противном случае этой переменной присваивается пустая строка. Таким образом поле "Автор" в форме для написания вопроса будет уже заполнено, так что залогиненным пользователям не придется его заполнять.

Перейдите в файл myquestions.html.php и добавьте в класс HTML_questions метод showForm():

function showForm($option, $name)
{
  ?>
  <form action="index.php" method="post">
    <table>
      <tr>
        <td width="100">
            <?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 $name;?>"/>
        </td>
      </tr>  
      <tr>
        <td width="100">
          <?php echo JText::_('COM_MYQUESTIONS_CITY');?>:
        </td>
        <td>
          <input class="text_area" type="text" name="city" id="city" size="50" maxlength="50"/>
        </td>
      </tr>
      <tr>
        <td width="100">
          <?php echo JText::_('COM_MYQUESTIONS_EMAIL');?>:
        </td>
        <td>
          <input class="text_area" type="text" name="email" id="email" size="50" maxlength="50"/>
        </td>
      </tr>          
      <tr>
        <td width="100">
          <?php echo JText::_('COM_MYQUESTIONS_QUESTION');?>:
        </td>
        <td>
          <textarea name='question' id='question' class='inputbox' rows='15' cols='38'></textarea>
        </td>
      </tr>
      <tr>
        <td width="100">
          <?php echo JText::_('COM_MYQUESTIONS_PUBLISHED');?>:
        </td>
        <td>
          <input type="hidden" name="published" value="0"/>
          <input type="checkbox" name="published" id="published" value="1"/>
        </td>
      </tr>
    </table>
    <input type="hidden" name="task" value="addquestion"/>
    <input type="hidden" name="option" value="<?php echo $option;?>"/>
    <input type="submit" class="button" id="button" value="<?php echo JText::_('COM_MYQUESTIONS_SENDBUTTON');?>"/>
  </form>
  <?php
}
        
Листинг .

Как видите, в данной форме сразу два элемента с именем published. Дело в том, что, когда флажок установлен в выбранное состояние, то сценарию-обработчику формы в числе других параметров приходит пара "имя_флажка=значение". Однако когда флажок не установлен, эта пара не посылается. Поэтому используется следующий прием: перед флажком в форме помещается одноименное скрытое поле со значением, равным нулю. Тогда если флажок не установлен, то сценарий получит пару published=0. Если же он установлен, то сценарий тоже получит эту пару, но сразу же последует пара published=1, которая перекроет значение скрытого поля.

Так как мы поместили на форму скрытый элемент task со значением addquestion, то она будет обработана при обработке задачи addquestion. Поэтому добавьте в конструкцию switch в файле myquestions.php следующий код:

case 'addquestion':
  addQuestion($option);
  break;
        

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

function addQuestion($option)
{
  $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 = nl2br(htmlspecialchars(JRequest::getVar('question', '', 'post', 'string',JREQUEST_ALLOWRAW), ENT_QUOTES));
  $row->IP = getenv('REMOTE_ADDR');
  $row->date = &JFactory::getDate()->toFormat();
  $row->id_cat = 1;

  if (!$row->store())
  {
    echo "<script> alert('".$row->getError()."'); window.history.go(-1); </script>\n";
    exit();
  }

  $mailer =& JFactory::getMailer();
  $mailer->setSender('test@mysite.ru');
  $mailer->addRecipient('admin@mysite.ru');
  $mailer->setSubject(JText::_('COM_MYQUESTIONS_ADMIN_LETTER_SUBJECT'));
  $mailer->setBody(JText::sprintf('COM_MYQUESTIONS_ADMIN_LETTER_NEW_QUESTION',$row->question));
  $mailer->IsHTML(true);
  if ($mailer->Send() !== true)
  {
    echo "<script> alert('".JText::_('COM_MYQUESTIONS_ADMIN_LETTER_ERROR')."'); window.history.go(-1); </script>\n";
    exit();
  }
  
  global $app;
  $app-> redirect(JRoute::_('index.php?option='.$option.'&task=view&view=all'), 
    JText::sprintf('COM_MYQUESTIONS_QUESTION_SENT',$row->name));
} 
        

Текст вопроса, введенный пользователем, пропускается через функцию htmlspecialchars(), преобразующую специальные символы в HTML-сущности. Таким путем предотвращается ввод нежелательных HTML-тегов. Затем результат пропускается через функцию nl2br(), вставляющую код разрыва строки <br/> перед каждым переводом строки, чтобы текст вопроса при выводе на веб-странице не слился в одну строку.

IP-адрес пользователя определяется с помощью функции getenv(), которая возвращает значение переменной окружения, в данном случае - REMOTE_ADDR.

По умолчанию вопросу присваивается категория с id, равным 1, то есть "Без категории".

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

COM_MYQUESTIONS_ADD_QUESTION="Задать вопрос"
COM_MYQUESTIONS_AUTHOR="Автор"
COM_MYQUESTIONS_DATE="Дата вопроса"
COM_MYQUESTIONS_QUESTION="Текст вопроса"
COM_MYQUESTIONS_CITY="Город"
COM_MYQUESTIONS_EMAIL="e-mail"
COM_MYQUESTIONS_CATEGORY="Категория"
COM_MYQUESTIONS_PUBLISHED="Отображать ли вопрос на сайте"
COM_MYQUESTIONS_SENDBUTTON="Отправить вопрос"
COM_MYQUESTIONS_QUESTION_SENT="Спасибо, %s! Ваш вопрос отправлен. Он будет опубликован на сайте после получения ответа"
COM_MYQUESTIONS_ADMIN_LETTER_SUBJECT="Новый вопрос на сайте"
COM_MYQUESTIONS_ADMIN_LETTER_NEW_QUESTION="<p>Добрый день!</p><p>На сайте появился новый вопрос:</p><p><i>%s</i></p>"
COM_MYQUESTIONS_ADMIN_LETTER_ERROR="Ошибка отправки письма"
        

Осталось добавить ссылку для написания вопроса. Измените начало функции showCategories() так:

function showCategories($rows, $option)
  {
    ?>
    <p><a href='index.php?option=<?=$option?>&task=showlist'><?=JText::_('COM_MYQUESTIONS_ALL_QUESTIONS')
    ?></a></p>
    <p><a href='index.php?option=<?=$option?>&task=showform'><?=JText::_('COM_MYQUESTIONS_ADD_QUESTION')
    ?></a></p>
        

Теперь на главной странице компонента во фронтенде появилась ссылка "Задать вопрос" ( рис. 5.1).

Ссылка "Задать вопрос"

увеличить изображение
Рис. 5.1. Ссылка "Задать вопрос"

При переходе по этой ссылке появляется форма для написания вопроса ( рис. 5.2). Обратите внимание, что в поле "Автор" подставилось имя текущего пользователя, если он залогинен.

Форма для написания вопроса

увеличить изображение
Рис. 5.2. Форма для написания вопроса

После написания вопроса и нажатия кнопки "Отправить вопрос" происходит перенаправление на главную страницу компонента с сообщением об успешной отправке вопроса ( рис. 5.3).

Сообщение об отправке вопроса

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

Зайдите в папку <путь к Денверу>/tmp/!sendmail и найдите в ней файл *.eml, содержащий письмо-уведомление администратора о новом вопросе.

SEF

Включите SEF в бэкенде. Для этого перейдите в меню "Сайт" - "Общие настройки" и убедитесь, что переключатель "Включить SEF (ЧПУ)" установлен в "Да". Если вы используете в качестве веб-сервера Apache со включенным mod_rewrite, то вы можете также установить переключатель "Перенаправление URL" в "Да"; тогда из ваших ссылок исчезнет строка "index.php". Вид раздела "Настройки SEO" при включенном mod_rewrite показан на рис. 5.4.

Раздел панели управления "Настройки SEO"

Рис. 5.4. Раздел панели управления "Настройки SEO"

Если ваша конфигурация не позволяет использовать mod_rewrite, SEF-ссылки все равно могут быть построены, но они будут включать строку "index.php", например: http://www.mysite.ru/index.php/one/two/three.

Нажмите кнопку "Сохранить и закрыть" для сохранения конфигурации. Если вы используете mod_rewrite, убедитесь, что вы переименовали находящийся в корневой папке Joomla файл htaccess.txt в .htaccess (если переименовать файл в проводнике Windows не удается, воспользуйтесь интерфейсом командной строки или каким-либо файловым менеджером, например, Total Commander).

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

public $sef = '0';
public $sef_rewrite = '0';
        

и измените оба значения на "1" вместо "0".

Генерация SEF-ссылок

Напишем функцию для генерации SEF-ссылок. Создайте файл /components/com_myquestions/router.php:

<?php
defined('_JEXEC') or die ('Restricted access');
function MyQuestionsBuildRoute(&$query)
{
  $segments = array();
  if (isset($query['task']))
  {
    $segments[] = $query['task'];
    unset($query['task' ]) ;
  }
  if(isset($query['id' ]))
  {
    $segments[] = $query['id'];
    unset($query['id']);
  }
  return $segments;
}
?>
            

Мы создаем пустой массив $segments. Затем проверяем, есть ли в массиве запроса элемент "task", и в этом случае добавляем значение задачи в массив $segments в качестве первого элемента и затем удаляем task из запроса. Далее мы повторяем тот же процесс для id. Наконец, возвращаем массив $segments, чтобы JRoute::_() могла закончить построение URL.

Исправим функции вывода нашего компонента так, чтобы они выводили SEF-ссылки вместо обычных. Откройте файл /components/com_myquestions/myquestions.html.php и измените код функции showCategories() класса HTML_questions следующим образом:

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

Измените выделенный код в функции HTML_questions::showQuestions():

foreach($rows as $row)
  {
    $link = JRoute::_('index.php?option='.$option.'&id='.$row->id.'&task=showquestion');
    $link_cat = JRoute::_('index.php?option='.$option.'&id_cat='.$row->id_cat.'&task=showlist');
    ?>
            

Измените также выделенный код в функции HTML_questions::showQuestion():

function showQuestion($row, $option, $row_cat)
{
  $link_cat = JRoute::_('index.php?option='.$option.'&id_cat='.$row->id_cat.'&task=showlist');
            

Теперь компонент будет генерировать SEF-ссылки по шаблону, установленному в функции MyQuestionsBuildRoute().

Декодирование SEF-ссылок

Если вы сейчас попытаетесь щелкнуть на одной из SEF-ссылок, то получите сообщение:

"Fatal error: Call to undefined function myquestionsParseRoute() in Y:\home\localhost\www\joomla\includes\router.php on line …".
            

Напишем функцию для декодирования SEF-ссылок.

Откройте файл /components/com_myquestions/router.php и добавьте следующую функцию:

function MyQuestionsParseRoute ($segments)
{
  $vars = array();
  $vars['task'] = @$segments[0];
  $vars['id'] = @$segments[1];
  return $vars;
}
            

Как видите, в функции MyQuestionsParseRoute() мы считали переменные task и id из массива $segments в том же порядке, в котором мы их записывали в одноименный массив в функции MyQuestionsBuildRoute().

Знаки "@" при получении элементов массива $segments используются для подавления вывода сообщений об обращении к несуществующим элементам массива, т.к. не все наши SEF-ссылки будут содержать id.

Теперь щелкните по какой-либо ссылке во фронтенде и обратите внимание на строку статуса в браузере. Вы должны увидеть URL вида: http://localhost/joomla/component/myquestions/showlist или http://localhost/joomla/component/myquestions/showquestion/1

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

JDocument - класс для работы с документом.
JRoute - класс для создания SEF-ссылок.
JUser - класс для работы с данными о пользователе.Документ
Документ - буфер, использующийся для хранения содержимого веб-страницы, которая будет показана пользователю после выполнения запроса.
Функция генерации SEF-ссылок - функция, которая принимает массив элементов HTTP-запроса и возвращает массив сегментов SEF-ссылки.
Функция декодирования SEF-ссылок - функция, которая из массива сегментов SEF-ссылки создает массив переменных HTTP-запроса.
Шаблон SEF-ссылок - последовательность сегментов.

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

SEF-ссылки в Joomla создаются с помощью метода JRoute::_(), который переводит внутреннюю ссылку, генерируемую Joomla, в SEF-ссылку. Чтобы компонент работал с SEF-ссылками, сгенерированными по собственному шаблону, необходимо создать в корневой папке его фронтенда файл router.php, в котором должны находиться функция для генерации SEF-ссылок и функция для их декодирования. Эти функции осуществляют взаимно обратные операции: первая из них из массива элементов HTTP-запроса создает массив сегментов SEF-ссылки, а вторая из массива сегментов SEF-ссылки создает массив переменных HTTP-запроса.

Так как SEF-ссылки не позволяют задать названия переменных запроса, то единственный способ определить, к какой переменной относится то или иное значение сегмента, - это использовать шаблон, который задает последовательность сегментов. Шаблон неявно задается в коде каждой из функций в файле router.php.

Для работы с документом и с данными пользователя в Joomla существуют соответственно классы JDocument и JUser.

Вопросы

  1. Какой метод переводит внутреннюю ссылку, генерируемую Joomla, в SEF-ссылку?
  2. Каким образом компоненты работают с SEF-ссылками?
  3. Для чего служат функции генерации и декодирования SEF-ссылок?
  4. Что такое шаблон SEF-ссылок и как он задается?
  5. Какие классы используются для работы с документом и с данными пользователя?

Упражнения

Адаптируйте код из раздела "Практика" для своего варианта (см. список вариантов в "Варианты заданий для лабораторных работ" ).

< Лекция 4 || Лекция 5: 12 || Лекция 6 >
Кирилл Гусаров
Кирилл Гусаров

В разделе "Первые папки и файлы. Добавление пунктов меню"

предлагается создать две файла:

- myquestions.php;

- admin.myquestions.php

с соответствуюшими адресами:

/components/com_myquestions/myquestions.php;

- /administrator/components/com_myquestions/admin.myquestions.php;

Так вот, при создании файла "admin.myquestions.php" В админке выдает ошибку - "Компонент не найден", а при переименовании его на  "myquestions.php" в последующем шаге, в админке не выводятся кнопки редактирования. 
Проверил кодировку, проверил правильность пути к файлам, пересохранил указанный код. Скажите что я делаю не так или в чем может быть причина?