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

Работа с базой данных

< Лекция 1 || Лекция 2: 123 || Лекция 3 >

Создание формы для ответа на вопрос

Как и ранее, отделим 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).

Фрагмент формы для ответа на вопрос

увеличить изображение
Рис. 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).

Результат сохранения ответа на вопрос

увеличить изображение
Рис. 2.2. Результат сохранения ответа на вопрос

С помощью phpMyAdmin вы можете проверить, что данные были сохранены в таблице базы данных jos_myquestions ( рис. 2.3).

Ответ на вопрос сохранен в базе данных

увеличить изображение
Рис. 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(), заменяющую амперсанды "&" на коды "&amp;" в соответствии со спецификацией 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.

Список вопросов

увеличить изображение
Рис. 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="Вопрос(ы) успешно удален(ы)"
        

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

JDatabase - абстрактный класс, предоставляющий доступ к соединению с базой данных, создающемуся при инициализации приложения Joomla.
JDatabaseQuery - класс, методы которого совпадают с ключевыми словами языка SQL и позволяют упростить создание сложных SQL-запросов.
JTable - класс, реализующий паттерн Active Record и использующийся для управления таблицами базы данных.
Префикс таблиц базы данных - строка, которая присоединяется к названию каждой таблицы Joomla в базе данных.
Реальный префикс - то конкретное сочетание символов, которое используется в названиях таблиц базы данных.
Связывание - процесс присвоения каждому полю производного от JTable класса значения элемента массива переменных запроса, так что ключ элемента совпадает с названием поля.
Символический префикс - сочетание "#__" (решетка и два знака подчеркивания), которое используется в запросах вместо реального префикса.

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

При работе с базой данных различают реальный и символический префиксы. Реальный префикс используется в названиях таблиц базы данных, а символический префикс ("#__") используется в запросах вместо реального префикса. При обработке запроса вместо символического префикса будет автоматически подставлен реальный.

Чтобы выполнить запрос к базе данных Joomla, необходимо осуществить пять операций: получение ссылки на объект JDatabase (абстрактный класс, предоставляющий доступ к соединению с базой данных), формирование запроса, задание запроса, выполнение запроса, загрузка результата.

Запрос может быть сформулирован в виде строки либо разбит на составляющие и построен с помощью методов класса JDatabaseQuery.

Запрос задается для последующего выполнения методом setQuery(), а выполняется либо методом query(), либо, если нам необходимо получить результат, одним из методов для получения форматированного результата: loadResult(), loadRow() и т.д.

Для каждой таблицы, использующейся расширением, необходимо создать класс, производный от JTable. Для каждого поля таблицы необходимо создать одноименное поле этого класса. Производный от JTable класс наследует в числе прочих методы bind(), store(), load() и delete(), позволяющие управлять записями таблицы без единой строки SQL-кода. Когда компонент получает массив переменных запроса, он осуществляет связывание, то есть присваивает каждому полю этого класса значение элемента массива, ключ которого совпадает с названием данного поля.

Существуют методы класса JTable для управления часто используемыми полями ordering, checked_out/checked_out_time, published и hits.

Вопросы

  1. Что такое реальный и символический префиксы?
  2. Какие операции необходимо осуществить для выполнения запроса к базе данных Joomla?
  3. Каким образом может быть сформулирован SQL-запрос?
  4. Какие методы задают и выполняют запрос?
  5. Для чего создается производный от JTable класс?
  6. В чем заключается связывание?
  7. Каким образом осуществляется управления часто используемыми полями?

Упражнения

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

< Лекция 1 || Лекция 2: 123 || Лекция 3 >
Кирилл Гусаров
Кирилл Гусаров

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

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

- myquestions.php;

- admin.myquestions.php

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

/components/com_myquestions/myquestions.php;

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

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