Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых
[Назад] [Содержание] [Дальше]

Практика применения Perl, PHP, Apache, MySQL для активных Web-сайтов

(С) Александр Фролов, Григорий Фролов, 2002

8. Кадровое агентство Трудоголик.Ру

8. Кадровое агентство Трудоголик.Ру.. 1

База данных кадрового агентства.. 4

Таблица зарегистрированных клиентов. 5

Таблица администраторов агентства. 5

Таблица названий городов. 6

Необходимое образование. 6

Условия работы.. 7

Список профессий. 7

Разделы каталога профессий. 7

Резюме. 8

Вакансии. 9

Узел www.trudogolik.ru.. 11

Регистрация посетителей. 11

Выбор идентификатора и пароля. 11

Интерфейс регистрации в агентстве Трудоголик.Ру. 12

Программная реализация. 14

Добавление вакансии. 21

Пользовательский интерфейс. 21

Программная реализация. 25

Программа формирования формы ввода сведений о вакансии. 25

Шаблон формы сведений о вакансии. 27

Программа обработки данных формы.. 30

Добавление резюме. 34

Пользовательский интерфейс. 35

Программная реализация. 37

Поиск вакансий. 37

Пользовательский интерфейс. 38

Программная реализация. 40

Отображение формы поиска. 40

Шаблон формы поиска. 41

Получение результатов запроса. 44

Шаблон страницы результатов поиска. 48

Поиск резюме. 50

Администрирование Web-узла кадрового агентства.. 51

Защита от несанкционированного доступа. 51

Пользовательский интерфейс. 51

Программная реализация. 53

Хранение идентификатора подключения в Cookie. 54

Первая проверка идентификатора и пароля. 55

Создание записи по умолчанию.. 55

Последующие проверки идентификатора и пароля. 56

Редактирование словарей. 57

Пользовательский интерфейс. 57

Программная реализация. 62

Программа просмотра и редактирования списка городов. 62

Шаблон страницы списка городов. 65

Редактирование списка администраторов. 67

Пользовательский интерфейс. 67

Программная реализация. 68

Добавление, поиск и удаление вакансий. 72

Работа с резюме. 75

Обработка ошибок. 77

 

 

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

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

Кроме того, посетителю узла предлагается зарегистрироваться и заполнить анкету. В анкете нужно изложить свои «притязания» на рабочее место или требования к сотрудникам. Поместив свою анкету на сервере такого виртуального кадрового агентства, Вы можете ожидать поступления предложений.

В процессе работы над книгой мы решили создать собственное, «учебное» виртуальное кадровое агентство Трудоголик.Ру, опубликовав все его исходные тексты и структуру базы данных.

При этом мы поставили перед собой задачу добиться работоспособности Web-приложений агентства как на платформе Linux и Unix-подобных платформах, так и на платформе Microsoft Windows. Вдобавок мы стремились обеспечить максимально возможную независимость от типа СУБД, предназначенной для хранения анкет и учетной информации агентства.

В результате в качестве базовых средств для реализации проекта мы выбрали:

·         Web-сервер Apache;

·         программы CGI, составленные на языке Perl;

·         модуль Perl DBI для доступа к базе данных;

·         СУБД MySQL.

Перечисленные средства были описаны в предыдущих главах книги и доступны на всех основных компьютерных платформах, в том числе на Linux и Microsoft Windows (причем бесплатно).

Главная страница нашего кадрового агентства Трудоголик.Ру, размещенного в Интернете по адресу http://www.trudogolik.ru, показана на рис. 8-1.

Рис. 8-1. Виртуальное кадровое агентство Трудоголик.Ру

Как видите, здесь имеется шесть разделов:

·         Поиск вакансий;

·         Поиск резюме;

·         Добавить вакансию;

·         Добавить резюме;

·         Бесплатная регистрация;

·         Восстановление забытых паролей.

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

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

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

Для обслуживания базы данных агентства Трудоголик.Ру мы создали отдельный Web-узел, ограничив к нему доступ (рис. 8-2).

Рис. 8-2. Административный Web-узел агентства Трудоголик.Ру

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

Далее в этой главе мы расскажем подробнее о базе данных и разделах общедоступного Web-узла виртуального кадрового агентства  http://www.trudogolik.ru, а также  административного узла. Мы приведем полные исходные тексты соответствующих Web-приложений, написанные на языке Perl.

База данных кадрового агентства

Вся информация о зарегистрированных пользователях, администраторах, резюме и вакансиях хранится в базе данных виртуального кадрового агентства Трудоголик.Ру. База данных называется trudogolik и содержит несколько таблиц, перечисленных в табл. 8-1.

Таблица 8-1. Таблицы базы данных trudogolik

Таблица

Описание

clients

Зарегистрированные клиенты кадрового агентства

managers

Администраторы, управляющие работой Web-узла агентства

city

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

education

Необходимое образование

job_condition

Условия работы

profession

Названия профессий

sections

Разделы, по которым группируются профессии

resume

Резюме посетителей, ищущих работу

vacations

Сведения о вакансиях

Далее мы рассмотрим структуру и назначение этих таблиц подробнее, так как ее понимание необходимо для изучения последующего материала (в частности, листингов программ CGI).

Таблица зарегистрированных клиентов

В этой таблице хранится вся информация о клиентах кадрового агентства, пожелавших зарегистрироваться (табл. 8-2).

Таблица 8-2. Таблица clients

Поле

Тип данных

Описание

id

int(11)

Уникальный идентификатор записи

email

varchar(255)

Адрес электронной почты, указанный при регистрации

password

varchar(255)

Автоматически сгенерированный пароль для доступа к закрытым разделам Web-узла агентства

add_date

timestamp(14)

Дата и время регистрации

Ниже мы привели сценарий SQL, при помощи которого можно создать таблицу clients:

CREATE TABLE clients (
  id int(11) unsigned NOT NULL auto_increment,
  email varchar(255) NOT NULL DEFAULT '0' ,
  password varchar(255) NOT NULL DEFAULT '0' ,
  add_date timestamp(14) ,
  PRIMARY KEY (id),
  KEY id_2 (id),
  KEY email (email),
  UNIQUE id (id)
);

Таблица администраторов агентства

В этой таблице хранится вся информация об администраторах, управляющих работой кадрового агентства (табл. 8-3).

Таблица 8-3. Таблица managers

Поле

Тип данных

Описание

id

int(11)

Уникальный идентификатор записи

login

varchar(50)

Идентификатор администратора

password

archar(50)

Пароль администратора

reg_ip

varchar(15)

Адрес IP, с которого была выполнена регистрация администратора

reg_date

varchar(255)

Дата регистрации администратора

user_id

varchar(255)

Идентификатор текущего сеанса администратора

comment

varchar(255)

Произвольный текстовый комментарий

Ниже мы привели сценарий SQL, для создания таблицы managers:

CREATE TABLE managers (
  id int(11) NOT NULL auto_increment,
  login varchar(50) NOT NULL DEFAULT '' ,
  password varchar(50) NOT NULL DEFAULT '' ,
  rights int(11) NOT NULL DEFAULT '0' ,
  reg_ip varchar(15) ,
  reg_date datetime ,
  user_id varchar(255) ,
  comment varchar(255) ,
  PRIMARY KEY (id),
  KEY login (login)
);

Управление таблицей администраторов выполняется при помощи отдельного Web-узла, показанного на рис. 8-2.

Таблица названий городов

Те, кто ищет или предлагает работу, обычно интересуются рабочими местами или сотрудниками в каком-то определенном городе. Пополняемый список городов хранится в таблице city (табл. 8‑4).

Таблица 8-4. Таблица city

Поле

Тип данных

Описание

id

int(11)

Уникальный идентификатор записи

title

varchar(255)

Название города

Вот сценарий SQL для создания этой таблицы:

CREATE TABLE city (
  id int(11) NOT NULL auto_increment,
  title varchar(255) NOT NULL DEFAULT '' ,
  PRIMARY KEY (id),
  KEY title (title)
);

Необходимое образование

В таблице education (табл. 8‑5) хранятся названия, обозначающие то или иное образование (такие как начальное, среднее, высшее и т.п.). Такая информация необходима при заполнении бланков резюме или вакансий, так как выполнение любой работы требует наличия определенного образования.

Таблица 8-5. Таблица education

Поле

Тип данных

Описание

id

int(11)

Уникальный идентификатор записи

title

varchar(255)

Название, обозначающее то или иное образование

Вот сценарий SQL для создания этой таблицы:

CREATE TABLE education (
  id int(11) NOT NULL auto_increment,
  title varchar(255) NOT NULL DEFAULT '' ,
  PRIMARY KEY (id),
  KEY title (title)
);

Условия работы

В таблице job_condition (табл. 8‑6) хранятся названия для условий работы (работа в течение полного дня, сменная работа, сутки через трое и т.п.). При заполнении бланка вакансии необходимо указать, какие условия работы предлагаются потенциальным сотрудникам.

Таблица 8-6. Таблица job_condition

Поле

Тип данных

Описание

id

int(11)

Уникальный идентификатор записи

title

varchar(255)

Название условия работы

Ниже приведен сценарий SQL для создания таблицы job_condition:

CREATE TABLE job_condition (
  id int(11) NOT NULL auto_increment,
  title varchar(255) NOT NULL DEFAULT '' ,
  PRIMARY KEY (id),
  KEY title (title)
);

Список профессий

В таблице profession (табл. 8‑7) хранятся названия для профессий. При заполнении бланка вакансии посетитель может указать одну из профессий, хранящихся в этой таблице, или добавить новую.

Таблица 8-7. Таблица profession

Поле

Тип данных

Описание

id

int(11)

Уникальный идентификатор записи

title

varchar(255)

Название профессии

Ниже приведен сценарий SQL для создания таблицы profession:

CREATE TABLE profession (
  id int(11) NOT NULL auto_increment,
  title varchar(255) NOT NULL DEFAULT '' ,
  PRIMARY KEY (id),
  KEY title (title)
);

Разделы каталога профессий

Для облегчения поиска нужной профессии предусмотрен каталог профессий. Этот каталог хранится в таблице sections (табл. 8‑8). Каждая запись таблицы может объединять несколько смежных или подобных профессий.

Таблица 8-8. Таблица sections

Поле

Тип данных

Описание

id

int(11)

Уникальный идентификатор записи

title

varchar(255)

Название раздела каталога

Ниже приведен сценарий SQL для создания таблицы profession:

CREATE TABLE sections (
  id int(11) unsigned NOT NULL auto_increment,
  title varchar(255) NOT NULL DEFAULT '0' ,
  PRIMARY KEY (id),
  KEY id_2 (id),
  KEY title (title),
  UNIQUE id (id)
);

Резюме

Таблица resume (табл. 8‑9) хранит сведения о зарегистрированных посетителях Web-узла кадрового агентства Трудоголик.Ру, ищущих работу. Это достаточно обширная информация, характеризующая соискателя и его притязания на рабочее место с разных сторон.

Таблица 8-9. Таблица resume

Поле

Тип данных

Описание

id

int(11)

Уникальный идентификатор записи

name

varchar(255)

Имя, фамилия, отчество

mf

int(11)

Пол (мужской, женский)

id_education

int(11)

Образование (идентификатор строки в таблице education)

id_profession

int(11)

Профессия (идентификатор строки в таблице profession)

id_section

int(11)

Раздел в каталоге профессий (идентификатор строки в таблице section)

id_job_condition

int(11)

Необходимые условия работы (идентификатор строки в таблице job_condition)

concurrent

int(11)

Отметка о необходимости работы по совместительству

id_city

int(11)

Место работы (идентификатор строки в таблице city)

experience

varchar(255)

Имеющийся опыт работы

requirements

text

Дополнительные требования к работе или другие сведения (произвольный текст)

min_age

int(11)

Возраст

max_age

int(11)

[не используется]

min_pay

int(11)

Минимальный оклад

max_pay

int(11)

[не используется]

phone

varchar(255)

Контактный телефон

fax

varchar(255)

Номер факса

email

varchar(255)

Адрес электронной почты

web

varchar(255)

Адрес личной Web-странички или Web-сервера

remove_date

datetime

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

add_date

timestamp(14)

Дата сохранения резюме в базе данных

Ниже приведен сценарий SQL для создания таблицы resume:

CREATE TABLE resume (
  id int(11) NOT NULL auto_increment,
  id_profession int(11) NOT NULL DEFAULT '0' ,
  id_job_condition int(11) NOT NULL DEFAULT '0' ,
  id_city int(11) NOT NULL DEFAULT '0' ,
  experience varchar(255) NOT NULL DEFAULT '' ,
  requirements text ,
  min_age int(11) ,
  max_age int(11) ,
  min_pay int(11) ,
  max_pay int(11) ,
  phone varchar(255) ,
  email varchar(255) ,
  web varchar(255) ,
  remove_date datetime NOT NULL DEFAULT '0000-00-00 00:00:00' ,
  id_section int(11) unsigned NOT NULL DEFAULT '0' ,
  concurrent int(11) unsigned NOT NULL DEFAULT '0' ,
  id_education int(11) unsigned NOT NULL DEFAULT '0' ,
  mf int(11) unsigned NOT NULL DEFAULT '0' ,
  name varchar(255) NOT NULL DEFAULT '0' ,
  fax varchar(255) NOT NULL DEFAULT '0' ,
  add_date timestamp(14) ,
  PRIMARY KEY (id)
);

Вакансии

В таблице vacations (табл. 8‑10) хранятся подробные сведения об имеющихся вакансиях. Ее структура очень похожа на структуру только что описанной таблицы resume, однако имеются и некоторые отличия.

Таблица 8-10. Таблица vacations

Поле

Тип данных

Описание

id

int(11)

Уникальный идентификатор записи

id_education

int(11)

Необходимое образование (идентификатор строки в таблице education)

id_profession

int(11)

Профессия (идентификатор строки в таблице profession)

id_section

int(11)

Раздел в каталоге профессий (идентификатор строки в таблице section)

id_job_condition

int(11)

Предлагаемые условия работы (идентификатор строки в таблице job_condition)

concurrent

int(11)

Возможность работы по совместительству

id_city

int(11)

Место работы (идентификатор строки в таблице city)

experience

varchar(255)

Необходимый опыт работы

mf

int(11)

Пол сотрудника (мужской, женский)

requirements

text

Дополнительные требования к сотруднику или другие сведения о вакансии (произвольный текст)

min_age

int(11)

Минимальный возраст сотрудника

max_age

int(11)

Максимальный возраст сотрудника

min_pay

int(11)

Минимальный предлагаемый оклад

max_pay

int(11)

Максимальный предлагаемый оклад

name

varchar(255)

Имя, фамилия, отчество контактного лица, к которому нужно обращаться по поводу получения работы

phone

varchar(255)

Контактный телефон

fax

varchar(255)

Номер факса

email

varchar(255)

Адрес электронной почты

web

varchar(255)

Адрес Web-сервера компании, предлагающей работу

remove_date

datetime

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

add_date

timestamp(14)

Дата сохранения сведений о вакансии в базе данных

Для создания таблицы vacations Вы можете воспользоваться следующим сценарием SQL:

CREATE TABLE vacations (
  id int(11) NOT NULL auto_increment,
  id_profession int(11) NOT NULL DEFAULT '0' ,
  id_job_condition int(11) NOT NULL DEFAULT '0' ,
  id_city int(11) NOT NULL DEFAULT '0' ,
  experience varchar(255) NOT NULL DEFAULT '' ,
  requirements text ,
  min_age int(11) ,
  max_age int(11) ,
  min_pay int(11) ,
  max_pay int(11) ,
  phone varchar(255) ,
  email varchar(255) ,
  web varchar(255) ,
  remove_date datetime NOT NULL DEFAULT '0000-00-00 00:00:00' ,
  id_section int(11) unsigned NOT NULL DEFAULT '0' ,
  concurrent int(11) unsigned NOT NULL DEFAULT '0' ,
  id_education int(11) unsigned NOT NULL DEFAULT '0' ,
  mf int(11) unsigned NOT NULL DEFAULT '0' ,
  name varchar(255) NOT NULL DEFAULT '0' ,
  fax varchar(255) NOT NULL DEFAULT '0' ,
  add_date timestamp(14) ,
  PRIMARY KEY (id)
);

Узел www.trudogolik.ru

Как мы только что сказали, на главной странице Web-узла http://www.trudogolik.ru, доступного всем посетителям, имеется шесть разделов, причем часть этих разделов доступна всем, а часть — только зарегистрированным пользователям.

Расскажем подробнее о разделах этого узла.

Регистрация посетителей

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

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

Выбор идентификатора и пароля

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

Но как выбрать идентификатор и пароль?

Здесь есть разные подходы.

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

Первый подход, предполагающий выбор идентификатора и пароля пользователем, обладает одним существенным недостатком — пользователи стремятся указать как можно более простой пароль или не указывать его вовсе. Простой пароль очень легко взломать, например, простым перебором (этот метод еще называют методом «грубой силы»). Кроме того, в Интернете имеются даже готовые словари наиболее часто употребляемых паролей, с использованием которых взлом узла будет секундным делом.

Если пароль создается (генерируется) информационной системой автоматически, разработчик может заставить посетителей применять «хорошие» пароли. Такие пароли не содержат общеупотребительных слов, содержать помимо букв еще и цифры, а также знаки препинания. Кроме того, можно создавать пароли любой необходимой длины, обеспечивающей хорошую устойчивость к взлому методом простого перебора.

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

·         на странице регистрации пользователь заполняет форму, указывая наряду с различными сведениями о себе (имя, телефон, адрес и т.д.) свой адрес электронной почты E-Mail;

·         программа CGI, выполняющая регистрацию, сохраняет полученную из формы информацию в базе данных, дополняя ее автоматически сгенерированным паролем;

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

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

Разумеется, перед тем как регистрировать нового пользователя, необходимо проверить по базе данных, не был ли ранее зарегистрирован указанный им адрес электронной почты. Такая простейшая защита позволит избежать регистрации нескольких пользователей по одному и тому же адресу электронной почты, но не дает полной гарантии, что дин и тот же пользователь не сможет зарегистрироваться несколько раз, указывая различные адреса E-Mail.

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

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

Интерфейс регистрации в агентстве Трудоголик.Ру

Щелкнув на главной странице узла Трудоголик.Ру ссылку Бесплатная регистрация, посетитель попадает в раздел регистрации (рис. 8-3).

Рис. 8-3. Страница регистрации

Процедура регистрации очень проста — чтобы ее выполнить, достаточно ввести адрес электронной почты в поле E-Mail, а затем щелкнуть кнопку Получить пароль.

Рис. 8-4. Успешное завершение регистрации

Сохранив в базе данных адрес электронной почты, программа регистрации агентства Трудоголик.Ру вышлет по этому адресу пароль для доступа к закрытым разделам Web-узла агентства (рис. 8-5).

Рис. 8-5. Пароль доступа будет выслан по электронной почте

А что произойдет, если кто-нибудь попытается зарегистрировать адрес E-Mail, который уже есть в базе данных агентства?

В этом случае в окне браузера появится сообщение о том, что данный адрес уже был использован (рис. 8-6).

Рис. 8-6. Сообщение о попытке повторной регистрации адреса E-Mail

Нужно просто выбрать другой адрес E-Mail и повторить регистрацию.

Программная реализация

Запуск программы регистрации выполняется при помощи ссылки Бесплатная регистрация, расположенной на главной странице Web-узла виртуального кадрового агентства:

<tr>
<td><IMG alt="" border=0 src="pic/bullet.gif"></td><td><IMG alt="" border=0 width=7 src="pic/null.gif"></td>
<td><font class="comic"><a href="cgiprg/get_password.pl">Бесплатная регистрация</a></font></td>
</tr>

Полный исходный текст этой страницы Вы найдете в листинге 8-1.

Листинг 8-1 Вы найдете в файле chap08\www.trudogolik.ru\root\index.html на прилагаемом к книге компакт-диске.

Как видите, программа регистрации хранится в файле с именем get_password.pl (листинг 8-2).

Листинг 8-2 Вы найдете в файле chap08\www.trudogolik.ru\cgi\get_password.pl на прилагаемом к книге компакт-диске.

#!/usr/bin/perl -w
use CGI qw(:all);
use HTML::Template;
use strict;
 
my $template = HTML::Template->new(filename => 'template/ru/password_get.htm');
 
if(param('ERR_EMAIL'))
{
  $template->param(ERR_EMAIL => 1);
}
if(param('ERR_DUPLICATE_EMAIL'))
{
  $template->param(ERR_DUPLICATE_EMAIL => 1);
}
if(param('ERR_MAILSERVER'))
{
  $template->param(ERR_MAILSERVER => 1);
}
 
print header (-charset=>'windows-1251'); 
print $template->output;

Программа очень простая, поэтому мы приводим ее исходный текст полностью.

Начальный фрагмент программы, содержащий список используемых модулей Perl, Вам уже знаком по предыдущим главам нашей книги.

Вначале программа записывает в переменную $template шаблон класса HTML::Template для страницы регистрации, показанной на рис. 8-3.

Далее она анализирует параметры ERR_EMAIL, ERR_DUPLICATE_EMAIL и ERR_MAILSERVER. При первом вызове программы эти параметры не определены, поэтому программа просто выводит шаблон в окно браузера. Полностью файл шаблона password_get.htm приведен в листинге 8-3.

Листинг 8-3 Вы найдете в файле chap08\www.trudogolik.ru\cgi\Template/ru/password_get.htm на прилагаемом к книге компакт-диске.

В файле шаблона мы поместили форму, а также условные шаблоны с именами ERR_EMAIL, ERR_DUPLICATE_EMAIL и ERR_MAILSERVER, необходимые для обработки ошибок, связанных с регистрацией:

<p>Для получения бесплатного пароля укажите свой адрес электронной почты в поле <b>E-Mail</b>. Затем щелкните кнопку <b>Получить пароль</b>, и пароль будет отправлен Вам по электронной почте с использованием указанного адреса.</p>

<TMPL_IF NAME="ERR_MAILSERVER">
<p><font color="red">Регистрация временно недоступна.</font></p>
</TMPL_IF>

<TMPL_IF NAME="INCOMPLETE_FORM">
<p><font color="red">
При заполнении формы были допущены ОШИБКИ.</font></p>
</TMPL_IF>

<form action="get_password1.pl" method="post">
<TABLE border="0" cellspacing="1" cellpadding="3" width="100%">
  <TR><TD valign="top" width="60"><b>E-Mail:</b>&nbsp;<font color="red"><sup>*</sup><font></TD>
    <TD><input type="text" name="EMAIL" size="40"></TD></TR>
</TABLE>
<TMPL_IF NAME="ERR_EMAIL">
<TABLE border="0" cellspacing="1" cellpadding="3" width="100%">
<tr><td colspan="2"><font color="red">ОШИБКА: не указан адрес E-Mail</font></td></tr>
</TABLE>
</TMPL_IF>
<TMPL_IF NAME="ERR_DUPLICATE_EMAIL">
<TABLE border="0" cellspacing="1" cellpadding="3" width="100%">
<tr><td colspan="2"><font color="red">ОШИБКА: этот адрес E-Mail уже есть в базе данных. Выберите другой.</font></td></tr>
</TABLE>
</TMPL_IF>
<TABLE border="0" cellspacing="1" cellpadding="3" width="100%">
<tr><td colspan="2">&nbsp;</td></tr>
  <TR>
    <td width="60">&nbsp;</td>
    <TD valign="top"><INPUT id=button1 name=button1 type=submit value="Получить пароль"></TD>
  </TR>
</TABLE>
</form>

При первом запуске программы регистрации все переменные шаблонов не определены, поэтому и сообщения об ошибках не отображаются. Это видно на рис. 8-3.

Когда посетитель вводит в форме адрес электронной почты и щелкает кнопку Получить пароль, запускается программа CGI, расположенная в файле get_password1.pl (листинг 8-4).

Листинг 8-4 Вы найдете в файле chap08\www.trudogolik.ru\cgi\get_password1.pl на прилагаемом к книге компакт-диске.

Начальный фрагмент программы get_password1.pl не имеет никаких особенностей:

#!/usr/bin/perl -w
use CGI qw(:all);
use HTML::Template;
use DBI qw(:sql_types);
use Digest::SHA1 qw(sha1_hex sha1_base64);
use strict;
require 'trudogolik.pl';

Здесь мы подключаем необходимые модули Perl, такие как HTML::Template и DBI. Обратите внимание, что мы также подключили модуль Digest::SHA1  для вычисления хеш-функции, а также описанный в предыдущей главе пакет trudogolik.ru. Хеш-функция используется нами в процессе автоматической генерации паролей регистрируемых пользователей.

Получив управление, программа извлекает адрес электронной почты, введенной пользователем, преобразует его в формат HTML при помощи функции Trudogolik::ASCII_TO_HTML, а затем создает на его основе пароль, вызывая для этого функцию generate_password:

my $email = Trudogolik::ASCII_TO_HTML(param('EMAIL'));
my $password=generate_password($email);

Функция Trudogolik::ASCII_TO_HTML определена в пакете trudogolik.pl следующим образом:

sub ASCII_TO_HTML($)
{
  my $ascii = shift @_;
  return HTML::Entities::encode($ascii, '><"&');
}

Получив текстовую строку через единственный параметр, функция заменяет в ней символы <>”& соответственно на символьные объекты &lt;, &gt;, &quot; и &amp;.

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

К счастью, в модуле Perl HTML::Entities имеется функция HTML::Entities::encode, специально предназначенная для выполнения данной операции. Через первый параметр мы передаем функции исходную строку, а через второй — список заменяемых символов (также в виде текстовой строки). Функция возвращает результат преобразования.

Что же касается функции генерации паролей generate_password, то она определена в файле get_password1.pl следующим образом:

sub generate_password($)
{
  my $email=shift @_;
  my $id=int(rand(100000));
  my $hash=sha1_base64($email.'pwd482'.$id);
  return substr($hash, 1, 4).substr($hash, 21, 4)
}

Получив исходную строку адреса электронной почты, функция generate_password добавляет к ней некую произвольно выбранную текстовую строку pwd482 и случайное число, полученное при помощи функции rand. Далее при помощи функции sha1_base64 из модуля Digest::SHA1 вычисляется хеш-функция от полученной таким образом строки. И, наконец, при помощи двух вызовов функции substr из результата вырезается восемь символов, образующих искомый пароль.

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

На следующем шаге наша программа проверяет, что в качестве адреса электронной почты пользователь ввел непустую строку:

my $error='';
if($email eq '')
{
  $error=$error.'&ERR_EMAIL=1';
}
if($error ne '')
{
  print "Location: get_password.pl?INCOMPLETE=1".$error."\n\n";
}

Если была введена пустая строка (т.е. если на странице, показанной на рис. 8-3, пользователь просто щелкнул кнопку Получить пароль), браузер перенаправляется по адресу get_password.pl. В результате описанная выше программа get_password.pl вновь получает управление, но теперь ей передаются параметры INCOMPLETE и ERR_EMAIL со значением 1.

Обратившись вновь к исходному тексту программы get_password.pl (листинг 8-2), мы видим, что при получении такого параметра программа устанавливает значение переменной шаблона ERR_EMAIL равным 1:

if(param('ERR_EMAIL'))
{
  $template->param(ERR_EMAIL => 1);
}

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

Рис. 8-7. Сообщение о вводе пустой строки вместо адреса E-Mail

Аналогичным образом обрабатываются и другие ошибки, связанные с регистрацией.

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

Для проверки адреса программа get_password1.pl открывает соединение с базой данных, вызывая для этого функцию Trudogolik::DB_OPEN:

my $dbh = Trudogolik::DB_OPEN();

Далее программа выдает параметризованный запрос SQL следующего вида:

my $sql="select id from clients where email = ?";
my $sth = Trudogolik::DB_SQL_PREPARE($dbh, $sql);
Trudogolik::DB_SQL_BIND($sth, 1, $email, SQL_VARCHAR);
Trudogolik::DB_SQL_EXECUTE($sth);

После выполнения запроса программа выбирает идентификаторы всех строк таблицы зарегистрированных посетителей clients с указанным адресом электронной почты;

my @rs = Trudogolik::DB_SQL_FETCHROW_ARRAY($sth);

В том случае, если такого адреса E-Mail в базе данных нет, программа добавляет его в таблицу clients, сохраняя вместе с адресом сгенерированный пароль, а также дату регистрации:

if(!@rs)
{
  my $sql="INSERT INTO clients SET email=?, password=?, add_date = NOW()";
  my $sth = Trudogolik::DB_SQL_PREPARE($dbh, $sql);
  Trudogolik::DB_SQL_BIND($sth, 1, $email, SQL_VARCHAR);
  Trudogolik::DB_SQL_BIND($sth, 2, $password, SQL_VARCHAR);
  Trudogolik::DB_SQL_EXECUTE($sth);

. . .
}

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

Текст почтового сообщения с паролем формируется в переменной $msg:

my $msg="Сервер WWW.TRUDOGOLIK.RU\nРегистрация завершена\n".
   "-----------------------------------------------\n\n".
   "Ваш пароль : $password";

Далее при помощи функции Trudogolik::send_mail мы отправляем сообщение через почтовый сервер SMTP:

my $relay="mars";

my $rc=Trudogolik::send_mail($relay, '@'.$relay.':'.$email,
  'web@Trudogolik.ru', Trudogolik::win2koi('
Регистрация'),
  Trudogolik::win2koi($msg));

if($rc != 0)
{
  Trudogolik::DB_CLOSE($dbh);
  $error=$error.'&ERR_MAILSERVER=1';
  print "Location: get_password.pl?INCOMPLETE=1".$error."\n\n";
}

Использованная здесь функция send_mail была описана нами в разделе «Функция send_mail» 5 главы. Обратите внимание, что в переменной $relay мы должны указать доменное имя почтового сервера SMTP. Этот сервер должен быть доступен из той сети, в которой установлен Web-сервер. При необходимости проконсультируйтесь с провайдером, подключившим Ваш Web-сервер к Интернету.

Если при отправке почты выяснилось, что почтовый сервер недоступен, функция Trudogolik::send_mail возвратит ненулевое значение. В этом случае программа закрывает соединение с базой данных и вновь запускает программу get_password.pl, передавая ей параметр ERR_MAILSERVER со значением 1. Посетитель увидит в окне своего браузера сообщение о временной недоступности регистрации (рис. 8-8).

Рис. 8-8. Сообщение о временной недоступности регистрации

Возможна также ситуация, когда после проверке по базе данных выяснилось, что пользователь пытается зарегистрировать адрес электронной почты, который уже был зарегистрирован ранее. При этом будет запущена программа get_password.pl, причем на этот раз ей будет передан параметр ERR_DUPLICATE_EMAIL со значением. равным единице:

else
{
  Trudogolik::DB_CLOSE($dbh);
  $error=$error.'&ERR_DUPLICATE_EMAIL=1';
  print "Location: get_password.pl?INCOMPLETE=1".$error."\n\n";
}

Соответствующее сообщение об ошибке уже было показано на рис. 8-6.

И, наконец, если ошибок нет, программа выводит на экран сообщение об успешной регистрации (рис. 8-9):

my $template = HTML::Template->new(
  filename => template/ru/password_get1.htm');
print header (-charset=>'windows-1251'); 
print $template->output;
. . .
Trudogolik::DB_CLOSE($dbh);

Рис. 8-9. Сообщение об успешном завершении регистрации

Исходный текст шаблона password_get1.htm Вы найдете в листинге 8-5.

Листинг 8-5 Вы найдете в файле chap08\www.trudogolik.ru\cgi\Template/ru/password_get1.htm на прилагаемом к книге компакт-диске.

Добавление вакансии

Страница Добавить вакансию (рис. 8-10, 8-11) доступна только зарегистрированным посетителям Web-узла виртуального кадрового агентства Трудоголик.Ру. Расположенная в этом разделе форма позволяет ввести всю необходимую информацию об имеющейся вакансии.

Пользовательский интерфейс

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

Рис. 8-10. Форма для добавления вакансии (часть 1)

В поле СВЕДЕНИЯ О ВАКАНСИИ нужно выбрать раздел каталога профессий (список Раздел), а затем в поле Профессия указать профессию. Если нужной профессии нет в списке, ее можно добавить в поле, расположенной под списком.

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

Далее в списке Город следует указать, в каком городе имеется данная вакансия. Если нужного вам города нет в списке, его можно добавить при помощи поля, расположенного ниже списка городов.

Рис. 8-11. Форма для добавления вакансии (часть 2)

Название поля ТРЕБОВАНИЯ К СОТРУДНИКУ говорит само за себя. Здесь нужно указать образование и стаж работы, необходимые для получения работы по данной вакансии, возраст (минимальный и максимальный), пол, оклад (минимальный и максимальный), а также прочие требования.

В поле КОНТАКТНАЯ ИНФОРМАЦИЯ нужно оставить данные о себе и своей компании. Это имя, телефон, факс, адрес электронной почты E-Mail, адрес Web-узла компании. Здесь же требуется указать срок, на который информация о вакансии размещается в базе данных агентства Трудоголик.Ру, а также пароль. Заметим, что пароль должен соответствовать адресу электронной почты, указанному при регистрации.

Для записи информации в базу данных щелкните кнопку Сохранить. Если все данные были введены правильно, в окне браузера появится сообщение о том, что сведения о вакансии сохранены в базе данных (рис. 8-12).

Рис. 8-12. Сведения о вакансии сохранены в базе данных

В противном случае, если при анализе введенных данных были обнаружены ошибки, Вы увидите ту же самую форму с сообщением об ошибках красного цвета (рис. 8-13).

Рис. 8-13. Сообщение об ошибках в форме

Кроме того, около полей формы, при заполнении которых были допущены ошибки, появятся уточняющие сообщения об ошибках (также красного цвета). Например, на рис. 8-14 показано уточняющее сообщение о том, что в форме был указан неправильный пароль.

Рис. 8-14. Уточняющее сообщение об ошибке

Для исправления ошибочной ситуации достаточно повторить ввод. Заметим, что возле поля ввода пароля предусмотрены ссылки Получить бесплатный пароль и Выслать забытый пароль. Вы сможете воспользоваться ими, если возникнут какие-либо проблемы с указанием пароля.

Программная реализация

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

Программа формирования формы ввода сведений о вакансии

Щелкнув ссылку Добавить вакансию на главной странице узла, посетитель запускает программу vacations_add.pl, исходный текст которой приведен в листинге 8-6.

Листинг 8-6 Вы найдете в файле chap08\www.trudogolik.ru\cgi\vacations_add.pl на прилагаемом к книге компакт-диске.

Получив управление, эта программа сразу открывает соединение с базой данных, вызывая для этого функцию Trudogolik::DB_OPEN:

my $dbh = Trudogolik::DB_OPEN();

В переменной $template сохраняется ссылка на шаблон страницы добавления вакансий vacations_add.htm, исходный текст которого Вы найдете в листинге 8-7:

my $template = HTML::Template->new(filename => 'template/ru/vacations_add.htm');

Далее мы несколько раз вызываем функцию Trudogolik::SELECT_FILL, заполняя из базы данных различные списки, расположенные в форме (это списки Раздел, Профессия, График работы, Город и  Образование):

Trudogolik::SELECT_FILL($dbh,
 "SELECT id,title FROM sections ORDER BY title");
$template->param(SECTION_LOOP => \@::loop);

Trudogolik::SELECT_FILL($dbh,
 "SELECT id,title FROM profession ORDER BY title");
$template->param(PROFESSION_LOOP => \@::loop);
 
Trudogolik::SELECT_FILL($dbh,
  "SELECT id,title FROM job_condition ORDER BY title");
$template->param(JOB_CONDITION_LOOP => \@::loop);

Trudogolik::SELECT_FILL($dbh,
  "SELECT id,title FROM city ORDER BY title");
$template->param(CITY_LOOP => \@::loop);

Trudogolik::SELECT_FILL($dbh,
  "SELECT id,title FROM education ORDER BY title");
$template->param(EDUCATION_LOOP => \@::loop);

В качестве первого параметра функции Trudogolik::SELECT_FILL передается идентификатор открытого соединения с базой данных, а в качестве второго — строка запроса SQL, извлекающая идентификаторы записи и содержимое поля title. Результат своей работы функция Trudogolik::SELECT_FILL сохраняет в глобальном хеше @::loop, ссылка на который используется затем для заполнения соответствующего параметра шаблона.

Исходный текст функции Trudogolik::SELECT_FILL, определенной в пакете trudogolik.pl, представлен ниже:

sub SELECT_FILL($$)
{
  (my $dbh, my $sql) = @_;
  my $sth = Trudogolik::DB_SQL_PREPARE($dbh, $sql);
  Trudogolik::DB_SQL_EXECUTE($sth);
  @::loop = ();
  while((my $id, my $title) =
     Trudogolik::DB_SQL_FETCHROW_ARRAY($sth))
  {
   my %row = (TITLE => $title, ID => $id); push(@::loop, \%row);
  }
  return \@::loop;
}

Подготовив запрос при помощи функции Trudogolik::DB_SQL_PREPARE, функция Trudogolik::SELECT_FILL исполняет его, вызывая для этого функцию Trudogolik::DB_SQL_EXECUTE. Далее результаты выполнения запроса извлекаются функцией Trudogolik::DB_SQL_FETCHROW_ARRAY, и сохраняются в глобальном хеше @::loop.

После завершения цикла функция Trudogolik::SELECT_FILL возвращает ссылку на заполненный хеш.

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

if(param('INCOMPLETE'))
{
  $template->param(INCOMPLETE_FORM => 1);
}
if(param('ERR_NAME'))
{
  $template->param(ERR_NAME => 1);
}
if(param('ERR_EMAIL'))
{
   $template->param(ERR_EMAIL => 1);
}
if(param('ERR_PASSWORD'))
{
  $template->param(ERR_PASSWORD => 1);
}
if(param('ERR_DUPLICATE_CITY'))
{
  $template->param(ERR_DUPLICATE_CITY => 1);
  $template->param(DUPLICATE_CITY_NAME => param('DUPLICATE_CITY_NAME'));
}
if(param('ERR_DUPLICATE_PROFESSION'))
{
  $template->param(ERR_DUPLICATE_PROFESSION => 1);
  $template->param(DUPLICATE_PROFESSION_NAME => param('DUPLICATE_PROFESSION_NAME'));
}

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

В частности, параметр INCOMPLETE устанавливается при обнаружении любой ошибки, параметр ERR_NAME — при обнаружении ошибки в поле имени, а параметр ERR_EMAIL — в поле адреса электронной почты.

После подготовки шаблон формы отправляется посетителю, а соединение с базой данных закрывается:

print header (-charset=>'windows-1251'); 
print $template->output;
Trudogolik::DB_CLOSE($dbh);

Шаблон формы сведений о вакансии

Рассмотрим теперь шаблон формы, предназначенной для сохранения информации о вакансии (листинг 8-7).

Листинг 8-7 Вы найдете в файле chap08\www.trudogolik.ru\cgi\Template/ru/vacations_add.htm на прилагаемом к книге компакт-диске.

В верхней части страницы предусмотрен условный шаблон INCOMPLETE_FORM для вывода сообщения о том, что при заполнении формы были допущены те или иные ошибки:

<TMPL_IF NAME="INCOMPLETE_FORM">
<p><font color="red">При заполнении формы были допущены ОШИБКИ.</font></p>
</TMPL_IF>

Далее следует форма, содержащая различные элементы управления, такие как текстовые поля, списки и флажки:

<form action="vacations_add1.pl" method="post">
. . .
</form>

Информация, введенная в этой форме, обрабатывается программой vacations_add1.pl, которую мы рассмотрим чуть позже.

Рассмотрим наиболее интересные фрагменты формы.

Вот, например, как определен шаблон для списка разделов профессий:

<TD><SELECT name="SECTION" class="comic">
<TMPL_LOOP NAME="SECTION_LOOP">
  <OPTION VALUE="<TMPL_VAR NAME=ID>"><TMPL_VAR NAME=TITLE></OPTION>
</TMPL_LOOP>
</SELECT></TD>

Список профессий состоит из нескольких строк и заполняется из соответствующей таблицы базы данных, поэтому здесь мы применили циклический шаблон <TMPL_LOOP>. Каждая строка списка определяется при помощи тега <OPTION> с атрибутом VALUE. Через этот атрибут передается идентификатор раздела в базе данных.

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

Шаблон для списка профессий немного сложнее, так как помимо собственно списка он содержит еще и текстовое поле для ввода новой профессии, а также шаблон для сообщения об ошибке, которая может появиться при добавлении профессии:

<TR><TD>
<SELECT name="PROFESSION" class="comic">
<TMPL_LOOP NAME="PROFESSION_LOOP">
  <OPTION VALUE="<TMPL_VAR NAME=ID>"><TMPL_VAR NAME=TITLE></OPTION>
</TMPL_LOOP>
</SELECT><br>
<INPUT name="PROFESSION_ADD" class="comic"></TD></TR>
<TMPL_IF NAME="ERR_DUPLICATE_PROFESSION">
<tr><td><font color="red">
ОШИБКА: профессия <b><TMPL_VAR NAME=DUPLICATE_PROFESSION_NAME></b> уже есть в базе данных</font></td></tr>
</TMPL_IF>

Заполнение списка профессий выполняется уже знакомым для Вас способом при помощи циклического шаблона <TMPL_LOOP> с именем PROFESSION_LOOP. Вслед за ним находится текстовое поле <INPUT> для ввода новой профессии, отсутствующей в базе данных.

Если посетитель попытается добавить профессию, которая уже есть в базе данных, в дело включится условный шаблон <TMPL_IF> с именем ERR_DUPLICATE_PROFESSION. В результате около поля ввода новой профессии появится сообщение о том, что такая профессия уже есть в базе данных.

Шаблон для заполнения списка условий работы не имеет никаких особенностей:

<TD><SELECT name="JOB_CONDITION" class="comic">
<TMPL_LOOP NAME="JOB_CONDITION_LOOP">
  <OPTION VALUE="<TMPL_VAR NAME=ID>"><TMPL_VAR NAME=TITLE></OPTION>
</TMPL_LOOP>
</SELECT></TD>

Для того чтобы указать на возможность работы по совместительству, в форме предусмотрен флажок CONCURRENT, реализованный с применением тега <INPUT>:

<TD valign="top" align="left" width="150">Совместительство</TD>
<TD><INPUT type=checkbox name="CONCURRENT" value="yes">&nbsp;
допускается</TD>

Элементы управления формы, предназначенные для выбора названия города, определены в шаблоне следующим образом:

<TD><SELECT name="CITY" class="comic">
<TMPL_LOOP NAME="CITY_LOOP">
  <OPTION VALUE="<TMPL_VAR NAME=ID>"><TMPL_VAR NAME=TITLE></OPTION>
</TMPL_LOOP>
</SELECT><br>
<INPUT name="CITY_ADD"></TD></TR>
<TMPL_IF NAME="ERR_DUPLICATE_CITY">
<tr><td><font color="red">
ОШИБКА: город <b><TMPL_VAR NAME=DUPLICATE_CITY_NAME></b> уже есть в базе данных</font></td></tr>
</TMPL_IF>

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

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

<TD><SELECT name="EDUCATION" class="comic">
<TMPL_LOOP NAME="EDUCATION_LOOP">
  <OPTION VALUE="<TMPL_VAR NAME=ID>"><TMPL_VAR NAME=TITLE></OPTION>
</TMPL_LOOP>
</SELECT></TD>

Что же касается стажа работы, то этот список мы формируем статически средствами тега <OPTION>:

<TD><SELECT name="EXPERIENCE" class="comic">
<OPTION VALUE="0">
любой</OPTION>
<OPTION VALUE="1">1
год</OPTION>
<OPTION VALUE="2">2
года</OPTION>
<OPTION VALUE="3">3
года</OPTION>
<OPTION VALUE="5">5
лет</OPTION>
<OPTION VALUE="10">10
лет</OPTION>
<OPTION VALUE="15">15
лет</OPTION>
</SELECT></TD>

При этом значение 0 соответствует любому стажу работы. Оно выбирается, если для получения работы по данной вакансии наличие стажа работы не имеет никакого значения.

Поле для ввода прочих требований создано при помощи тега <TEXTAREA> и не имеет никаких особенностей:

<TD valign="top" align="left" width="150">Прочие требования (до 2000 символов)</TD>
<TD><TEXTAREA name="REQUIREMENTS" ROWS="8" COLS="40" class="comic"></TEXTAREA></TD>

Что же касается ограничений по возрасту, то они задаются при помощи двух тегов <INPUT> с именами AGE_FROM (минимальный возраст) и AGE_TO (максимальный возраст):

<TD valign="top" align="left" width="150">Возраст</TD>
<TD>
от&nbsp;<INPUT style="HEIGHT: 22px; WIDTH: 53px" name="AGE_FROM" class="comic">&nbsp;до&nbsp;<INPUT style="HEIGHT: 22px; WIDTH: 52px" name="AGE_TO" class="comic">&nbsp;лет</TD>

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

Желательный или требуемый пол сотрудника задается при помощи статического списка <SELECT>:

<TD valign="top" align="left" width="150">Пол</TD>
<TD><SELECT name="MF" class="comic">
<OPTION VALUE="0">
любой</OPTION>
<OPTION VALUE="1">
мужской</OPTION>
<OPTION VALUE="2">
женский</OPTION>
</SELECT></TD>

Минимальный и максимальный оклад, предлагаемый в рамках данной вакансии, задается при помощи двух текстовых полей <INPUT> с именами PAY_FROM и PAY_TO, соответственно:

<TD valign="top" align="left" width="150">Оклад</TD>
<TD>от&nbsp;<INPUT style="HEIGHT: 22px; WIDTH: 53px" name="PAY_FROM" class="comic">&nbsp;до&nbsp;<INPUT style="HEIGHT: 22px; WIDTH: 52px" name="PAY_TO" class="comic">&nbsp;у.е.</TD>

Программа обработки формы допускает отсутствие информации в этих полях.

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

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

<TD valign="top" align="left" width="150">Имя&nbsp;<font color="red"><sup>*</sup><font></TD>
<TD><INPUT name="NAME" class="comic"></TD></TR>
<TMPL_IF NAME="ERR_NAME">
<tr><td><font color="red">
ОШИБКА: не указано имя</font></td></tr>
</TMPL_IF>

Номер телефона и факса — необязательные поля формы, поэтому для них шаблоны сообщений об ошибке не предусмотрены:

<TR>
  <TD valign="top" align="left" width="150">
Телефон</TD>
  <TD><INPUT name="PHONE" class="comic"></TD></TR>
<TR>
  <TD valign="top" align="left" width="150">
Факс</TD>
  <TD><INPUT name="FAX" class="comic"></TD></TR>
<TR>

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

<TD valign="top" align="left" width="150">E-Mail&nbsp;<font color="red"><sup>*</sup><font></TD>
<TD><INPUT name="EMAIL" class="comic"></TD></TR>
<TMPL_IF NAME="ERR_EMAIL">
<tr><td><font color="red">
ОШИБКА: не указан адрес E-Mail</font></td></tr>
</TMPL_IF>

Адрес Web-узла компании указывать не обязательно:

<TR>
  <TD valign="top" align="left" width="150">Web-
сайт</TD>
  <TD><INPUT name="WEB" class="comic"></TD></TR>
<TR>

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

<TD valign="top" align="left" width="150">Хранить</TD>
<TD>
<SELECT name="KEEP" class="comic">
  <OPTION VALUE="1">1
день</OPTION>
  <OPTION VALUE="3">3
дня</OPTION>
  <OPTION VALUE="10">10
дней</OPTION>
  <OPTION VALUE="30">30
дней</OPTION>
</SELECT></TD>

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

<TD valign="top" align="left" width="150">Пароль&nbsp;<font color="red"><sup>*</sup><font></TD>
<TD><INPUT name="PASSWORD" type="password" class="comic"></TD></TR>
<TMPL_IF NAME="ERR_PASSWORD">
<tr><td><font color="red">
ОШИБКА: неправильный пароль</font></td></tr>
</TMPL_IF>

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

<TR>
<TD valign="top"><INPUT id=button1 name=button1 type=submit value="Сохранить" class="comic"></TD>
<td><a href="get_password.pl">[Получить бесплатный пароль]</a><br><a href="email_password.pl">[Выслать забытый пароль]</a></td>
</TR>

Программа обработки данных формы

Когда посетитель отправляет заполненную форму сведений о вакансии на сервер, в дело включается программа vacations_add1.pl, исходный текст которой представлен в листинге 8-8.

Листинг 8-8 Вы найдете в файле chap08\www.trudogolik.ru\cgi\vacations_add1.pl на прилагаемом к книге компакт-диске.

Получив управление, программа открывает соединение с базой данных, вызывая для этого функцию Trudogolik::DB_OPEN:

my $dbh = Trudogolik::DB_OPEN();

Далее она получает и сохраняет в локальных переменных данные из полей формы:

my $email = Trudogolik::ASCII_TO_HTML(param('EMAIL'));
my $password=param('PASSWORD');
my $id_section=param('SECTION');
my $id_profession=param('PROFESSION');
my $profession_add = Trudogolik::ASCII_TO_HTML(param('PROFESSION_ADD'));
my $id_job_condition=param('JOB_CONDITION');
my $concurrent=param('CONCURRENT');
my $id_city=param('CITY');
my $city_add = Trudogolik::ASCII_TO_HTML(param('CITY_ADD'));
my $id_education=param('EDUCATION');
my $experience=Trudogolik::ASCII_TO_HTML(param('EXPERIENCE'));
my $requirements = Trudogolik::ASCII_TO_HTML(param('REQUIREMENTS'));
my $age_from = Trudogolik::ASCII_TO_HTML(param('AGE_FROM'));
my $age_to = Trudogolik::ASCII_TO_HTML(param('AGE_TO'));
my $pay_from = Trudogolik::ASCII_TO_HTML(param('PAY_FROM'));
my $pay_to = Trudogolik::ASCII_TO_HTML(param('PAY_TO'));
my $mf=param('MF');
my $name = Trudogolik::ASCII_TO_HTML(param('NAME'));
my $phone = Trudogolik::ASCII_TO_HTML(param('PHONE'));
my $fax = Trudogolik::ASCII_TO_HTML(param('FAX'));
my $web = Trudogolik::ASCII_TO_HTML(param('WEB'));
my $keep=param('KEEP');

Обратите внимание на то, что перед использованием содержимое каждого текстового поля преобразуется функцией Trudogolik::ASCII_TO_HTML. Как мы уже говорили, эта функция заменяет специальные символы HTML (кавычки, угловые скобки и символ &) на символьные объекты. Такое преобразование необходимо для корректного отображения введенной информации на страницах узла.

Далее в программе определяется переменная $error, предназначенная для хранения строки описания ошибки:

my $error='';

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

if($profession_add ne '')
{
  my $sql="select id from profession where title = ?";
  my $sth = Trudogolik::DB_SQL_PREPARE($dbh, $sql);
  Trudogolik::DB_SQL_BIND($sth, 1, $profession_add, SQL_VARCHAR);
  Trudogolik::DB_SQL_EXECUTE($sth);
  my @rs = Trudogolik::DB_SQL_FETCHROW_ARRAY($sth);
   
  if(!@rs)
  {
    my $sql="INSERT profession (title) VALUES (?)";
    my $sth = Trudogolik::DB_SQL_PREPARE($dbh, $sql);
    Trudogolik::DB_SQL_BIND($sth, 1, $profession_add, SQL_VARCHAR);
    Trudogolik::DB_SQL_EXECUTE($sth);
   
    $sql="SELECT id FROM profession WHERE title = ?";
    $sth = Trudogolik::DB_SQL_PREPARE($dbh, $sql);
    Trudogolik::DB_SQL_BIND($sth, 1, $profession_add, SQL_VARCHAR);
    Trudogolik::DB_SQL_EXECUTE($sth);
    $id_profession = Trudogolik::DB_SQL_FETCHROW_ARRAY($sth);
  }
  else
  {
$error=$error.'&ERR_DUPLICATE_PROFESSION=1&DUPLICATE_PROFESSION_NAME='.$profession_add;
  }
}

Сразу же после добавления профессии программа извлекает идентификатор соответствующей записи с помощью оператора SELECT и сохраняет этот идентификатор в переменной $id_profession для дальнейшего использования.

В том случае если была предпринята попытка добавления уже существующей профессии, программа записывает в переменную $error идентификатор ошибки ERR_DUPLICATE_PROFESSION, а также название профессии, вызвавшее ошибку DUPLICATE_PROFESSION_NAME.

Аналогичным образом обрабатывается попытка добавления нового названия города:

if($city_add ne '')
{
  my $sql="select id from city where title = ?";
  my $sth = Trudogolik::DB_SQL_PREPARE($dbh, $sql);
  Trudogolik::DB_SQL_BIND($sth, 1, $city_add, SQL_VARCHAR);
  Trudogolik::DB_SQL_EXECUTE($sth);
  my @rs = Trudogolik::DB_SQL_FETCHROW_ARRAY($sth);
   
  if(!@rs)
  {
    my $sql="INSERT city (title) VALUES (?)";
    my $sth = Trudogolik::DB_SQL_PREPARE($dbh, $sql);
    Trudogolik::DB_SQL_BIND($sth, 1, $city_add, SQL_VARCHAR);
    Trudogolik::DB_SQL_EXECUTE($sth);
   
    $sql="SELECT id FROM city WHERE title = ?";
    $sth = Trudogolik::DB_SQL_PREPARE($dbh, $sql);
    Trudogolik::DB_SQL_BIND($sth, 1, $city_add, SQL_VARCHAR);
    Trudogolik::DB_SQL_EXECUTE($sth);
    $id_city = Trudogolik::DB_SQL_FETCHROW_ARRAY($sth);
  }
  else
  {
$error=$error.'&ERR_DUPLICATE_CITY=1&DUPLICATE_CITY_NAME='.$city_add;
  }
}

Так как посетитель может не указать минимальный или максимальный возраст, то программа обрабатывает содержимое полей возраста следующим образом:

if($age_from eq '')
{
  $age_from=0;
}
if($age_to eq '')
{
  $age_to=$age_from;
}

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

Аналогично обрабатывается информация о минимальном и максимальном окладе:

if($pay_from eq '')
{
  $pay_from=0;
}
if($pay_to eq '')
{
  $pay_to=$pay_from;
}

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

if($name eq '')
{
  $error=$error.'&ERR_NAME=1';
}
if($email eq '')
{
  $error=$error.'&ERR_EMAIL=1';
}

При ошибках к переменной $error дописываются соответствующие идентификаторы ошибок.

Последнее, что проверяется перед сохранением анкеты на вакансию в базе данных, это адрес электронной почты и пароль:

if(!Trudogolik::USER_REGISTERED($dbh, $email, $password))
{
  $error=$error.'&ERR_PASSWORD=1';
}

Для выполнения такой проверки мы использовали функцию с именем Trudogolik::USER_REGISTERED, определенную в пакете trudogolik.pl следующим образом:

sub USER_REGISTERED($$$)
{
  (my $dbh, my $email, my $password) = @_;
  if($email eq '' or $password eq '') { return 0; }
 
  my $sql="select id, password from clients where email = ?";
  my $sth = Trudogolik::DB_SQL_PREPARE($dbh, $sql);
  Trudogolik::DB_SQL_BIND($sth, 1, $email, SQL_VARCHAR);
  Trudogolik::DB_SQL_EXECUTE($sth);
  (my $id, my $pwd) = Trudogolik::DB_SQL_FETCHROW_ARRAY($sth);

  if($password eq $pwd) { return 1; }
  return 0;
}

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

Далее программа обработки формы проверяет содержимое переменной $error:

if($error ne '')
{
  print "Location: vacations_add.pl?INCOMPLETE=1".$error."\n\n";
}
else
{
  . . .
}

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

Последнее, что делает программа перед добавлением сведений о вакансии в базу данных, это обработка переменной $concurrent. В этой переменной хранится признак возможности работы по совместительству. Если в форме был отмечен флажок Совместительство допускается, программа записывает в эту переменную значение 1, а если нет — значение 0:

if($concurrent eq 'yes')
{
  $concurrent=1
}
else
{
  $concurrent=0
};

После выполнения всех проверок и замен программа добавляет сведения о вакансии в таблицу vacations при помощи параметризованной команды SQL:

my $sql="INSERT INTO vacations SET id_section=?, id_profession=?, id_job_condition=?, concurrent=?, id_city = ?, id_education = ?, experience = ?, requirements = ?, min_age = ?,   max_age=?, min_pay=?, max_pay=?, mf=?, name=?, phone=?, fax=?, email=?, web=?,
add_date = NOW(), remove_date = DATE_ADD(CURDATE(), INTERVAL ? DAY)";

my $sth = Trudogolik::DB_SQL_PREPARE($dbh, $sql);
Trudogolik::DB_SQL_BIND($sth, 1, $id_section, SQL_INTEGER);
Trudogolik::DB_SQL_BIND($sth, 2, $id_profession, SQL_INTEGER);
Trudogolik::DB_SQL_BIND($sth, 3, $id_job_condition, SQL_INTEGER);
Trudogolik::DB_SQL_BIND($sth, 4, $concurrent, SQL_INTEGER);
Trudogolik::DB_SQL_BIND($sth, 5, $id_city, SQL_INTEGER);
Trudogolik::DB_SQL_BIND($sth, 6, $id_education, SQL_INTEGER);
Trudogolik::DB_SQL_BIND($sth, 7, $experience, SQL_VARCHAR);
Trudogolik::DB_SQL_BIND($sth, 8, $requirements);
Trudogolik::DB_SQL_BIND($sth, 9, $age_from, SQL_INTEGER);
Trudogolik::DB_SQL_BIND($sth, 10, $age_to, SQL_INTEGER);
Trudogolik::DB_SQL_BIND($sth, 11, $pay_from, SQL_INTEGER);
Trudogolik::DB_SQL_BIND($sth, 12, $pay_to, SQL_INTEGER);
Trudogolik::DB_SQL_BIND($sth, 13, $mf, SQL_INTEGER);
Trudogolik::DB_SQL_BIND($sth, 14, $name, SQL_VARCHAR);
Trudogolik::DB_SQL_BIND($sth, 15, $phone, SQL_VARCHAR);
Trudogolik::DB_SQL_BIND($sth, 16, $fax, SQL_VARCHAR);
Trudogolik::DB_SQL_BIND($sth, 17, $email, SQL_VARCHAR);
Trudogolik::DB_SQL_BIND($sth, 18, $web, SQL_VARCHAR);
Trudogolik::DB_SQL_BIND($sth, 19, $keep, SQL_INTEGER);
Trudogolik::DB_SQL_EXECUTE($sth);

Обратите внимание, что при добавлении автоматически заполняется поле даты добавления анкеты add_date, а также поле даты удаления анкеты remove_date. Это позволит очистить таблицу вакансий простым запросом, сравнивающим текущую дату и дату удаления анкеты.

Перед тем как завершить свою работу, программа обработки вакансий выводит шаблон страницы с сообщением об успешном сохранении вакансии, показанной на рис. 8-12, и закрывает соединение с базой данных:

my $template = HTML::Template->new(filename => 'template/ru/vacations_add1.htm');
print header (-charset=>'windows-1251'); 
print $template->output;
. . .
Trudogolik::DB_CLOSE($dbh);

При этом используется простой статический шаблон, исходный текст которого приведен в листинге 8-9.

Листинг 8-9 Вы найдете в файле chap08\www.trudogolik.ru\cgi\Template/ru/vacations_add1.htm на прилагаемом к книге компакт-диске.

Добавление резюме

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

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

Пользовательский интерфейс

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

Рис. 8-15. Форма для добавления резюме (часть 1)

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

Название профессии и города выбирается из базы данных или добавляется при помощи соответствующих текстовых полей (если база данных не содержит нужные названия).

Нижняя часть формы показана на рис. 8-16.

Рис. 8-16. Форма для добавления резюме (часть 2)

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

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

После заполнения формы нужно щелкнуть кнопку Сохранить. Если все поля формы были заполнены правильно, через некоторое время в окне браузера появится сообщение об успешном сохранении резюме в базе данных (рис. 8-17).

Рис. 8-17. Сообщение об успешном добавлении резюме

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

Программная реализация

Когда посетитель щелкает ссылку Добавить резюме, запускается программа resume_add.pl (листинг 8-10).

Листинг 8-10 Вы найдете в файле chap08\www.trudogolik.ru\cgi\resume _add.pl на прилагаемом к книге компакт-диске.

Изучая листинг программы resume _add.pl, Вы заметите, что он практически полностью повторяет листинг программы vacations_add.pl, предназначенной для вывода формы вакансии. Отличие заключается лишь в использованном шаблоне формы (листинг 8-11), поэтому с этой программой Вы сможете разобраться самостоятельно.

Листинг 8-11 Вы найдете в файле chap08\www.trudogolik.ru\cgi\Template/ru/ resume _add.htm на прилагаемом к книге компакт-диске.

Программа обработки данных формы резюме находится в файле resume_add1.pl (листинг 8-12). Данная программа почти полностью аналогична подробно разобранной программе vacations _add1.pl, поэтому мы тоже оставляем ее Вам на самостоятельное изучение.

Листинг 8-12 Вы найдете в файле chap08\www.trudogolik.ru\cgi\resume _add1.pl на прилагаемом к книге компакт-диске.

После успешного добавления резюме в браузере посетителя появляется сообщение, показанное на рис. 8-17. Оно формируется программой resume_add1.pl с использованием шаблона, хранящегося в файле resume _add1.htm (листинг 8-13).

Листинг 8-13 Вы найдете в файле chap08\www.trudogolik.ru\cgi\Template/ru/ resume _add1.htm на прилагаемом к книге компакт-диске.

Поиск вакансий

Раздел Поиск вакансий Web-узла виртуального кадрового агентства Трудоголик.Ру, также как и раздел Поиск резюме, доступен любому посетителю без предварительной регистрации.

Рассмотрим пользовательский интерфейс этого раздела, а также его программную реализацию.

Пользовательский интерфейс

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

Рис. 8-18. Форма для поиска вакансий

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

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

Списки Образование и Стаж работы позволяют указать профессиональные знания и опыт, необходимые для получения работы по вакансиям. Здесь же можно указать свой возраст (поле Возраст), пол (список Пол), а также желаемый оклад (список Оклад). Предусмотрена также возможность поиска по ключевым словам, указанным в поле Ключевое слово.

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

На рис. 8-19 мы показали окно с результатами поиска вакансий.

Рис. 8-19. Просмотр результатов поиска

Как видите, для каждой вакансии отображается полные сведения, хранящиеся в базе данных агентства.

Если при поиске было найдено более пяти вакансий, в нижней части окна появляется кнопка Следующая страница (на рис. 8-19 не показана). В том случае, когда по результатам запроса не было найдено ни одной вакансии, в окне браузера появится страница с кнопкой Новый поиск (рис. 8-20).

Рис. 8-20. Ничего не найдено

Щелкнув эту кнопку, посетитель окажется снова на странице поиска, показанной на рис. 8-18. Здесь он может попробовать повторить запрос, выбрав другие условия.

Программная реализация

Подсистему поиска Web-узла виртуального кадрового агентства Трудоголик.Ру образуют две программы CGI. Одна из них отображает форму поиска, а другая — выполняет запрос к базе данных.

Отображение формы поиска

Первая программа vacations_search.pl (листинг 8-14) выводит в окно браузера форму поиска, показанную на рис. 8-18. Ее особенность заключается в том, что она выполняет предварительную инициализацию элементов управления (списков), расположенных в форме, из базы данных.

Листинг 8-14 Вы найдете в файле chap08\www.trudogolik.ru\cgi\vacations_search.pl на прилагаемом к книге компакт-диске.

Получив управление, программа vacations_search.pl открывает соединение с базой данных, вызывая для этого функцию Trudogolik::DB_OPEN():

my $dbh = Trudogolik::DB_OPEN();

Далее она сохраняет в переменной $template ссылку на шаблон vacations_search.htm, используемый для подготовки формы:

my $template = HTML::Template->new(filename => 'template/ru/vacations_search.htm');

Исходный текст этого шаблона приведен в листинге 8-15. Мы рассмотрим его чуть позже.

На следующем этапе программа подготавливает шаблоны списков, заполняя их сведениями из базы данных:

Trudogolik::SELECT_FILL($dbh, "SELECT id,title FROM sections ORDER BY title");
$template->param(SECTION_LOOP => \@::loop);

Trudogolik::SELECT_FILL($dbh, "SELECT id,title FROM profession ORDER BY title");
$template->param(PROFESSION_LOOP => \@::loop);
 
Trudogolik::SELECT_FILL($dbh, "SELECT id,title FROM job_condition ORDER BY title");
$template->param(JOB_CONDITION_LOOP => \@::loop);

Trudogolik::SELECT_FILL($dbh, "SELECT id,title FROM city ORDER BY title");
$template->param(CITY_LOOP => \@::loop);

Trudogolik::SELECT_FILL($dbh, "SELECT id,title FROM education ORDER BY title");
$template->param(EDUCATION_LOOP => \@::loop);

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

Следующий блок условных операторов нужен для обработки возможных ошибок, допущенных пользователем при заполнении формы:

if(param('INCOMPLETE'))
{
  $template->param(INCOMPLETE_FORM => 1);
}
if(param('ERR_NAME'))
{
  $template->param(ERR_NAME => 1);
}
if(param('ERR_EMAIL'))
{
  $template->param(ERR_EMAIL => 1);
}
if(param('ERR_DUPLICATE_CITY'))
{
  $template->param(ERR_DUPLICATE_CITY => 1);
  $template->param(DUPLICATE_CITY_NAME => param('DUPLICATE_CITY_NAME'));
}
if(param('ERR_DUPLICATE_PROFESSION'))
{
  $template->param(ERR_DUPLICATE_PROFESSION => 1);
  $template->param(DUPLICATE_PROFESSION_NAME => param('DUPLICATE_PROFESSION_NAME'));
}

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

И, наконец, перед тем как завершить свою работу, программа отправляет пользователю страницу, созданную с применением шаблона vacations_search.htm и закрывает соединение с базой данных:

print header (-charset=>'windows-1251'); 
print $template->output;
Trudogolik::DB_CLOSE($dbh);

Шаблон формы поиска

Рассмотрим исходный текст шаблона формы поиска vacations_search.htm, представленный в листинге 8-15.

Листинг 8-15 Вы найдете в файле chap08\www.trudogolik.ru\cgi\Template/ru/vacations_search.htm на прилагаемом к книге компакт-диске.

В начале файла находится условный шаблон с именем INCOMPLETE_FORM, отвечающий за выдачу сообщения о том, что при заполнении формы были допущены ошибки:

<TMPL_IF NAME="INCOMPLETE_FORM">
<p><font color="red">
При заполнении формы были допущены ОШИБКИ.</font></p>
</TMPL_IF>

При первом отображении формы этот условный шаблон не участвует в формировании страницы.

Далее располагается форма запроса, обращающаяся к программе vacations_search1.pl:

<form action="vacations_search1.pl" method="post">
. . .
</form>

Внутри этой формы определены все элементы управления, предназначенные для определения параметров поиска.

Раздел профессий выбирается при помощи следующего списка <SELECT>:

<TD align="left" width="150">Раздел</TD>
<TD><SELECT name="SECTION" class="comic">
  <OPTION VALUE="0" selected>-
все разделы -</OPTION>
<TMPL_LOOP NAME="SECTION_LOOP">
  <OPTION VALUE="<TMPL_VAR NAME=ID>"><TMPL_VAR NAME=TITLE></OPTION>
</TMPL_LOOP>
</SELECT></TD>

Список названий разделов заполняется из базы данных при помощи циклического шаблона SECTION_LOOP.

Обратите внимание, что первая строка все разделы формируется отдельно. Значение параметра VALUE для этой строки равно нулю. Что же касается остальных строк, то для них значение этого параметра выбирается из поля id соответствующей таблицы базы данных, идентифицирующего строки таблицы уникальным образом. Так как содержимое поля id не может быть равно нулю, это позволяет использовать нулевое значение параметра VALUE для исключения названия раздела из условий поиска.

Аналогичным образом формируются списки названий профессии и графика работы — посетитель может выбрать какую-либо конкретную профессию или график работы, либо исключить данные условия поиска:

<TD align="left" width="150">Профессия</TD>
<TD><SELECT name="PROFESSION" class="comic">
  <OPTION VALUE="0" selected>-
все профессии -</OPTION>
<TMPL_LOOP NAME="PROFESSION_LOOP">
  <OPTION VALUE="<TMPL_VAR NAME=ID>"><TMPL_VAR NAME=TITLE></OPTION>
</TMPL_LOOP>
</SELECT></TD></TR>
  <TR>
<TD align="left" width="150">
График работы</TD>
<TD><SELECT name="JOB_CONDITION" class="comic">
  <OPTION VALUE="0" selected>-
любой -</OPTION>
<TMPL_LOOP NAME="JOB_CONDITION_LOOP">
  <OPTION VALUE="<TMPL_VAR NAME=ID>"><TMPL_VAR NAME=TITLE></OPTION>
</TMPL_LOOP>
</SELECT></TD></TR>

Возможность работы по совместительству учитывается при помощи флажка с именем CONCURRENT:

<TD align="left" width="150">Совместительство</TD>
<TD><INPUT type=checkbox name="CONCURRENT" value="yes" checked>&nbsp;
допускается</TD>

Если посетитель отметит этот флажок при поиске, будут найдены все вакансии, допускающие работу по совместительству.

Ниже мы показали списки, предназначенные для выбора города и образования:

<TD align="left" width="150">Город</TD>
<TD><SELECT name="CITY" class="comic">
  <OPTION VALUE="0" selected>-
все города -</OPTION>
<TMPL_LOOP NAME="CITY_LOOP">
  <OPTION VALUE="<TMPL_VAR NAME=ID>"><TMPL_VAR NAME=TITLE></OPTION>
</TMPL_LOOP>
  </SELECT></TD></TR>
<tr><td colspan="2">&nbsp;</td></tr>
<TR>
<TD align="left" width="150">
Образование</TD>
<TD><SELECT name="EDUCATION" class="comic">
  <OPTION VALUE="0" selected>-
любое -</OPTION>
<TMPL_LOOP NAME="EDUCATION_LOOP">
  <OPTION VALUE="<TMPL_VAR NAME=ID>"><TMPL_VAR NAME=TITLE></OPTION>
</TMPL_LOOP>
</SELECT></TD></TR>

Информация для заполнения этих списков берется из базы данных.

Что же касается стажа работы, то соответствующий список определен статически:

<TD align="left" width="150">Стаж работы</TD>
<TD><SELECT name="EXPERIENCE" class="comic">
  <OPTION VALUE="0" selected>-
любой -</OPTION>
  <OPTION VALUE="1">1
год</OPTION>
  <OPTION VALUE="2">2
года</OPTION>
  <OPTION VALUE="3">3
года</OPTION>
  <OPTION VALUE="5">5
лет</OPTION>
  <OPTION VALUE="10">10
лет</OPTION>
  <OPTION VALUE="15">15
лет</OPTION>
</SELECT></TD>

Посетители могут указать свой возраст в поле AGE:

<TD align="left" width="150">Возраст</TD>
<TD><INPUT style="HEIGHT: 22px; WIDTH: 52px" class="comic" name="AGE">&nbsp;
лет</TD>

Пол задается при помощи статического списка:

<TD align="left" width="150">Пол</TD>
<TD><SELECT name="MF" class="comic">
  <OPTION VALUE="0" selected>-
любой- </OPTION>
  <OPTION VALUE="1">
мужской</OPTION>
  <OPTION VALUE="2">
женский</OPTION>
</SELECT></TD>

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

Желаемый оклад можно задать в текстовом поле PAY:

<TD align="left" width="150">Оклад</TD>
<TD><INPUT style="HEIGHT: 22px; WIDTH: 53px" name="PAY" class="comic">&nbsp;
у.е.</TD>

При этом будут найдены вакансии с размером оплаты не ниже указанного значения.

Для поиска по ключевому слову в форме предусмотрено поле KEYWORDS:

<TD align="left" width="150">Ключевое слово</TD>
<TD valign="top"><INPUT name="KEYWORDS" class="comic"></TD>

И, наконец, для ограничения поиска только новыми сведениями можно выбрать период поиска из статического списка PERIOD:

<TD align="left" width="150">Период поиска</TD>
<TD><SELECT name="PERIOD" class="comic">
  <OPTION VALUE="1" selected>1
день</OPTION>
  <OPTION VALUE="3">3
дня</OPTION>
  <OPTION VALUE="7">7
дней</OPTION>
  <OPTION VALUE="10">10
дней</OPTION>
  <OPTION VALUE="30">30
дней</OPTION>
</SELECT></TD>

Для отправки данных формы на сервер предназначена кнопка Найти вакансию, определенная в форме следующим образом:

<TD align="left" valign="bottom"><INPUT id=button1 name=button1  class="comic" type=submit value="Найти вакансию"></TD>

Обратите также внимание на скрытое поле NEXT_ROW, предусмотренное для пролистывания многостраничных результатов запроса:

<INPUT type="hidden" name="NEXT_ROW" value="0">

Об этом поле мы расскажем чуть позже, а сейчас расскажем о получении результатов запроса.

Получение результатов запроса

Когда посетитель заполнит форму поиска и щелкнет кнопку Найти вакансию, управление будет передано программе vacations_search1.pl (листинг 8-16).

Листинг 8-16 Вы найдете в файле chap08\www.trudogolik.ru\cgi\vacations_search1.pl на прилагаемом к книге компакт-диске.

Получив управление, эта программа открывает соединение с базой данных:

my $dbh = Trudogolik::DB_OPEN();

Далее она извлекает содержимое всех полей формы, преобразуя при необходимости специальные символы в символьные объекты HTML при помощи функции Trudogolik::ASCII_TO_HTML:

my $id_section=param('SECTION');
my $id_profession=param('PROFESSION');
my $id_job_condition=param('JOB_CONDITION');
my $concurrent=param('CONCURRENT');
my $id_city=param('CITY');
my $id_education=param('EDUCATION');
my $experience=param('EXPERIENCE');
my $age = Trudogolik::ASCII_TO_HTML(param('AGE'));
my $pay = Trudogolik::ASCII_TO_HTML(param('PAY'));
my $mf=param('MF');
my $keywords = Trudogolik::ASCII_TO_HTML(param('KEYWORDS'));
my $period=param('PERIOD');

Перед дальнейшим использованием программа выполняет некоторые проверки введенных параметров:

if($age eq '')
{
  $age=0;
}
if($pay eq '')
{
  $pay=0;
}
if($concurrent eq 'yes')
{
   $concurrent=1;
}
else
{
  $concurrent=0;
};

Если посетитель не указал свой возраст или размер оклада, в соответствующие переменные записываются нулевые значения. Кроме того, если был отмечен флажок работы по совместительству, в переменную $concurrent записывается значение 1, а если нет — записывается значение 0.

Теперь в зависимости от заданных параметров поиска программа должна сформировать строку запроса SQL.

Исходная строка показана ниже:

my $sql="select add_date, id_section, id_profession, id_city,   min_age, max_age, min_pay, max_pay, id_education, id_job_condition, experience, concurrent, mf, name, phone, fax, email, web, requirements from vacations where add_date > DATE_ADD(CURDATE(), INTERVAL -$period DAY) ";

Здесь выбираются все поля таблицы вакансий vacations, дата сохранения которых соответствует указанному периоду просмотра.

Далее к исходной строке в зависимости от содержимого полей формы запроса дописываются те или иные дополнительные условия:

if($pay != 0)
{
  $sql = $sql." AND max_pay >= $pay";
}
if($id_section != 0)
{
  $sql = $sql." AND id_section=$id_section";
}
if($id_profession != 0)
{
  $sql = $sql." AND id_profession=$id_profession";
}
if($id_job_condition != 0)
{
  $sql = $sql." AND id_job_condition=$id_job_condition";
}
if($concurrent == 0)
{
  $sql = $sql." AND concurrent=0";
}
if($id_city != 0)
{
   $sql = $sql." AND id_city=$id_city";
}
if($id_education != 0)
{
  $sql = $sql." AND id_education=$id_education";
}
if($experience != 0)
{
  $sql = $sql." AND experience >= $experience";
}
if($age != 0)
{
  $sql = $sql." (AND max_age >= $age AND (min_age <= $age OR max_age=0))";
}
if($mf != 0)
{
  $sql = $sql." AND mf = $mf";
}
if($keywords ne '')
{
  $sql = $sql." AND requirements LIKE ?";
}

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

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

my $next_row;
my $page_size=2;
$next_row = param('NEXT_ROW');

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

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

$sql = $sql." ORDER BY add_date DESC LIMIT $next_row, $page_size";

Параметр LIMIT указывает, что результат запроса нужно выдать со смещением $next_row, причем следует отобразить только первые $page_size строк результата запроса.

После использования номер текущей строки нужно увеличить на количество строк, расположенных на одной странице:

$next_row += $page_size;

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

my $sth = Trudogolik::DB_SQL_PREPARE($dbh, $sql);

Если посетитель указал ключевое слово для поиска, нужно выполнить привязку соответствующего параметра:

if($keywords ne '')
{
  Trudogolik::DB_SQL_BIND($sth, 1, '%'.$keywords.'%', SQL_VARCHAR);
}

Теперь можно запускать команду на выполнение:

Trudogolik::DB_SQL_EXECUTE($sth);

На следующем этапе программа извлекает результаты запроса функцией Trudogolik::DB_SQL_FETCHROW_ARRAY, а затем заполняет циклический шаблон результата поиска:

my $founded_records = 0;
 
@::loop = ();
while((my $add_date, my $id_section, my $id_profession, my $id_city,   my $min_age, my $max_age, my $min_pay, my $max_pay, my $id_education,  my $id_job_condition, my $experience, my $concurrent, my $mf, my $name, my $phone, my $fax, my $email, my $web, my $requirements) =
   Trudogolik::DB_SQL_FETCHROW_ARRAY($sth))
{
    $add_date = substr($add_date, 6, 2).'.'.substr($add_date, 4, 2).'.'.substr($add_date, 0, 4).' '.substr($add_date, 8, 2).':'.substr($add_date, 10, 2).':'.substr($add_date, 12, 2);

  my $section=Trudogolik::GET_TITLE($dbh, 'sections', $id_section);
  my $profession=Trudogolik::GET_TITLE($dbh, 'profession', $id_profession);
  my $city=Trudogolik::GET_TITLE($dbh, 'city', $id_city);
  my $education=Trudogolik::GET_TITLE($dbh, 'education', $id_education);
  my $job_condition=Trudogolik::GET_TITLE($dbh, 'job_condition', $id_job_condition);
 
  my $mf_used=0;
  if($mf != 0)
  {
    $mf_used=1;
    if($mf == 1)
    {
      $mf=1;
    }
    elsif($mf == 2)
    {
      $mf=0;
    }
    else
    {
      $mf_used=0;
    }
  }
 
  my %row = (ADD_DATE => $add_date, SECTION => $section, PROFESSION => $profession, CITY => $city,
  MIN_AGE => $min_age, MAX_AGE => $max_age, MIN_PAY => $min_pay, MAX_PAY => $max_pay,
  EDUCATION => $education, JOB_CONDITION => $job_condition, EXPERIENCE => $experience,
  CONCURRENT => $concurrent, MF => $mf, MF_USED => $mf_used,
  NAME => $name, PHONE => $phone, FAX => $fax, EMAIL => $email, WEB => $web, REQUIREMENTS => $requirements);
    push(@::loop, \%row);
  $founded_records += 1;
}
my $template = HTML::Template->new(filename => 'template/ru/vacations_search1.htm');
$template->param(THIS_LOOP => \@::loop);

В этом цикле мы применили функцию substr для извлечения отдельных полей даты с целью формирования строки даты в нужном нам формате.

Мы также использовали функцию Trudogolik::GET_TITLE, позволяющую извлечь содержимое поля title таблиц словарей по уникальному идентификатору соответствующей строки, который хранится в поле id:

sub GET_TITLE($$$)
{
  (my $dbh, my $table, my $id_table) = @_;
 
  my $sql="select id, title from $table where id = ?";
  my $sth = Trudogolik::DB_SQL_PREPARE($dbh, $sql);
  Trudogolik::DB_SQL_BIND($sth, 1, $id_table, SQL_INTEGER);
  Trudogolik::DB_SQL_EXECUTE($sth);
  (my $id, my $title) = Trudogolik::DB_SQL_FETCHROW_ARRAY($sth);
  return $title;

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

$template->param(RQ_SECTION => $id_section);
$template->param(RQ_PROFESSION => $id_profession);
$template->param(RQ_JOB_CONDITION => $id_job_condition);
$template->param(RQ_CONCURRENT => param('CONCURRENT'));
$template->param(RQ_CITY => $id_city);
$template->param(RQ_EDUCATION => $id_education);
$template->param(RQ_EXPERIENCE => param('EXPERIENCE'));
$template->param(RQ_AGE => param('AGE'));
$template->param(RQ_MF => param('MF'));
$template->param(RQ_PAY => param('PAY'));
$template->param(RQ_KEYWORDS => param('KEYWORDS'));
$template->param(RQ_PERIOD => $period);
$template->param(RQ_NEXT_ROW => $next_row);

Это необходимо для выдачи повторных запросов в процессе просмотра многостраничных результатов поиска.

Отдельно обрабатывается последняя страница результатов поиска:

if($founded_records >= $page_size)
{
  $template->param(NOT_LAST_PAGE => 1);
}
elsif($founded_records == 0)
{
  $template->param(NO_PAGES => 1);
}

Если в процессе просмотра была достигнута последняя страница, применяется условный шаблон NO_PAGES.

И, наконец, перед тем как завершить работу, программа выводит сформированную по шаблону страницу и закрывает соединение с базой данных:

print header (-charset=>'windows-1251'); 
print $template->output;
Trudogolik::DB_CLOSE($dbh);

Шаблон страницы результатов поиска

Шаблон страницы результатов поиска находится в файле vacations_search1.htm (листинг 8-17).

Листинг 8-17 Вы найдете в файле chap08\www.trudogolik.ru\cgi\Template/ru/vacations_search1.htm на прилагаемом к книге компакт-диске.

Список найденных вакансий располагается внутри циклического шаблона THIS_LOOP:

<H1>Список вакансий</H1>
<hr>
<TMPL_LOOP NAME="THIS_LOOP">
<TABLE border="0" cellspacing="0" cellpadding="3" width="100%">
<tr><td width="150"><b><TMPL_VAR NAME=ADD_DATE></b><td width="150">
Раздел:&nbsp;<b><TMPL_VAR NAME=SECTION></b></td><td>Город:&nbsp;<b><TMPL_VAR NAME=CITY></b></td></tr>
<tr><td><IMG alt="" border=0 height=10 src="../pic/null.gif"></td></tr>
</table>
<TABLE border="0" cellspacing="1" cellpadding="3" width="100%">
<tr valign="top">
<td rowspan="7" width="20"><IMG alt="" border=0  src="../pic/null.gif" width="20"></td>
<td width="100">
Требуется:</td>
<td width="200"><b><TMPL_VAR NAME=PROFESSION></b><TMPL_IF NAME="MF_USED"> (<TMPL_IF NAME="MF">
мужчина<TMPL_ELSE>женщина</TMPL_IF>)</td></TMPL_IF>
<td rowspan="7" valign="top"><b>
Прочие требования:</b><br><TMPL_VAR NAME=REQUIREMENTS></td>
</tr>
<tr valign="top"><td width="100">
График:</td><td width="200"><TMPL_VAR NAME=JOB_CONDITION><TMPL_IF NAME="CONCURRENT"><br>(допускается совместительство)</TMPL_IF></td></tr>
<tr valign="top"><td width="100">
Оклад:</td><td width="200">от <b><TMPL_VAR NAME=MIN_PAY></b> у.е. до <b><TMPL_VAR NAME=MAX_PAY></b> у.е.</td></tr>
<tr valign="top"><td width="100">
Возраст:</td><td width="200">от <b><TMPL_VAR NAME=MIN_AGE></b> лет до <b><TMPL_VAR NAME=MAX_AGE></b> лет</td></tr>
<tr valign="top"><td width="100">
Образование:</td><td width="200"><TMPL_VAR NAME=EDUCATION></td></tr>
<tr valign="top"><td width="100">
Минимальный стаж, лет:</td><td width="200"><TMPL_VAR NAME=EXPERIENCE></td></tr>
<tr valign="top"><td width="100" valign="top">
Контактное лицо:</td><td width="200"><b><TMPL_VAR NAME=NAME></b><br>тел.: <TMPL_VAR NAME=PHONE><br>факс: <TMPL_VAR NAME=FAX><br>E-Mail: <a href="mailto:<TMPL_VAR NAME=EMAIL>"><TMPL_VAR NAME=EMAIL></a><br>Web-сайт: <a href="<TMPL_VAR NAME=WEB>"><TMPL_VAR NAME=WEB></a></td></tr>
</table>
<hr>
</TMPL_LOOP>

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

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

<form action="vacations_search1.pl" method="post">
  <INPUT type="hidden" name="SECTION" value="<TMPL_VAR NAME=RQ_SECTION>">
  <INPUT type="hidden" name="PROFESSION" value="<TMPL_VAR NAME=RQ_PROFESSION>">
  <INPUT type="hidden" name="JOB_CONDITION" value="<TMPL_VAR NAME=RQ_JOB_CONDITION>">
  <INPUT type="hidden" name="CONCURRENT" value="<TMPL_VAR NAME=RQ_CONCURRENT>">
  <INPUT type="hidden" name="CITY" value="<TMPL_VAR NAME=RQ_CITY>">
  <INPUT type="hidden" name="EDUCATION" value="<TMPL_VAR NAME=RQ_EDUCATION>">
  <INPUT type="hidden" name="EXPERIENCE" value="<TMPL_VAR NAME=RQ_EXPERIENCE>">
  <INPUT type="hidden" name="AGE" value="<TMPL_VAR NAME=RQ_AGE>">
  <INPUT type="hidden" name="MF" value="<TMPL_VAR NAME=RQ_MF>">
  <INPUT type="hidden" name="PAY" value="<TMPL_VAR NAME=RQ_PAY>">
   <INPUT type="hidden" name="KEYWORDS" value="<TMPL_VAR NAME=RQ_KEYWORDS>">
   <INPUT type="hidden" name="PERIOD" value="<TMPL_VAR NAME=RQ_PERIOD>">
   <INPUT type="hidden" name="NEXT_ROW" value="<TMPL_VAR NAME=RQ_NEXT_ROW>">
<TMPL_IF NAME="NOT_LAST_PAGE">
   <P><INPUT id=button1 name=button1 type=submit value="
Следующая страница >>"></P>
   </form>
<TMPL_ELSE>
   </form>
<TMPL_IF NAME="NO_PAGES">
<p>
Извините, других вакансий, удовлетворяющих условию поиска, нет!</p>
</TMPL_IF>
<form action="vacations_search.pl" method="post">
    <P><INPUT id=button1 name=button1 type=submit value="
Новый поиск"></P>
</form>
</TMPL_IF>

Внешний вид этой формы зависит от того, была ли достигнута при поиске последняя страница. Если была, то в нижней части создается форма с кнопкой Новый поиск, запускающая программу vacations_search.pl. Если же нет, то в форме появляется кнопка Следующая страница, щелчок которой приводит к запуску программы vacations_search1.pl. Эта программа выведет на экран следующую страницу с результатами запроса.

Поиск резюме

Раздел Поиск резюме (рис. 8-21) аналогичен только что описанному разделу Поиск вакансий, поэтому мы оставляем его Вам на самостоятельное изучение.

Рис. 8-21. Форма для поиска резюме

Исходный текст программы, отображающей в окне браузера форму поиска резюме, представлен в листинге 8-18.

Листинг 8-18 Вы найдете в файле chap08\www.trudogolik.ru\cgi\resume_search1.pl на прилагаемом к книге компакт-диске.

При формировании страницы поиска эта программа использует шаблон resume_search1.htm (листинг 8-19).

Листинг 8-19 Вы найдете в файле chap08\www.trudogolik.ru\cgi\Template/ru/resume_search1.htm на прилагаемом к книге компакт-диске.

Когда посетитель щелкает кнопку Найти резюме (рис. 8-21), управление передается программе поиска resume_search1.pl (листинг 8-20).

Листинг 8-20 Вы найдете в файле chap08\www.trudogolik.ru\cgi\resume_search1.pl на прилагаемом к книге компакт-диске.

Для создания страниц с результатами поиска мы подготовили шаблон, исходный текст которого Вы найдете в листинге 8-21.

Листинг 8-21 Вы найдете в файле chap08\www.trudogolik.ru\cgi\Template/ru/resume_search1.htm на прилагаемом к книге компакт-диске.

Администрирование Web-узла кадрового агентства

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

Для выполнения этих административных функций мы предусмотрели специальный Web-узел, объединенный с узлом http://www.trudogolik.ru общей базой данных.

Защита от несанкционированного доступа

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

Пользовательский интерфейс

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

Первый уровень реализован средствами Web-сервера Apache, описанными в разделе «Парольный доступ к страницам Web-узла» 3 главы нашей книги. Для этого мы создали и подключили файл идентификаторов и паролей. В результате при попытке доступа к главной или другим страницам административного Web-узла на экране появляется диалоговое окно аутентификации, показанное на рис. 8-22.

Рис. 8-22. Узел администрирования доступен не всем пользователям Интернета

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

После ввода правильного идентификатора и пароля в окне браузера появится первая страница узла администрирования (рис. 8-23).

Рис. 8-23. Первая страница узла администрирования

Щелкнув ссылку Администрирование сайта TRUDOGOLIK.RU, расположенную на этой странице, пользователь столкнется со вторым уровнем аутентификации, реализованным с использованием базы данных и механизма Cookie. При этом ему будет снова предложено ввести идентификатор и пароль, зарегистрированные в таблице managers базы данных виртуального кадрового агентства Трудоголик.Ру (рис. 8-24).

Рис. 8-24. Еще один уровень защиты от несанкционированного доступа

Заметим, что идентификатор и пароль из базы данных, могут не совпадать с идентификатором и паролем, используемым для ограничения доступа средствами Web-сервера Apache или IIS.

Прорвавшись через оба уровня защиты, пользователь, наконец, получит доступ к разделам администрирования, показанным на рис. 8-25.

Рис. 8-25. Разделы узла администрирования

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

Программная реализация

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

Когда администратор узла виртуального кадрового агентства Трудоголик.Ру щелкает ссылку Администрирование сайта TRUDOGOLIK.RU (рис. 8-23), управление получает программа аутентификации login.pl, исходный текст которой представлен в листинге 8-22.

Листинг 8-22 Вы найдете в файле chap08\Admin123Trudogolik\cgi\login.pl на прилагаемом к книге компакт-диске.

Вот начальный фрагмент этой программы:

#!/usr/bin/perl -w
use CGI qw(:standard);
use HTML::Template;
use Digest::SHA1 qw(sha1_hex sha1_base64);
use DBI qw(:sql_types);
use HTML::Entities ();
use strict;
require 'trudogolik.pl';

Обратите внимание, что помимо уже знакомых Вам модулей Perl в исходный текст программы login.pl мы включили модуль Digest::SHA1, предназначенный для вычисления хеша с использованием алгоритма SHA-1. Данный модуль позволяет вычислить хеш размером 160 бит для блока данных произвольной длины. Как Вы увидите дальше, мы используем этот модуль для повышения безопасности работы с паролями администраторов.

Кроме того, программа login.pl обращается к функциям, определенным в упоминавшемся ранее пакете trudogolik.pl. Исходный текст версии этого пакета, разработанного для узла администрирования кадрового агентства, Вы найдете в листинге 8-23.

Листинг 8-23 Вы найдете в файле chap08\Admin123Trudogolik\cgi\trudogolik.pl на прилагаемом к книге компакт-диске.

Итак, продолжим изучение листинга программы login.pl.

Получив управление, программа login.pl открывает соединение с базой данных и устанавливает в качестве текущей базу trudogolik:

my $dbh = Trudogolik::DB_OPEN();
Trudogolik::DB_SQL_DO($dbh, "use trudogolik");

Хранение идентификатора подключения в Cookie

Чтобы не заставлять администратора вводить свой идентификатор и пароль при обращении к каждой странице административного Web-узла, мы храним закодированный идентификатор подключения в Cookie на диске рабочей станции администратора. Первое, что делает программа login.pl после подключения к базе данных, это проверяет, ввел  администратор правильный идентификатор и пароль, или нет. Для этого вызывается функция Trudogolik::check_user_key:

my $template_path='template/ru/';
if(Trudogolik::check_user_key($dbh))
{
  Trudogolik::PRINT_TEMPLATE($template_path.'index.htm');
}

Если администратор правильно ввел парольную информацию, программа переходит сразу на страницу выбора функций администрирования (рис. 8-25), используя для этого шаблон index.htm и функцию Trudogolik::PRINT_TEMPLATE.

Рассмотрим исходный текст функции Trudogolik::check_user_key

sub check_user_key($)
{
  my $dbh = shift @_;
  my $user_id_cookie=cookie('trudogolik_ru_user_id');

  my $sql="select id from managers where user_id = ?";
  my $sth = Trudogolik::DB_SQL_PREPARE($dbh, $sql);
  Trudogolik::DB_SQL_BIND($sth, 1, $user_id_cookie, SQL_VARCHAR);
  Trudogolik::DB_SQL_EXECUTE($sth);
  my @logins = Trudogolik::DB_SQL_FETCHROW_ARRAY($sth);
  return @logins;
}

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

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

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

На следующем этапе функция Trudogolik::check_user_key пытается найти в таблице managers запись администратора, содержащую идентификатор соединения, извлеченный из Cookie.

Массив полей первой строки, извлеченной из таблицы managers, возвращается функцией в вызывающую программу. Если этот массив не пуст, то таблица managers содержит идентификатор соединения, извлеченный из Cookie и посетителю можно показывать первую страницу разделов администрирования. Мы это делаем с помощью функции Trudogolik::PRINT_TEMPLATE, определенной в пакете trudogolik.pl следующим образом:

sub PRINT_TEMPLATE($)
{
  my $template_path = shift @_;

  print "Content-Type: text/html\n";
  print "Pragma: no-cache\n";
  print "Cache-Control: no-cache\n";
  print "Expires: Thu Jan  1 01:01:01 1970\n";
  print "Charset: windows-1251\n\n";
  my $template = HTML::Template->new(filename => $template_path);
  print $template->output;
}

Обратите внимание, что в заголовке HTTP мы указываем (тремя различными способами)  на необходимость отмены кэширования данной страницы.

Первая проверка идентификатора и пароля

Рассмотрим теперь случай, когда администратор ввод свой идентификатор и пароль в первый раз. При этом функция Trudogolik::check_user_key не найдет в файле managers ни одной подходящей записи и вернет пустое значение. В ответ на это программа login.pl займется проверкой пароля.

Прежде всего, программа login.pl попытается извлечь адрес IP рабочей станции посетителя, идентификатор и пароль:

my $host=remote_host();
my $login=param('LOGIN');
my $password=param('PASSWORD');

Если программа login.pl вызывается из окна, показанного на рис. 8-23, то строки идентификатора и пароля будут пустыми, а если из окна, показанного на рис. 8-24, — то в переменные $login и $password попадут значения, введенные посетителем в соответствующих полях формы.

Далее программа проверяет, содержит ли таблица managers какие либо записи:

my $sql="select login,password from managers limit 1";
my $sth = Trudogolik::DB_SQL_PREPARE($dbh, $sql);
Trudogolik::DB_SQL_EXECUTE($sth);
my @logins = Trudogolik::DB_SQL_FETCHROW_ARRAY($sth);

Создание записи по умолчанию

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

if(@logins == 0)
{
  my $user_id=generate_user_id('frolov');
  $sql = "insert into managers (login, password, rights, reg_ip, reg_date, user_id, comment) values ('frolov', '123', 128, '$host', NOW(), '$user_id', '[Just added]')";
  Trudogolik::DB_SQL_DO($dbh, $sql);

  Trudogolik::PRINT_TEMPLATE_COOKIE($template_path.'index.htm', "trudogolik_ru_user_id", $user_id);
}

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

Кроме того, в записи сохраняется дата ее создания, а также идентификатор соединения пользователя $user_id, созданный функцией generate_user_id:

sub generate_user_id($)
{
  my $login=shift @_;
  my $id=int(rand(100000));
  return $login.".".sha1_base64('pwd'.$id);
}

Получив в качестве параметра идентификатор пользователя, функция generate_user_id выполняет над ним некоторые преобразования, дополняя его значением хэша от случайного числа, полученного при помощи функции rand. Хеш генерируется функцией sha1_base64, определенной в пакете Digest::SHA1.

Добавив новую запись в таблицу managers, программа login.pl сохраняет значение идентификатора соединения, вычисленное функцией generate_user_id, в Cookie. Когда посетитель обратится к программе login.pl в следующий раз, эта программа извлечет значение идентификатора соединения из Cookie и проверит его по базе данных. Если там имеется такой идентификатор соединения, посетителю будет позволено перейти к странице разделов узла администрирования (рис. 8-25) без повторного ввода пароля.

Вот как выглядит исходный текст функции Trudogolik::PRINT_TEMPLATE_COOKIE, предназначенной для сохранения значения Cookie и создания страницы на базе шаблона:

sub PRINT_TEMPLATE_COOKIE($$$)
{
  (my $template_path, my $cookie_name, my $cookie_value) = @_;

  print "Content-Type: text/html\n";
  print "Pragma: no-cache\n";
  print "Cache-Control: no-cache\n";
  print "Expires: Thu Jan  1 01:01:01 1970\n";
  print "Charset: windows-1251\n";
  print "Set-Cookie: ".$cookie_name."=".$cookie_value."\n\n";
  my $template = HTML::Template->new(filename => $template_path);
  print $template->output;
}

Чтобы установить Cookie, функция Trudogolik::PRINT_TEMPLATE_COOKIE создает заголовок HTTP с именем Set-Cookie. Кроме того, наша функция отменяет кэширование создаваемой страницы.

Последующие проверки идентификатора и пароля

Теперь мы рассмотрим случай, когда таблица managers уже содержит одну или несколько записей, но посетитель вводит идентификатор и пароль на странице аутентификации (рис. 8-24).

В этом случае программа проверяет введенный идентификатор и пароль по таблице managers:

$sql="select login,password from managers where login = ? and password = ?";
$sth = Trudogolik::DB_SQL_PREPARE($dbh, $sql);
Trudogolik::DB_SQL_BIND($sth, 1, $login, SQL_VARCHAR);
Trudogolik::DB_SQL_BIND($sth, 2, $password, SQL_VARCHAR);
Trudogolik::DB_SQL_EXECUTE($sth);
@logins = Trudogolik::DB_SQL_FETCHROW_ARRAY($sth);

Если идентификатор и пароль были введены правильно, программа создает идентификатор подключения (вызывая функцию generate_user_id), записывает этот идентификатор в базу данных и в Cookie, а затем загружает страницу разделов администрирования (рис. 8-25):

if(@logins != 0)
{
  my $user_id=generate_user_id($login);
   
  $sql="update managers set user_id = ? where login = ? and password = ?";
  $sth = Trudogolik::DB_SQL_PREPARE($dbh, $sql);
  Trudogolik::DB_SQL_BIND($sth, 1, $user_id, SQL_VARCHAR);
  Trudogolik::DB_SQL_BIND($sth, 2, $login, SQL_VARCHAR);
  Trudogolik::DB_SQL_BIND($sth, 3, $password, SQL_VARCHAR);
  Trudogolik::DB_SQL_EXECUTE($sth);
   
  Trudogolik::PRINT_TEMPLATE_COOKIE($template_path.'index.htm', "trudogolik_ru_user_id", $user_id);
}

В противном случае посетитель вновь оказывается на странице ввода идентификатор и пароля (рис. 8-24):

else
{
  Trudogolik::PRINT_TEMPLATE($template_path.'denied.htm');
}

Перед тем как завершить свою работу, программа login.pl закрывает соединение с базой данных:

Trudogolik::DB_CLOSE($dbh);

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

Редактирование словарей

Несколько разделов узла администрирования виртуального кадрового агентства Трудоголик.Ру предназначены для редактирования словарной информации:

·         список названий городов;

·         список условий работы;

·         названия разделов каталога профессий;

·         названия профессий;

·         список уровней образования

Пользовательский интерфейс

Вся словарная информация хранится в таблицах базы данных, имеющих одинаковую структуру. Словарные таблицы содержат поля идентификатора id и названия title. Это сильно упрощает создание пользовательского интерфейса и программ обслуживания словарей.

На рис. 8-26 мы показали страницу, предназначенную для редактирования списка городов.

Рис. 8-26. Редактирование списка городов

При помощи кнопки Добавить и расположенного слева от него текстового поля администратор может добавить в список новое название города.

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

В верхней части страницы находятся ссылки Разделы и Администрирвоание. Первая из них отправит пользователя на входную страницу узла администрирования (рис. 8-23), а вторая — на страницу списка разделов администрирования (рис. 8-25).

Страница, предназначенная для редактирования списка с названиями условий работы, показана на рис. 8-27.

Рис. 8-27. Редактирование списка условий работы

Как видите, она имеет тот же пользовательский интерфейс, что и страница редактирования списка  городов, показанная на рис 8-26.

Редактирование названий разделов каталога профессий выполняется на странице, показанной на рис. 8-28.

Рис. 8-28. Редактирование разделов каталога профессий

Чтобы изменить список профессий, воспользуйтесь страницей, показанной на рис. 8-29.

Рис. 8-29. Редактирование списка профессий

И, наконец, на рис. 8-30 мы показали страницу, на которой администратор может отредактировать список уровней образования.

Рис. 8-30. Редактирование списка уровней образования

Программная реализация

Так как структура всех словарных таблиц нашей базы данных одинаковая, мы ограничимся рассмотрением программной реализации только одной страницы, предназначенной для редактирования названий городов (рис. 8-26). Остальные программы Вы изучите самостоятельно.

Программа просмотра и редактирования списка городов

Итак, щелкнув на странице разделов администрирования ссылку Города (рис. 8-25), Вы тем самым запустите на выполнение программу city.pl, исходный текст которой приведен в листинге 8-24.

Листинг 8-24 Вы найдете в файле chap08\Admin123Trudogolik\cgi\city.pl на прилагаемом к книге компакт-диске.

Начальный фрагмент этой программы не имеет никаких особенностей:

#!/usr/bin/perl -w
use CGI qw(:all);
use HTML::Template;
use DBI qw(:sql_types);
use strict;
require 'trudogolik.pl';

Здесь мы перечисляем необходимые на модули, а также подключаем пакет trudogolik.pl (листинг 8-23).

В программе объявлена переменная, хранящая признак попытки добавления в базу данных названия города, которое там уже есть:

my $duplicate_name;

Первое, что делает программа city.pl, получив управление, это извлечение идентификатора соединения из Cookie и открытие базы данных:

my $user_id_cookie=cookie('trudogolik_ru_user_id');
my $dbh = Trudogolik::DB_OPEN();

Если администратор успешно подключился к административному Web-приложению, указав правильный идентификатор и пароль, то в переменную $user_id_cookie будет записано значение идентификатора соединения, созданного на втором этапе аутентификации программой login.pl. Напомним, что этот идентификатор хранится не только в Cookie, но и в записи таблицы managers данного посетителя.

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

if(Trudogolik::USER_LOGGED($dbh, $user_id_cookie))
{
  . . .
}

Эта задача решается при помощи функции Trudogolik::USER_LOGGED, исходный текст которой приведен ниже:

sub USER_LOGGED($$)
{
  (my $dbh, my $user_id_cookie) = @_;

  my $sql="select id from managers where user_id = ?";
  my $sth = Trudogolik::DB_SQL_PREPARE($dbh, $sql);
  Trudogolik::DB_SQL_BIND($sth, 1, $user_id_cookie, SQL_VARCHAR);
  Trudogolik::DB_SQL_EXECUTE($sth);
  my @logins = Trudogolik::DB_SQL_FETCHROW_ARRAY($sth);

  if(!@logins)
  {
    Trudogolik::PRINT_TEMPLATE(
$Trudogolik::template_path_ru.'denied.htm');
    return 0;
  } 
  return 1;
}

Данная функция ищет идентификатор соединения в таблице managers. Если его там нет, функция отправляет посетителя на страницу ввода идентификатора и пароля (рис. 8-24), а если есть — возвращает значение 1.

При удачной проверке идентификатора соединения программа city.pl продолжает свою работу. Прежде всего, она получает значения параметров mode и title:

my $mode=param('mode');
my $title = Trudogolik::ASCII_TO_HTML(param('title'));

Добавляемое название города, введенное через параметр title, сохраняется в одноименной переменной.

Если администратор добавляет новый город, параметр mode имеет значение add. В этом случае, а также, если переменная $title с названием нового города не пустая, программа добавляет это название в список городов:

if($mode eq 'add' and $title ne '')
{
  my $sql="select id from city where title = ?";
  my $sth = Trudogolik::DB_SQL_PREPARE($dbh, $sql);
  Trudogolik::DB_SQL_BIND($sth, 1, $title, SQL_VARCHAR);
  Trudogolik::DB_SQL_EXECUTE($sth);
  my @rs = Trudogolik::DB_SQL_FETCHROW_ARRAY($sth);
   
  if(!@rs)
  {
    my $sql="INSERT city (title) VALUES (?)";
    my $sth = Trudogolik::DB_SQL_PREPARE($dbh, $sql);
    Trudogolik::DB_SQL_BIND($sth, 1, $title, SQL_VARCHAR);
    Trudogolik::DB_SQL_EXECUTE($sth);
    $duplicate_name=0;
  }
  else
  {
    $duplicate_name=1;
  }
}

Добавление выполняется в два этапа. Вначале программа проверяет, нет ли уже такого названия в таблице городов city. Если такое название там уже есть, в переменную $duplicate_name записывается значение 1. Если же название новое, оно добавляется в таблицу city, после чего в переменную $duplicate_name записывается нулевое значение.

В режиме обновления списка городов значение параметра mode равно update. В этом случае программа удаляет и обновляет отмеченные города:

elsif($mode eq 'update')
{
  my @chkboxes=param();
  for(my $index=0; $index<@chkboxes; $index++)
  {
    $_="$chkboxes[$index]";
 
    # Удаляем отмеченные города
    if(s/del//)
    {
      my $sql="DELETE FROM city WHERE id=$_";
      Trudogolik::DB_SQL_DO($dbh, $sql);
    } 

    # Переименовываем отмеченные города
    elsif(s/upd//)
    {
      my $txt=param('text'.$_);
      my $id=$_;
   
      my $sql="select id from city where title = ?";
      my $sth = Trudogolik::DB_SQL_PREPARE($dbh, $sql);
      Trudogolik::DB_SQL_BIND($sth, 1, $txt, SQL_VARCHAR);
      Trudogolik::DB_SQL_EXECUTE($sth);
      my @rs = Trudogolik::DB_SQL_FETCHROW_ARRAY($sth);
   
      # Если уже есть такой, не переименовываем
      if(!@rs)
      {
        my $sql="UPDATE city SET title=? WHERE id=?";
        my $sth = Trudogolik::DB_SQL_PREPARE($dbh, $sql);
        Trudogolik::DB_SQL_BIND($sth, 1, $txt, SQL_VARCHAR);
        Trudogolik::DB_SQL_BIND($sth, 2, $id, SQL_INTEGER);
        Trudogolik::DB_SQL_EXECUTE($sth);
      }
    } 
  }
}

Обработка списка городов выполняется в цикле. Получив список всех параметров при помощи функции param, программа находит в них отмеченные флажки Удалить и Изменить. Перед тем как переименовать город, программа проверяет, не получится ли в результате переименования дублирование названий городов. Если посетитель выбрал новое название города, которое уже есть в базе данных, переименование не выполняется.

После выполнения всех необходимых операций со списком городов программа отображает текущее содержимое таблицы city в цикле:

my $template = HTML::Template->new(filename => 'template/ru/city/index.htm');

my $sth = Trudogolik::DB_SQL_PREPARE($dbh, "SELECT id,title FROM city ORDER BY title");
Trudogolik::DB_SQL_EXECUTE($sth);

my $i=0;
my $row_class;
@::loop = ();
while((my $id, my $title) = Trudogolik::DB_SQL_FETCHROW_ARRAY($sth))
{
  if($i % 2)
  {
    $row_class = 'even';
  }
  else
  {
    $row_class = 'odd';
  }
  my %row = (TITLE => $title, ID => $id, ROW_CLASS => $row_class);
  push(@::loop, \%row);
  $i++;
}

После завершения цикла программа завершает заполнение шаблона, выводит его и закрывает соединение с базой данных:

$template->param(DUPLICATE_NAME => $duplicate_name);
$template->param(ADDED_CITY => $title);
 
$template->param(THIS_LOOP => \@::loop);
print header (-charset=>'windows-1251'); 
print $template->output;
Trudogolik::DB_CLOSE($dbh);

Шаблон страницы списка городов

В листинге 8-25 мы привели исходный текст шаблона, применяющегося для формирования страницы редактирования списка городов, показанной на рис. 8-26.

Листинг 8-25 Вы найдете в файле chap08\Admin123Trudogolik\cgi\template\ru\city\index.htm на прилагаемом к книге компакт-диске.

В верхней части шаблона имеется форма, предназначенная для добавления в список нового названия города:

<form action="http://test.trudogolik.ru/cgiprg/city.pl" method="post">
<TABLE BORDER=0 CELLSPACING=1 CELLPADDING=1>
<TR><TD><INPUT type="text" id="title" name="title" value="<TMPL_VAR NAME=ADDED_CITY>"></TD>
<TD><INPUT id=button1 name=button1 type=submit value="
Добавить"></TD></TR>
<TMPL_IF NAME="DUPLICATE_NAME">
<TR><TD colspan="2"><font color="red"><b><TMPL_VAR NAME=ADDED_CITY></b>
уже есть в базе данных!</font></TD></TR>
</TMPL_IF>
</TABLE>
<INPUT type="hidden" id=text1 name=mode value="add">
</form>

В форме предусмотрено текстовое поле типа <INPUT> с именем title, а также кнопка Добавить. Обратите внимание, что содержимое этого текстового поля можно инициализировать при помощи шаблона ADDED_CITY.

Скрытое поле с именем mode имеет значение add. Это поле сообщает программе city.pl о необходимости добавить город, название которого передается ей через параметр title.

Для обработки ошибки, связанной с попыткой добавления уже существующего названия города, мы предусмотрели условный шаблон DUPLICATE_NAME. Формируемое им сообщение об ошибке показано на рис. 8-31.

Рис. 8-31. Сообщение об ошибке при добавлении названия города Москва

Вторая форма, размещенная в нашем шаблоне, предназначена для изменения и удаления названий городов:

<form action="http://test.trudogolik.ru/cgiprg/city.pl" id=form1 name=form1 method="post">

<table border="0" cellspacing="1" cellpadding="3">
<tr bgcolor="#B9D7CC">
<td><b>ID</b></td>
<td><b>
Название</b></td>
<td colspan="3"><b>
Операция</b></td>
</tr>

<TMPL_LOOP NAME="THIS_LOOP">
<tr class="<TMPL_VAR NAME=ROW_CLASS>">
<td><TMPL_VAR NAME=ID></td>
<td><TMPL_VAR NAME=TITLE></td>
<td><INPUT type="text" id=text<TMPL_VAR NAME=ID> name=text<TMPL_VAR NAME=ID> value="<TMPL_VAR NAME=TITLE>">&nbsp;<INPUT type="checkbox" id=upd<TMPL_VAR NAME=ID> name=upd<TMPL_VAR NAME=ID>>
Изменить</td>
<td><INPUT type="checkbox" id=del<TMPL_VAR NAME=ID> name=del<TMPL_VAR NAME=ID>>
Удалить</td>
</tr>
</TMPL_LOOP>

<tr bgcolor="#B9D7CC"><td colspan="7">&nbsp;</td></tr>
<tr><td colspan="7">&nbsp;</td></tr>
<tr><td colspan="4"><INPUT type="submit" value="
Обновить" id=button1 name=button1></td><tr>
</table>
<INPUT type="hidden" id=text1 name=mode value="update">
</form>

Внутри этой формы имеется циклический шаблон THIS_LOOP, отвечающий за формирование списка названий городов, имеющихся в базе данных. Обратите внимание на имена флажков, предназначенных для изменения и удаления названий городов. Эти имена образуются из префиксов upd и del, вслед за которыми идет цифровой идентификатор записи соответствующего города. Пользуясь префиксами, программа city.pl может выделить имена городов, предназначенные для изменения или удаления.

Шаблон ROW_CLASS используется для чересстрочного раскрашивания таблицы. При ее выводе программа чередует два различных стиля оформления для четных и нечетных строк.

В форме также предусмотрено скрытое поле mode со значением update, сообщающее программе city.pl о том, что нужно обновить список городов, анализируя состояние флажков.

Редактирование списка администраторов

Этот раздел предназначен для редактирования содержимого таблицы managers, в которой записана учетная информация администраторов Web-узла виртуального кадрового агентства Трудоголик.Ру.

Пользовательский интерфейс

На рис. 8-32 мы показали внешний вид соответствующей страницы. Она появится в окне браузера посетителя, щелкнувшего ссылку Администраторы на странице разделов администрирования (рис. 8-25).

Рис. 8-32. Редактирование списка администраторов (адреса IP регистрации закрашены специально)

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

Во-первых, форма, расположенная в верхней части страницы, содержит два поля — для ввода идентификатора и пароля. Во-вторых, в таблице администраторов, помимо идентификатора и пароля, отображается информация об адресе IP, использованном при регистрации, а также дата регистрации.

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

Программная реализация

Исходный текст программы managers.pl, отвечающей за редактирование учетной информации администраторов представлен в листинге 8-26.

Листинг 8-26 Вы найдете в файле chap08\Admin123Trudogolik\cgi\managers.pl на прилагаемом к книге компакт-диске.

Получив управление, эта программа, так же как и только что рассмотренная программа city.pl, проверяет, находится ли в Cookie правильный идентификатор соединения и открывает базу данных:

my $duplicate_name;
my $user_id_cookie=cookie('trudogolik_ru_user_id');
my $dbh = Trudogolik::DB_OPEN();

Если идентификатор соединения правильный, программа продолжает свою работу, а если нет — выводит в окно браузера форму для ввода пароля и идентификатора:

if(Trudogolik::USER_LOGGED($dbh, $user_id_cookie))
{
  . . .
}

Продолжив работу, программа получает параметры запустившей ее формы, а также адрес IP рабочей станции посетителя:

my $mode=param('mode');
my $host=remote_host();
my $login=param('login');
my $password=param('password');

Если выполняется добавление нового администратора, значение параметра mode равно add. В этом случае программа проверяет, нет ли уже в базе данных администратора с идентификатором, таким же, что и добавляемый идентификатор. Если такого идентификатора нет, в таблице managers появляется учетная запись нового администратора:

if($mode eq 'add' and $login ne '' and $password ne '')
{
  my $sql="select id from managers where login = ?";
  my $sth = Trudogolik::DB_SQL_PREPARE($dbh, $sql);
  Trudogolik::DB_SQL_BIND($sth, 1, $login, SQL_VARCHAR);
  Trudogolik::DB_SQL_EXECUTE($sth);
  my @rs = Trudogolik::DB_SQL_FETCHROW_ARRAY($sth);
   
  if(!@rs)
  {
    my $sql="INSERT managers (login, password, rights, reg_ip, reg_date, comment) VALUES (?, ?, '128', ?, NOW(),'[Added by administrator]')";
    my $sth = Trudogolik::DB_SQL_PREPARE($dbh, $sql);
    Trudogolik::DB_SQL_BIND($sth, 1, $login, SQL_VARCHAR);
    Trudogolik::DB_SQL_BIND($sth, 2, $password, SQL_VARCHAR);
    Trudogolik::DB_SQL_BIND($sth, 3, $host, SQL_VARCHAR);
    Trudogolik::DB_SQL_EXECUTE($sth);
    $duplicate_name=0;
  }
  else
  {
    $duplicate_name=1;
  }
}

Учетная запись нового администратора содержит идентификатор, пароль, права доступа, адрес IP при регистрации, дату  регистрации, а также комментарий в виде строки [Added by administrator] («добавлено администратором»).

Удаление и обновление учетных записей администраторов выполняется аналогично удалению и обновлению списка городов:

elsif($mode eq 'update')
{
  my @chkboxes=param();
  for(my $index=0; $index<@chkboxes; $index++)
  {
    $_="$chkboxes[$index]";
 
    #
Удаляем отмеченные учетные записи
    if(s/del//)
    {
      my $sql="DELETE FROM managers WHERE id=$_";
      Trudogolik::DB_SQL_DO($dbh, $sql);
    } 

    #
Изменяем пароли
    elsif(s/upd//)
    {
      my $txt=param('text'.$_);
      my $id=$_;
      my $sql="UPDATE managers SET password=? WHERE id=?";
      my $sth = Trudogolik::DB_SQL_PREPARE($dbh, $sql);
      Trudogolik::DB_SQL_BIND($sth, 1, $txt, SQL_VARCHAR);
      Trudogolik::DB_SQL_BIND($sth, 2, $id, SQL_INTEGER);
      Trudogolik::DB_SQL_EXECUTE($sth);
    } 
  }
}

На следующем этапе программа отображает все записи, имеющиеся в таблице managers. Для этого она выдает команду SELECT и заполняет таблицу, пользуясь циклическим шаблоном:

my $template = HTML::Template->new(filename => 'template/ru/managers/index.htm');

my $sth = Trudogolik::DB_SQL_PREPARE($dbh, "SELECT id,login,password,reg_ip,reg_date, comment FROM managers ORDER BY login");
Trudogolik::DB_SQL_EXECUTE($sth);

my $i=0;
my $row_class;
@::loop = ();
while((my $id, my $login, my $password, my $reg_ip, my $reg_date, my $comment) = Trudogolik::DB_SQL_FETCHROW_ARRAY($sth))
{
  if($i % 2) { $row_class = 'even'; } else { $row_class = 'odd'; }
  if($reg_ip eq '') { $reg_ip='[not available]'; }
  if($reg_date eq '')
  {
     $reg_date='[not available]';
  }
  else
  {
    (my $yyyy, my $mm, my $dd, my $hh, my $min, my $ss) = $reg_date =~ /(\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+)/;
    $reg_date="$dd.$mm.$yyyy $hh:$min:$ss";
  }
 
  my %row = (ID => $id, LOGIN => $login, PASSWORD => $password, REG_IP => $reg_ip, REG_DATE => $reg_date, ROW_CLASS => $row_class);
  push(@::loop, \%row);
  $i++;
}

$template->param(DUPLICATE_NAME => $duplicate_name);
$template->param(ADDED_CITY => $login);
 
$template->param(THIS_LOOP => \@::loop);
print header (-charset=>'windows-1251'); 
print $template->output;

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

Перед завершением своей работы программа закрывает соединение с базой данных:

Trudogolik::DB_CLOSE($dbh);

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

Листинг 8-27 Вы найдете в файле chap08\Admin123Trudogolik\cgi\template\ru\managers\index.htm на прилагаемом к книге компакт-диске.

В верхней части шаблона определена форма ввода новой учетной записи:

<form action="http://test.trudogolik.ru/cgiprg/managers.pl" method="post">
<TABLE BORDER=0 CELLSPACING=1 CELLPADDING=1>
<tr><td>Идентификатор</td><TD><INPUT type="text" id="login" name="login"></TD>
<TD rowspan="2" valign="top"><INPUT id=button1 name=button1 type=submit value="Добавить"></TD></tr>
<tr><td>Пароль</td><TD><INPUT type="password" id="password" name="password"></TD></tr>
<TMPL_IF NAME="DUPLICATE_NAME">
<TR><TD colspan="2"><font color="red"><b><TMPL_VAR NAME=ADDED_CITY></b> уже есть в базе данных!</font></TD></TR>
</TMPL_IF>
</TABLE>
<INPUT type="hidden" id=text1 name=mode value="add">
</form>

Помимо двух текстовых полей с именами login и password, здесь имеется условный шаблон DUPLICATE_NAME и скрытое поле mode. Шаблон используется для обработки ошибок, связанных с попытками добавления уже существующих идентификаторов, а скрытое поле со значением add используется программой managers.pl для того чтобы перейти в режим добавления новой учетной записи.

Вторая форма, предусмотренная в шаблоне, предназначена для отображения и редактирования списка администраторов:

<form action="http://test.trudogolik.ru/cgiprg/managers.pl" id=form1 name=form1 method="post">

<table border="0" cellspacing="1" cellpadding="3">
<tr bgcolor="#B9D7CC">
<td><b>ID</b></td>
<td><b>Идентификатор</b></td>
<td><b>Пароль</b></td>
<td><b>IP регистраци</b></td>
<td><b>Дата регистрации</b></td>
<td colspan="2"><b>Операция</b></td>
</tr>

<TMPL_LOOP NAME="THIS_LOOP">
<tr class="<TMPL_VAR NAME=ROW_CLASS>">
<td><TMPL_VAR NAME=ID></td>
<td><TMPL_VAR NAME=LOGIN></td>
<td><INPUT type="password" id="pwd<TMPL_VAR NAME=ID>" name="pwd<TMPL_VAR NAME=ID>" value="<TMPL_VAR NAME=PASSWORD>" SIZE="10"></td>
<td><TMPL_VAR NAME=REG_IP></td>
<td><TMPL_VAR NAME=REG_DATE></td>
<td><INPUT type="checkbox" id=upd<TMPL_VAR NAME=ID> name=upd<TMPL_VAR NAME=ID>>Изменить</td>
<td><INPUT type="checkbox" id=del<TMPL_VAR NAME=ID> name=del<TMPL_VAR NAME=ID>>Удалить</td>
</tr>
</TMPL_LOOP>

<tr bgcolor="#B9D7CC"><td colspan="7">&nbsp;</td></tr>
<tr><td colspan="7">&nbsp;</td></tr>
<tr><td colspan="7"><INPUT type="submit" value="Обновить" id=button1 name=button1></td><tr>
</table>
<INPUT type="hidden" id=text1 name=mode value="update">
</form>

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

Добавление, поиск и удаление вакансий

Создавая Web-узел администрирования для виртуального агентства Трудоголик.Ру, мы предусмотрели разделы, с помощью которых можно добавлять, просматривать и удалять вакансии, а также резюме. Функции добавления и просмотра очень похожи на аналогичные функции Web-узла http://www.trudogolik.ru, подробно описанные в начале этой главы. Поэтому здесь мы  коснемся главным образом только функций удаления вакансий, доступных только администраторам и не предусмотренных в разделах узла «общего пользования».

Страница добавления новых вакансий, расположенная на Web-узле администрирования, показана на рис. 8-33.

Рис. 8-33. Форма для добавления сведений о вакансии

Как видите, она очень похожа на страницу добавления вакансии узла http://www.trudogolik.ru, показанную на рис. 8-10 и 8-11. Программы CGI и шаблоны, обслуживающие эту страницу, мы оставляем Вам на самостоятельное изучение.

На рис. 8-34 мы показали страницу, предназначенную для поиска вакансий.

Рис. 8-34. Форма для поиска вакансии

Эта страница аналогична странице поиска вакансий узла http://www.trudogolik.ru, о которой мы уже рассказывали в начале этой главы.

Что же касается результатов поиска (рис. 8-35), то здесь имеется одно важное отличие от аналогичной страницы узла общего доступа, показанной на рис. 8-19.

Рис. 8-35. Результаты поиска вакансии

Обратите внимание, что около каждой вакансии имеется кнопка Удалить, с помощью которой администратор может удалить сведения о вакансии из базы данных. Разумеется, функция удаления вакансий (и резюме) должна быть доступна только администраторам.

Удаление вакансий выполняется программой vacation_delete.pl, исходный текст которой приведен в листинге 8-28.

Листинг 8-28 Вы найдете в файле chap08\Admin123Trudogolik\cgi\vacation_delete.pl на прилагаемом к книге компакт-диске.

Получив управление, программа открывает соединение с базой данных и проверяет идентификатор соединения, хранящийся в Cookie:

my $dbh = Trudogolik::DB_OPEN();

my  $user_id_cookie=cookie('trudogolik_ru_user_id');
if(Trudogolik::USER_LOGGED($dbh, $user_id_cookie))
{
  my $id=param('ID');
  my $sql="delete from vacations where id = ?";
  my $sth = Trudogolik::DB_SQL_PREPARE($dbh, $sql);
  Trudogolik::DB_SQL_BIND($sth, 1, $id, SQL_INTEGER);
  Trudogolik::DB_SQL_EXECUTE($sth);

  my $template = HTML::Template->new(filename => 'template/ru/index.htm');
  print header (-charset=>'windows-1251'); 
  print $template->output;
}

Trudogolik::DB_CLOSE($dbh);

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

Работа с резюме

Работа администратора с резюме выполняется аналогично работе с вакансиями.

На странице добавления резюме (рис. 8-36) администратор может добавить в базу данных новое резюме.

Рис. 8-36. Форма для добавления сведений о резюме

С помощью страницы поиска (рис. 8-37) администратор может найти нужные ему резюме.

Рис. 8-37. Форма для поиска резюме

Страницы результатов поиска резюме содержат кнопку Удалить около каждой найденной записи. С помощью этой кнопки администратор может удалить любое резюме из базы данных виртуального агентства.

Обработка ошибок

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

При возникновении таких ошибок в окно администратора загружается страница, внешний вид которой показан на рис. 8-38.

Рис. 8-38. Обнаружена ошибка

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

Программа email_error.pl, исходный текст которой приведен в листинге 8-29, отвечает за отправку сообщения по электронной почте.

Листинг 8-29 Вы найдете в файле chap08\Admin123Trudogolik\cgi\email_error.pl на прилагаемом к книге компакт-диске.

Эта программа формирует сообщение об ошибке, принимая параметры ERR_FILE, ERR_LINE и ERR_MSG:

my $msg="Сервер ADMIN123.TRUDOGOLIK.RU\nОшибка Web-приложения\n".
 "-----------------------------------------------\n\n".
 "
Файл ".param("ERR_FILE").", cтрока
 ".param("ERR_LINE")."\n\n".param("ERR_MSG");

Первый из этих параметров ссылается на файл, в котором произошла ошибка, второй — на номер строки файла, вызвавшей ошибку, и, наконец, третий — текст сообщения об ошибке.

Созданное таким способом сообщение отправляется по почте уже знакомой Вам функцией send_mail:

my $rc=send_mail('@'.$relay.':alexandre@frolov.pp.ru', 
  'web@Trudogolik.ru', 'Error', win2koi($msg));

print header (-charset=>'windows-1251', -expires=>'-1m');
if($rc != 0)
{
  my $template = HTML::Template->new(filename => $Trudogolik::template_path_ru.'mail_err.htm');
  print $template->output;
}
else
{
  my $template = HTML::Template->new(filename => $Trudogolik::template_path_ru.'mail_ok.htm');
  print $template->output;
}

На рис.  8-39 мы показали, в каком виде будет получено это сообщение.

Рис. 8-39. Сообщение об ошибке, присланное по электронной почте

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

[Назад] [Содержание] [Дальше]