Использование программного обеспечения при проектировании, создании и развертывании Web-сайта : Часть 10.Функциональные возможности экстранет Web-сайта

В этой серии статей группа разработчиков IBM® Internet Technology Group проектирует, разрабатывает и развертывает Web-сайт для вымышленной компании International Business Council, используя набор свободно доступного программного обеспечения. В данной статье вы узнаете, как определить экстранет, чтобы удовлетворить требования клиентов, и исследуете технические приемы создания экстранет Web-сайта.

Элистер Льюис-Боуэн, старший инженер-программист, IBM

Элистер Льюис-Боуэн (Alister Lewis-Bowen) работает старшим инженером-программистом в IBM Internet Technology Group. Он занимается Интернет и web-технологиями как сотрудник IBM UK с 1993. Элистер был переведен в США для работы над Web-сайтами спортивных событий, спонсируемых IBM, а затем как старший Web-мастер ibm.com. В настоящее время он помогает создавать семантические Web-прототипы.



Стефен Эванчик, инженер-программист, IBM

Стефен Эванчик (Stephen Evanchik) работает инженером-программистом в IBM Internet Technology Group. Он принимает участие во многих проектах с открытыми исходными кодами, самым известным из которых является его IBM TrackPoint-драйвер в ядре Linux. Стефен в настоящее время работает с появляющимися семантическими Web-технологиями.



Луис Вайцман, старший инженер-программист, IBM

Луис Вайцман (Louis Weitzman) работает старшим инженером-программистом в IBM Internet Technology Group. В течение 30 лет он работал на стыке дизайна и вычислений. Помогал разрабатывать XML, фрагментированную систему управления содержимым, используемую для ibm.com, а в настоящее время участвует в обеспечении процесса проектирования новых проектов.



30.05.2007

Введение

В данной серии статей группа разработчиков создает специализированный Web-сайт для вымышленной компании International Business Council (IBC). Для сайта требуется создать хранилище документов, дискуссионные группы, специализированные рабочие группы, систему планирования конференций и описания запланированных сессий.

Во второй части данной серии статей мы исследовали этап анализа, который помог прояснить бизнес-цели и задачи пользователей IBC. Мы собрали требования, реализуя политику использования Web-сайта IBC, которые определили среду, где сообщество пользователей (членов IBC) может просматривать и совместно работать с конфиденциальными материалами. Такими требованиями являются:

  • Каждый пользователь должен аутентифицироваться перед просмотром или работой с любой информацией.
  • Если пользователь не активен (то есть, не взаимодействовал с Web-сайтом дольше определенного времени), его аутентификация удаляется.
  • Если пользователь не аутентифицирован, ему отображается только информация, связанная с повторением процесса аутентификации.
  • Каждый пользователь при первой успешной аутентификации должен согласиться с условиями использования.
  • Подтверждение согласия с условиями использования должно регулярно осуществляться каждым пользователем.

Экстранет. Частная сеть, использующая Internet-протоколы, сетевые соединения и, возможно, общедоступную телекоммуникационную систему для защищенного хранения и совместного использования информации организации или для работы с поставщиками, производителями, партнерами, клиентами и другими участниками бизнес-деятельности.

Эти требования соответствуют определению экстранет. В данной статье вы узнаете, как создать страницу входа в систему (login page); кроме того, вы узнаете о завершении сессий и о предоставлении информации, которую пользователь должен подтвердить перед продолжением просмотра Web-сайта. Вы будете использовать шаблоны темы, созданные в седьмой части, адаптируете модуль Automated Logout (выход по времени) с предупреждающим сообщением и разработаете новый модуль для реализации системы подтверждения согласия с условиями.

Мы надеемся, что созданные нами экстранет-функции, а также объяснение изменений существующего модуля и создания нового, будут полезны. Приведенная здесь информация должна рассматриваться не как строгий набор правил разработки, а лишь как отправная точка для создания ваших собственных решений. Итак, начнем со страницы входа в систему.

Вход в экстранет

Поскольку мы создали "интернет в интернете", необходимо гарантировать, что никакая информация в этой частной сети не может быть просмотрена никем, кроме пользователей сети. При просмотре Web-сайта IBC мы всегда должны видеть только страницу входа в систему - предположим, что нет автоматической аутентификации по куки или обычных инструментов хранения данных аутентификации, присутствующих в современных браузерах (инструменты хранения куки и паролей выходят за рамки обсуждения данной статьи). На рисунке 1 показана страница входа в систему IBC.

Рисунок 1. Страница входа в систему для Web-сайта IBC
Рисунок 1. Страница входа в систему для Web-сайта IBC

В седьмой части данной серии статей вы узнали о методике выбора альтернативного файла шаблона, основанной на факте аутентификации пользователя Drupal. В ней предусмотрено использование некоторого простого PHP-кода в самом начале файла page.tpl.php. Для удобства этот код, отображающий страницу входа в систему для не аутентифицированного пользователя, приведен в листинге 1.

Листинг 1. Фрагмент кода из page.tpl.php
<?php
global $user;
if (!$user->uid) {
  include('login.tpl.php');
  return;
}
?>

Свойство uid появляется в объекте $user только после аутентификации пользователя, предоставляя надежный метод указания системе тематического оформления Drupal использовать другой файл шаблона. В данном случае мы назвали шаблон страницы входа в систему login.tpl.php.

Как показано на рисунке 1, внешний вид страницы входа в систему очень отличается от схемы всех остальных страниц Web-сайта IBC, показанных в предыдущих статьях. Вне компании IBC отображается только информация для аутентификации. Файл шаблона login.tpl.php написан исключительно на XHTML. Можно было бы создать нашу собственную форму, гарантируя корректные значения для активизации системы аутентификации Drupal. Но мы выбрали более безопасный путь - Drupal создает форму за нас и встраивает ее в нашу XHTML-структуру.

Код в листинге 2 демонстрирует использование функции module_invoke для защищенного вызова функции user_login в user.module и вывод полученной формы входа в систему. Теперь у нас есть сгенерированная Drupal форма входа в систему на отдельной странице (управляемой нашей темой), которая отображается только тогда, когда пользователь не аутентифицирован.

Листинг 2. Встраивание сгенерированной Drupal формы входа в систему
<div id="login_form">
<h2>Please log in</h2>
  <?php
  print module_invoke('user','login');
  if ($messages != "") { print $messages; }
  ?>
</div>

Время неактивности и выход из системы

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

Первоначально мы реализовали решение, в котором добавили код в файл session.inc (часть ядра Drupal), так чтобы можно было внедриться в цикл HTTP-запроса на самой ранней стадии, достаточной для управления сессией пользователя после истечения предопределенного времени неактивности. Наше решение работало хорошо, но рекомендовать его нельзя. Изменение ядра Drupal вне нормального цикла разработки приводит к плохим последствиям. Особенно при обновлении до следующей версии Drupal.

Недавно мы узнали, что в проект Drupal была введена заплатка, разрешающая загрузку другого файла session.inc во время фазы начальной загрузки (bootstrap) цикла HTTPD-запроса.

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

Сервер
Помещение всей логики, определяющей длительность нахождения пользователя в неактивном состоянии и принудительного его выхода из системы, на сервер. Преимущества - это, в основном, укрепление системы защиты. Храня код на стороне сервера, вы уменьшаете риск любой несанкционированной деятельности посредством межсерверных сценариев (cross-site scripting), SQL-внедрений (SQL injection) и т.д. Недостатки состоят в том, что вы можете контролировать время только от запроса к запросу. Мы также рассмотрели организацию поминутного триггера с использованием задачи cron для контроля времени.
Клиент
Использование решения на стороне клиента означает, что вся логика хранится на клиентской машине, используя некое подобие JavaScript™. Конечно же, при плохом проектировании это может сделать Web-сайт уязвимым для атак с межсерверными сценариями. Преимущество заключается в том, что можно контролировать длительность периода неактивности в режиме реального времени. Кроме того, легче создать предупреждающее сообщение о предстоящем автоматическом выходе.

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

Изменение модуля Automated Logout

Этот модуль предоставил отличную отправную точку для разработки нашего решения; мы начали с установки его в среде Drupal. Помня обо всех предыдущих размышлениях, мы захотели обеспечить нашим клиентам (IBC) гибкий выбор использования либо сервера, либо клиентской машины. Наше внимание привлек модуль Automated Logout. Этот модуль предоставляет логику измерения времени неактивности пользователя на основе последовательных запросов и выхода из системы (то есть, он удаляет информацию сессии и перенаправляет пользователя на первую страницу), если эта неактивность превысила предопределенное значение. Мы начали с установки этого модуля в среде Drupal.

Мы добавили несколько функций в модуль Automated Logout, включая:

  1. Установку всех полей базы данных и переменных, необходимых для разрешения клиентских сценариев.
  2. Конфигурирование сценария на стороне клиента для выполнения логики автоматического выхода.
  3. Передачу серверных переменных в клиентский сценарий.
  4. Вставку JavaScript-кода, измеряющего время неактивности пользователя, предупреждающего пользователя о том, сколько времени осталось до автовыхода, и указывающего серверу удалить пользователя из сессии.
  5. Перенаправление пользователя на ту страницу Web-сайта, с которой можно снова войти в систему, если он был автоматически отключен.

Теперь давайте создадим базу данных и переменные.

Осуществление модификаций базы данных и создание новых переменных

Согласно требованиям нам нужно несколько дополнительных переменных для реализации этой новой функциональности с использованием модуля autologout.module. К этим переменным относятся:

  • Флажок, использовать или нет клиентский сценарий.
  • Время, по истечении которого показать предупреждение.
  • Текст предупреждения.
  • Текущий URL страницы для возврата пользователя (перед его автоматическим отключением).

Возможно, вы знакомы с переменной назначения (destination) в Drupal. Она может быть определена в скрытом элементе input формы и передана в функцию drupal_goto для перенаправления после подтверждения формы. Мы думали использовать ее для запоминания местоположения пользователя, но остановились на простой записи URL-адреса в таблицу autologout, для того чтобы можно было извлечь его в любой точке цикла запроса модуля или в последующих запросах.

В девятой части данной серии статей рассмотрена работа файла установки модуля и процедура его обновления. В листинге 3 показано, как обновить существующую таблицу autologout функцией update для хранения URL.

Листинг 3. Обновление схемы таблицы autologout в файле autologout.install
function autologout_update_1() {
  global $db_type;
  
  switch ($db_type) {
    case 'mysql':
    case 'mysqli':
      db_query('ALTER TABLE {autologout} ADD COLUMN url VARCHAR(255) NOT NULL ');
      break;
      
    case 'pgsql':
      break;
  }
}

Если этот модуль уже установлен, администратор Web-сайта может запустить сценарий update.php для обновления таблицы autologout. Если этот модуль только что установлен, Drupal запустит функцию update, чтобы гарантировать актуальность схемы для таблицы autologout.

Создать новые переменные в таблице variables можно при помощи функции variable_set в файле install. Однако для совместимости с autologout.module мы добавили объект класса autologout_default_settings, определенный в начале файла. Дополнения показаны в листинге 4. Обратите внимание на то, что переменная $alert_text содержит текст, начинающийся с символа %. Она используется позднее для подстановки значений в текст предупреждения.

Листинг 4. Дополнительные переменные и значения по умолчанию в файле autologout.module
class autologout_default_settings {
  ...
  var $clientsidetrigger = FALSE; // Первоначально запрещено 
  var $alert_time = 160;		  // по умолчанию 2 минуты
  var $alert_text = 'ALERT! %user_name, you have been inactive for some time. '.
                    'You will be automatically logged out in %time_remaining seconds '.
                    'unless you interact with our web site.';
  ...
}

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

Теперь, когда у нас имеются переменные, которые, как мы полагаем, понадобятся нам для логики на стороне клиента, необходимо сделать их доступными на странице настроек модуля (admin/settings/autologout). Используя код, приведенный в листинге 5, можно добавить кнопку-флажок для разрешения логики на стороне клиента и элементы input для разрешения изменения значений времени и текста предупреждения.

Листинг 5. Дополнительные элементы формы для изменения новых переменных на странице настроек autologout
function autologout_settings() {
...
  $form['autologout']['clientsidetrigger'] = array(
    '#type' => 'checkbox',
    '#title' => t('Enable client side timeout'),
    '#default_value' => _autologout_local_settings('clientsidetrigger'),
    '#description' => t('Check this to allow javascript to trigger the '.
                        'auto logout instead of relying on the HTTP '.
                        'request mechanism. Enabling this disables any '.
                        'use of the browser refresh delta')
  );
  $form['autologout']['alert_time'] = array(
    '#type' => 'textfield',
    '#title' => t('Alert time value in seconds'),
    '#default_value' => _autologout_local_settings('alert_time'),
    '#size' => 10,
    '#maxlength' => 12,
    '#description' => t('The length of time, in seconds, before auto logout '.
                        'when an alert is shown warning the user. This time '.
                        'needs to be smaller than the timeout setting if an '.
                        'alert is displayed. This settings works only when '.
                        'the client side trigger is enabled.')
  );
  $form['autologout']['alert_text'] = array(
    '#type' => 'textarea',
    '#title' => t('Alert text'),
    '#default_value' => _autologout_local_settings('alert_text'),
    '#cols' => 60,
    '#rows' => 3,
    '#description' => t('The text to be used in the text alert dialog that '.
                        'warns the user of a pending auto logout. You can '.
                        'use the variables %timeout, %alert_time and %user_name.')
  );
...
}

Три этих новых элемента создаются при помощи Drupal form API и используют существующую функцию _autologout_local_settings для извлечения значений, применяемых для заполнения элементов формы. Когда Drupal обрабатывает форму параметров, общее имя родительского массива (если оно есть) используется для извлечения записи в таблице variables; имя и значение потомка будет сериализироваться в поле value.

Например, элемент $form['autologout']['alert_text'] содержит родительское имя autologout. Drupal будет сериализировать ключ alert_text и связанное с этим элементом формы значение после подтверждения формы, а также извлекать запись в таблице переменных, используя autologout для поля name и сериализированную пару ключ/значение для поля value. Функция _autologout_local_settings эффективно десериализирует пары ключ/значение из таблицы переменных, предоставляя значение для установки ключа, переданного как аргумент.

В модуле Compliance, который мы вскоре рассмотрим, используется другой метод организации хранения параметров в таблице variable.

Передача серверных переменных в клиентский сценарий

Теперь необходим способ передачи серверных переменных в JavaScript, исполняющийся на стороне клиента. Одним из возможных методов является создание JavaScript-кода в виде переменной в модуле, замещение переменных в нем и предоставления этого измененного кода в Drupal для включения в Web-страницу.

Другим, возможно, более типичным методом является создание новых именованных контейнеров в Web-странице (например, элементов DIV со значениями атрибута id), для того чтобы JavaScript-код мог извлекать их во время выполнения. Борцы за чистоту семантики языка разметки, возможно, не поддержат эту идею, и, кроме того, страница без стилевого оформления с этой дополнительной разметкой определенно будет показывать нежелательное содержимое. Здесь мы покажем, как создать скрытые элементы формы в закрытых (closure) переменных, содержащих нужные для JavaScript переменные. Хотя это все равно добавляет не семантическую разметку, содержимое, по крайней мере, будет скрыто от просмотра при отключении стилевого оформления.

Существующий autologout.module использует ловушку footer (сноска) для хранения логики проверки равенства продолжительности времени простоя требуемому значению (перед закрытием сессии) и перенаправления пользователя на первую страницу. В листинге 6 показаны изменения, добавленные в эту существующую функцию.

Листинг 6. Изменение в ловушке footer файла autologout.module
function autologout_footer() {
...
  $footer = '';
...   
  if (_autologout_local_settings('enabled')) {
    if (_autologout_local_settings('clientsidetrigger')) {

      $alert_text = t(_autologout_local_settings('alert_text'), array(
        '%timeout'        => (int)_autologout_local_settings('timeout'),
        '%time_remaining' => (int)_autologout_local_settings('alert_time'),
        '%user_name'      => check_plain($user->name)
      ));

      $form = array();
      $form['clientsidetrigger']['timeout'] = array(
        '#type'  => 'hidden',
        '#value' => (int)_autologout_local_settings('timeout');
      );
      $form['clientsidetrigger']['alert_time'] = array(
        '#type'  => 'hidden',
        '#value' => (int)_autologout_local_settings('alert_time')
      );
      $form['clientsidetrigger']['alert_text'] = array(
        '#type'  => 'hidden',
        '#value' => check_plain($alert_text)
      );

      $footer = drupal_get_form('autologout_clientside_trigger_js', $form);

      drupal_add_js(drupal_get_path('module', 'autologout') . '/clientsidetrigger.js');

    }
    else {
...
    // существующая логика для измерения времени на стороне сервера и выхода из системы
...
    }
  }
  return $footer;
}

Чтобы сохранить существующие функции, но разрешить возможность применения подхода на стороне клиента, мы поместим эту логику в условное выражение, использующее переменную $clientsidetrigger для определения варианта.

Давайте рассмотрим логику, добавленную для разрешения функций на стороне клиента. Во-первых, формируется $alert_text при помощи функции t. Это позволяет выполнять подстановку содержимого %string в тексте предупреждения соответствующими значениями. Далее, при помощи Drupal Forms API создаются скрытые элементы формы, использующиеся для хранения аргументов, которые должны извлекаться логикой на стороне клиента. Функция drupal_get_form генерирует XHTML-код формы, который будет представлен в локальной переменной closure механизмом phptemplate. Чтобы сделать его видимым на Web-странице, используется приведенный в листинге 7 код перед окончанием XHTML-структуры, определенной в шаблоне page.tpl.php.

Листинг 7. Включение XHTML-кода локальной переменной closure в Web-страницу, определенную в файле page.tpl.php
<?php print $closure; ?>

JavaScript-код, являющийся логикой на стороне клиента, предоставляется в Drupal для включения в Web-страницу при помощи функции drupal_add_js. Функция drupal_get_path помогает идентифицировать путь autologout.module, куда помещается новый JavaScript-файл.

Сценарий на стороне клиента

autologout.module поставляется с простым таймером обратного отсчета, реализованным на JavaScript. Он может отображаться в блоке для напоминания пользователю времени завершения сессии. Хотя можно было добавить сюда предупреждение и логику выхода из системы, мы решили сохранить это изменение отдельно для упрощения реализации.

Функция JavaScript-кода на стороне клиента должна создать таймер обратного отсчета от определенного значения до нуля, при котором пользователь перенаправляется по URL, где выполняется его выход из системы. В определенное время отобразится предупреждение, указывающее пользователю о том, что выполнится выход из системы, если он не будет взаимодействовать с Web-сайтом.

JavaScript-код на стороне клиента для данной модификации содержится в файле clientsidetrigger.js, находящемся в том же каталоге, что и autologout.module. Как рассматривалось в предыдущем разделе, он предоставляется системе Drupal при помощи функции drupal_add_js. Первое, что делает этот сценарий - указывает Drupal выполнить его при загрузке Web-страницы, как показано в листинге 8.

Листинг 8. Реализация выполнения JavaScript-кода при загрузке Web-страницы
if (isJsEnabled()) {
	addLoadEvent(autologoutClientsideTriggerStart);
}

Drupal 4.7 предоставляет некоторые полезные JavaScript-программы. Функция isJsEnabled тестирует доступность необходимых JavaScript-методов, таких как getElementsByTagName и getElementById. Без них другие JavaScript-функции, предоставляемые средой Drupal, могут не работать. Функция addLoadEvent добавляет JavaScript-функцию к событию window.onload, гарантируя ее выполнение при загрузке Web-страницы.

В листинге 8 вы можете увидеть, что функция autologoutClientsideTriggerStart добавляется к событию window.onload.

Функция autologoutClientsideTriggerStart инициализирует логику обратного отсчета, как показано в листинге 9.

Листинг 9. Инициализация таймера обратного счета на стороне клиента
var AUTOLOGOUT_CLIENTSIDE_TRIGGER_ENABLED = 0;
var AUTOLOGOUT_CLIENTSIDE_TRIGGER_TIMEOUT_ID;

function autologoutClientsideTriggerStart() {
  var countdown = parseInt(document.getElementById('edit-timeout').value);
  var alertTime = parseInt(document.getElementById('edit-alert_time').value);
  AUTOLOGOUT_CLIENTSIDE_TRIGGER_ENABLED = 1;
  autologoutClientsideTriggerCountdown(countdown,alertTime);	
}

Вот две глобальные переменные, определенные для обработки цикла, созданного функцией setTimeout и используемого позднее. Вы можете увидеть, как значения в элементах формы помещаются в JavaScript-переменные countdown и alertTime. Мы устанавливаем нашу глобальную переменную цикла и начинаем обратный отсчет путем вызова функции autologoutClientsideTriggerCountdown.

Таймер обратного счета - это простая структура setTimout, используемая в JavaScript-приложении для запланированных по времени операций. В листинге 10 показан код, используемый для создания логики таймера.

Листинг 10. Таймер обратного счета на стороне клиента
function autologoutClientsideTriggerCountdown(countdown,alertTime) {
  if (countdown == 0) {
    clearTimeout(AUTOLOGOUT_CLIENTSIDE_TRIGGER_TIMEOUT_ID);
    AUTOLOGOUT_CLIENTSIDE_TRIGGER_ENABLED = 0;
    window.location.href = window.location.protocol + '//' + 
                           window.location.hostname + ':' + 
                           window.location.port + 
                           '/autologout/logout';
  }
  if (countdown == alertTime) {
    autologoutClientsideTriggerAlert();
  }
  if (AUTOLOGOUT_CLIENTSIDE_TRIGGER_ENABLED) {
    countdown --;
    AUTOLOGOUT_CLIENTSIDE_TRIGGER_TIMEOUT_ID = 
        setTimeout('autologoutClientsideTriggerCountdown(' + countdown + ', 
                                                         ' + alertTime + ')', 
                                                         1000);
  }
}

Функция autologoutClientsideTriggerCountdown проверяет переменную $countdown. Если она равна переменной $alertTime, используется функция autologoutClientsideTriggerAlert для отображения предупреждения пользователю. Если переменная $countdown равна нулю, пользователь перенаправляется по URL /autologout/logout.

Логика создания предупреждения помещена в отдельную функцию, называемую autologoutClientsideTriggerAlert(). Это означает, что мы можем загрузить текст предупреждения один раз из элемента input при формировании предупреждения. В листинге 11 приведена эта функция.

Листинг 11. Гарантирование выполнения JavaScript-функции при загрузке Web-страницы
function autologoutClientsideTriggerAlert() {
  alert(document.getElementById('edit-alert_text').value);
}

Это очень простое предупреждение, но вы можете создать более изощренный механизм предупреждений, переопределяя эту JavaScript-функцию одной из ваших собственных в файле page.tpl.php или в новом JavaScript-файле, предоставляемом Drupal при помощи функции drupal_add_js.

Управление выходом из системы на стороне сервера

Поскольку логика на стороне клиента вызывает URL /autologout/logout, нам необходимо создать новый элемент меню. Для этого существующий autologout.module нуждается в новой ловушке menu, как показано в листинге 12.

Листинг 12. Регистрация URL выхода из системы в autologout.module
function autologout_menu($may_cache) {
  $items = array();
  
  if ($may_cache) {
    $items[] = array('path'     => 'autologout/logout',
                     'title'    => t('Autologut'), 
                     'access'   => TRUE,
                     'type'     => MENU_CALLBACK,
                     'callback' => '_autologout_logout');
  }
  return $items;
}

Этот URL будет теперь вызывать функцию _autologout_logout. Существующий autologout.module выполняет логику выхода из системы в ловушке footer, как описано выше. Мы повторно используем этот код, помещая его в отдельную функцию и вызывая его из оригинального месторасположения в ловушке footer. Функция называется _autologout_logout; фрагмент кода показан в листинге 13.

Возвращение пользователя на то же место

Для удобства использования сайта при повторной аутентификации пользователи должны быть направлены на ту же Web-страницу, с которой они были автоматически исключены из системы. Вот для чего используется столбец URL в базе данных. Вы можете выполнить несколько изменений в autologout.module для создания данной функциональной возможности:

  • Проверить, что все запросы INSERT к таблице autologout содержат новый столбец URL.
  • URL в таблице autologout в функции _autologout_logout перед принудительным выходом пользователя из системы.
  • Перенаправить пользователя по сохраненному URL, найденному в таблице autologout после повторного входа его в систему.
  • Очистить все сохраненные URL в таблице autologout, если пользователь вышел из системы вручную.

Единственный экземпляр существующего запроса INSERT в autologout.module находится в логике операции обновления в ловушке user. Эта операция была изменена так, чтобы поле URL устанавливалось в пустую строку.

Для сохранения URL при автоматическом выходе из системы мы добавили код, приведенный в листинге 13, в функцию _autologout_logout перед вызовом drupal_goto, перенаправляющим пользователя на первую страницу.

Листинг 13. Добавление к функции _autologout_logout() для хранения URL
function _autologout_logout() {
...
  global $user;
  $r = db_query("SELECT * FROM {autologout} WHERE uid = %d", 
                $user->uid);

  if (db_num_rows($r) > 0) {
    $r = db_query("UPDATE {autologout} SET url = '%s' WHERE uid = %d",   
    check_url($_SERVER["HTTP_REFERER"]), $user->uid);
  }
  else {
    $r = db_query("INSERT INTO {autologout} SET uid = %d, url = '%s'", 
    $user->uid, check_url($_SERVER["HTTP_REFERER"]));
  }
  if (!$r) {
    watchdog('user', 'Unable to insert current url before auto logout', 
             WATCHDOG_ERROR);
  }
...
}

Это вполне стандартный код, сохраняющий URL в таблице autologout с использованием ID пользователя в качестве ключа. Для контроля при возникновении проблемы выдается сообщение watchdog.

Чтобы обеспечить перенаправление пользователя по URL после повторного его входа в систему, можно использовать операцию log-in в существующей ловушке user. autologout.module использует оператор switch в существующей ловушке user для определения операции, активизировавшей вызов функции. В листинге 14 приведено выражение case, используемое для извлечения URL и перенаправления пользователя по этому URL.

Листинг 14. Перенаправление пользователя после входа в autologin.module
function autologout_user($op, &$edit, &$account, $category = NULL) {
  ...
  case 'login':
    $q = db_query("SELECT url FROM {autologout} WHERE uid = %d",   
                  $account->uid);
    if ($r = db_fetch_object($q)) {
      if (isset($r->url) and $r->url != '') {
        drupal_goto($r->url);
      }
    }
    break;
  ...

При успешном извлечении URL из таблицы autologout (для ID пользователя) функция drupal_goto перенаправляет пользователя по URL.

Конечно же, если пользователь покидает Web-сайт самостоятельно, любой URL, записанный для этого пользователя, должен быть сброшен в пустую строку. Для этого можно использовать операцию logout в существующей ловушке user. В листинге 15 продолжается ловушка user, показывающая, как это сделать.

Листинг 15. Очистка сохраненного URL
  ...
  case 'logout':
    $r = db_query("SELECT * FROM {autologout} WHERE uid = %d", 
                  $account->uid);
    if (db_num_rows($r) > 0) {
      $r = db_query("UPDATE {autologout} SET url = '' WHERE uid = %d", 
                    $account->uid);
      if (!$r) {
        watchdog('user', 'Unable to reset URL before logout',  
                 WATCHDOG_ERROR);
      }
    }
    break;
  ...

Эти изменения в autologout.module разрешают организацию таймаута на стороне клиента с предупреждением и дополнительными функциональными возможностями по записи последнего URL пользователя, для которого выполняется автоматический выход из системы.

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


Механизм соответствия

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

Перед созданием любого нового модуля проверьте, есть ли уже существующий модуль, которого, возможно, будет достаточно, или его можно будет усовершенствовать и использовать как отправную точку для нового модуля. Мы знали о существовании модуля Legal, функции которого пересекаются с функциями нашего нового модуля. Модуль Legal предоставляет страницу "Terms and Conditions" для подтверждения посетителем во время процесса регистрации. Однако наши требования были более высоки. После создания группой администраторов IBC пользовательской учетной записи новый член должен увидеть страницу соглашения во время его первого входа в систему перед просмотром какого-либо содержимого. Тем не менее, модуль Legal явился отличной отправной точкой.

Модуль Compliance предоставляет страницу с информацией о соглашении, аналогичную показанной на рисунке 2, сразу после входа пользователя в систему.

Рисунок 2. Страница соглашения, отображаемая после входа в систему, запрашивающая подтверждение пользователя
Рисунок 2. Страница соглашения, отображаемая после входа в систему, запрашивающая подтверждение пользователя

Эта страница запрашивает подтверждение пользователем соглашения и ограничивает его взаимодействие с Web-сайтом до подтверждения. Она запоминает, какой пользователь подтвердил соглашение, и создает вид для показа соглашения (рисунок 3) в целях проверки. Этот модуль также предоставляет цикл просмотра, гарантирующий подтверждение соглашения пользователями Web-сайта через регулярные промежутки времени. Конструкция базового модуля рассматривалась в шестой части данной серии статей, поэтому мы не будем говорить об общей структуре модуля, а вместо этого сконцентрируемся на деталях конкретной реализации данного (нового) модуля соглашения.

Рисунок 3. Страница аудита соглашений для администраторов
Рисунок 3. Страница аудита соглашений для администраторов

Установка файла

Схема таблицы данных для модуля соглашений, содержит:

uidID пользователя в качестве ключа
accept_dateДата последнего подтверждения соглашения
expiration_dateДата, когда пользователю нужно повторно подтвердить соглашение (если разрешен цикл просмотра)
urlВременное хранилище URL для перенаправления пользователя туда, куда он собирался попасть перед получением запроса о подтверждении соглашения

Использование столбца expiration_date излишне, поскольку значение может быть вычислено путем добавления смещения времени просмотра ко времени accept_date. Однако этот столбец используется для удобства.

В листинге 16 показан код, создающий таблицу для хранения соглашения в базе данных mysql/mysqli.

Листинг 16. Создание таблицы users_compliance в compliance.install
function compliance_install() {
  global $db_type;
  $created = FALSE;
	
  switch ($db_type) {
    case 'mysql':
    case 'mysqli':
      $q = db_query("
CREATE TABLE IF NOT EXISTS users_compliance (
 uid               integer unsigned NOT NULL default '0',
 accept_date       integer NOT NULL default '0',
 expiration_date   integer NOT NULL default '0',
 url               varchar(255) NOT NULL default '', 
 PRIMARY KEY (uid)
);
      ");
      if($q) {
        $created = TRUE;
      }
      break;

    case 'pgsql':
      break;
  }

  _compliance_install_init();

  if ($created) {
    drupal_set_message(t('Compliance module
 installation was successful.'));
  }
  else {
    drupal_set_message(t('Installation for the 
Compliance module was unsuccessful.'));
  }
}

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

Листинг 17. Установка значений по умолчанию модуля соглашения в файле compliance.install
function _compliance_install_init() {
  variable_set('compliance_page_node', 0);
  variable_set('compliance_page_tile', t('Terms and conditions'));
  variable_set('compliance_review_enabled', 0);
  variable_set('compliance_review_cycle_time', 365);
  variable_set('compliance_message',t('Please read the following and answer the '.
                                      'question at the bottom of the page. Thank you.'));
  variable_set('compliance_question',t('Do you agree to these term and conditions?'));
  variable_set('compliance_positive_answer',t('Agree'));
  variable_set('compliance_negative_answer',t('Disagree'));
  variable_set('compliance_disagree_url', '/logout');	
  variable_set('compliance_reset', 0);
}

Функция variable_set сохраняет эти переменные и их значения в таблице variables. Теперь, когда этот модуль установлен путем разрешения его в списке модулей, представленных в admin/modules, будет создана таблица базы данных и значения по умолчанию для модуля, установленного в системе Drupal. Эти переменные редактируются параметрами соглашения (admin/settings/compliance) при наличии соответствующих прав доступа.

Переменными являются:

compliance_page_nodeID узла страницы, используемой для описания соглашения.
compliance_page_titleЗаголовок соглашения при отображении в виде формы для подтверждения пользователем.
compliance_review_enabledФлажок, используемый для разрешения или запрещения механизма цикла просмотра.
compliance_review_cycle_timeВремя (в днях), оставшееся до запроса пользователю просмотреть его подтверждение соглашения.
compliance_messageНеобязательное сообщение, помещаемое в верхней части соглашения при отображении в виде формы для подтверждения пользователем.
compliance_questionЗапрос пользователю подтвердить соглашение в форме.
compliance_positive_answerТекст, используемый в кнопке, которую нажимает пользователь для положительного ответа на вопрос по соглашению.
compliance_negative_answerТекст, используемый в кнопке, которую нажимает пользователь для негативного ответа на вопрос по соглашению.
compliance_disagree_urlURL, по которому перенаправляется пользователь, если его ответ негативен. Положительный ответ перенаправит пользователя на Web-страницу, на которую он хотел попасть первоначально.

Права доступа

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

Мы предполагаем, что просмотр соглашения вместе с формой, призывающей пользователя подтвердить свое согласие с соглашением, это то, что может просматривать каждый аутентифицированный пользователь. Единственным ограничением по доступу является разрешение администратору конфигурировать модуль compliance и просматривать страницу аудита соглашений. Страница аудита показывает, какие пользователи подтвердили соглашение. В листинге 18 показаны ловушки perm и access, реализующие эти права доступа.

Листинг 18. Конфигурирование прав доступа в compliance.module
/***
 *	Реализация hook_perm  
 */
function compliance_perm() {
  return array('administer compliance','audit compliance');
}

/**
 * Реализация hook_access().
 */
function compliance_access($op, $node) {
  if ($op == 'view') {
    return user_access('access content');
  }
  else if ($op == 'create' || $op == 'update' ||  $op == 'delete') {
    if(user_access('administer compliance')) {
      return true;
    }
    else {
      return false;
    }
  }
  else {
    return false;
  }
}

Определения URL

Страница, содержащая соглашение, создается как узел базовой страницы (/node/add/page). Для Web-сайта IBC мы использовали модуль Path, поставляемый с Drupal 4.7, для создания псевдонима узла страницы с соглашением для URL /tncs, а именно "terms and conditions". Этот псевдоним используется в ссылках внизу страницы, для того чтобы пользователь мог просмотреть соглашение в любое время.

Страница с соглашением, /compliance, является узлом страницы tncs, окруженным некоторым дополнительным содержимым, позволяющим пользователям зарегистрировать их подтверждение. Мы определяем этот URL в ловушке menu, показанной в листинге 19.

Еще один URL, /compliance/audit, регистрируется в ловушке menu. Он предоставляет базу данных, содержащую информацию о том, какие пользователи подтвердили соглашение (рисунок 3).

Обратите внимание на то, что CSS-файл (Cascading StyleSheet), используемый нами для стилевого оформления выделяемой информации на странице аудита соглашений, по умолчанию предоставляется системе Drupal при помощи функции theme_add_style.

Листинг 19. Регистрация URL-адресов в ловушке menu модуля compliance.module
function compliance_menu($may_cache) {
  $items = array();
  if ($may_cache) {
    $items[] = array('path'     => 'compliance',
                     'title'    => variable_get('compliance_page_title', 
                                                   t('Terms and conditions')),
                     'access'   => user_access('access content'),
                     'type'     => MENU_CALLBACK,
                     'callback' => 'compliance_display'); 
    $items[] = array('path'     => 'compliance/audit',
                     'title'    => t("Compliance audit"),
                     'access'   => user_access('audit compliance'),
                     'type'     => MENU_CALLBACK,
                     'callback' => 'compliance_audit'); 
    theme_add_style(drupal_get_path('module', 'compliance') . 
                                    '/compliance.css');
  }
  return $items;
}

Параметры

Согласно шестой части данной серии статей, ловушка settings обеспечивает форму интерактивного управления сохраненными переменными для вашего модуля. Эта ловушка использует Drupal Forms API для создания схемы формы. Интересные фрагменты ловушки settings в модуле compliance показаны в листинге 20.

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

Обратите внимание на то, что имя массива $form для каждого элемента соответствует имени переменной в функции variable_get. Когда Drupal обрабатывает подтвержденную форму, созданную при помощи ловушки settings, он использует это имя массива в качестве переменной при сохранении значения связанного элемента формы. То есть, подтвержденное значение элемента формы, определенное в $form['autologout']['alert_time'], будет записано в таблицу переменных как alert_time. Это несколько иной подход к способу сохранения переменных в функции autologout_settings.

Листинг 20. Создание элементов options узла страницы для виджета select в ловушке settings модуля compliance.module
function compliance_settings() {
...
  $q = db_query('SELECT n.nid, r.title FROM node n INNER JOIN 
                node_revisions r USING (vid) WHERE n.type="page"');
  while ($r = db_fetch_object($q)) {
    $options[$r->nid] = t($r->title);
    $selected = $r->nid;
  }
  if (count($options) == 0) {
    drupal_set_message(t('Please %link 
that will display the compliance terms '.
                         'and conditions and then
 come back to this page to complete '.
                         'the compliance settings.', 
                         array('%link' =>
 l(t('add a page'), 'node/add/page/'))));
    return;
  }
...
  $form['compliance_page'][
'compliance_page_node'] = array(
    '#type'          => 'select',
    '#title'         => t('Page'),
    '#default_value' =>
 variable_get('compliance_page_node', $selected),
    '#options'       => $options,
    '#description'   => t
('This is the page displayed to the user describing '.
                          'the compliance terms and conditions. You can create '.
                          'a %link if no existing pages are appropriate to use.',
                          array('%link' => l(t('new page'), 'node/add/page/')))
  );
  $form['compliance_page']['compliance_page_title'] = array(
    '#type'          => 'textfield',
    '#title'         => t('Title'),
    '#default_value' => variable_get('compliance_page_title', 
                                     t('Terms and conditions')),
    '#description'   => t('This is the text of the title that will '.
                          'override the
 one that comes with the node chosen above.')
  );
  $form['compliance_ack'] = array(
    '#type'          => 'fieldset',
    '#title'         => t('Acknowledgment'),
    '#weight'        => -14,
    '#description'   => t('So that we can confirm that the user has read '.
                          'and/or agrees to this compliance content, a question '.
                          'is presented to ask the user to acknowledge this '.
                          'compliance page before moving on. If a positive answer '.
                          'is given, the user is redirected to the original page '.
                          'they would have gone to. If a negative answer is given, '.
                          'then the user is redirected to the URL defined below.')
  );
  $form['compliance_ack']['compliance_message'] = array(
    '#type'          => 'textfield',
    '#title'         => t('Message'),
    '#default_value' => variable_get('compliance_message', 
                                     t('Please read the following and answer '.
                                       'the question at the bottom of the page. '
                                       'Thank you.')),
    '#description'   => t('This is the text of a message that will appear at '.
                          'the top of the compliance content intended to '.
                          'instruct the user to read the page and answer '.
                          'the question defined below.')
  );
...
}

Страница соглашения

Как показано в листинге 19, URL /compliance регистрируется в системе меню Drupal для активизации функции compliance_display. В листинге 21 показана эта функция. Мы хотим использовать созданный узел страницы для отображения соглашения, но управлять им из модуля compliance, для того чтобы мы могли использовать такие функции как ловушка view для добавления содержимого, например, формы подтверждения.

Листинг 21. Управление отображением страницы соглашения в compliance.module
function compliance_display() {

  $nid = variable_get('compliance_page_node', 0);
  if ($nid == 0 || !is_numeric($nid)) {
    drupal_not_found();
  }
  $node = node_load($nid);

  if ($node->nid) {
   $title = check_plain(variable_get('compliance_page_title', 
t('Terms and conditions')));
   drupal_set_title($title);
   $node->title = $title;
   $node->type = 'compliance';
   return node_show($node, 'view');
  }
}

Первая половина этой функции использует ID узла для извлечения объекта node узла страницы с использованием функции node_load. Вторая половина переопределяет заголовок страницы и узла, используя сохраненный текст заголовка, изменяет тип узла с page на compliance, а затем запрашивает систему Drupal показать узел с использованием функции node_show.

Переключение значения типа узла дает возможность модулю compliance управлять узлом страницы. В листинге 22 показано, как можно использовать ловушку view для добавления дополнительного содержимого.

Листинг 22. Добавление содержимого к существующей странице compliance в compliance.module
function compliance_view(&$node) {

  $form = array();
  $form['compliance_ack_question'] = array(
    '#type'  => 'markup',
    '#value' => '<p>'.variable_get('compliance_question', 
                 t('Do you agree to these term and conditions?')).'</p>'
  );
  $form['compliance_ack_disagree'] = array(
    '#type'  => 'submit',
    '#value' => variable_get('compliance_negative_answer', t('Disagree'))
  );
  $form['compliance_ack_agree'] = array(
    '#type'  => 'submit',
    '#value' => variable_get('compliance_positive_answer', t('Agree'))
  );

  $node->body .= drupal_get_form('compliance_ack', $form);

  drupal_set_message(variable_get('compliance_message', 
                                  t('Please read the following and answer the '.
                                    'question at the bottom of the page. '.
                                    'Thank you.')));

  return $node;
}

Форма запроса пользователю о подтверждение соглашения создается при помощи Form API. Она оформлена темой в XHTML с использованием функции drupal_get_form. Эта функция использует ID формы compliance_ack для установки возможных функций обратного вызова (callbacks), обрабатывающих проверку корректности формы и ее предъявление (рассматриваются в следующем разделе). Эта XHTML-форма затем добавляется к телу содержимого узла существующей страницы, в данном случае compliance. Затем сохраненное сообщение добавляется в очередь сообщений Drupal при помощи функции drupal_set_message.

Обработка формы подтверждения

Использование функции drupal_get_form с ID формы compliance_ack указывает Drupal просмотреть наличие функций-ловушек submit и validate, подключенных к этому ID формы. Для данного ID формы функции-ловушки называются соответственно compliance_ack_submit и compliance_ack_validate. Модуль compliance использует только ловушку submit, показанную в листинге 23.

Листинг 23. Обработка формы подтверждения в compliance.module
function compliance_ack_submit($form_id, $form_values) {

  switch($_POST["op"]) {
    case variable_get('compliance_positive_answer', t('Agree')):
      $url = _compliance_get_destination();
      _compliance_set_accept();
      drupal_goto($url);
      break;
    case variable_get('compliance_negative_answer', t('Disagree')):
      drupal_goto(variable_get('compliance_disagree_url', '/logout'));
      break;
  }
}

Оператор switch используется для тестирования переменной $op, переданной обратно в массиве $_POST после предъявления формы. Если пользователем был выбран положительный ответ, извлекается URL оригинального запроса (рассматривается в следующем разделе) при помощи функции _compliance_get_destination, показанной в листинге 24. В записи для пользователя в таблице users_compliance обновляются даты приема и истечения срока при помощи функции _compliance_set_accept, показанной в листинге 25. Пользователи перенаправляются по URL оригинального запроса. При выборе отрицательного ответа пользователь перенаправляется к сохраненному URL, специально используемому для негативных ответов.

Функция _compliance_get_destination, показанная в листинге 24, просто извлекает информацию об URL, предварительно сохраненную для пользователя при его входе в систему. Если такого URL нет, возвращается пустая строка. Передача пустой строки в функцию drupal_goto перенаправляет пользователя на первую страницу.

Листинг 24. Извлечение сохраненного URL в compliance.module
function _compliance_get_destination($uid = NULL) {

  global $user;
  if ($uid == NULL) {
    $uid = $user->uid;
  }

  $url = '';

  $q = db_query('SELECT url FROM
 {users_compliance} WHERE uid = %d', $uid);
  if (db_num_rows($q) > 0) {
    $r = db_fetch_object($q);
    $url = $r->url;
  }

  return $url;
}

Функция _compliance_set_accept обновляет или создает запись в таблице user_compliance с временем подтверждения соглашения и временем истечения срока. Время истечения срока используется только при разрешенном цикле просмотра соглашений.

Листинг 25. Сохранение факта подтверждения соглашения в compliance.module
function _compliance_set_accept($uid = NULL) {

	global $user;
	if ($uid == NULL) {
		$uid = $user->uid;
	}

	$now = time();
	$later = $now + (variable_get
('compliance_review_cycle_time',365) * 60 * 60 * 24 );

	db_query("DELETE FROM
 {users_compliance} WHERE uid = %d", $user->uid);
	db_query("INSERT INTO
 {users_compliance} SET uid = %d, accept_date = %d, ".
	         "expiration_date = %d, url=''", $user->uid, $now, $later);

	watchdog('user',
 'Compliance has been acknowledged', WATCHDOG_NOTICE);
}

Активизация отображения страницы соглашения

Страница соглашения должна отображаться при аутентификации пользователя или входе его в систему. Модуль compliance запрашивает пользователя подтвердить соглашение перед выполнением какого-либо следующего действия. Использование выражения login в ловушке user, показанной в листинге 26, позволяет нам гарантировать это.

Листинг 26. Активизация отображения страницы соглашения после входа в систему в compliance.module
function compliance_user
($op, &$edit, &$account, $category = NULL) { 
 
  if ($account->uid < 2) { 
   return; // UID 0 или UID 1 не применимы
  }

  switch ($op) {
    case 'login':
    if (_compliance_expired()) {
      _compliance_set_destination();
      drupal_goto('compliance');
    }
    break;
  }
}

Поскольку первый пользователь системы Drupal считается особенным (пользователь root), мы не применяем для него процесс подтверждения соглашения. Используя оператор switch для тестирования значения login в переменной $op, мы проверяем, давал ли пользователь ранее положительный ответ по подтверждению соглашения, или он должен опять просмотреть и подтвердить соглашение, при помощи функции _compliance_expired, показанной в листинге 27.

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

Функция _compliance_expired показана в листинге 27. Поле даты истечения срока извлекается из записи о пользователе в таблице users_compliance. Если такой записи нет или поле равно нулю, пользователь должен просмотреть страницу соглашения. Если разрешен цикл просмотра соглашений и дата истечения срока уже прошла, пользователь должен просмотреть страницу соглашения.

Листинг 27. Проверка необходимости отображения страницы соглашения в compliance.module
function _compliance_expired($uid = NULL) {

  global $user;
  if ($uid == NULL) {
    $uid = $user->uid;
  }

  $q = db_query('SELECT expiration
_date FROM {users_compliance} '.
                'WHERE uid = %d', $user->uid);

  if (db_num_rows($q) > 0) {
    $r = db_fetch_object($q);
    if (((time() > (int)$r->expiration_date)) && 
         (variable_get('compliance_review_enabled', 0) == 1)) {
      // срок истек – необходимо повторное подтверждение
      return true;
    }
    elseif ((int)$r-> expiration_date == 0) {
      // запись существует, но нет подтверждений
      return true;
    }
  }
  else {
    // нет подтверждений
    return true;
  }

  return false;
}

Функция _compliance_set_destination, показанная в листинге 28, создает или обновляет запись пользователя в таблице users_compliance, гарантируя установку поля URL в текущий URL.

Листинг 28. Сохранение URL назначения в таблице user_compliance в compliance.module
function _compliance_set_destination($uid = NULL) {

  global $user;
  if ($uid == NULL) {
    $uid = $user->uid;
  }

  $q = db_query('SELECT * FROM
 {users_compliance} WHERE uid = %d', $uid);
  $url = check_url(url($_GET["q"]));

  if (db_num_rows($q) > 0) {
    $q = db_query('UPDATE {users_compliance} 
SET url = "%s" WHERE '.
                  'uid = %d', $url ,$uid);
  }
  else {
    $q = db_query('INSERT INTO {users_compliance} '.
                  '(uid, accept_date, expiration_date, url) '.
                  'VALUES (%d, %d, %d, "%s")', $uid, 0, 0, $url);
  }

  return $q;
}

URL извлекается при помощи функции url со значением $_GET["q"] и передается в функцию check_url. Для вызова check_url важно исключить какое-либо злонамеренное использование, вызванное межсерверными сценариями (cross-site scripting).

Страница аудита соглашений

Страница аудита, показанная на рисунке 3, отображается по URL /compliance/audit. Этот URL активизирует вызов функции compliance_audit, показанной в листинге 29. Эта страница, в основном, обеспечивает оформленный темой вид таблицы user_compliance. По умолчанию оформленное темой отображение выделяет тех пользователей, которые ответили отрицательно на предложение подтвердить соглашение или должен просмотреть его повторно.

Листинг 29. Создание страницы аудита в compliance.module
function compliance_audit() {

  $q = db_query('SELECT u.uid,u
.name,uc.accept_date,uc.expiration_date '.
                'FROM users_compliance uc
 RIGHT OUTER JOIN users u USING '.
                '(uid) ORDER BY u.name ASC');  

  $acknowledments = array();

  while ($r = db_fetch_object($q)) {
    $acknowledments[] = $r;
  }

  return theme('compliance_audit', $acknowledments);
}

Данные таблицы users_compliance извлекаются строка за строкой в массив, а затем передаются в функцию theme для создания XHTML-содержимого в теле страницы аудита соглашения. Ловушка compliance_audit функции theme также передается этой функции. Используя процесс выбора функции theme, описанный в седьмой части серии статей, Drupal ищет соответствующую функцию theme для создания XHTML.

По умолчанию функция theme для страницы аудита, называемая theme_compliance_audit, предоставляется в файле compliance.module и показана в листинге 30.

Листинг 30. Тема по умолчанию для страницы аудита в compliance.module
function theme_compliance_audit($ack) {

  $header = array(t('User name'),
 t('Accept date'), t('Expiration date'),t('Status'));

  $rows = array();

  foreach($ack as $row) {

    if ($row->uid < 2) {
      continue;
    }

    $class = 'compliance_okay';
    $status = 'Okay';
    $accept = ' - ';
    $expiration = ' - ';

    if ((time() > $row->expiration_date) &&
          variable_get('compliance_review_enabled',0) == 1) {
      $class = 'compliance_expired';
      $status = 'Expired';
    }

    if (!isset($row->expiration_date)) {
      $class = 'compliance_unaccounted';
      $status = 'Unaccounted';
    }

    $name = l($row->name,'user/'.$row->uid);
    if (isset($row->accept_date)) {
      $accept = format_date($row->
accept_date,'custom','l, F j, Y');
    }

    if (isset($row->expiration_date)) {
      $expiration = format_date($row->
expiration_date,'custom','l, F j, Y');
    }

    $rows[] = array('data'=>
                    array($name,$accept,$expiration,$status),
'class'=>$class);
  }

  $output = '<div id="compliance-audit">';
  $output .= '<h2>Compliance audit</h2>';
  if (variable_get('compliance_review_enabled',0) == 1) {
    $output .= '<p>A review cycle of ' . 
              format_plural(variable_get('
compliance_review_cycle_time',365),
                            'a day','%count days') .
 ' has been enabled.</p>';
  }
  $output .= theme('table', $header, $rows);
  $output .= '</div>';

  return $output;
}

Здесь каждая строка из базы данных добавляется в массив $rows, который затем оформляется в виде таблицы. Любые ссылки создаются при помощи функции l. Вы заметите, что классы добавляются в массив строк, чтобы помочь идентифицировать различные состояния соглашений пользователей. Эти стили определены в файле compliance.css, добавляемом в ловушку menu, описанную ранее.

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


Резюме

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

Не пропустите следующую статью данной серии, в которой мы рассмотрим систему таксономии Drupal. Вы узнаете, как использовать ее для организации информации и навигации по содержимому.


Благодарности

Авторы благодарят Моше Вайцмана (Moshe Weitzman), Кэроли Нидьеси (Karoly Negyesi) (a.k.a. "chx"), Бориса Мэнна (Boris Mann) и Джеффа Роббинса (Jeff Robbins) за их участие и предложения.

Ресурсы

Научиться

Получить продукты и технологии

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Open source
ArticleID=227185
ArticleTitle=Использование программного обеспечения при проектировании, создании и развертывании Web-сайта : Часть 10.Функциональные возможности экстранет Web-сайта
publish-date=05302007