PHP: ФАЙЛОВЫЙ МЕНЕДЖЕР
В этой главе, самой объёмной из всех, будет рассмотрен довольно большой сценарий - файловый менеджер. Расположив его на странице, вы дадите посетителю возможность работать с файлами в определенной директории вашего сайта. Это может потребоваться, например, при выделении коллеге раздела сайта для самостоятельного ведения, таким образом при помощи web-интерфейса он сможет размещать материалы в своем разделе, не обращаясь к владельцу самого сайта.
В данном сценарии будет не так уж и много новых команд, не рассмотренных в предыдущих главах. Основная цель его приведения и разбора, помимо собственно предоставления текста такого сценария, - это демонстрация реально работающей большой программы. Просто вчитайтесь в код и постарайтесь понять логику программ и принципы использования команд.
ПРОЕКТИРОВАНИЕ
Перед началом работы стоит сесть и продумать план функционирования будущего сценария. Возможность каких действий он будет предоставлять пользователю? Ну, наверное, таковых по меньшей мере четыре:
во-первых, файловый менеджер должен позволять посетителю удалять файлы и папки с сайта; во-вторых - их копировать; в-третьих - их переименовывать; и в четвертых - создавать новые папки. Можно было бы прибавить еще возможность загрузки файлов на сайт, но сценарий, реализующий ее, рассматривался в 7-й главе этой книги, так что здесь его рассматривать не будем. Добавить его к готовой программе, думается, вам труда не составит.
Ну и, разумеется, файловый менеджер должен отображать список файлов в определенной директории, а также позволять путешествовать по всем вложенным в эту директорию папкам и выбирать файлы и папки для проведения действий.
Комментарий:
Функция перемещения файлов раскладывается на два этапа — их копирование в другую папку и удаление с исходного места. Рассматриваемый в этой главе файловый менеджер позволяет и копировать, и удалять файлы, так что переместить файлы сего помощью тоже возможно. При желании вы можете впоследствии дополнить его функцией перемещения, попросту совместив подпрограммы копирования и удаления файлов.
Как все это сделать?
Как посетителю выбрать вид действия над файлами? Можно, конечно, выделить для каждого указанного выше действия по отдельной web-странице, разместить на этих страницах соответствующие сценарии, при этом для их выполнения посетителю придется заходить на нужную страницу. Однако это весьма неудобно. Лучше дать посетителю возможность выбирать вид действия прямо на той же самой странице, где отображается список файлов.
Как осуществить такой выбор действия? Можно, конечно, создать группу единственного выбора, состоящую из нескольких radio buttons (она представляет собой группу кружков, отметить из которых можно только один - рис. 11.1, слева), а в сценарии выполнения действия определять, какой пункт был отмечен. Однако интереснее будет для выбора действия использовать кнопки типа submit (рис. 11.1, справа), нажатие которых вызовет переход на страницу-обработчик формы, содержащей эти кнопки. Посетитель, отметив файлы, просто нажмет нужную кнопку и будет ждать результата.
Рис. 11.1. Способы выбора действия. Лучше тот, что справа.
Комментарий:
Возникает вопрос: а как сценарий-обработчик различит, какая кнопка из четырех была нажата? Очень просто. Каждой кнопке необходимо присвоить имя и проверять в сценарии-обработчике значение переменной с этим именем. Кнопка - это часть формы, следовательно, для нее тоже создается переменная с тем же именем, а также элементы массивов $ HTTP_POST_VARS[имя кнопки] и в РНР версии 4.1 и старше - и $ POST [имя кнопки]. Обработка формы происходит так, что в ту переменную, имя которой совпадает с именем нажатой кнопки, будет помещено значение value этой кнопки (т. е. то, что было надписью на ней), переменные же остальных трех кнопок будут пустыми. На основании «пустоты» или «непустоты» соответствующих переменных и будем судить о том, какое действие выбрал посетитель.
Для того, чтобы посетитель имел возможность указать сценарию объекты действий - т. е. над какими файлами эти действия должны производиться - достаточно против каждого имени файла поставить checkbox (пункт, который можно отметить галочкой, см. на рис. 11.3-11.6), разместив все checkbox'bi в единой форме, а затем анализировать состояние checkbox`ов в программе-обработчике формы.
Итак, в первом файле нашего файлового менеджера должна располагаться форма, содержащая список файлов и папок в текущей директории (выводимый специальным сценарием, позволяющим также перемещаться по вложенным папкам) со спескЬох'ами возле каждого названия файла или папки, а в низу формы должны располагаться четыре кнопки типа submit с различными именами.
Но простого выбора действия недостаточно для начала его выполнения. Перед тем, как совершать работу, у посетителя необходимо еще выяснить:
Следовательно, необходим промежуточный этап - запрос дополнительных данных от посетителя. Именно на страницу со сценариями, запрашивающими нужные сведения, и должен совершаться переход с основной страницы менеджера файлов при выборе посетителем какого-либо действия. В зависимости от выбранного действия запрашивается соответствующая информация. Лучше всего осуществить ее
ввод в расположенную на промежуточной странице пользовательскую форму.
После получения от посетителя дополнительной информации можно и выполнять сами действия. Сценарии, выполняющие их, должны находиться в отдельном файле, который будет назначен обработчиком форм на промежуточной странице.
После выполнения действий вполне логично осуществлять автоматический переход на основную страницу файлового менеджера.
Итого для реализации сценария потребуется создать три файла:
Графическую схему смотрите на рис. 11.2.
Рис. 11.2. Схема сценария файлового менеджера
Остается добавить пожелание позволять посетителю работать только с файлами в определенной директории, а сами страницы со сценариями разместить вне ее, чтобы посетитель по недосмотру либо по злому умыслу не мог испортить содержимое сайта.
Что ж - от общих слов перейдем к самому тексту файлов сценария.
ФАЙЛ 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. Обратите внимание на адресную строку браузера.
Рис. 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>");
Все это делается, естественно, лишь в том случае, если текущая папка - не корневая.
}
Теперь можно и сканировать текущую папку. Получаем список файлов в ней (пояснения смотрите в гл. 6 - в описании сценария "Папкопотрошилка"):
$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.4. Файловый менеджер. Главная страница. Курсор - на ссылке, ведущей на папку
Рис. 11.5. Файловый менеджер. Главная страница. Исходный код Рассмотрим следующие компоненты файлового менеджера.
ФАЙЛ ZAPROS.PHP
Сценарии этого файла запрашивают от посетителя дополнительную информацию для проведения действия. Начало сценария:
<html><?php
Сразу же выведем заголовок формы, общий для всех четырех вариантов запросов. В нем точно так же передадим выполняющему собственно действия сценарию на странице do.php имя текущей папки:
echo ("<form action=do.php?folder=$folder method=post>");
Бесспорно, можно было бы и не передавать имя текущей папки, а сообщать обработчику конкретные имена файлов, над которыми совершается действие, с полным путем к ним, однако так все-таки проще.
УДАЛЕНИЕ, ЗАПРОС ИНФОРМАЦИИ
Если на основной странице файлового менеджера - 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[]. Однако для более легкого понимания кода выбран первый вариант. При использовании настоящего сценария на практике вы можете сами заменить имена переменных на имена элементов упомянутых массивов.
рядом с каждым именем удаляемого файла поместим скрытое поле, в которое поместим это самое имя. Сценарию, выполняющему удаление, останется только совершить эту операцию над теми файлами, имена которых ему будут переданы.
Имя же папки, в которой эти файлы располагаются, передается сценарию - исполнителю действия через адресную строку, указанную в заголовке формы запроса информации - точно так же, как оно было передано данному сценарию, запрашивающему дополнительную информацию. Вы могли видеть этот заголовок несколькими абзацами выше.
Рис. 11.6. Удаление файлов и папок. Запрос подтверждения
Как и на первой странице, все эти скрытые поля сделаем массивом, для удобства - даже присвоим ему то же имя flf ]:
echo ("<input type=hidden name=fl[] value=$i>$i из папки $folder<br>");
}
И наконец, выведем кнопку, запускающую собственно удаление. Назовем ее точно так же, как и расположенную на основной странице - для единообразия:
echo ("<input type=submit value = \"Удалить" name=udal>");
В результате в том случае, если посетитель на основной странице отметит файлы и папки и нажмет кнопку "Удалить", то на странице запроса подтверждения (рис. 11.6) ему будет выдан список выбранных им файлов и предложено нажать одну из двух кнопок - "Удалить" или "Отмена" (код, выводящий последнюю, размещен в конце страницы -смотрите ниже).
КОПИРОВАНИЕ, ЗАПРОС ИНФОРМАЦИИ
Для того, чтобы произвести копирование файлов и папок из одной папки в другую, простого согласия пользователя на это действие мало. Нужно еще узнать, а в какую папку необходимо производить копирование.
А что нужно, чтобы это узнать? Необходимо выдать запрос на получение имени этой папки у пользователя. Можно, конечно, предоставить пользователю возможность просто ввести новый путь в поле ввода текста, как при использовании команды сору в командной строке 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);
РЕКУРСИВНАЯ ФУНКЦИЯ ВЫВОДА СПИСКА ДОСТУПНЫХ ДЛЯ КОПИРОВАНИЯ ПАПОК НА АККАУНТЕ
В реальном сценарии эта функция должна находиться в его начале или в любом случае до места ее первоначального вызова (см. полный текст рассматриваемых сценариев в конце главы). Поэтому при создании программы, подобной этому файловому менеджеру, лучше всего поставить такую функцию сразу перед блоком команд выдачи запроса дополнительной информации для копирования.
Функции передается один параметр - имя папки, список вложенных папок в которой она должна выдать.
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.7. Копирование. Запрос папки назначения
После вывода списка папок, которые могут служить местом назначения копирования, осталось лишь добавить к нему корневую папку аккаунта, если, конечно, копируемые файлы находятся не в ней (как вы могли заметить, имя этой папки вышеприведенная функция не выводит)...
if ($begin!=$folder) {
echo ("<brxinput name=rd type=radio value=$begin>$begin<br>");
и вывести кнопку запуска копирования.
echo ("<input type=submit value=\"Скопировать\" name=copy>");
Все.
}
Если посетитель на основной странице файлового менеджера отметит файлы и выберет функцию копирования, то ему будет выдан список папок, в которые может быть произведено копирование (рис. 11.7). Выбрав любую из них, для запуска копирования останется лишь нажать соответствующую кнопку.
ПЕРЕИМЕНОВАНИЕ, ЗАПРОС ИНФОРМАЦИИ
Для переименования файла нужно узнать у пользователя новое имя для этого файла. Именно это и делает выводимая нижеследующим сценарием на страницу форма. Однако сценарию-обработчику данной формы необходимо передать как старое имя файла, так и новое, чтобы ему было понятно, какой файл необходимо переименовывать.
Если на основной странице была нажата кнопка "Переименовать"...
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, переданной ему в ссылке на страницу со сценарием-обработчиком, указанной в заголовке формы (см. ранее).
Рис. 11.8. Переименование. Запрос новых имен
СОЗДАНИЕ НОВОЙ ПАПКИ, ЗАПРОС ИНФОРМАЦИИ
Для создания новой папки нужно получить от пользователя всего одно слово - имя этой самой новой папки.
Если на основной странице файлового менеджера было выбрано действие "Создание папки"...
if ($md!="")
то выведем на страницу поле для его ввода (его содержимое передастся сценарию-обработчику в переменной Snewname):
echo ("Введите имя папки: <brxinput type=text size=30 name=newname>");
и кнопку, запускающую это самое создание:
echo ("<input type=submit value=\"Создать папку \ " name=md>");
Собственно, и все.
}
?>
Посетитель увидит поле ввода для имени новой папки (рис. 11.9)
Рис. 11.9. Создание новой папки. Поле ввода ее имени
КНОПКА ОТМЕНЫ
Для того, чтобы со страницы запроса информации можно было уйти без последствий, если посетитель передумает что-либо делать со своими файлами, поместим на эту страницу кнопку "Отмена" (см. рис. 11.6-11.9):
<brxinput type=submit value="OTMeHa" name=otmena>
Собственно, это и есть весь текст этой страницы:
</form></html>
Графическое оформление - уже на вкус сайтостроителя.
ФАЙЛ DO.PHP
Сценарий этого файла выполняет выбранное посетителем действие и перенаправляет его назад на основную страницу файлового менеджера.
На этой странице будет находиться только программный код. Поскольку пользователь после выполнения действия сразу будет перенаправлен на основную страницу, какой-либо дизайн данной страницы излишен.
Начнем сценарий. Точно так же в его начале запишем в переменную имя корневой папки аккаунта - дабы потом его использовать в сценариях:
<?php
$begin="files";
Целесообразность проверки содержимого переменной Sfolder на предмет возможности попытки взлома сайта может представиться сомнительной. Эта переменная передается посредством заголовка формы и в адресной строке не отображается, однако ничто не мешает злоумышленнику сохрацить страницу с формой запроса информации на свой жесткий диск, исправить ее содержимое и, заменив относительные ссылки на абсолютные, перейти с сохраненного варианта на страницу выполнения действия. Так что проверять переменную Sfolder на наличие в ней ссылки на родительский каталог (двух точек подряд), а также удостовериться, что путь, записанный в ней, начинается с имени корневой папки аккаунта, всё же необходимо. Если последнее неверно или в Sfolder можно найти две точки подряд, то дальнейшее выполнение кода прекращается:
If
((strpos($folder,$begin)!=0)||(strpos($folder,"..")
!=False))
158
exit; }
Примечание:
Команда exi t полностью прекращает выполнение кода и выведение текста на странице, на которой она расположена - как если бы именно на ней страница заканчивалась. Обратите внимание, что эта команда завершает не выполнение РНР-программы, а вывод страницы - т. е. HTML-код после завершающего тэга РНР-сценария, если таковой есть, выводиться также не будет.
Хотя, бесспорно, если посетитель может загружать на свой аккаунт файлы с PHP-программами, и эти программы могут выполняться, никакие "защиты" в файловом менеджере не спасут сайт от взлома. Так что "защита" в этом сценарии является скорее демонстрацией.
УДАЛЕНИЕ, ВЫПОЛНЕНИЕ ДЕЙСТВИЯ
Для удаления файлов в РНР существует специальная команда - 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).
Рис. 11.10. Удаление выполнено
КОПИРОВАНИЕ, ВЫПОЛНЕНИЕ ДЕЙСТВИЯ
С копированием будет посложнее. Напомню, что из сценария запроса дополнительной информации передан массив Sfl, состоящий из имен копируемых файлов и папок, и переменная $rd, содержащая имя с полным путем к той папке, в которую планируется осуществить копирование. Ну и, разумеется, путь к текущей папке, в которой изначально и находятся копируемые файлы - $folder.
Сначала напишем функцию копирования целой папки со всеми вложенными в нее папками на новое место. Эта функция, как и функции удаления папки и вывода списка всех папок на аккаунте, будет рекурсивной - т. е. с вызовом самой себя из своего кода. Алгоритм ее прост:
Рано или поздно функция дойдет до папок, содержащих только файлы, которые и станут прерывать рекурсию.
Функция будет получать полное имя (вместе с путем) папки, в которой находится копируемая папка, имя этой самой копируемой папки, и имя (вместе с путем) папки назначения копирования (т. е. той, в которую будет производиться копирование).
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" - для посетителей из Интернета. Вы можете разрешить или запретить три вида действий: чтение, запись и запуск на исполнение (последнее имеет смысл только для программ).
Рис. 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.12. Файлы скопированы
ПЕРЕИМЕНОВАНИЕ, ВЫПОЛНЕНИЕ ДЕЙСТВИЯ
В том случае, если посетитель выбрал в качестве требуемого действия переименование, то на странице запроса дополнительной информации ему пришлось ввести новые имена для тех файлов и папок, переименовать которые он пожелал. Сценарию же выполнения действия передалось два массива со старыми и новыми именами этих обьектов -$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 поставить знак @ - тогда сообщений об ошибке не будет (стандартный способ запрета на вывод такого сообщения), но тогда пользователь даже не узнает об ошибке.
Рис. 11.13. Файлы переименованы
Рис. 11.14. Ошибка при переименовании - сообщение РНР
А можно использовать несколько оригинальное решение - заранее посмотреть, есть ли в папке файл с таким же именем, как и то, которое пользователь желает дать переименовываемому файлу, и если есть, то добавить к новому имени файла спереди знак подчеркивания - "_". Код, реализующий это, прост:
while (file_exists($folder."/".$rfl[$i])==True)
{
$rfl[$i] = "_".$rfl[$i];
}
Как нетрудно понять, он добавляет в начало нового имени файла знак "_", если файл с таким именем уже существует в той же папке, куда предполагается копировать файл. Если же и таковой файл - со знаком "_" в начале -уже в папке имеется, то к новому имени добавляется еще один такой символ, и так до тех пор, пока новое имя не станет уникальным.
СОЗДАНИЕ НОВОЙ ПАПКИ, ВЫПОЛНЕНИЕ ДЕЙСТВИЯ
Создание новой папки выполняет самый маленький фрагмент кода во всем сценарии. Даже меньше того, что выводил запрос имени для этой новой папки.
if ($md!="") {
Как и в сценарии переименования файла, исключим из имени новой папки недопустимые символы...
$newname=strtr($newname, " []{},/\!@#$%л&*",
и создадим папку - командой mkdir, не забыв указать в ее параметрах полный путь к новой папке:
mkdir ($folder."/".$newname, 06 66);
Описание команды mkdir вы уже видели выше - в подразделе "Копирование, выполнение действия". Собственно, и все (рис. 11.15).
Рис. 11.15. Папка создана
ЗАВЕРШЕНИЕ ДЕЙСТВИЯ
Ну и по окончании выполнения действий перенаправим посетителя на основную страницу файлового менеджера - в ту же директорию, в которой он и находился, передав сценарию на основной странице путь к ней через переменную 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
забудьте разве что вставить код проверки содержимого авторизационных переменных на все страницы файлового менеджера, а не только на основную.) Можно также научить сценарий выдавать осмысленные сообщения об ошибках, если они возникнут, заставить его выдавать подтверждения, скажем, в случае существования в папке назначения копирования файлов и папок, одноименных копируемым - дабы предотвратить случайное их переписывание. Но это все остается уже на ваше усмотрение.
ТЕКСТ СЦЕНАРИЯ
Для лучшего восприятия информации данной главы далее приводится текст разобранного выше сценария без каких-либо комментариев.
Файл 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) {
$ПО=0;
foreach ($fl as $i) {
if ($fllnm==$folder."/".$i) {
$no=l; } }
if ($no==0) {
if ($fllnm!=$folder) {
echo ("<input name=rd type=radio value=$fllnm>$fllnm<br>");
}
tree ($fllnm); } } } V
closedir($hdl); }
if ($copy!="") {
$begin="files";
echo ("Куда копировать файлы?<br>"); foreach ($fl as $i) {
echo ("<input type=hidden name=fl[] value=$i>$folder/$i<br>");
}
echo ("<br>Выберите папку для копирования:<br>"); tree($begin); if ($begin!=$folder) {
echo ("<input name=rd type=radio value=$begin>$begin<br>");
177
}
echo ("<input type=submit value=\"Скопировать\" name=сору>"); }
if ($ren!="") {
echo ("Переименовать файлы?<Ьг>"); foreach ($fl as $i) {
echo ("<input type=hidden name=afl[] value=$i>"); echo ("$i");
echo ("<input type=text size=3 0 name=rfl[] value=$ixbr>") ;
}
echo ("<input type=submit value=\"Переименовать\" name=ren>");
}
if ($md!="") {
echo ("Введите имя папки:<brxinput type=text size=3 0 name=newnamexbr><input type=submit value=\"Создать папку\" name=md>");
} ?>
<brxinput type=submit value="OTMeHa" name=otx/form>
</html>
Файл do.php
<?php
$begin="files";
if ((strpos($folder,$begin)!=0)|| (strpos ($folder, ".. . ") !=False) ) {
exit; }
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) {
delfiles ($folder."/".$i) ; rmdir ($folder."/".$i); }
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")