Первый сайт на PHP

         

Авторизация на основе cookiesПросто форма...



Рисунок 8.6. Авторизация на основе cookies. Просто форма...


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

<?php

foreach (file("passw/passwr") as $k)

{

if (substr($k, 0, -2)=="$login $pass")

{

$rez=l;

И вот - сама установка cookie под именем auth. Ему устанавливается "время жизни" - 3600 с, т. е. час.

SetCookie("auth","$login $pass",time()+3 600);

} } ?>

Дальше должен находиться сценарий, который в зависимости от исхода авторизации - т. е. значения переменной $rez - выводит различную информацию. Например, можно отослать посетителя назад на исходную страницу командой Header ("Location..."), как в примере предыдущего раздела этой главы.

На всех остальных страницах "защищенной зоны" должен находиться сценарий проверки содержимого переменной auth (т. е. той, чье имя совпадает с именем поставленного cookie) - т. е. точно такой же скрипт, как и на той, где cookie ставился, только строка проверки должна выглядеть как

if (substr($k, 0, -2)=="$auth"),

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

Желательно также сделать страницу "выхода", включив в нее команду установку cookie без параметров - SetCookie ("имя cookie").

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


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

Стоит сказать, что использовать имя cookie как переменную можно только в том случае, если в файле настройки РНР - php.ini - есть параметр register_globals. Там, где этого параметра нет (например, в РНР версии 4.2 и выше он по умолчанию неактивен), работать с cookies как с обычными переменными не получится. Информацию, помещенную в них, придется получать из массива с названием SHTTPCOOKIEVARS (создается автоматически при обнаружении cookies от данного сайта), используя имя нужного cookie в качестве индекса:



<?php

foreach (file("passw/passwr") as $k)

{

if (substr($k, 0, -2)==$HTTP_COOKIE_VARS['auth'])

{ $rez=l;

}

}

...

В РНР версии 4.1 и выше вместо $HTTP_COOKIE_VARS можно использовать массив $_СООК1Е.

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


АВТОРИЗАЦИЯ С ПОМОЩЬЮ COOKIES



АВТОРИЗАЦИЯ С ПОМОЩЬЮ COOKIES

Cookie - это файл в специальном формате, который присылается сервером браузеру посетителя сайта, расположенном на этом сервере (Рисунок 8.5). Браузер, если он поддерживает cookie (и эта поддержка в нем не отключена), помещает его в особое место и впоследствии отправляет назад на сервер при поступлении от него запроса. Иными словами, cookie позволяет серверу хранить свою информацию на компьютерах посетителей и считывать ее оттуда при необходимости. (Современные стандарты безопасности предусматривают, что каждый сервер (Вернее, узел с собственным именем (любого уровня)). может получить назад только те cookie, которые были установлены лично им, так что даже о том, какие сайты еще посещал посетитель, с помощью cookie узнать нельзя.)



АВТОРИЗАЦИЯ С ПОМОЩЬЮ СЕССИЙ



АВТОРИЗАЦИЯ С ПОМОЩЬЮ СЕССИЙ

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

В связи с этим возникает вопрос: а нельзя ли как-нибудь избежать необходимости каждый раз осуществлять проверку логина и пароля посетителя? Чтобы, единожды авторизовав посетителя, впоследствии предоставлять ему доступ на страницы защищенной зоны без каких-либо проверок? Именно так, кстати, действует защита на основе средств web-сервера - файлов .htaccess, описанная в первом разделе главы. Но можно ли сделать то же самое средствами РНР? Да и вообще использовать cookie для хранения паролей не очень желательно: его содержимое может узнать любой, кто воспользуется компьютером, на котором этот cookie сохранен (многие браузеры хранят cookie в предназначенной для них папке, даже если "время жизни" cookie истекло и он больше не принимается сервером).

Напрашивается первое предложение: а почему бы, например, после успешной авторизации не отправить посетителю cookie с какой-либо пометкой (например, устанавливать в 1 значение переменной в этом cookie), а впоследствии проверять не наличие записанных в cookie логина и пароля в файле паролей или базе данных, а присутствие в cookie этой самой пометки, одинаковой для всех, прошедших авторизацию? Или даже сделать разные типы пометок и в зависимости от типа предоставлять посетителю разные возможности на сайте?

Сделать-то так можно, да вот устойчивость такой системы авторизации к взлому будет не особо великой. Злоумышленнику будет достаточно узнать, что за пометку помещает сценарий авторизации в cookie, чтобы получить к защищенной зоне полный доступ - просто вручную создав такой cookie. (А если при проверке "пометки" использовался не элемент массива $HTTP_COOKIE_VARS, а одноименная переменная, то и просто подставив ее значение в адресной строке при заходе на страницу с такой проверкой: например, page . php?auth=1.) Кроме того, просмотреть значение cookie на компьютере посетителя и узнать, какие его имя и значение являются "пометкой", тоже не так трудно.

Но самое главное - посетители нередко отключают использование cookie при своих путешествиях по Интернету. При отключенных cookie описанная выше система авторизации на их основе работать не будет.

Как же быть?

Следует использовать весьма интересный механизм сессий, появившийся в 4-й версии РНР.



АВТОРИЗАЦИЯ С ПОМОЩЬЮ ЗАГОЛОВКА



АВТОРИЗАЦИЯ С ПОМОЩЬЮ ЗАГОЛОВКА

В РНР есть команда Header - она позволяет отправить браузеру посетителя, запросившему страницу с содержащим эту команду сценарием, определенную служебную информацию - так называемый "заголовок". Существует довольно много вариантов заголовков (например, заголовок "Location: http://адрес" приведет кперенаправ-лению на указанный URL; то же самое, что и при использовании мета-тэга http-equiv с параметром "Refresh"), однако для авторизации нам потребуется заголовок "WWW-Authenticate".

Примечание:

Заголовок - это данные, передаваемые браузеру до передачи самой web-страницы, сообщающие ему некоторые параметры передаваемого файла ипи определенные команды. Список всех возможных заголовков, которые обязаны поддерживать современные браузеры, можно найти в спецификациях протокола HTTP - они есть, например, на сайте http://www.w3.org. PHP-команда Header выполняет всего одно действие - она просто передает то, что указано в ее параметре, в браузер, запросивший страницу, на которой она находится, в качестве заголовка.

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

В том случае, если браузер получает заголовок "WWW-Authenticate", то он выдает посетителю стандартное окно для ввода логина и пароля, которое вы наверняка много раз видели (Рисунок 8.2). Как только посетитель нажимает кнопку Ok этого окна, браузер вновь заходит на ту страницу, с которой этот заголовок был ему послан, но на этот раз уже передает сценарию на этой странице две переменные - то, что было введено в поля ввода логина и пароля. Web-сервер дает этим переменным имена $PHP_AUTH_USER и $PHP_AUTH_PW, и их становится можно использовать в остальных сценариях на странице как любые другие переменные - использовать в выражениях, сравнивать с каким-либо эталоном, присваивать им какие-либо другие значения, наконец.

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


Однако переменные $PHP_AUTH_USER и $PHP_AUTH_PW - не простые. Если они один раз были определены, то впоследствии они передаются всем web-страницам, которые загружаются в то же самое окно браузера, где произошла авторизация! Иными словами, если по каким-то причинам требуется проверять логин и пароль посетителя на каждой из страниц сайта (скажем, выводить разную информацию авторизованным и неавторизованным посетителям), то каждый раз запрашивать эти данные не нужно - достаточно использовать значения переменных $PHP_AUTH_USER и $PHP_AUTH_PW. Значения данных переменных теряются в случае закрытия окна браузера, в котором изначально произошла авторизация (а в другие окна они и не передаются). При выходе за пределы виртуального сервера, на котором произошла авторизация (обычно его границы совпадают с границами ак-каунта), данные переменные перестают передаваться страницам, однако при повторном входе на исходный адрес вновь становятся доступными (это обеспечение безопасности - за пределами вашего виртуального сервера логины и пароли ваших посетителей никто узнать из их браузеров не сможет).

Кстати, при использовании предыдущего способа - средствами Apache - в переменные $PHP_AUTH_USER и $PHP_AUTH_PW тоже помещаются значения логина и пароля, введенные пользователем. В принципе вы можете найти им какое-нибудь применение.

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



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

Итак, начало сценария. Обратите внимание, что для того, чтобы он сработал, до команды Header в выдаваемый документ не должно ничего выводиться: ни результат выполнения команд РНР, ни простое содержимое страницы, - так уж воспринимают web-страницы браузеры. В частности, данный сценарий должен располагаться в самом начале страницы, и символы <?php должны быть на ней самыми первыми, перед ними не должно быть даже пробела.

<?php

Поскольку после выдачи окна авторизации браузер вновь вызывает web-страницу, передавая ей авторизационные данные, то можно проверить их еще до отправки браузеру "заголовка WWW-Authenticate. В самом деле - если окно авторизации не выводилось вообще, то переменные $PHP_AUTH_USER $PHP_AUTH_PW будут пустыми (вернее, вообще не определены), а если выводилось - то в них окажется информация, введенная посетителем (т. е. логин и пароль).

Наиболее простым вариантом будет указание логина и пароля в тексте самой программы авторизации - ведь все равно код на РНР, размещающийся на странице, посетители увидеть не смогут1. В этом случае команда проверки содержимого переменных $PHP_AUTH_USER и $PHP_AUTH_PW на соответствие указанным будет выглядеть как

if (($PHP_AUTH_USER!="login")($PHP_AUTH_PW!= "parol"))

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

Вернее, смогут лишь в том случае, если данный код располагается в фаше не с тем расширением, которое указано в настройках web-cepeepa как признак страниц с программами на РНР. 70

полнится - переменные $PHP_AUTH_USER и SPHPAUTH PW в таком случае еще не будут определены.

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



Header("WWW-Authenticate: Basic
realm=\"Защищенная зона"\"");

Браузер, получив такое, выдаст посетителю окно (такое же, как на Рисунок 8.1) с запросом логина с паролем. После нажатия кнопки Ok страница будет загружена вновь и в том случае, если логин и пароль соответствовали указанным в ее тексте, будет выводиться остальной ее текст - тот, что последует за командой if, за ее закрывающей фигурной скобкой. Ну а если логин и пароль будут введены неправильно, то окно авторизации выскочит вновь - и у посетителя появится еще один шанс правильно авторизоваться. И так до тех пор, пока не будут введены правильные логин и пароль.

Однако в выдаваемом окне есть еще кнопка "Отмена"! и в том случае, если посетитель нажмет ее, то код просто начнет выполняться дальше, со следующей после Header команды. Следовательно, в этот код и нужно вставить те команды, которые сообщают посетителю, так и не сумевшему ввести правильные логин с паролем, что он не может войти в защищенную зону. (Не забудьте, что все эти команды должны находиться в пределах блока оператора if- ведь нам надо, чтобы они выполнились только в случае нажатия кнопки "Отмена"!).

Выдадим браузеру заголовок, сообщающий об отказе в авторизации (требуется некоторым браузерам, а заодно и обнуляет переменные $PHP_AUTHJJSER и $PHP_AUTH_PW):

Header ("HTTP/1.0 401 Unauthorized");

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

echo ("<р>Доступ закрыт!</р>");

(При желании вы можете сделать целую web-страницу, на которой подробно рассказать, например, как можно достать правильные логин с паролем, и вставлять ее текст сюда в случае отказа в авторизации с помощью команды include:

include ("noauth.php");

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



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

exit; }

Она полностью прекращает как вывод web-страницы посетителю, так и выполнение какого-либо кода. Вот, собственно, и все:

?>

Для наглядности - все строчки кода вместе:

<?php

if (($ PHP_AUTH_USER! = "log in") | | ($ PHP_AUTH_PW! = "parol"))

{

Header("WWW-Authenticate: Basic realm=\"Защищенная зона"\"");

Header("HTTP/1.0 401 Unauthorized");

...текст страницы, выдающейся посетителю в случае нажатия им кнопки "Отмена"...

exit;

} ?>

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

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

Допустим, файл, содержащий логины и пароли, располагается в папке passw и называется passwr, а формат его прост - запись типа "логин пароль" (через пробел) на каждой строчке (см. Рисунок 8.3). Для того, чтобы этот файл нельзя было загрузить через web-интерфейс, просто набрав его имя (и тем самым получив на экран все его содержимое), можете сделать это имя как можно более длинным и заковыристым (все равно оно фигурирует только в программном коде, т. е. из Сети его узнать будет никак нельзя), а можете просто запретить чтение данного файла из Web, соответственно установив его атрибуты, например, в 770 (в CuteFTP это делается пунктом CHMOD из контекстного меню файла, Рисунок 8.4).

Примечание:

Еще запретить чтение содержимого директорий из Web можно, указав в файле настроек web-сервера Apache (именующемся httpd.conf) в разделе описания соответствующего виртуального сервера параметр Location (например:

<Location /passw>

deny from all

</Location>

- в три строки), однако доступ к файлу настроек web-сервера есть не всегда.


АВТОРИЗАЦИЯ СРЕДСТВАМИ WEB-CEPBEPA



АВТОРИЗАЦИЯ СРЕДСТВАМИ WEB-CEPBEPA

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

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

Выглядят эти команды так. Вначале указывается название защищенной зоны - AuthName. Именно это название будет впоследствии выводиться в запросе посетителю (Рисунок 8.1).

AuthName "Private Zone"

AuthType Basic

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

ются специальные программы - такую программу вы можете взять, например, в разделе технической поддержки компании Valuehost по адресу http://support.valuehost.ru/bbs/files/69-htpasswd.exe. Запускать ее следует из командной строки в формате 69-htpasswd.exe -с имя_файла_паролей логин, а в открывшемся окне ввести пароль (используя только латинские буквы). Чтобы добавить новые логины и пароли в уже имеющийся файл, эту программу следует запускать без параметра -с.

По традиции файл с паролями посетителей принято называть .htpasswd. Обычно Apache настраивается так, что файлы с именами .htaccess и .htpasswd невозможно просмотреть через Web - при такой попытке будет выдаваться лишь сообщение о запрещении доступа. Однако выполнение такой настройки (для этого надо указать несколько параметров в httpd.conf - конфигурационном файле Apache) - целиком на совести администраторов web-сервера.


Обратите внимание, что путь к файлу паролей следует указывать абсолютный - т. е. от корневого каталога сервера с указанием всего дерева каталогов. На серверах хостинга он обычно имеет вид /pub/home/имя аккаунта/..../имя файла паролей, а на вашем локальном компьютере зависит от местоположения web-сервера и его настроек, например, может выглядеть и как f:/www/exper/cov/.htpasswd.

AuthUserFile /pub/home/exper/cov/.htpasswd require valid-user

Комментарий:

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

Итак, пожелав "запаролить" доступ к ресурсам какой-либо папки, создайте файл с именем .htaccess с вышеуказанными параметрами.

Комментарий:

Сделать это командой "Проводника" или "Нортона" вам не удастся, так как эти программы не допускают создание файлов без имени (a .htaccess они воспринимают именно как расширение без имени!), поэтому наберите соответствующий текст в какой-нибудь программе, позволяющей это совершить (например, ViewText Георгия Гуляева, http://www. altailand. ru).

Создав файл .htaccess, загрузите программу для создания файла паролей и поработайте с нею. После этого загрузите оба файла на свой сайт: .htaccess - в закрываемую папку, а файл с паролями - в соответствии с прописанным в .htaccess путем к нему (Рисунок 8.1).


Чтобы файл не "достали" из Интернета, установите его параметры вот так...



Рисунок 8.4. Чтобы файл не "достали" из Интернета, установите его параметры вот так...


Итак, начнем сценарий. Командой file считаем файл построчно в массив...

Примечание:

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

...и начнем сравнивать пару "логин-пароль" каждой строчки файла (т. е. каждый элемент массива) с той парой, что мы получили от пользователя. Массив даже нет нужды именовать - достаточно просто указать команду file в цикле обработки всех элементов массива f oreach (как упоминалось в гл. 3, этот оператор считывает каждый элемент указанного в его параметрах массива в переменную с именем, указанным после ключевого слова as, и выполняет для каждого элемента массива код, указанный в фигурных скобках).

<?php

foreach (file("passw/passwr") as $k)

{

Комментарий:

Оператор foreach будет работать только в РНР 4.0 и выше. Если вы можете использовать лишь РНРЗ, то вместо этого оператора можно использовать цикл for, указав в его параметрах величину массива:

$b=file("passw/passwr");

for ($i = 1; $i < $sizeof($b); $i++)

{

Для удобства можно записать значение очередного элемента массива в переменную:

$value=$k[$i];

Поскольку каждая строчка файла завершалась символом перевода строки (вернее, двумя символами - с ASCII-кодами 10 и 13), то его необходимо удалять перед сравнением (в введенных пользователем значениях символа перевода строки-то нет!) - это делает функция substr.

if (substr($k, 0, -2)=="$PHP_AUTH_USER $PHP_AUTH_PW") {

Примечание:

Команда substr предназначена для выделения из строки ее части. Строка (или переменная, ее содержащая) должна быть указана в первом параметре команды. Второй параметр - позиция, с которой начинается выделяемая часть (вернее, число символов, которые необходимо пропустить до начала выделения части строки), а третий -количество выделяемых символов.

Второй параметр может быть и отрицательным. В этом случае отсчет позиции начала выделяемой части будет идти не с начала, а с конца строки. Иными словами, в результате выполнения команды substr ("qwertyuiop", -3, 2) из строки "qwertyuiop" будет выделена строка io - она начинается за 3 символа от конца исходной строки и продолжается 2 символа.


Третий параметр тоже может быть отрицательным. В этом случае будет выделена строка, начинающаяся с указанной во втором параметре позиции и оканчивающаяся за столько символов до конца строки, сколько указано в третьем параметре. Иными словами, в результате выполнения команды substr ("qwertyuiop", 3, -2) из строки "qwertyuiop" будет выделена строка rtyui - она начинается после 3 символа исходной строки и заканчивается за 2 символа до ее окончания.

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

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

$rez=l;

} }

Собственно, и все - проверка завершена. Теперь в том случае, если переменная $rez не равна 1, следует выдать окно авторизации и получить от посетителя логин и пароль, а если равна - то выводить страницу дальше.

if ($rez!=l) {

Header("WWW-Authenticate: Basic realm=\"Защищенная зона"\"");

Header("HTTP/1.0 401 Unauthorized");

...текст страницы, выдающейся посетителю в случае нажатия им кнопки "Отмена"...

exit;

} ?>

Как уже говорилось, переменные SPHPAUTHUSER и $PHP_AUTH_PW передаются всем страницам, которые загружаются в то же самое окно браузера - т. е. на которые посетитель переходит. Поэтому их можно использовать для проверки его прав на выполнение того или иного действия без новых запросов. Скажем, если на какой-нибудь странице, на которую посетитель должен перейти лишь после авторизации, должна производиться загрузка файла, то перед загрузкой (в сценарии-обработчике загруженного файла, подробнее -см.) следует вновь проверить соответствие переданных этой странице переменных SPHPAUTHJJSER и $PHP_AUTH_PW какой-нибудь паре логина и пароля в файле паролей:



<?php

foreach (file("passw/passwr") as $k)

{

if (substr($k, 0, -2)=="$PHP_AUTH_USER $PHP_AUTH_PW")

{ ...команды загрузки файла...

} } ?>

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

Иными словами - один раз введенные посетителем правильные логин с паролем записываются в переменные SPHPAUTHUSER и $PHP_AUTH_PW до тех пор, пока посетитель не закроет окно браузера (и все окна, открытые по ссылкам командой "Открыть в новом окне" из окна, где произошла авторизация). На тех страницах, куда посетитель может попасть после авторизации, значения этих переменных можно проверять, сравнивая с каким-либо эталоном, например, записанными в скрытом файле логинами и паролями, и выдавать посетителю в зависимости от соответствия эталону его авторизационных данных разную информацию. Это предотвратит возможность попасть в "закрытую зону" помимо окна авторизации, через набор адреса в адресной строке или по закладке.

Например, для отправки на страницу авторизации всех, кто ее не прошел, можно воспользоваться кодом

<?php

foreach (file("passw/passwr") as $k)

77

if (substr($k, 0, -2)=="$PHP_AUTH_USER $PHP_AUTH_PW")

{ $rez=l; } }

if ($rez!=l) {

Header ("Location: auth.php"); } ?>

где auth.php - страница с кодом выдачи окна авторизации. Заголовок Location, переданный браузеру, вызывает его переход на указанную в нем страницу. Так как в данном коде используется команда Header, то она сработает без ошибок лишь в том случае, если до нее в браузер посетителя ничего не выдавалось (кроме разве что других заголовков и cookies).

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

Однако есть еще один прием регламентации доступа к страницам сайта - с использованием файлов cookies.


Cookie изнутри



Рисунок 8.5. Cookie изнутри


Примечание:

Cookie можно установить (т. е. прислать на компьютер посетителя) и средствами РНР. Для этого используется команда SetCookie, имеющая параметры: имя cookie, информация, записанная в cookie, время жизни cookie - указывается количество секунд, после истечения которых с 1 января 1970 года cookie не должен считы-ваться с компьютера посетителя (так уж измеряется время в операционных системах типа Unix - с начала "эпохи Unix" 01,01.1970), адреса сайта и каталога на нем, где cookie должен быть действителен, и указание на протокол передачи cookie (подробнее смотрите в Описании РНР). Считать cookie можно простой командой echo ("имя cookie"). Можно сказать, что, как только cookie установлен, сценариям на всех страницах того сайта, на котором он был поставлен, становится доступна переменная с тем же именем, что и у cookie, и тем содержимым, которое было записано в нем (если в файле настройки РНР установлен в on параметр register_globals).

Кроме того, значения cookie помещаются в массив $HTTP_COOKIE_VARS и доступны в его элементах, одноименных с именами cookie - SHTTP_COOKIE_VARS['umh cookie'] (если в файле настройки РНР установлен в on параметр track_vars), а в РНР версии 4.1 и выше - еще и в массив $_С00К1Е.

Для удаления cookie достаточно записать в него пустое значение (это сделает команда SetCookie с единственным параметром -именем cookie).

Для установки времени жизни cookie можно сначала узнать текущее "время Unix" командой time(), а потом просто прибавить к нему то количество секунд, которое cookie должен просуществовать после его установки на компьютер посетителя. Если время жизни для cookie не установлено, то он проживет до закрытия всех окон браузера посетителя.

Как и отправка заголовков командой Header, установка cookie должна предшествовать любому выводу в выдаваемый документ: как результатов выполнения команд РНР, так и простого содержимого страницы. Иначе возникнет ошибка.

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

Вот фрагменты сценария, в которых видна технология использования cookies. На той странице, откуда должен осуществляться вход в "защищенную зону", следует поставить простую форму для ввода логина и пароля (см.Рисунок 8.6). Например, такую:

<FORM ACTION="up.php" METHOD=POST> Логин: <INPUT NAME="login" TYPE="text"><br> Пароль: <INPUT NAME="pass" TYPE="password"><br> <INPUT TYPE="submit" VALUE="Войти"></FORM>



Файл php.ini, раздел настроек параметров сессий



Рисунок 8.11. Файл php.ini, раздел настроек параметров сессий


Чтобы использовать в сценарии на странице возможности работы с сессиями, необходимо включить в него команду session_start () (так как при работе с сессиями используются cookie, то данная команда должна находиться в начале страницы, перед какими-либо выводимыми в браузер данными) - как при первоначальной установке переменных, так и при последующей работе с ними (если в файле php.iniустановлен в 1 параметр session.auto_start, то это делать не обязательно). Чтобы указать, какие переменные следует сохранять в качестве данных сессии, следует использовать команду session register("имя первой переменной", "имя второй переменной",... и т. д.), а чтобы закрыть сессию - команду session_destroy (). При закрытии сессии переменные, переданные сценарию с ее помощью, не обнуляются (последнее делает команда session_unset () ;), так что их можно использовать и в остальной части сценария.

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

Переменные сессии доступны в сценариях сайта по своим изначальным именам. Скажем, если командой session_register переменная $а была зарегистрирована в качестве сессионной, то ее значение будет доступно под тем же самым именем — $а - на всех страницах сайта, где используются сессии (т. е. в их начале размещена команда session_start ()).

Однако в целях безопасности лучше работать в сценарии с переменными сессии через автоматически создаваемые массивы $HTTP_SESSION_VARS и (в РНР версий 4.1 и старше) SSESSION, используя одноименные с переменными элементы этих массивов. Дело в том, что в этом случае сценарий будет огражден от возможных попыток злоумышленников передать ему значения этих переменных через указание их в адресной строке, если сессия не была открыта (в указанные массивы попадают те и только те данные, что были получены с сессией). Такая передача может привести, скажем, к тому, что переменная - пометка об успешном прохождении авторизации будет получена сценарием не из данных сессии (в которых она может появиться только после успешного ввода посетителем правильных логина и пароля), а от злоумышленника.



Файлы с данными сессий в папке



Рисунок 8.7. Файлы с данными сессий в папке временных файлов сервера. Имена файлов соответствуют идентификаторам сессий




Пароль на папку средствами web-сервера? Достаточно двух файлов - .htaccess и .htpasswd...



Рисунок 8.1. Пароль на папку средствами web-сервера? Достаточно двух файлов - .htaccess и .htpasswd...


Вот и все! Теперь при попытке запроса любого ресурса из защищенной папки (в том числе и картинок, включенных в другие страницы тэгом < img...>) посетителю будет выдан стандартный запрос логина и пароля (Рисунок 8.2). Если логин и пароль совпадают с хранящимися в файле паролей (по умолчанию есть три попытки ввода), то доступ разрешается, если нет - средствами web-сервера выводится соответствующее сообщение.



ПРИМЕР СЦЕНАРИЯ



ПРИМЕР СЦЕНАРИЯ

Вот пример сценария, в котором используется авторизация на основе заголовка WWW-Authenticate. Он состоит из двух страниц - на первой логин с паролем проверяются и в том случае, если они есть в файле паролей, то посетителю выводится форма для загрузки файла. На второй странице осуществляется загрузка файла.

Файл 1

<?php

foreach (file("passw/passwr") as $k)

{

if (substr($k, 0, -2)=="$PHP_AUTH_USER $PHP_AUTH_PW")

{$rez=l;} 1

if ($rez!=l) {

Header("WWW-Authenticate: Basic realm=\"Защищенная зона" \" ") ;

Header("HTTP/1.0 401 Unauthorized"); echo ("<р>Доступ закрыт!</р>"); exit;

<FORM ENCTYPE= "multipart/form-data" АСТION ="Файл 2" METHOD=POST>

Закачать файл:<INPUT NAME="zak" TYPE="file"> <INPUT TYPE="submit" VALUE="Закачать">

</FORM>

Файл 2

<?php

foreach (file("passw/passwr") as $k) {

if (substr($k, 0, -2)=="$PHP_AUTH_USER $PHP_AUTH_PW") { $rez=l; }

if ($rez!=l)

{

echo ("<р>Доступ закрыт!</р>");

exit;

if ($zak=="none")

{echo ("Вы забыли указать файл...");} elseif (copy($zak, "папка для файлов/$гак_пате"))

{echo("Файл $zak_name загружен");} else

{ echo("He удалось скопировать $zak_name");} ?>

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



СЦЕНАРИЙ АВТОРИЗАЦИИ



СЦЕНАРИЙ АВТОРИЗАЦИИ

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

Начало сценария на странице проверки логина с паролем может быть таким:

<?php

foreach (file("passw/passwr") as $k)

{if (substr($k, 0, -2)=="$PHP_AUTH_USER $PHP_AUTH_PW")

{$rez=l;}}

if ($rez!=l) {Header("WWW-Authenticate: Basic realm=\"Защищенная зона"\""); Header("HTTP/1.0 401 Unauthorized");

...текст страницы, выдающейся посетителю в случае нажатия им кнопки "Отмена"...

exit,-}

или таким (если логин и пароль передаются из формы в переменных Slogin и Spass):

<?php

foreach (file("passw/passwr") as $k)

{if (substr($k, 0, -2)=="$login $pass"){$rez=l;

if ($rez!=l) {...текст страницы, выдающейся посетителю в случае ввода неправильных логина и пароля ...

exit;}

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


Продолжение же сценария довольно простое. Создаем сессию...

session_start();

...регистрируем переменную:

session_register("auth");

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

$auth=1;

Собственно, и все.

?>

Дальше следует текст страницы, которую посетитель должен увидеть сразу же после успешной авторизации.

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

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

<?php session_start();

if ($auth!=l)

{...текст страницы, выдающейся посетителю в случае попытки несанкционированного доступа...

exit;}

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

Страница выхода из "защищенной зоны" должна содержать код (если в файле php.ini установлен в 1 параметр session.autostart, то указывать команду session_start () на каждой странице, где используются переменные сессии или производятся действия с самой сессией, не обязательно).

<?php

session_start(); session_destroy(); ?>

После его выполнения для посещения страниц "защищенной зоны" вновь потребуется авторизация.

Если посетитель не воспользовался страницей выхода из защищенной зоны, то время, в течение которого из его браузера можно попасть на другие ее страницы, определяется настройками в файле php.ini. По умолчанию cookie с идентификатором сессии, устанавливаемым данному посетителю, существует до закрытия всех окон браузера, а сами данные сессии хранятся несколько часов. Существует команда session_set_cookie_params () (подробную информацию о ней смотрите в Описании РНР), с помощью которой можно установить другое "время жизни" cookie, однако для изменения настроек в файле php.ini необходимо иметь права администратора для web-сервера.



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

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

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


СЕССИИ



СЕССИИ

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

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

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



Содержимое cookie с идентификатором сессии



Рисунок 8.9. Содержимое cookie с идентификатором сессии




Содержимое одного из таких файлов.



Рисунок 8.8. Содержимое одного из таких файлов. В сессии сохранены переменные: legus, wql, wq2, wq3



Если у посетителя браузер принимает cookie, то ему высылается cookie (с определенным именем - по умолчанию "PHPSESSID"), содержащий так называемый "идентификатор сессии" (Рисунок 8.9), а если нет, то web-сервер автоматически помещает данный идентификатор в переменную PHPSESSID в каждую ссылку (Рисунок 8.10) на выдаваемых посетителю страницах сайта (естественно, "внутреннюю" - т. е. ведущую на другие страницы того же самого сайта, с тем же самым доменным именем). Таким образом, идентификатор передается на сервер при каждом заходе посетителя на какую-либо из страниц сайта. При этом идентификатор выбирается либо из соответствующего cookie, установленного посетителю при открытии сессии, либо из адресной строки ссылки, куда этот идентификатор автоматически помещается web-сервером.


Ссылка с идентификатором сессии



Рисунок 8.10. Ссылка с идентификатором сессии


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

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

Бесспорно, набор сохраненных переменных, относящихся к одной сессии, будет существовать на сервере не вечно. В параметрах файла конфигурации РНР - php.ini - указывается, какое время жизни устанавливается для cookie с идентификатором сессии (по умолчанию 0 -т. е. до закрытия окна браузера и всех открытых из него окон), а также через какое время данные сессий из папки временных файлов удаляются физически (Рисунок 8.11). Кроме того, существует специальная команда "разрушения сессии", которая при своем выполнении уничтожает сохраненные в папке временных файлов данные сессии и тем самым делает недействительным идентификатор сессии. Параметры устанавливаемых cookie, в частности, их "время жизни" также можно задать специальной командой в сценарии на РНР, однако время хранения данных сессии в папке временных файлов определяется параметром в php.ini, так что при использовании виртуального хостинга вам не всегда удастся настроить работу с сессиями полностью так, как вам бы хотелось.



Запрос на вход в папку



Рисунок 8.2. Запрос на вход в папку


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

Использовать данный способ удается не всегда - администрация сервера иной раз не позволяет это делать посетителям, да и программа для создания файла паролей не всегда под рукой. Однако средства РНР позволяют обойтись без применения файлов .htaccess.



РНР: ГОСТЕВАЯ КНИГА



ГЛАВА 9. РНР: ГОСТЕВАЯ КНИГА

Вы наверняка неоднократно встречали в Интернете такой сервис, как гостевые книги, т. е. web-страницы, на которых каждый посетитель может оставить свой отзыв, который будет «виден» другим посетителям, впоследствии зашедшим на страницу. Скорее всего, вы думали, что создание гостевой книги требует долгого и сложного программирования.

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

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

Схема работы сценария простой гостевой книги такова.

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

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


В РНР есть интересная функция - time (); она выдает количество секунд между 1 января 1970 года (этот момент считается началом "эпохи Unix") и текущим временем, так называемую "временную метку Unix". (В настоящее время эта величина - чуть больше миллиарда.) Посмотрите - если имя файла с сообщением составлять из индекса гостевой книги и временной метки Unix (см. Рисунок 9.1), то, во-первых, каждое сообщение будет обладать своим уникальным именем (посылка нескольких сообщений в гостевую книгу разными пользователями в одну и ту же секунду теоретически возможна, но маловероятна), а, во-вторых, их легко можно будет отсортировать по времени появления (время ведь вспять не течет - каждое новое сообщение будет получать большую метку, нежели любое предыдущее).

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

Гостевая книга - все ее файлы



Рисунок 9.1. Гостевая книга - все ее файлы


Итак, вот алгоритм работы сценария гостевой книги:

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

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

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

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

Ну а теперь посмотрим, как все это реализуется на РНР.

В текст каждой web-страницы, на которой должна быть расположена гостевая книга, следует вставить следующий код (см. Рисунок 9.2):

<?php

$пот="уникальная аббревиатура книги, без пробелов и специальных символов, например, book01";

include ("niz.php"); ?>

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

Поскольку именно код файла niz.php отображает сообщения гостевой книги, то и вышеприведенный фрагмент кода следует помещать именно там, где эти сообщения должны на web-странице располагаться.



Внешний вид гостевой книги и файлов с ее сообщениями



Рисунок 9.3. Внешний вид гостевой книги и файлов с ее сообщениями


При работе приведенного выше сценария после добавления нового сообщения в гостевую книгу (Рисунок 9.3) посетитель окажется на странице обработки отзывов, в данном случае otziv.php. Можно поместить на ней, например, фразу с благодарностью за добавленное сообщение. Однако куда лучше будет, если после добавления нового сообщения посетитель автоматически вернется в гостевую книгу, куда он только что добавил свое сообщение. Для того чтобы это сделать, можно поместить вконец обработчика строчку Header ("Location: имя_меЬ-страницы_с_гостевой_книгой"); указав имя нужной страницы (например, передав его в форме вместе с остальными переменными, т. е. количеством отзывов и индексом страницы), или просто включить обработчик в сам файл niz.php, а в качестве страницы-обработчика формы указать ту же самую страницу с гостевой книгой.

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

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

В каждую страницу, на которой располагается гостевая книга, следует включить такой сценарий:

<?php

$пот="имя (без расширения) web-страницы, на которой расположена гостевая книга";

include ("niz.php"); ?>

Ну а файл niz.php должен содержать весь остальной код:

<?рпр

$dirct="gb"; if ($otziv!="")

{

$otznam=$nom.time();

$hdl = fopen("$dirct/$otznam", "w+"); fwrite($hdl,nl2br(strip_tags($HTTP_POST_VARS['ot ziv'])));

fclose($hdl);

}

$hdl=opendir($dirct); while ($file = readdir($hdl))

{

if (strstr($file, $nom)==True) { $a[]=$file;

$l=sizeof($a); if ($l!=0)

{

rsort($a);


foreach ($a as $k)

include ("$dirct/$k");

echo ("<br>(разделитель сообщений)");

Ваш комментарий:

<form method="post" action="<?php echo ("$nom".".php"); ?>" name="form">

<textarea name="otziv" cols="60" rows="10" wrap="virtual"></textarea>

<input name="submit" type="submit" value="Послать отзыв"></form>

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

Хотя в принципе указывать в сценарии имя файла в качестве индекса гостевой книги не надо. В начало кода в файле niz.php можно включить код, который бы этот самый индекс определял бы автоматически. Скажем, брал бы значение переменной $PHP_SELF - она содержит имя текущего файла вместе с путем к нему от корневой директории сайта (даже если она находится в файле, включенном в текущий с помощью оператора include), скажем, "/guestbooks/gbOl.php", а затем вытаскивал из него само это имя.

В РНР версии 4.1 и выше имя файла без расширения можно узнать командой basename (), указав в ее параметрах это самое расширение:

$nom=basename($PHP_SELF, ".php");

Примечание:

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

В РНР версии до 4.1 команда basename () имя файла без расширения выдать не сможет, однако расширение можно отбросить путем использования команды substr, указав в ее третьем параметре отрицательную величину - число символов от конца строки, которые не должны включаться в выделяемую строку (подробнее см. ):



$nom=substr(basename($PHP_SELF), 0, -4);

Если эту команду определения имени файла без расширения включить в самое начало кода файла niz.php, то установку значения переменной Snom на web-страницах, содержащих гостевые книги, можно убрать - достаточно одного оператора include:

<?php include ("niz.php"); "?>

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

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

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

<?php

$dirct="gb";

$nom=substr(basename($PHP_SELF), 0, -4);

if ($otziv!=B")

{

$otznam=$nom.time();

$hdl = fopen("$dirct/$otznam", "w+")

fwrite($hdl, nl2br(strip_tags($HTTP_POST_VARS['otziv']))

fclose($hdl); }

$hdl=opendir($dirct); while ($file = readdir($hdl)) {

if (strstr($file, $nom)==True) { $a[]=$file;

$1=sizeof($a); if ($l!=0) {

rsort($a);

if ($l>10)
{

for ($i = 0; $i < 10; $i++)

{

include ("$dirct/$a[$i]n);

echo ("<br>(разделитель сообщений)");

else

foreach ($a as $k)
{

include ("$dirct/$kB);

echo ("<br>(разделитель сообщений)");
}

}

}
?>

Ваш комментарий:

<form method="post" action="<?php echo ($nom.".php"); ?>" name="form">

<textarea name="otziv" cols="60" rows="10" wrap="virtual"></textarea>

<input name="submit" type="submit" value="Послать отзыв">

</form>

(Измененная часть кода выделена жирным.)

Тогда на странице отобразятся лишь последние 10 сообщений. (Код, выводящий остальные сообщения так же, по десяткам, сделайте самостоятельно.)

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


Всего три файла - и гостевая книгаА можно даже два



Рисунок 9.2. Всего три файла - и гостевая книга. А можно даже два


Содержимое же файла niz.php (см. Рисунок 9.2) может быть таким (пояснения относятся к коду под ними):

<?php

Укажем имя папки, в которой будут сохраняться отзывы (ее, разумеется, вначале надо будет создать на аккаунте сайта вручную). Само имя может быть любым - важно лишь, чтобы оно не содержало пробелов или специальных символов:

$dirct="gb";

Ну а далее следует уже знакомый вам сценарий "Папкопотрошилки" , применяемый к этой самой папке с отзывами. Вот практически точно такой же, как и в "Папкопотрошилке", код, записывающий в массив $а[] имена всех файлов, в имени которых содержится указанный выше индекс книги:

$hdl=opendir($dirct) ;

while ($file = readdir($hdl))

if (strstr($file, $nom)!=False)

$a[]=$file;

closedir($hdl);

Примечание:

Функция strstr ищет в своем первом аргументе строку, указанную вторым аргументом, и возвращает True, если ее там находит.

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

$l=sizeof($a);

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

if ($l!=0)

{

rsort($a);

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

Ну и, наконец, вставим все файлы с сообщениями в страницу с гостевой книгой с помощью оператора include, перебрав последовательно элементы массива с именами этих файлов конструкцией foreach:

foreach ($a as $value)


{

include ("$dirct/$value");

echo ("<br>(разделитель сообщений)");

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

Комментарий:

Оператор foreach будет работать только в РНР 4.0 и выше. Если вы можете использовать лишь РНРЗ, то вместо него можно использовать цикл for, указав в его параметрах величину массива $1:

for ($k = 1; $k < $1; $k++) {

Для удобства можно записать значение очередного элемента массива в переменную: $value=$a[$k] ;

Все - код вывода имеющихся сообщений завершен!

}>

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

<form method="post" action="otziv.php">

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

<input name="nom" type="hidden" value="<?php echo $nom; ?>">

Комментарий:

Скрытое поле (типа hidden) не отображается в браузере, однако передается вместе с формой.

Ну и - непосредственно поле ввода сообщения, уже, ясное дело, не скрытое:

<textarea name="otziv" cols="60" rows="10" wrap="virtual"></textarea>

И вездесущая кнопка отправки формы:

<input name="submit" type="submit" value="Добавить отзыв"></form>

Теперь осталось сделать программу-обработчик новых отзывов. Как это ни удивительно, но она уместится всего в пять строк. Разместить ее надо в том файле, имя которого указано в заголовке формы для ввода сообщения - в нашем случае это otziv.php (см. Рисунок 9.2).



<?php

Укажем сценарию имя папки с отзывами:

$dirct="gb";

В принципе, можно было бы это имя и передать через форму с помощью скрытого поля - типа hidden. А можно было бы и вообще не запоминать в переменную - указывать в сценарии в нужных местах само имя папки (т. е. "gb" в нашем случае) - и дело с концом. Просто так несколько нагляднее, да и в случае необходимости изменить это имя проделать данную операцию будет достаточно лишь в этом месте.

Сгенерируем имя для нового файла с сообщением - просто соединим вместе индекс гостевой книги и временную метку Unix, полученную функцией time():

$otznam=$nom.time();

Как вы помните, при передаче сценарию РНР информации через форму значения ее полей записываются в переменные, имена которых соответствуют значениям параметров пате этих полей, поэтому индекс гостевой книги, переданный через поле формы с именем пот, и оказался в переменной Snom.

Теперь создадим новый файл со сгенерированным именем и откроем его для записи - все это делается одной командой - f open с параметром w+.

Примечание:

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

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

• r - открыть файл только для чтения и приготовиться читать его с начала.;

• r+ - открыть файл для чтения и для записи и приготовиться работать с ним с его начала;

• w - открыть файл только для записи, предварительно удалив из него все содержимое, причем если файл с указанным именем не существует, то создается новый файл с таким именем;



• w+ - открыть файл как для записи, так и для возможного последующего чтения, предварительно удалив из него все содержимое, причем если файл с указанным именем не существует, то создается новый файл с таким именем;

• а - открыть файл только для записи и приготовиться дописывать данные в его конец. Если файл с указанным именем не существует, то создается новый файл с таким именем;

• а+ - открыть файл для записи и для чтения и приготовиться дописывать данные в его конец. Если файл с указанным именем не существует, то создается новый файл с таким именем.

Открываемый файл может располагаться и на удаленном сервере - в этом случае он будет доступен только для чтения независимо от параметров открытия файла. Путь к файлу в таком случае следует указывать полностью - начиная с http://muftp://.

(В нашем случае можно также использовать параметр а+ - различие этих двух параметров, заключающееся в том, что fopen с параметром w+ очищает все содержимое открываемого файла, a fopen с параметром а+ нет, несущественно, так как файл все равно создается новый):

$hdl = fopen("$dirct/$otznam", "w+");

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

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

fwrite($hdl,strip_tags($otziv));

Примечание:

Команда fwrite (дескриптор файла, записываемая в файл строка) записывает указанную во втором параметре строку в файл, дескриптор которого указан в ее первом параметре.



То место в файле, с которого совершается чтение данных и в которое осуществляется запись, называется указателем файла. (Если файл представить как тетрадь, то указатель - это открытая страница, вернее, номер открытой страницы.) При открытии файла командой fopen с параметрами r, r+, w или w+ указатель файла ставится на его начало, а при открытии с параметром а или а + -в самый конец.

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

Функция strip tags (строка) вырезает из строки, указанной в ее параметрах, все тэги — т. е. "все в угловых скобках", как HTML, так и РНР, ASP и другие, возвращая эту строку с вырезанными тэгами. Если какие-либо тэги вырезать не следует, то их можно перечислить во втором параметре данной функции: команда

strip_tags (строка, ' <a><b><ixu> ') ;

вырежет из указанной в первом параметре строки все тэги, кроме <а>, <Ь>, </>, <и>, т. е. оставит посетителю возможность оформлять ими свой текст.

В результате злоумышленник не сможет разместить в гостевой книге ни HTML-текст, ни PHP-программу, а значит, не сможет ни испортить дизайн сайта, ни выполнить на нем какие-либо свои команды РНР.

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

fwrite($hdl, htmlspecialchars($otziv));

Примечание:

Функция htmlspecialchars (строка) конвертирует все "специальные символы" в указанной в ее параметре строке в так называемые "мнемоники HTML", которые отображаются браузером на странице как эти самые символы. Конвертация происходит следующим образом:



• & (амперсанд) заменяется на сочетание символов &атр;

• " ( двойная кавычка) заменяется на сочетание символов &quot ;

• < (меньше) заменяется на сочетание символов &11 /

• > (больше) заменяется на сочетание cимволов &gt ;

Сочетания символов "&атр;", "&quot;", "&lt;", "&gt;" отображаются в браузере как амперсанд, двойная кавычка, знаки "меньше" и "больше" соответственно.

В РНР третьей (начиная с подверсии 3.0.17) и четвертой (начиная с подверсии 4.0.3) версий в качестве второго аргумента можно также указать параметр ENTjQUOTES или ENTNOQUOTES. Если указан первый, то помимо вышеуказанных замен выполняется еще и замена символа ' (одинарной кавычки) на сочетание символов &#039;, а если указан второй - то никакие кавычки не заменяются.

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

Комментарий:

Любую информацию, запрашиваемую от посетителя и впоследствии выводимую на какую-либо страницу, весьма желательно перед выводом обработать какой-нибудь из этих функций, htmlspecialchars или strip_tags, - для обеспечения устойчивости сайта к взлому. Ибо даже в том случае, если вы на одной странице запрашиваете у посетителя e-mail, а на другой - его выводите на страницу, злоумышленник в поле ввода e-mail'a может поместить код на РНР, и тогда, будучи включенным в выведенную страницу без какой-либо обработки, этот код благополучно исполнится! А ведь в этом коде может быть что угодно - вплоть до команд удаления файлов. Поэтому не забывайте обрабатывать данными функциями все информацию, что была введена в элементы формы и будет отображаться на какой-либо странице.

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



Если Вы желаете, чтобы при отображении на странице сообщений сохранялась их разбивка отправителями на абзацы, то обработайте записываемое в файл сообщение командой nl2br для конвертации символов конца строки в тэги <br>, которые этот разрыв строки и означают:

fwrite($hdl,nl2br(strip_tags($otziv)));

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

$otziv=nl2br(strip_tags($otziv)); fwrite($hdl,$otziv);

Примечание:

Функция nl2bг (строка) вставляет перед каждым символом начала строки, встреченном в строке в ее параметре, тэг <br> -в РНР версии до 4.0.5, или <Ъг /> - в РНР более поздних версий. (Последний тэг совместим и с языком XML.)

Файл можно закрыть - и закончить сценарий.

fclose($hdl);

?>

Такой код будет работать в РНР версии 3. В РНР версии 4 и выше с этим кодом тоже проблем не будет, однако лишь в том случае, если в конфигурационном файле РНР установлена как on опция register_globals. Настройка данного файла - прерогатива администратора web-сервера (хотя по умолчанию данная опция включена). Если register_globals установить в off, то переменные формы по их именам в сценарии-обработчике доступны не будут.

Однако все переменные, передающиеся через форму, записываются еще и в специальный массив - с именем $HTTP_POST_VARS (если передача произведена методом post - т. е. в адресной строке значения передаваемых переменных не отображаются; вид передачи указывается в заголовке формы) или $HTTP_GET_VARS (если передача произведена методом get-с отображением в адресной строке передаваемых переменных). В РНР4 так происходит всегда, а в РНРЗ - только в том случае, если в конфигурационном файле РНР (настраиваемом администратором web-сервера) установлена как on опция track_vars. Элементы этих массивов названы именами этих переменных; так, переменная, указанная в поле с именем otziv формы, передающей введенные в нее значения методом POST сценарию в файле scen.php, будет доступна в этом сценарии в элементе массива $HTTP_POST_VARS['otziv'].



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

<?php

$dirct="gb";

$otznam=$HTTP_POST_VARS['nom'].time();

$hdl = fopen("$dirct/$otznam", "w+");

fwrite($hdl,nl2br(strip_tags($HTTP_POST_VARS['ot ziv1])));

fclose($hdl);

?>

Начиная с РНР версии 4.1, кроме массивов $HTTP_POST_VARS и $HTTP_GET_VARS, программе на РНР доступны идентичные им массивы $_POST и $_GET, так что в том случае, если на сервере, где должны быть размещены ваши сценарии, установлен РНР этой или более старшей версий, то вы можете использовать и такие имена.

Версию РНР вы можете узнать, включив в сценарий команду phpinf о ();. Она даст исчерпывающую информацию как по этому, так и по множеству других вопросов.


ФАЙЛ NIZ.PHP



ФАЙЛ NIZ.PHP

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

<?php

Файлы со значениями среднего балла и количества оценок будут храниться в папке с именем cnt. Запишем для удобства ее имя в переменную:

$dirct="cnt";

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

$nom=substr(basename($PHP_SELF), 0, -4);

...а сами файлы назовем на основе этого имени, получив имя файла с количеством оценок прибавлением к нему окончания "kol", а имя файла со средним баллом - окончания "est" (Рисунок 10.1):

$kolvooc="$nom"."kol"; $ocenka="$nom"."est";



ФАЙЛ OTZIV.PHP



ФАЙЛ OTZIV.PHP

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

<?php

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

$namecook=$nom."haveest";

...и весь дальнейший код выполним только в том случае, если такого cookie установлено не было.

if ($$namecook=="")

Примечание:

Вместо этой строки можно использовать строку

i f ($HTTP_COOKIE_VARS[$haveestim]=="") или в PHP версии 4.1 и выше — строку

if ($_COOKIE[$haveestim]=="") как уже указывалось выше.

Комментарий:

Казалось бы — а зачем, собственно, проверять на этой странице, установлен ли cookie? Ведь форма для выставления оценки на предыдущей странице может появиться на ней только в том случае, если cookie отсутствует, не так ли? Но все дело в том, что посетитель, желающий сделать "накрутку" статьи, проголосовав за нее множество раз, вполне может сохранить на своем компьютере локальную копию статьи с формой для выставления оценки, и уже с нее осуществлять голосование. Ясно, что отображение формы на локальной копии страницы от наличия или отсутствия cookie не зависит, — поэтому и приходится осуществлять проверку еще и здесь.

Итак, если посетитель еще не голосовал за данную статью...

{

...установим cookie, говорящий, что такое голосование наконец совершилось. Время жизни cookie установим в месяц - пожалуй, хватит. (Пояснение по функции установки cookie смотрите в ).

SetCookie("$namecook","1",time()+2 592000);

Запишем в переменные имена директории с файлами оценок и самих этих файлов:

$dirct="cnt";


$kolvooc="$nom"."kol";

$ocenka="$nom"."est";

Если файлы оценок не существуют (т. е. выставляемая оценка - вообще первая по счету)...

if ((file_exists("$dirct/$kolvooc")!=True)

(file_exists("$dirct/$name2")!=True)) {

...то запишем в файл со сведениями о количестве оценивших число 1 (так ведь и есть, не правда ли?)...

$hdll = fopen("$dirct/$kolvooc", "a+"); fwrite($hdll,l); fclose($hdll);

...а в файл со сведениями о среднем балле - выставленную посетителем оценку (она ведь и есть "среднее" от самой себя):

$hdl2 = fopen("$dirct/$ocenka", "a+");

fwrite($hdl2,$ocen);

fclose($hdl2);

Если же файлы со сведениями об оценке уже существуют...

else

...то считаем для начала содержимое файла с количеством оценок в переменную

kvo...

$hdll = fopen("$dirct/$kolvooc", "r+");

$kvo ¦ fread($hdll, filesize("cnt/$kolvooc"));

...а затем увеличим значение этой переменной на 1 - что и будет новым количеством оценок, с учетом последней выставленной:

$kvo++;

Теперь нам надо вернуть точку считывания (так называемый "указатель") в начало файла - для того, чтобы записать в файл новое значение количества оценок. Ведь в результате проведения операции чтения количества оценок из файла точка считывания - указатель - переместилась в его конец. Для совершения данной операции воспользуемся командой rewind:

rewind($hdll);

Примечание:

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

Следует помнить, что если файл был открыт командой fopen с параметром а или a+, то независимо от положения указателя запись новых данных командой fwrite будет осуществляться вконец файла.

Запишем новое значение количества оценок в предназначенный для хранения этой величины файл...



fwrite($hdll,$kvo);

...и закроем его.

fclose($hdll);

Теперь разберемся со средним баллом. Откроем файл, где хранится его значение...

$hdl2 = fopen("$dirct/$ocenka", "r+");

...запишем это значение в переменную...

$sred= fread($hdl2, filesize("cnt/$ocenka"));

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

$sred=($sred*($kvo-l)+$ocen)/$kvo;

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

rewind($hdl2); fwrite($hdl2,$sred); fclose($hdl2);

Собственно, и все.

Можно выводить информацию посетителю об итоговом результате (Рисунок 10.6) или краткую благодарность.

echo ("Благодарим вас за оценку!");


Файл php.iniВышеупомянутые настройки



Рисунок 10.3. Файл php.ini. Вышеупомянутые настройки


Если cookie не установлен - т. е. посетитель ранее не голосовал за данную статью...

{

то выведем ему форму для голосования

. ?>

<form method="post" action="otziv.php">

Передадим в скрытом поле формы имя файла со статьей без расширения - для определения на основе него сценарием-обработчиком имен файлов со сведениями о количестве оценок и среднем балле, а также полное имя, вместе с путем, файла со статьей - для вывода ссылки "Назад" на странице со сценарием-обработчиком. В принципе можно было бы передавать через форму только полное имя файла со статьей, а "чистое" имя файла статьи определять в сценарии-обработчике точно так же, как и в сценарии из niz.php - на основе функции basename, но для сокращения длины кода в обработчике воспользуемся передачей его через форму.

<input name="nom" type="hidden" value="<?php echo $nom; ?>">

<input type=hidden name=nazad value=<?php echo ($PHP_SELF); ?>>

Выведем форму ввода оценки:

Поставьте оценку статье:

<SELECT NAME=ocen>

<OPTION VALUE=5>5 (Отлично)

«DPTION VALUE=4>4 (Хорошо)

<OPTION VALUE=3>3 (Удовлетворительно)

<OPTION VALUE=2>2 (Плохо)

<OPTION VALOE=1>1 (Очень плохо)

</SELECT>

В результате в сценарий-обработчик будет передана переменная Socen (и одноименные элементы массивов $HTTP_POST_VARS, $_POST при соответствующих версиях РНР и настройках в php.ini) со значением, равным параметру value выбранного пользователем пункта выпадающего списка.

Выведем кнопку отправки формы (Рисунок 10.4).



Файлы системы оцениванияСлева



Рисунок 10.1. Файлы системы оценивания.

Слева - статьи, файлы со сценариями и папка с файлами оценок, справа -содержимое этой папки



Теперь выведем сведения об уже выставленных оценках.

echo ("Оценок этой статье - ");

Если файл со сведениями о количестве оценок существует (он создается при первом оценивании)...

if (file_exists("$dirct/$kolvooc")==True)

{

...то вставим его значение в документ.

include ("$dirct/$kolvooc");

Если же такового файла нет, т. е. документ ни разу не оценивался...

}

else

{

...выведем значение "0".

echo ( " 0 " ) ;

}

Если файл со средним баллом существует...

if (file_exists("$dirct/$ocenka")==True)

{

...то надо вывести его значение.

echo (". Средний балл - ");

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

$hdl в fopen("$dirct/$ocenka", "r+");

...и считаем в переменную Ssred все содержимое этого файла.

$sred = fread($hdl, filesize("$dirct/$ocenka"));

Примечание:

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

Функция filesize (полный путь к файлу) возвращает размер указанного в ее параметре файла в байтах.

Файл можно закрыть...

fclose($hdl);

...а переменную $sred - округлить до десятых

$sred=round ($sred, 1);

Примечание:

Функция round (число, количество разрядов) округляет дробное число в ее первом параметре до количества разрядов, указанного в ее втором параметре. Скажем, round (число, 1) округлит число до десятых, round (число, 2) - до сотых и т. д.


В РНР до четвертой версии функция round могла округлять числа только до целых, поэтому в том случае, если вы располагаете только такой версией РНР, то команда округления до десятых должна выглядеть как

$sred=(round ($sred*10))/10; ...и вывести на страницу.

echo ("$sred.");

}

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

$haveestim=$nom."haveest";

...и проверим, определена ли переменная с таким именем - т. е. установлен ли одноименный cookie или нет (Рисунок 10.2). Можно было бы также проверять, какое значение имеет данная переменная, но для нашего сценария это неважно - если переменная установлена и cookie определен, то посетитель уже голосовал за данную статью: ведь cookie с данным именем устанавливается ему в этом и только в этом случае.

if ($$haveestim=="")

Обратите внимание на имя проверяемой переменной - это имя само является значением переменной Shaveestim (подобную конструкцию допускают правила РНР, подробнее смотрите в ).


Но голосование уже состоялось



Рисунок 10.5. ...но голосование уже состоялось


Вот и все.



PHP: СИСТЕМА ОЦЕНКИ МАТЕРИАЛОВ



PHP: СИСТЕМА ОЦЕНКИ МАТЕРИАЛОВ

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

Сначала подумаем. Что должен делать код выставления оценки? Ну вначале, наверное, принимать от посетителя значение оценки (скажем, из формы с элементами <select. . .>. . .<option>. . ..). Однако, если эта оценка является первой за всю историю существования статьи, то необходимо записать ее в файл. Если же ранее статья уже была оценена, то следует считать имеющееся значение из файла и вычислить среднее арифметическое всех оценок, включая только что добавленную. В самом деле, нам ведь нужна именно средняя оценка, не так ли? А затем это новое значение средней оценки необходимо в тот же файл и записать.

Среднюю оценку можно посчитать по очевидной формуле

(средний балл*количество оценок)+новодобавленная оценка /

количество оценок + 1

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

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

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


Комментарий:

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

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

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

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

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

<?php include ("niz.php"); ?>


После попытки повторного голосования



Рисунок 10.7. После попытки повторного голосования


Если же посетитель уже голосовал за данную статью...

}

else {

...то сообщим ему об этом (Рисунок 10.7) - и ничего делать не станем, echo ("Вы уже голосовали за эту статью!");

}

Сценарий закончен. ?>

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

<?php echo ("<a href=$nazad>Ha3afl</a>"); ?>

Переменная Snazad была передана через форму, помните?

Комментарий:

В данном сценарии для упрощения восприятия значения переменных, передаваемых через форму, брались из одноименных переменных в сценарии-обработчике - так как на безопасность работы программы это здесь не влияет: даже если злоумышленник подставит значение переменной cookie в адресную строку, то он только потеряет возможность проголосовать, и ничего больше. Если вы желаете сделать код лучше соответствующим правилам РНР - замените их на одноименные элементы массива $HTTP_POST_VARS[] (в РНР версии до 4.1) или SPOSTfJ (в РНР версии 4.1 и старше).

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



Система оценивания в действии...



Рисунок 10.4. Система оценивания в действии...


<input name="submit" type="submit" value="Послать оценку"></form>

<?php

А если посетитель уже голосовал за данную статью - т. е. cookie с соответствующим именем у него установлен...

}

else {

...то сообщим ему об этом (Рисунок 10.5).

echo ("Вы уже голосовали за эту статью!");



Содержимое cookie - пометки о состоявшемся голосовании



Рисунок 10.2. Содержимое cookie - пометки о состоявшемся голосовании


Поскольку значения cookies доступны и через массив $HTTP_COOKIE_VARS[], то проверить наличие cookie можно и на основе анализа значений этого массива:

if ($HTTP_COOKIE_VARS[$haveestim]=="")

или в РНР версии 4.1 и выше

if ($_COOKIE[$haveestim]==" ")

Комментарий:

Помните, что доступность данных cookie зависит от настроек в файле php.ini (Рисунок 10.3) - если в файле php.ini установлен в on параметр register_globals, то содержимое cookie доступно в сценарии в переменной с тем же именем, что и cookie, а если ephp.ini установлен в on параметр trackjvars, то содержимое cookie доступно в сценарии в одноименном с этим cookie элементе массива SHTTPCOOKIEVARSfJ (с РНР 4.1 -и $_СООК1Е[]).

Использовать массивы $HTTP_COOKIE_VARS[] и $_СООК1Е[] лучше с точки зрения безопасности. Если в сценарии используются одноименные cookie переменные, а не элементы этих массивов, то в том случае, если cookie не установлен, злоумышленник все равно может передать сценарию значение такой переменной, попросту указав его в адресной строке браузера. В указанные же массивы попадают исключительно полученные с cookie данные. Однако в рассматриваемом в данной главе сценарии это несущественно. 118



ТЕКСТ СЦЕНАРИЯ



ТЕКСТ СЦЕНАРИЯ

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

Вставка в файлы со статьями:

<?php include ("niz.php"); ?>

Файл niz.php

<?php

$dirct="cnt";

$nom=substr(basename($PHP_SELF), 0, -4); $kolvooc="$nom"."kol"; $ocenka="$nom"."est"; echo ("Оценок этой статье - "); if (file_exists("$dirct/$kolvooc")==True) {

include ("$dirct/$kolvooc"); }

else {

echo (" 0") ; }

if (file_exists("$dirct/$ocenka")==True) {

echo (". Средний балл - "); $hdl = fopen("$dirct/$ocenka", "r+"); $sred = fread($hdl, filesize("$dirct/$ocenka")); fclose($hdl); $sred=round ($sred, 1); echo ("$sred."); }

$haveestim=$nom."haveest"; if ($$haveestim=="")

<form method="post" action="otziv.php">

<input name="nom" type="hidden" value="<?php echo $nom; ?>">

<input type=hidden name=nazad value=<?php echo ($PHP_SELF); ?>>

Поставьте оценку статье:

<SELECT NAME=ocen>

<OPTION VALUE=5>5 (Отлично)

<OPTION VALUE=4>4 (Хорошо)

<OPTION VALUE=3>3 (Удовлетворительно)

<OPTION VALUE=2>2 (Плохо)

<OPTION VALUE=1>1 (Очень плохо)

</SELECT>

<input name="submit" type="submit" value="Пocлaть оценку"></form>

<?php

}

else {

echo ("Вы уже голосовали за*эту статью!"); } ?>

Файл otziv.php

<?php

$namecook=$nom."haveest"; if ($$namecook=="") {

SetCookie("$namecook","1",time()+2592000);

$dirct="cnt";

$kolvooc="$nom"."kol";

$ocenka="$nom"."est";

if ((file_exists("$dirct/$kolvooc")!=True) (file_exists("$dirct/$name2")!=True))

{

$hdll = fopen("$dirct/$kolvooc", "a+");

fwrite($hdll,1)j


128

fclose($hdll);

$hdl2 = fopen("$dirct/$ocenka", "a+");

fwrite($hdl2,$ocen);

fclose($hdl2);

else

$hdll = fopen("$dirct/$kolvooc", "r+");

$kvo = fread($hdll, filesize("cnt/$kolvooc"))

$kvo++;

rewind($hdll);

fwrite($hdll,$kvo); .

fclose($hdll);

$hdl2 = fopen("$dirct/$ocenka", "r+");

$sred= fread($hdl2, filesize("cnt/$ocenka"));

$sred=($sred*($kvo-l)+$ocen)/$kvo;

rewind($hdl2);

fwrite($hdl2,$sred);

fclose($hdl2);

echo ("Благодарим вас за оценку!"); else echo (" Вы уже голосовали за эту статью!");

Ниже в этом же файле:

<?php echo ("<a href=$nazad>Ha3afl</a>"); ?>


ФАЙЛ DO.PHP



ФАЙЛ DO.PHP

Сценарий этого файла выполняет выбранное посетителем действие и перенаправляет его назад на основную страницу файлового менеджера.

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

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

<?php

$begin="files";

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

If

((strpos($folder,$begin)!=0)(strpos($folder,"..")

!=False))

158

exit; }

Примечание:

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

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



ФАЙЛ INDEX.PHP



ФАЙЛ INDEX.PHP

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

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

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

Проще всего передавать сценарию информацию о том, список содержимого какой папки подлежит выводу на экран, через переменную в адресной строке - попросту указав в ней путь от корневой папки к текущей. Скажем, если корневая папка имеет название files, то она будет открываться по ссылке index.php?f old=f iles, а вложенная в нее папка folded - по ссылке index. php?f old= files /folder 1. (Как видно из текста ссылок, информация о пути к текущей папке будет доступна сценарию на странице из значения переменной Sfold, и именно значение этой переменной и будет подставляться в сценарии в качестве имени папки для сканирования.)

Например, на Рисунок 11.3 файловый менеджер отображает содержимое папки f iles/Folder2/Folder3. Обратите внимание на адресную строку браузера.



ФАЙЛ ZAPROS.PHP



ФАЙЛ ZAPROS.PHP

Сценарии этого файла запрашивают от посетителя дополнительную информацию для проведения действия. Начало сценария:

<html><?php

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

echo ("<form action=do.php?folder=$folder method=post>");

Бесспорно, можно было бы и не передавать имя текущей папки, а сообщать обработчику конкретные имена файлов, над которыми совершается действие, с полным путем к ним, однако так все-таки проще.



Файловый менеджер в работе



Рисунок 11.3. Файловый менеджер в работе


Комментарий:

Нетрудно заметить, что подобная конструкция весьма уязвима для взлома. В самом деле, - если подставить вместо имени корневой папки - files — имя любой другой папки, находящейся на том же уровне, что и папка files, то сценарий "Патопотрошилки" тихо-спокойно выведет и ее содержимое, - чего, возможно, делать не следует, а оно для этого может быть совсем не предназначено! А если указать в пути вместо имени каталога символы /../ (двумя точками обозначается родительский каталог, одной - текущий), то посетитель сможет добраться и до корневой папки всего сайта, и фактически получит возможность удалять, переименовывать, просматривать все файлы на сайте-аккаунте, так как весь сайт будет в его распоряжении.

Изменить же адресную строку - задача совсем не сложная, просто наилегчайшая. Поэтому такое положение дел, понятно, не так

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

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

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


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

Итак, начинаем:

<htmlx?php

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

$begin="files";

Комментарий:

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

Теперь проверим:

определена ли вообще содержащая путь к отображаемой папке переменная $fold (т. е. присутствует ли она в адресной строке - ее там не будет, если к странице с файловым менеджером обратились по внешней ссылке, а не с этой же страницы по ссылке на какую-либо папку из списка файлов и папок);

не содержит ли она двух точек подряд - признака попытки взлома;

начинается ли наличествующий в ней путь с имени корневой папки аккаунта (помещенного строчкой выше в переменную Sbegin):

if ((strpos($fold,$begin)!=0)| (strpos($fold,"..")!=False)($fold==""))

{

Примечание:

Функция strpos (строка - объект поиска, искомая комбинация символов) возвращает либо номер позиции, на которой в указанной в ее первом параметре строке находится указанная во втором параметре комбинация символов, либо False - если таковая не найдена вообще.



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

if (strpos ("строка", "символы") ===0) {... будет верно, если "строка" начинается с "символов".

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

if (is_integer (strpos ("строка ", "символы")==false) {...

выполнится, только если в "строке" "символы" не найдены.

и если переменная не определена или путь в ней не начинается с имени корневой папки или содержит две точки подряд, то выведем содержимое корневой папки - для удобства запишем ее имя в переменную $dirct, с которой мы и будем в дальнейшем работать как с содержащей путь к текущей папке:

$dirct=$begin;

}

else

{

а если переменная Sfold "в порядке", то в переменную Sdirct поместим именно ее значение:

$dirct=$fold;

}

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

echo ("<form action=zapros.php?folder=$dirct method=post>");

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

if ($dirct!=$begin) {

для чего выделим из пути к текущей папке его часть с начала вплоть до последнего слэша - разделителя директорий; это и будет путь к родительской папке (скажем, если путь к текущей папке -f iles/folderl/papkal, то путь к родительской папке будет выглядеть как f iles/f olderl):



$back=substr ($dirct, 0, strrpos($dirct, "/"));

Примечание:

Команда substr (строка, начало выделения, длина выделения) предназначена для выделения из строки ее части. Строка (или переменная, ее содержащая) должна быть указана в первом параметре команды. Второй параметр - позиция, с которой начинается выделяемая часть (вернее, число символов, которые необходимо пропустить до начала выделения части строки), а третий -количество выделяемых символов.

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

и выведем ссылку на родительскую папку, попросту передав полученный путь к ней через переменную $fold:

echo ("<a href=index.рbр?:?о1с1=$bаск>Корневая папка</а><br>");

Все это делается, естественно, лишь в том случае, если текущая папка - не корневая.

}

Теперь можно и сканировать текущую папку. Получаем список файлов в ней (пояснения смотрите в - в описании сценария "Папкопотрошилка"):

$hdl=opendir($dirct); while ($file = readdir($hdl)) {

if (($file!="..")&&($file!=".")) { $a[]=$file;

} }

closedir($hdl);

В том случае, если файлы в папке есть...

if (sizeof($a)>0) {

отсортируем массив с их именами по алфавиту. К сожалению, при этом имена папок окажутся перемешанными с именами файлов, но делать специальную функцию сортировки пока не будем: asort($a);

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

С каждым именем файла или папки...

foreach ($a as $k) { поступим следующим образом.

Примечание:

Оператор foreach считывает указанную в его параметрах переменную, в данном случае $к, все элементы массива, в данном случае $а, по очереди выполняя каждый раз указанный после него в фигурных скобках код, в котором может использоваться указанная переменная. Foreach будет работать только в РНР 4.0 и выше. Если вы можете использовать лишь РНРЗ, то вместо него можно использовать цикл for, указав в его параметрах величину массива $1.



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

$full=$dirct."/". $k; Теперь выведем checkbox:

echo ("<input name=fl[] value=$k type=checkbox>");

При передаче формы странице-обработчику будет передан массив $fl, состоящий из значений атрибутов value отмеченных checkbox'oe (только отмеченных - неотмеченные игнорируются). А в качестве значений атрибутов value мы указываем имена файлов. Так что сценарию-обработчику будет полностью ясно, с какими файлами ему работать - путь к текущей папке будет передан в переменной в ссылке на обработчик, указанной в заголовке формы, а имена файлов передадутся в массиве $fl.

Если очередной элемент массива с именами файлов в текущей директории является папкой...

if (is__dir ($full)==True)

Примечание:

Функция is_dir возвращает True, если указанный в ее параметре объект существует и является папкой.

то выведем ссылку на нее. Вернее, не на нее, а на этот же файл нашего файлового менеджера - index.php, передав ему в качестве пути к текущей папке - в значении переменной Sfold - записанный нами ранее в переменную Sfull полный путь к данной папке:

echo ("<a href=index.php?fold=$full><b>nariKa $k</bx/a>") ;

Ну и укажем в качестве текста ссылки название папки, пояснив, что эта ссылка ведет именно на папку (см. Рисунок 11.4).

В результате перехода по такой ссылке файлу index.php будет передан новый путь - путь к "открываемой" папке - и посетитель сможет увидеть список файлов в ней.

Если же очередной элемент массива с именами файлов в текущей директории - всего лишь файл...

}

else {

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

echo ("<a href=$full>$k</a>");



}

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

echo ("<br>");

и перейдем к следующему элементу с именами файлов в текущей директории.

}

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

} ?>

Теперь отобразим кнопки выбора действия над выделенными файлами. Их будет четыре - "Удалить", "Переименовать", "Копировать", "Создать папку" с именами "udal", "ren", "copy", "md" соответственно.

<br><input type=submit value="Удaлить" name=udal><br><input type=submit | value="nepeименовать" name=renxbrxinput type=submit value="Koпировать" name=copy><br><input type=submit value="Создать папку" name=md>

Собственно, сам текст сценария закончен...

< / f ormx /html>

хотя, бесспорно, внешний вид страницы с ним будет весьма аскетичным (Рисунок 11.4, 11.5). Но художественное оформление страницы - уже ваша прерогатива.


Файловый менеджерГлавная страница



Рисунок 11.5. Файловый менеджер. Главная страница. Исходный код Рассмотрим следующие компоненты файлового менеджера.






Файловый менеджерГлавная страницаКурсор - на ссылке, ведущей на папку



Рисунок 11.4. Файловый менеджер. Главная страница. Курсор - на ссылке, ведущей на папку




"Калькулятор параметров доступа" из CuteFTP



Рисунок 11.11 . "Калькулятор параметров доступа" из CuteFTP


Теперь начнем копировать файлы из исходной папки в новосоздан-ную (используем старый добрый сценарий "папкопотрошилки"): исходная папка при этом - $f Id. " /" . $шп, а новосозданная -

$tgt."/".$nm.

$hdl=opendir($fld."/".$nm) ; while ($file = readdir($hdl)) { if (($file!=" . .")&&($file! = "."))

Если очередной объект из "потрошимой" папки $f Id. " / " . $nm -директория...

if (is_dir($fld."/".$nm."/".$file)==True) {

о применим рекурсию - вновь вызовем функцию с ору fold, только араметры ей уже передадим несколько другие:

copyfold($fld."/".$nm, $file, tgt."/".$nm);

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

Если же очередной объект из "потрошимой" папки $f Id." /" . $nm является обычным файлом...

}

else

{

то просто скопируем его из исходной папки в папку назначения -и дело с концом.

copy ($fld."/".$nm."/".$file, $tgt."/".$nm."/".$file);

Примечание:

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

Если файл назначения уже существует, он будет перезаписан без вывода какого-либо подтверждения.

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


copy ("/files/data/filel.htm", "/files/last/'filel.htm");

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

Работа функции завершена. Закроем открытые циклы и условные операторы...

}

} }

и открытую "потрошимую" директорию:

closedir($hdl); }

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

if ($copy!="")

{

Переберем все объекты, подлежащие копированию: foreach ($fl as $i)

{ Если очередной объект - папка...

if (is_dir($folder."/".$i)==True)

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

copyfold($folder, $i, $rd);

Комментарий:

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

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



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

if (!(strpos ($rd, $?older."/".$i)===0)) { copyfold($folder, $i, $rd);

}

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

Если же очередной объект - файл...

}

else {

то его скопируем.

copy ($folder."/".$i, $rd."/".$i);

Осталось закрыть фигурными скобками запущенные циклы и условные операторы.

} }

}

Процесс копирования завершен (Рисунок 11.12).


КНОПКА ОТМЕНЫ


Для того, чтобы со страницы запроса информации можно было уйти без последствий, если посетитель передумает что-либо делать со своими файлами, поместим на эту страницу кнопку "Отмена" (см. Рисунок 11.6-11.9):

<brxinput type=submit value="OTMeHa" name=otmena>

Собственно, это и есть весь текст этой страницы:

</form></html>

Графическое оформление - уже на вкус сайтостроителя.



КОПИРОВАНИЕ, ВЫПОЛНЕНИЕ ДЕЙСТВИЯ


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

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

1. Создать в папке назначения папку с тем же именем, что и у копируемой.

2. Копировать из копируемой папки в новосозданную папку все ее содержимое.

3. Если очередной копируемый объект - папка, то перейти к пункту 1, приняв в качестве папки назначения - новосозданную папку, а в качестве копируемой папки - этот самый очередной копируемый объект, оказавшийся папкой.

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

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

function copyfold ($fld, $nm, $tgt)

Внутри функции эти данные будут доступны в переменных $fld, $nm, $tgt соответственно.

Сначала создадим в папке назначения копирования папку с таким же именем, что и имя копируемой папки. Естественно, если там таковой еще нет:

if (file_exists($tgt."/".$nm)!=True)

mkdir ($tgt."/".$nm, 0666);

Примечание:

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

Параметры доступа, или атрибуты файла или папки - это восьмеричное число, сообщающее web-серверу о том, что можно делать с файлом, которому эти параметры установлены. Например позволить его читать только другим сценариям на том же аккаунте, но не посетителям из Сети. Узнать о соответствии значений параметров доступа их восьмеричному представлению можно, например в FTP-клиенте CuteFTP, воспользовавшись его окном выставления атрибутов файла (Рисунок 11.11), доступном через пункт "Chmod" меню правой кнопки мыши любого файла на сайте. "Owner permissions" - это раз решения для других программ на том же аккаунте, a "Public permissions" - для посетителей из Интернета. Вы можете разрешить или запретить три вида действий: чтение, запись и запуск на исполнение (последнее имеет смысл только для программ).



КОПИРОВАНИЕ, ЗАПРОС ИНФОРМАЦИИ


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

А что нужно, чтобы это узнать? Необходимо выдать запрос на получение имени этой папки у пользователя. Можно, конечно, предоставить пользователю возможность просто ввести новый путь в поле ввода текста, как при использовании команды сору в командной строке MS-DOS. Но это представляется весьма неудобным. Лучше, наверное, вывести пользователю список всех имеющихся на его аккаунте папок, из которых он сможет выбрать нужную. Пометить имя каждой папки радиокнопкой (кружком из группы единственного выбора, см. Рисунок 11.1, 11.7) - и для выбора папки назначения копирования достаточно будет только ткнуть мышью в нужном месте).

Но как вывести список всех папок? Ведь они могут быть вложены друг в друга, так что простое сканирование корневой директории не поможет... А команд в нашем распоряжении для достижения данной цели не так и много. Считать список содержимого каталога, определить, является ли тот или иной элемент, содержащийся в каталоге, так же директорией, - вот, собственно, и все, что нам может предложить РНР.

И тут придет на помощь принцип рекурсии.

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

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


Как же принцип рекурсии можно использовать для построения списка всех папок на аккаунте? А алгоритм построения этого списка при использовании рекурсии прост:

Получить список файлов и папок в текущей директории.

Перебирать элементы из этого списка по одному.

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

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

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

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

Начало кода:

if ($copy!="")

{

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

$begin="files";

Выводим запрос пользователю...

echo ("Объекты для копирования:<br>");

и, точно так же, как в блоке кода, посвященном удалению файлов, выводим список объектов, подлежащих копированию (сравните - код практически такой же):

foreach ($fl as $i)

{

echo ("<input type=hidden name=fl[] value=$i>$folder/$i<br>") ;

Еще один запрос...

echo ("<bг>Выберите папку для копирования :<br>") ;

и начинаем вывод дерева папок - вызываем функцию tree.

tree($begin);


КопированиеЗапрос папки назначения



Рисунок 11.7. Копирование. Запрос папки назначения


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

if ($begin!=$folder) {

echo ("<brxinput name=rd type=radio value=$begin>$begin<br>");

и вывести кнопку запуска копирования.

echo ("<input type=submit value=\"Скопировать\" name=copy>");

Все.

}

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



Ошибка при переименовании - сообщение РНР



Рисунок 11.14. Ошибка при переименовании - сообщение РНР


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

while (file_exists($folder."/".$rfl[$i])==True)

{

$rfl[$i] = "_".$rfl[$i];

}

Как нетрудно понять, он добавляет в начало нового имени файла знак "_", если файл с таким именем уже существует в той же папке, куда предполагается копировать файл. Если же и таковой файл - со знаком "_" в начале -уже в папке имеется, то к новому имени добавляется еще один такой символ, и так до тех пор, пока новое имя не станет уникальным.



ПЕРЕИМЕНОВАНИЕ, ВЫПОЛНЕНИЕ ДЕЙСТВИЯ


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

if ($ren!="")

{

Переберем все элементы массива со старыми именами - $af 1. Поскольку для совершения собственно переименования для каждого элемента массива со старыми именами нам также потребуется элемент массива $rf 1 с новыми именами под тем же порядковым номером, то перебирать эти элементы будем подряд, по номерам - с помощью цикла for:

for ($i = 0; $i < sizeof($afl); $i++)

Если новое имя не совпадает со старым и не является пустой строкой...

if (($rfl[$i]!="")&($rfl[$i]!=$afl[$i]))

то можно переименовывать. Однако посетитель вполне может в новом имени указать символы, которые недопустимы для имен файлов - от пробелов до слэшей. Возвращать посетителя на этап ввода имени с выведением ему сообщения о недопустимых символах не будем - просто заменим все такие символы на знаки подчеркивания: "_". Тем более что такую замену можно совершить специальной командой PHP - strtr:

(Количество знаков подчеркивания в третьем параметре функции равно количеству указанных во втором недопустимых символов.)

Примечание:

Функция strtr("строка", "заменяемые символы", "заменяющие символы") заменяет в строке, указанной в ее первом параметре, символы, приведенные в строке в ее втором параметре, на символы из строки в ее третьем параметре, стоящие в этой строке на тех же местах, что и заменяемые символы в строке в ее втором параметре.

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

Функция просматривает строку в первом параметре символ за символом. Если очередной символ этой строки встречается и в строке во втором параметре, то функция:


1. Смотрит, какой символ стоит в строке в ее третьем параметре на том же самом месте от начала строки, что и встреченный символ в строке во втором параметре.

2. Заменяет в строке в первом параметре этот очередной символ на найденный в третьей строке. Например результатом выполнения функции

strtr ("Оабвапабаво", "ба", "ру")

будет строка

"Оурвупуруво"

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

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

И наконец, само переименование - командой rename:

rename ($folder."/".$af1[$i], $folder."/".$rfl[$i]) ;

Примечание:

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

Вот и все (Рисунок 11.13).

} } }

Комментарий:

Если при переименовывании файла в качестве нового имени задать имя уже существующего файла, то команда rename выдаст сообщение об ошибке (Рисунок 11.14). Это и испортит внешний вид страницы, и затруднит действия пользователя. Можно, конечно, перед командой rename поставить знак @ - тогда сообщений об ошибке не будет (стандартный способ запрета на вывод такого сообщения), но тогда пользователь даже не узнает об ошибке.


ПЕРЕИМЕНОВАНИЕ, ЗАПРОС ИНФОРМАЦИИ


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

Если на основной странице была нажата кнопка "Переименовать"...

if ($ren!="") { то выведем пояснение пользователю...

echo ("Переименовать файлы?<bг>");

и для каждого файла или папки, чье имя было отмечено в соответствующем checkbox'e на главной странице...

foreach ($fl as $i)

{

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

echo ("<input type=hidden name=afl[] value=$i>");

Выведем старое имя файла...

echo ("$i");

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

echo ("<input type=text size=30 name=rfl[] value=$ixbr>") ;

Такие поля выведем для каждого файла или папки, подлежащего переименованию.

}

И выведем кнопку, запускающую процесс переименования путем перехода на страницу со сценарием-обработчиком с передачей этому сценарию отличного от пустой строки значения переменной $rеn:

echo ("<input type=submit value=\"Переименовать\" name=ren>");

Блок запроса дополнительной информации по переименованию файлов или папок закончен.

}

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

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

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



PHP: ФАЙЛОВЫЙ МЕНЕДЖЕР



PHP: ФАЙЛОВЫЙ МЕНЕДЖЕР

В этой главе, самой объёмной из всех, будет рассмотрен довольно большой сценарий - файловый менеджер. Расположив его на странице, вы дадите посетителю возможность работать с файлами в определенной директории вашего сайта. Это может потребоваться, например, при выделении коллеге раздела сайта для самостоятельного ведения, таким образом при помощи web-интерфейса он сможет размещать материалы в своем разделе, не обращаясь к владельцу самого сайта.

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



ПРОЕКТИРОВАНИЕ



ПРОЕКТИРОВАНИЕ

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

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

во-вторых - их копировать;

в-третьих - их переименовывать;

и в четвертых - создавать новые папки.

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

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

Комментарий:

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

Как все это сделать?

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

Как осуществить такой выбор действия? Можно, конечно, создать группу единственного выбора, состоящую из нескольких radio buttons (она представляет собой группу кружков, отметить из которых можно только один - Рисунок 11.1, слева), а в сценарии выполнения действия определять, какой пункт был отмечен. Однако интереснее будет для выбора действия использовать кнопки типа submit (Рисунок 11.1, справа), нажатие которых вызовет переход на страницу-обработчик формы, содержащей эти кнопки. Посетитель, отметив файлы, просто нажмет нужную кнопку и будет ждать результата.



РЕКУРСИВНАЯ ФУНКЦИЯ ВЫВОДА СПИСКА ДОСТУПНЫХ ДЛЯ КОПИРОВАНИЯ ПАПОК НА АККАУНТЕ


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

Функции передается один параметр - имя папки, список вложенных папок в которой она должна выдать.

function tree($fId)

Поскольку в функции будут использоваться переменные еще и из других частей программы на странице, такие как Sfolder (путь к текущей папке) и массив $fl (список копируемых объектов; имена тех папок, что перечислены в нем, выводиться на страницу не будут, так как папку нельзя скопировать саму в себя или в свою же вложенную папку), то эти переменные необходимо объявить как глобальные в самой функции, указав их в ее начале после слова global:

global $folder; global $fl;

Комментарий:

Массив Sfl был передан сценарию запроса дополнительной информации с помощью формы на основной странице файлового менеджера — методом POST. Поэтому он также доступен через массив $HTTP_POST_VARS - как его элемент $HTTP_POST_VARS['fl'J (если в файле конфигурации РНР установлен в on параметр track_vars). Для использования этого массива в функции его надо также объявить глобальным - командой global $HTTP_POST_VARS;.

В РНР 4.1 версий и выше массив Sfl доступен и через массив $_POST. В отличие от $HTTPJPOST_VARS этот массив автоглобальный — т. е. для использования в функциях его элементов объявление самого массива в функциях производить не надо.

Если вы пожелаете заменить в рассматриваемой программе все вхождения переменной-массива Sfl на соответствующие им элементы массивов SHTTPPOSTVARS или SPOST, то помните, что последние нельзя для вставки их значений в строку указывать в тексте строки — для этого следует использовать оператор конкатенации (точку).

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


$hdl=opendir($fld);

while ($file = readdir($hdl)) if ( ($file!=°.")&&($file!=".."))

Для удобства запишем полное имя - вместе с путем - очередного взятого из папки объекта в переменную $f llnm:

$fllnm=$fld."/".$file;

Если этот объект - тоже папка...

if (is_dir($fllnm)==True)

то выясним:

не является ли данная папка одновременно и объектом копирования? Если является - то, во-первых, в списке папок она появиться не должна - папку нельзя скопировать саму в себя, а, во-вторых, сканировать ее вложенные папки тоже незачем - копировать одну папку в другую, вложенную в нее, еще никому не удавалось;

не в этой ли самой папке находится копируемый файл? Если в этой же самой папке - то выводить ее имя бессмысленно: копирование файла на свое же место возможно, но никаких за собой последствий не влечет.

Для начала сравним полное имя (вместе с путем от корневой директории аккаунта) очередной найденной в сканируемой директории папки со всеми именами копируемых объектов (естественно, тоже полными). Если хоть одно такое имя совпадет с именем папки - то выводить имя этой папки в список доступных для копирования нельзя.

$по=0;

foreach ($fl as $i)

if ($fllnm==$folder."/".$i) $no=l;

Переменная $по примет значение 0, если совпадений не было, и 1, если были.

Комментарий:

Обратите внимание на способ фиксирования совпадения имен папок при их переборе - при помощи изменения значения ранее установленной переменной: в данном случае - $nо.

Используйте такой же способ, если вам надо узнать, произошло ли то или иное событие внутри какого-нибудь цикла - установите до цикла переменную в ноль, а внутри цикла в случае совершения события присвойте ей значение 1. Тогда после окончания цикла переменная будет равна 1, если событие произошло, и 0, если нет.

Итак - если очередная папка из сканируемой директории не является объектом копирования...

if ($no==0)

{ и эти объекты копирования расположены не в ней...

if ($fllnm!=$folder)

то ее имя можно вывести в качестве возможного пункта назначения копирования, снабдив его radio button - т. е. "кружком" для единственного выбора. (После отправки формы результат выбора окажется в переменной $rd в сценарии выполнения действия.)



echo ("<input name=rd type=radio value=$fllnm>$fllnm<br>");

Комментарий:

При отправке формы, содержащей radio buttons, сценарию-обработчику передается всего одна относящаяся к этим элементам формы переменная, имя которой совпадает с именем отмеченной radio button, а значением является содержимое параметра value отмеченной radio button.

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

То, что папка содержит копируемые файлы, является препятствием к выводу имени этой папки на экран как возможнего пункта назначения копирования. Но это отнюдь не значит, что в данной папке не должны сканироваться вложенные папки. Поэтому оператор i f определяет, не содержит ли рассматриваемая папка копируемых файлов, завершаем...

} и вот он - рекурсивный вызов функции tree:

tree ($fllnm);

Осталось закрыть все незавершенные операторы и циклы,

}

}

}

}

и "потрошимую" директорию.

closedir($hdl);

Функция вывода списка директорий,- пунктов назначения копирования завершена.

}

Работать она будет так. Изначально, как вы помните, функция вызывается с параметром Sbegin, именем корневой директории аккаунта. Функция сканирует эту директорию и, как только натыкается на вложенную папку, проверяет, можно ли ее сканировать, после чего, возможно, эта вложенная папка превращается в сканируемую. И опять: функция сканирует уже эту вложенную папку, и, если опять натыкается на папку, вложенную в эту вложенную папку, то начинает сканировать уже ее. И так продолжается до тех пор, пока функция не доберется до папки, где вложенных папок нет (согласитесь, что такая рано или поздно найдется). Дойдя до такой, функция возвращается на шаг назад и сканирует следующую вложенную папку. Если таковой не находит, то возврат идет дальше. Попробуйте себе все это представить - и сразу поймете, если еще не поняли.

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


Схема сценария файлового менеджера



Рисунок 11.2. Схема сценария файлового менеджера


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

Что ж - от общих слов перейдем к самому тексту файлов сценария.



СОЗДАНИЕ НОВОЙ ПАПКИ, ВЫПОЛНЕНИЕ ДЕЙСТВИЯ


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

if ($md!="") {

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

$newname=strtr($newname, " []{},/\!@#$%л&*",

и создадим папку - командой mkdir, не забыв указать в ее параметрах полный путь к новой папке:

mkdir ($folder."/".$newname, 06 66);

Описание команды mkdir вы уже видели выше - в подразделе "Копирование, выполнение действия". Собственно, и все (Рисунок 11.15).



СОЗДАНИЕ НОВОЙ ПАПКИ, ЗАПРОС ИНФОРМАЦИИ


Для создания новой папки нужно получить от пользователя всего одно слово - имя этой самой новой папки.

Если на основной странице файлового менеджера было выбрано действие "Создание папки"...

if ($md!="")

то выведем на страницу поле для его ввода (его содержимое передастся сценарию-обработчику в переменной Snewname):

echo ("Введите имя папки: <brxinput type=text size=30 name=newname>");

и кнопку, запускающую это самое создание:

echo ("<input type=submit value=\"Создать папку \ " name=md>");

Собственно, и все.

}

?>

Посетитель увидит поле ввода для имени новой папки (Рисунок 11.9)



Создание новой папкиПоле ввода ее имени



Рисунок 11.9. Создание новой папки. Поле ввода ее имени




Способы выбора действияЛучше тот, что справа.



Рисунок 11.1. Способы выбора действия. Лучше тот, что справа.


Комментарий:

Возникает вопрос: а как сценарий-обработчик различит, какая кнопка из четырех была нажата? Очень просто. Каждой кнопке необходимо присвоить имя и проверять в сценарии-обработчике значение переменной с этим именем. Кнопка - это часть формы, следовательно, для нее тоже создается переменная с тем же именем, а также элементы массивов $ HTTP_POST_VARS[имя кнопки] и в РНР версии 4.1 и старше - и $ POST [имя кнопки]. Обработка формы происходит так, что в ту переменную, имя которой совпадает с именем нажатой кнопки, будет помещено значение value этой кнопки (т. е. то, что было надписью на ней), переменные же остальных трех кнопок будут пустыми. На основании «пустоты» или «непустоты» соответствующих переменных и будем судить о том, какое действие выбрал посетитель.

Для того, чтобы посетитель имел возможность указать сценарию объекты действий - т. е. над какими файлами эти действия должны производиться - достаточно против каждого имени файла поставить checkbox (пункт, который можно отметить галочкой, см. на Рисунок 11.3-11.6), разместив все checkbox'bi в единой форме, а затем анализировать состояние checkbox`ов в программе-обработчике формы.

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

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

для проведения копирования - папку, в которую копировать выбранные файлы;

для совершения переименования - новые имена для переименовываемых файлов;

для создания новой папки - имя этой новой папки;

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


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

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

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

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

Итого для реализации сценария потребуется создать три файла:

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

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

файл действия, собственно выполняющий выбранное действие, а после его выполнения - автоматически перенаправляющий посетителя на основной файл.

Графическую схему смотрите на Рисунок 11.2.


ТЕКСТ СЦЕНАРИЯ



ТЕКСТ СЦЕНАРИЯ

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

Файл index.php

<html> <?php

$begin="files";

if ((strpos($fold,$begin)!=0)(strpos($fold,"..")!=False) | | ($fold== ¦¦'¦)) {

$dirct=$begin; }

else {

$dirct=$fold; }

echo ("<form action=zapros.php?folder=$dirct method=post>");

if ($dirct!=$begin) {

$back=substr ($dirct, 0, strrpos($dirct, "/")); echo ("<br><bxa href=index.php?fold=$back>KopHeBaH папка</ах/Ь><br>") ;

}

$hdl=opendir($dirct); while ($file = readdir($hdl)) {

if (($file!="..")&&($file!=".")) { $a[]=$file;

} }

closedir($hdl);

175

if (sizeof($a)>0)

asort($a); foreach ($a as $k)

$full=$dirct."/". $k;

echo ("<input name=fl[] value=$k type=checkbox>") ;

if (is_dir($full)==True)

echo ("<a href=index.php?fold=$fullxb>rianKa $k</bx/a>") ;

else

echo ("<a href=$full>$k</a>"); echo ("<br>");

?><brxinput type=submit value= "Удалить " name=udalxbr><input type=submit value="Переименовать" name=renxbrxinput type=submit value="Копировать" name=copyxbrxinput type=submit value="Создать папку" name=md>

</form></html>

Файл zapros.php

<htmlx?php

echo ("<form action=do.php?folder=$folder method=post>"); if ($udal!="") {

echo ("Удалить файлы?<br>"); foreach ($fl as $i) {

echo ("<input type=hidden name=fl[] value=$i>$i из папки $folder<br>");

}

echo ("<input type=submit value=\"Удалить\" name=udal>"); }

function tree($fld) {

global $ folder; global $fl;

176

$hdl=opendir($fld); while ($file = readdir($hdl)) {

if (($file!=".")&&($file!="..")) {

$fllnm=$fld."/".$file; if (is_dir($fllnm)==True) {


else {

unlink ($folder."/".$i); } } }

if ($ren!="") {

for ($i = 0; $i < sizeof ($afl); $i++) {

if (($rfl[$i] ! = -)&($rfl[$i] !=$afl[$i])& (strpos($afl[$i],"..")==False)) {

$rfl[$i]=strtr($rfl[$i], " []{},/\!@#$%л&*",

'.__________________________" ) I

rename ($folder."/".$af1[$i] , $folder."/".$rfl[$i]); }

} }

function copyfold ($rt, $fld, $tgt) {

if (file_exists($tgt."/".$fId)!=True) {

mkdir ($tgt."/".$fld, 0666); }

179

$hdl=opendir($rt.V".$fld); while ($file = readdir($hdl)) {

if (($file!="..")&&($file!=".")) {

if (is_dir($rt."/".$fld."/".$file)==True) {

copyfold($rt."/"-$fld, $file, $tgt."/".$fId); }

else {

copy ($rt."/".$fld."/".$file, $tgt."/".$fld."/".$file) ;

} } } closedir($hdl);

}

if ($copy!="") {

foreach ($fl as $i) {

if (is_dir($folder."/".$i)==True) {

if (!(strpos ($rd, $folder."/".$i)===0)) {

copyfold($folder, $i, $rd); } }

else { copy ($folder."/".$i, $rd."/".$i);

} } }

if ($md!="") { $newname=$folder."/".strtr($newname,

{},/\!@#$%л&*",

"____________________")

mkdir ($newname, 0666);

Header ("Location: index.php?fold=$folder")


Удаление файлов и папокЗапрос подтверждения



Рисунок 11.6. Удаление файлов и папок. Запрос подтверждения


Как и на первой странице, все эти скрытые поля сделаем массивом, для удобства - даже присвоим ему то же имя flf ]:

echo ("<input type=hidden name=fl[] value=$i>$i из папки $folder<br>");

}

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

echo ("<input type=submit value = \"Удалить" name=udal>");

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



УДАЛЕНИЕ, ВЫПОЛНЕНИЕ ДЕЙСТВИЯ


Для удаления файлов в РНР существует специальная команда - unlink.

Примечание:

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

Команда rmdir (имя директории с полным путем к ней) удаляет указанную в ее параметре директорию, если она пустая. Если удаляемая папка не пуста или сценарий не имеет прав на удаление директории, то на страницу выводится сообщение об ошибке.

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

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

function delfiles($fId)

$hdl=opendir($fld); while ($file = readdir($hdl)) {

if (($file!=".")&&($file!="..")) {

if (is_dir($fld."/".$file)==True) {

delfiles ($fId.»/".$file); rmdir ($fld."/".$file); }

else {

unlink ($fld."/".$file); } } }

closedir($hdl);

}

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

Сам код выполнения удаления выбранных пользователем файлов прост.

Если в качестве действия выбрано удаление...

if ($udal!="")


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

foreach ($fl as $i)

проверим на то, не является ли он директорией.

if (is_dir($folder."/".$i)==True)

{

Если является, то удалим все файлы в этой директории - рекурсивной функцией delf iles...

delfiles ($folder."/".$i); а затем и саму, уже опустошенную директорию - командой rmdir.

rmdir ($folder."/".$i); } else

А если этот объект, подлежащий удалению, является обычным файлом...

то для него припасена уже упомянутая выше команда unlink: unlink ($folder."/".$i); }

И так поступим с каждым объектом, имя которого передано сценарию через массив $fl, а путь к нему - через переменную Sfolder в адресной строке.

} Собственно, и все (Рисунок 11.10).


УДАЛЕНИЕ, ЗАПРОС ИНФОРМАЦИИ


Если на основной странице файлового менеджера - index.php - была нажата кнопка "Удалить", имя которой - "udal", то значение переменной $udal' в сценарии на странице-обработчике будет отличным от нуля (вернее, оно будет представлять собой значение атрибута value кнопки "Удалить" на основной странице -^собственно слово "Удалить"). Если была нажата какая-нибудь другая кнопка, то значение переменной $udal определено не будет. Поэтому для того, чтобы узнать, было ли посетителем выбрано в качестве желаемого действия удаление, проверим содержимое этой переменной:

if ($udal!="")

Выведем небольшое пояснение посетителю...

echo ("Удалить файлы?<bг>");

и список файлов, которые планируется удалить. Их список передан в форму в массиве $fl. В этот массив включены параметры value тех checkbox'oB, которые были отмечены посетителем на основной странице менеджера файлов перед тем, как нажать кнопку с названием действия (а в эти параметры, как вы наверняка помните, были помещены имена файлов и папок, отображавшихся на основной странице файлового менеджера - путь к ним передан через переменную Sfolder в адресной строке). Переберем все элементы массива $ f 1...

foreach ($fl as $i)

{

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

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

Строго говоря, вместо имен переменных, равных именам элементов формы, обрабатываемой сценарием на этой странице, следовало бы использовать имена соответствующих элементов массивов SHTTP_POST_VARS[] и (в РНР начиная с версии 4.1) $_POST[]. Однако для более легкого понимания кода выбран первый вариант. При использовании настоящего сценария на практике вы можете сами заменить имена переменных на имена элементов упомянутых массивов.

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

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



ЗАВЕРШЕНИЕ ДЕЙСТВИЯ


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

Если на странице запроса дополнительной информации посетителем была нажата кнопка "Отмена", то ни один из вышеприведенных блоков исполнения действия выполнен не будет - ни одна из переменных Sudal, $copy, $ren, $md определена не будет. А эта команда выполнится - т. е. после нажатия кнопки "Отмена" на странице запроса дополнительной информации посетитель окажется на основной странице файлового менеджера.

Header ("Location: index.php?fold=$folder");

Примечание:

Заголовок - это данные, передаваемые браузеру до передачи самой web-страницы, сообщающие ему некоторые параметры передаваемого файла или определенные команды. Список всех возможных заголовков, которые обязаны поддерживать современные браузеры, можно найти в спецификациях протокола HTTP - они есть, например, на сайте http://www.w3.org. PHP-команда Header выполняет всего одно действие -она просто передает то, что указано в ее параметре, в браузер, запросивший страницу, на которой она находится, в качестве заголовка.

Заголовок "Location" осуществляет перенаправление браузера на страницу, указанную в его параметре.

Сценарий можно завершить - посетитель уже на другой странице...

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

Однако если в процессе выполнения сценария возникнет ошибка (скажем, новую папку создать не удастся, так как окажется, что папка с таким же именем уже существует, или возникла проблема при переименовании файла - см.Рисунок 11.14), то на страницу будет выведено сообщение об ошибке, а команда Header не сработает - и тоже выдаст сообщение об ошибке (так как заголовок можно отправлять только до какого-либо вывода на страницу). Если вас это не устраивает - то можете либо предварять все команды символом "@" (он запрещает выводить сообщения об ошибках), либо снабдить страницу со сценариями выполнения действия в самом низу небольшим комментарием (с информацией вроде "Возникла ошибка, извините...") и ссылкой на главную страницу файлового менеджера.


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

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

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