PHP: Полезные приемы
Автор: Антон Орлов, orlovs.pp.ru

  Главная   Учебник   Статьи   FAQ   Книги   Ссылки  

Глава 11. PHP: файловый менеджер

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

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

Проектировка

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

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

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

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

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

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

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

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


Рис.11.1. Способы выбора действия. Лучше тот, что справа.

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

Возникает вопрос - а как сценарий-обработчик различит, какая кнопка из четырех была нажата? Очень просто - каждой кнопке присвоим имя и будем проверять в сценарии-обработчике значение переменной с этим именем, так как кнопка - тоже часть формы и для нее тоже создается переменная с тем же именем, что и у кнопки (а также элементы массивов $HTTP_POST_VARS[имя_кнопки] и в PHP версии 4.1 и старше - и $_POST[имя_кнопки]). Обработка формы происходит так, что в ту пе-ременную, имя которой совпадает с именем нажатой кнопки, будет помещено зна-чение value этой кнопки (т.е. то, что было надписью на ней), переменные же ос-тальных трех кнопок будут пустыми. На основании пустоты или непустоты соот-ветствующих переменных и будем судить о том, какое действие выбрал посетитель.

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

Итак, в первом файле нашего файлового менеджера должна распо-лагаться форма, содержащая список файлов и папок в текущей дирек-тории (выводимый специальным сценарием, позволяющим также пе-ремещаться по вложенным папкам) со checkbox'ами возле каждого названия файла или папки, а в низу формы должны располагаться че-тыре кнопки типа submit с различными именами.

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

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

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

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

Итого для реализации сценария потребуется создать три файла:

Графическую схему смотрите на рис. 11.2.


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

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

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

Файл index.php

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

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

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

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

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


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

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

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

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

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

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

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

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

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

<html><?php

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

$begin="files";

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

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

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

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

Примечание:

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

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

if (strpos("строка", "символы")===0) {…

будет верно, если "строка" начинается с "символов".

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

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

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

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

$dirct=$begin;
}
else
{

...а если переменная $fold "в порядке", то в переменную $dirct по-местим именно ее значение:

$dirct=$fold;
}

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

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

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

if ($dirct!=$begin)
{

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

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

Примечание:

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

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

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

echo ("<a href=index.php?fold=$back>Корневая папка</a><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 считывает в указанную в его параметрах переменную - в дан-ном случае $k - все элементы массива - в данном случае $a - по очереди, выполняя каждый раз указанный после него в фигурных скобках код, в котором указанная переменная может использоваться. Foreach будет работать только в PHP 4.0 и выше. Если вы можете использовать лишь PHP3, то вместо него можно использовать цикл for, указав в его параметрах величину массива $l.

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

$full=$dirct."/".$k;

Теперь выведем checkbox:

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

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

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

if (is_dir($full)==True)
{

Примечание:

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

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

echo ("<a href=index.php?fold=$full><b>Папка $k</b></a>");

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

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

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

}
else
{

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

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

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

echo ("<br>");

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

}

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

}
?>

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

<br><input type=submit value="Удалить" name=udal><br><input type=submit value="Переименовать" name=ren><br><input type=submit value="Копировать" name=copy><br><input type=submit value="Создать папку" name=md>

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

</form></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 (Строго говоря, вместо имен переменных, равных именам элементов формы, обрабатываемой сценарием на этой странице, следовало бы использовать имена соответствующих элементов массивов $HTTP_POST_VARS[] и (в PHP начиная с версии 4.1) $_POST[]. Однако для более легкого понимания кода выбран первый вариант. При использовании настоящего сценария на практике вы можете сами за-менить имена переменных на имена элементов упомянутых массивов) в сценарии на странице-обработчике будет отлич-ным от нуля (вернее, оно будет представлять собой значение атрибута value кнопки "Удалить" на основной странице - собственно слово "Удалить"). Если была нажата какая-нибудь другая кнопка, то значе-ние переменной $udal определено не будет. Поэтому для того, что-бы узнать, было ли посетителем выбрано в качестве желаемого дейст-вия удаление, проверим содержимое этой переменной:

if ($udal!="")
{

Выведем небольшое пояснение посетителю...

echo ("Удалить файлы?<br>");

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

foreach ($fl as $i)
  {

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

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

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

Как и на первой странице, все эти скрытые поля сделаем массивом, для удобства - даже с тем же именем fl[]:

 echo ("<input type=hidden name=fl[] value=$i>$i из папки $folder<br>");
  }

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

echo ("<input type=submit value=\"Удалить\" name=udal>");
}

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


Рис.11.6. Удаление файлов и папок. Запрос подтверждения.

Копирование, запрос информации

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

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

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

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

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

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

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

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

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

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

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

Начало кода:

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.
tree($begin);

Рекурсивная функция вывода списка доступных для копирования папок на аккаунте

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

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

function tree($fld)
{

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

global $folder;
global $fl;

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

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

В PHP 4.1 версий и выше массив $fl доступен и через массив $_POST. В отличие от $HTTP_POST_VARS этот массив автоглобальный - то есть для использования в функциях его элементов обьявление самого массива в функциях производить не надо.

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

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

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

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

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

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

   if (is_dir($fllnm)==True)
    {

...то выясним:

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

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

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

     $no=0;
     foreach ($fl as $i)
      {
       if ($fllnm==$folder."/".$i)
        {
         $no=1;
        }
      }

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

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

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

Используйте такой же способ, если вам надо узнать, произошло ли то или иное событие внутри какого-нибудь цикла - установите до цикла переменную в ноль, а внутри цикла в случае совершения события присвойте ей значение 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 в форме отмечен-ным может быть только один элемент, то переменная в любом случае передастся всего одна.

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

        }
...и вот он - рекурсивный вызов функции tree:
       tree ($fllnm);

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

      }
    }
  }
 }
... и "потрошимую" директорию.
closedir($hdl);

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

}

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

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


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

if ($begin!=$folder)
{
echo ("<br><input name=rd type=radio value=$begin>$begin<br>");
}
...и вывести кнопку запуска копирования.
echo ("<input type=submit value=\"Скопировать\" name=copy>");
Все.
}

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


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

Переименование, запрос информации

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

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

if ($ren!="")
{
...то выведем пояснение пользователю...
echo ("Переименовать файлы?<br>");

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

foreach ($fl as $i)
{

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

echo ("<input type=hidden name=afl[] value=$i>");
Выведем старое имя файла...
echo ("$i");

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

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

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

}

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

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

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

}

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


Рис.11.8. Переименование. Запрос новых имен.

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

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

Создание новой папки, запрос информации

Для создания новой папки нужно получить от пользователя всего одно слово - имя этой самой новой папки.

Если на основной странице файлового менеджера было выбрано действие "Создание папки"...

if ($md!="")
{

...то выведем на страницу поле для его ввода (его содержимое пере-дастся сценарию-обработчику в переменной $newname):

echo ("Введите имя папки:<br><input type=text size=30 name=newname>");
и кнопку, запускающую это самое создание:
echo ("<input type=submit value=\"Создать папку\" name=md>");
Собственно, и все.
}
?>

Посетитель увидит поле ввода для имени новой папки (рис.11.9)


Рис.11.9. Создание новой папки. Поле ввода ее имени.

Кнопка отмены

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

<br><input type=submit value="Отмена" name=otmena>
Собственно, это и есть весь текст этой страницы:
</form></html>

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


Файл do.php

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

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

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

<?php
$begin="files";

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

if ((strpos($folder,$begin)!=0)||(strpos($folder,"..")!=False))
{
exit;
}

Примечание:

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

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

Удаление, выполнение действия.

Для удаления файлов в PHP существует специальная команда - unlink.

Примечание:

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

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

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

И снова используем рекурсию. Вот как выглядит рекурсивный ал-горитм удаления всего содержимого папки:

function delfiles($fld)
{
$hdl=opendir($fld);
while ($file = readdir($hdl))
 {
  if (($file!=".")&&($file!=".."))
   {
    if (is_dir($fld."/".$file)==True)
     {
      delfiles ($fld."/".$file);
      rmdir ($fld."/".$file);
     }
     else
     {
      unlink ($fld."/".$file);
     }
   }
 }
closedir($hdl);
}

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

Сам код выполнения удаления выбранных пользователем файлов прост.

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

if ($udal!="")
{

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

 foreach ($fl as $i)
  {
...проверим на то, не является ли он директорией.
   if (is_dir($folder."/".$i)==True)
    {

Если является, то удалим все файлы в этой директории - рекурсив-ной функцией delfiles...

     delfiles ($folder."/".$i);

...а затем и саму, уже опустошенную директорию - командой rmdir.

     rmdir ($folder."/".$i);
    }
    else

А если этот объект, подлежащий удалению, простой файл...

    {
...то для него припасена уже упомянутая выше команда unlink:
     unlink ($folder."/".$i);
    }

И так - с каждым обьектом, имя которого передано сценарию через массив $fl, а путь к нему - через переменную $folder в адресной стро-ке.

  }
Собственно, и все (рис.11.10).
}

Рис.11.10. Удаление выполнено.

Копирование, выполнение действия.

С копированием будет посложнее. Напомню, что из сценария за-проса дополнительной информации передан массив $fl, состоящий из имен копируемых файлов и папок, и переменная $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.10), доступном через пункт "Chmod" меню правой кнопки мыши любого файла на сайте. "Owner permissions" - это разрешения для других программ на том же аккаунте, а "Public permissions" - для посетителей из Интернета. Вы можете разрешить или за-претить три вида действий: чтение, запись и запуск на исполнение (последнее имеет смысл только для программ).


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

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

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

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

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

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

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

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

Если же очередной объект из "потрошимой" папки $fld."/".$nm - простой файл…

       }
      else
       {

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

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

Примечание:

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

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

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

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

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

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

       }
     }
   }
...и открытую "потрошимую" директорию:
  closedir($hdl);
 }

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

if ($copy!="")
 {

Переберем все обьекты, подлежащие копированию:

  foreach ($fl as $i)
   {

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

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

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

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

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

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

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

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

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

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

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

     }
     else
     {

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

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

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

     }
   }
 }

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


Рис.11.12. Файлы скопированы.

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

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

if ($ren!="")
{

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

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

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

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

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

$rfl[$i]=strtr($rfl[$i], " []{},/\!@#$%^&*", "____________________");

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

Примечание:

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

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

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

  1. Смотрит, какой символ стоит в строке в ее третьем параметре на том же са-мом месте от начала строки, что и встреченный символ в строке во втором пара-метре.
  2. Заменяет в строке в первом параметре этот очередной символ на найденный в третьей строке.

Например, результатом выполнения функции

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

будет строка

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

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

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

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

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

Примечание:

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

Вот и все.

   }
 }
}

Рис.11.13. Файлы переименованы.

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

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


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

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

while (file_exists($folder."/".$rfl[$i])==True)
{
$rfl[$i]="_".$rfl[$i];
}

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

Создание новой папки, выполнение действия.

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

if ($md!="")
{

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

$newname=strtr($newname, " []{},/\!@#$%^&*", "____________________");

...и создадим папку - командой mkdir, не забыв указать в ее пара-метрах полный путь к новой папке:

mkdir ($folder."/".$newname, 0666);

Описание команды mkdir вы уже видели выше - в подразделе "Ко-пирование, выполнение действия".

Собственно, и все (рис.11.15).

}

Рис.11.15. Папка создана.

Завершение действия

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

Если на странице запроса дополнительной информации посетите-лем была нажата кнопка "Отмена", то ни один из вышеприведенных блоков исполнения действия выполнен не будет - ни одна из пере-менных $udal, $copy, $ren, $md определена не будет. А эта команда выполнится - то есть после нажатия кнопки "Отмена" на странице за-проса дополнительной информации посетитель окажется на основной странице файлового менеджера.

Header ("Location: index.php?fold=$folder");

Примечание:

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

Заголовок "Location" осуществляет перенаправление браузера на страницу, ука-занную в его параметре.

Сценарий можно завершить - посетитель ведь уже на другой стра-нице...

?>

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

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


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

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


Текст сценария

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

Файл 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><b><a href=index.php?fold=$back>Корневая папка</a></b><br>");
 }
$hdl=opendir($dirct);
while ($file = readdir($hdl))
    {
    if (($file!="..")&&($file!="."))
        {
        $a[]=$file;
        }
     }
closedir($hdl);
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=$full><b>Папка $k</b></a>");
     }
    else
     {
      echo ("<a href=$full>$k</a>");
     }
    echo ("<br>");
   }
 }
?><br><input type=submit value="Удалить" name=udal><br><input type=submit value="Переименовать" name=ren><br><input type=submit value="Копировать" name=copy><br><input type=submit value="Создать папку" name=md>
</form></html>

Файл zapros.php

<html><?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;
$hdl=opendir($fld);
while ($file = readdir($hdl))
{
 if (($file!=".")&&($file!=".."))
  {
   $fllnm=$fld."/".$file;
   if (is_dir($fllnm)==True)
    {
     $no=0;
     foreach ($fl as $i)
      {
      if ($fllnm==$folder."/".$i)
       {
        $no=1;
       }
      }
      if ($no==0)
       {
        if ($fllnm!=$folder)
         {
          echo ("<input name=rd type=radio value=$fllnm>$fllnm<br>");
         }
        tree ($fllnm);
        }
    }
  }
}
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>");
 }
echo ("<input type=submit value=\"Скопировать\" name=copy>");
}
if ($ren!="")
 {
  echo ("Переименовать файлы?<br>");
  foreach ($fl as $i)
   {
    echo ("<input type=hidden name=afl[] value=$i>");
    echo ("$i");
    echo ("<input type=text size=30 name=rfl[] value=$i><br>");
   }
  echo ("<input type=submit value=\"Переименовать\" name=ren>");
 }
if ($md!="")
 {
  echo ("Введите имя папки:<br><input type=text size=30 name=newname><br><input type=submit value=\"Создать папку\" name=md>");
 }
?>
<br><input type=submit value="Отмена" name=ot></form>
</html>
Файл do.php
<?php
$begin="files";
if ((strpos($folder,$begin)!=0)||
(strpos($folder,"..")!=False))
 {
  exit;
 }
function delfiles($fld)
 {
  $hdl=opendir($fld);
  while ($file = readdir($hdl)) 
   {
    if (($file!=".")&&($file!=".."))
     {
    if (is_dir($fld."/".$file)==True)
       {
        delfiles ($fld."/".$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], " []{},/\!@#$%^&*", "____________________");
      rename ($folder."/".$afl[$i], $folder."/".$rfl[$i]);
     }
   }
 }
function copyfold ($rt, $fld, $tgt)
 {
  if (file_exists($tgt."/".$fld)!=True)
   {
    mkdir ($tgt."/".$fld, 0666);
   }
  $hdl=opendir($rt."/".$fld);
  while ($file = readdir($hdl)) 
   {
    if (($file!="..")&&($file!="."))
     {
      if (is_dir($rt."/".$fld."/".$file)==True)
       {
        copyfold($rt."/".$fld, $file, $tgt."/".$fld);
       }
      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");
?>
Hosted by uCoz