Сер 282013
 

Изучаем PHP. Тонкости экстремальной отладки скриптов.

Иногда сайт ведёт себя неадекватно. А разработка сайта всё затягивается. Очень трудно разобраться, в чём проблема, когда вообще выпадает Белый лист, и нигде нет никаких ошибок.

Дело в том, что многие проекты находятся на обычных хостингах, администрация которых действует по правилу «меньше знаешь, лучше спишь». Обычно под выполнение скриптов зарезервированы довольно скудные ресурсы. А вывод ошибок подавляется, или перенаправляется. Как же быть в этой ситуации?

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

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

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

<?php
ini_set("error_reporting",E_ALL & ~E_NOTICE);
ini_set("display_errors",1);
ini_set("error_log","");
function errPrint(){
    print_r(error_get_last());
}
register_shutdown_function("errPrint");
 ...
?>

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

Разберём приведённый выше PHP код:

Описание функции ini_set:

(string) ini_set ($server_var_name,$server_var_value) – устанавливает значение $server_var_value для переменной среды выполнения PHP $server_var_name, в случае удачного присвоения возвращает значение переменной, иначе возвращает false.

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

Для начала мы включаем отображение ошибок ini_set(“display_errors”,1).

После этого мы устанавливаем значение для вывода ошибок “error_reporting”, E_ALL & ~E_NOTICE, равное отображению всех ошибок, но без подсказок E_NOTICE.

Примечание автора: E_NOTICE – подсказки, как «правильно программировать» на усмотрение PHP интерпретатора. Зачастую, подсказки могут быть очень полезны при отладке сайта в целом, но при отладке в критических ситуациях они, в основном, несут малополезную информацию да ещё и в огромном количестве.

Затем мы переопределяем вывод ошибок в стандартный поток браузера посетителя ini_set(“error_log”,””);.

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

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

Но, благо, с версии 5.2 (о чудо!), заявленная ещё в php 4, функция – перехватчик register_shutdown_function заработала!.

Описание функции register_shutdown_function:

(void) register_shutdown_function ( callback $function ) – принимает имя функции $function, которая выполняется по завершению исполнения кода, даже во время возникновения критической ошибки с прерыванием работы скрипта.

Примечание автора: в данном случае функция $function должна быть определена (или подключена) до передачи в register_shutdown_function.

Определим функцию errPrint, которая распечатает последнюю ошибку print_r(error_get_last()) после завершения скрипта. И передадим название этой функции обработчику register_shutdown_function:

<?php
register_shutdown_function("errPrint");
?>

Описание функции error_get_last:

(array) error_get_last ( void ) – возвращает в виде массива информацию о последней случившейся ошибке. Массив содержит ключи “type” – тип ошибки, “message” – подробное сообщение об ошибке, “file” – путь к файлу, в котором произошла ошибка, “line” – номер строки, в которой произошла ошибка и номер символа.

А в нашем случае, последняя «случившаяся» ошибка – и есть та самая, которая приводит к остановке выполнения скрипта.

Благодаря такому подходу можно будет «отловить» буквально любую ошибку и получить детальную информацию о ней.

Не забывайте отключать информацию о выводе ошибок в браузер пользователя!
Но, после отладки php скрипта и устранения критической ошибки, не забывайте отключить полностью информацию о выводе ошибок в браузер пользователя. Связано это как с соображениями безопасности (многие ошибки и предупреждения могут доступной для злоумышленника конфиденциальную информацию, например: об абсолютном пути на сервере к Вашим скриптам, их местоположении и т.п.), а так же могут препятствовать нормальной инициализации сессий и работе функций, передающих заголовки header, так и с эстетической точки зрения не стоит выводить много малопонятной информации в браузер посетителя Вашего сайта. Для этого можно подавить полностью вывод ошибок. Или же переопределить вывод ошибок в файл с помощью функции ini_set(“error_log”,”путь к файлу ошибок, доступному на запись”);. При этом желательно, чтобы этот файл находился вне папок Вашего сайта, или доступ к файлу извне был полностью ограничен.

Примечание автора:

Приведём код полного подавления ошибок:

<?php
ini_set("error_reporting",0);
ini_set("display_errors",0);
ini_set("error_log","путь к файлу хранения ошибок");
?>

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

<?php
@mkdir("/site/cahce",0777);
?>

Хотя, настоятельно рекомендую Вам вместо этого «ленивого» способа с помощью условных операторов обработать все ситуации, которые приводят к появлению данной ошибки. Это добавит стабильности и увеличит «запас прочности» Ваших скриптов во время возникновения внештатных ситуаций, и оставит меньше «почвы для размышления» злоумышленникам.
Я всё равно вижу белую страницу!

Здесь может быть несколько причин:

Данный подход универсален для php 5.2 и выше. Для того, чтобы узнать версию Вашего PHP интерпретатора, добавьте в Ваш скрипт вызов функции phpinfo();
В первых строках будет описана версия PHP.

Если версия php ниже 5.2:

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

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

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

Приведём пример выполнения пошаговой отладки с простой установкой флагов состояний:

<?php
function add_user_avatar($user_id,$avatar_name,$watermark){
...
};
echo "вызов функции add_user_avatar";
add_user_avatar($user_id,$avatar_name,$watermark);
echo "окончание работы функции add_user_avatar";
die();
?>

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

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

Сер 282013
 

Практикум PHP. Умная загрузка изображений.

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

Но на просторах интернета было найдено очень интересное решение.

Colin VEROT написал замечательный класс, который всего в несколько строк обработает загрузку изображения или выполнит действие над уже существующей фотографией на сервере. Класс основан на gd library и имеет множество настроек. Отлично справляется с php 4 + версии и изображениями png, gif, jpeg форматов.

Вот немногое из того, что класс умеет:

Обработка изображений из массива $_FILES
Работа над изображениями на сервере
Изменение размеров изображений (с сохранением пропорций или с «растягиванием» изображений). При этом доступны такие фильтры, как:
Не изменять/изменять изображения меньше указанной длинны/высоты
Не изменять/изменять изображения больше указанной длинны/высоты
Дополнение изображений указанным цветом до определённых размеров
Преобразования всех входящих изображений к одному типу
Переименование изображений или их перезапись, если указанный путь сохранения уже содержит такой файл
«Обрезка» области изображений по указанным параметрам
Наложение водяного знака watermark а и текста
Даёт управлять именем нового изображения, его расширением, правами, устанавливаемыми на файл
Обладает множеством встроенных эффектов – управляет яркостью и контрастностью, имеет несколько предопределённых эффектов (отражение, замена цветов – инвертирование, оттенки серого, размытие, поворот, рамки, скос и т.п.).
Кроме загрузки изображений, управляется и с загрузкой файлов (может контролировать загружаемые файлы по mime типам).
Скрипт ведёт статистику изменений, которую можно просмотреть в режиме отладки. И множество переменных для контроля.
А теперь давайте рассмотрим примеры обработки изображений из массива $_FILES с помощью данного класса, и почувствуйте, насколько всё просто:

Примечание автора: более подробные примеры и полное описание на английском Вы можете обнаружить прямо вначале файла класса class.upload.php

Для начала скачаем класс и поместим его в web директорию нашего сайта, подключаем его в нужном месте:

require_once(dirname(__FILE__)."/class.upload.php");

Создадим сам объект класса:

$handle = new Upload($file);

Где $file – путь к файлу, может быть так же объектом из массива $_FILES.

После чего укажем, куда сохранять файл:

$savepath = dirname(__FILE__)."/images";

Обработаем и сохраним файл:

$handle->Process($savepath);
$handle->Clean();

Вот и всё! Файл загружен. Осталось только проверить Его наличие по указанному пути $savepath с помощью функции file_exists().

bool file_exists ( string $filename ) – проверяет существование файла, указанного в строковой переменной $filename, если файл существует: возвращает true, иначе – false.

Переменные, которые можно просмотреть до обработки изображения и вызова [имя объекта]->Process([путь сохранения файла]): и до вызова [имя объекта]->clean():

$handle-> file_src_name – имя файла до обработки;
$handle->file_src_name_body – имя файла без расширения до обработки;
$handle->file_src_name_ext – расширение файла до обработки;
$handle->file_src_pathname – полный путь к файлу;
$handle->file_src_size – размер файла;
$handle->file_src_error – ошибка во время обработки/загрузки файла;
$handle->file_is_image – является ли файл изображением?
$handle->image_src_x – ширина изображения?
$handle->image_src_y – высота изображения?
$handle->image_src_pixels – сколько пикселей всего?
$handle->image_src_type – тип изображения.
Переменные, которые можно просмотреть после обработки изображения и вызова [имя объекта]->Process([путь сохранения файла]): и до вызова [имя объекта]->clean():

$handle-> file_dst_path – путь к файлу после обработки;
$handle->dst_name_body – имя файла без расширения после обработки;
$handle->file_dst_name_ext – расширение изображения после обработки;
$handle->file_dst_name – имя файла после обработки;
$handle->file_dst_pathname – полный путь к файлу после обработки;
$handle->image_dst_x – ширина обработанного изображения;
$handle->image_convert – тип преобразованного изображения.
А теперь давайте рассмотрим настройки, которые мы задаём после инициализации объекта класса Upload и до вызова [имя объекта]->Process([путь сохранения файла]):

Загрузить только «изображения»:

$handle->allowed = array("image/*");

Добавим ещё разрешение на загрузку для zip архивов:

$handle->allowed[]   = "application/x-zip";

А что делать, если Файл с таким именем уже существует? Не проблема:

$handle->file_auto_rename = true;

И файл будет переименован.
А как задать имя файла?

$handle->file_new_name_body  =  $image_name;

Преобразовать все загружаемые изображения к типу jpeg?

$handle->image_convert       = "jpg";

А как перезаписать файл, если он уже существует?

$handle->file_overwrite      = true;

Добавить к изображению Watermark слева внизу? Нет проблем!

$handle->image_watermark                 = PATH_SITE."/".$watermark_bigger;
$handle->image_watermark_position            = "LR";

Изменить размеры изображения до 500 на 500 с соблюдением пропорций?

$handle->image_resize        = true;
$handle->image_x             = 500;
$handle->image_y             = 500;
$handle->image_ratio         = true;

«Дополнить» изображения до указанного размера фоновым цветом при изменении размера?

$handle->image_resize            = true;
$handle->image_ratio_fill        = true;
$handle->image_ratio             = true;
$handle->image_background_color  = "#FF00FF";

Запретить изменять размеры изображений меньше 500 пикселей в ширину и длину?

$handle->image_ratio_no_zoom_in  = true;

«Растянуть» изображение без соблюдения пропорций?

$handle->image_ratio             = false;

Сменить права на файл после его обработки?

$handle->dir_chmod = 0777;

Попробуем сменить размер загружаемого файла (в байтах)?

$handle->file_max_size = "1024";

Запретим загрузку только определённых файлов по mime type?

$handle->forbidden = array("application/*");

Изменяем яркость изображения?

$handle->image_brightness = 40;

Отражаем изображение по горизонатли?

$handle->image_flip = "h";

Повернём изображение на 90 градусов?

$handle->image_rotate = 90;

Урежем изображение по заданному образцу на 50 пикселей сверху, 40 срава, 30 снизу и 20 слева?

$handle->image_crop = array(50,40,30,20);

Добавим к изображению белую рамку?

$handle->image_border = "3px";
$handle->image_border_color = "#FFFFFF";

Добавим эффект растворения к чёрному с отражением и 60 процентной прозрачностью отражения?

$handle->image_reflection_height = "25%";
$handle->image_default_color = "#000000";
$handle->image_reflection_opacity = 60;

А в следующей статье мы расширим класс с помощью curl (если поддерживается) или url wrapper ов функции fopen, file_get_contents для загрузки и сохранения изображений, расположенных на других сайтах.

Сер 282013
 

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

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

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

Итак, перейдем к сути вопроса.

Допустим, у нас есть страница, отображающая новости раздела, в контексте которой согласно id страницы $_GET[‘ limitfrom’] категории $_GET[‘id’] мы выбираем количество новостей $_GET[‘limit’] из связанной таблицы материалов `news`.

Обычно (утрировано и упращенно) это выглядит, например, так:

<?php
$id         = $_GET['id'];
$limitfrom  = $_GET['limitfrom'];
$limit      = $_GET['limit'];
$sql        = 'SELECT * FROM news WHERE category_id ='
                  . (int)$id.'  LIMIT '. (int)$limitfrom.','.(int)$limit;
$resource   = mysql_query($sql);
If($resource && is_resource(resource)){
    //обработка данных
} else {
   echo mysql_error();
   die();
}
?>

Пример «взят» не из воздуха, вот так, приблизительно, но в более сложной форме выглядит разбитие на постраничную навигацию в разделах известного новостного движка DLE. А также вот так (echo mysql_error(); die()) выводится, зачастую, в браузер посетителя возникновение mysql ошибки в Joomla 1.5 и более новых версий (чуть сложнее, но смысл остается тот же).

Здесь была осознанно допущена одна оплошность. Дело в том, что данный вариант будет работать отлично! И в повседневной работе программист не увидит выполнение echo mysql_error().

Но почему не следует выводить в браузер отладочную информацию и ошибки исполнения PHP?

Итак, первое, из за того, что мы обходимся без проверки, что же приходит со стороны клиента, например, отрицательные значения $id и $limit, которые допускаются для типа данных integer, или несуществующие значения приведут к выводу отладочной информации mysql_error(). Итак, ясным по белому, мы дадим информацию злоумышленнику о существующих таблицах и пищу для размышления, как же проще осуществить mysql injection в другом месте, где используются эти же таблицы при составлении запросов. Или же в некоторых случаях покажем посетителям техническую информацию, которую видеть они не должны. С одной стороны здесь нам помогут более строгие проверки входных данных. Перепишем этот пример с использованием выше сказанного:

 

<?php
$id         = abs((int)$_GET['id']);
$limit      = abs((int)$_GET['limit']);
$limit      = $limit ? $limit : 10;
$limitfrom  = abs((int)$_GET['limitfrom']);
$sql        = 'SELECT * FROM news '
.($id && !empty($id) ? 'WHERE category_id='. $id
: '')
.( $limitfrom && !empty($limitfrom) ?
'  LIMIT '. $limitfrom.', '. $limit.''
: '  LIMIT '. $limit.'');
$resource   = mysql_query($sql);
If($resource && is_resource(resource)){
    //обработка данных
} else {
   echo mysql_error();
   die();
}
?>

Мы предотвратили, как минимум, возникновение нескольких исключительных ситуаций. Мы знаем, что нам нужны только лишь целые положительные значения $id,$limit – поэтому используем явное приведени типов для этих переменных, оператор приведения к целому (int) и функцию получения целого числа abs(). Также мы знаем, что количество выбранных новостей не должно быть равно нулю, поэтому определяем минимальный «порог» в 10 новостей $limit = $limit ? $limit : 10;, формируя запрос, мы проверяем данные, и если они есть и отличны от 0, то добавляем их в mysql запрос по частям.

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

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

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

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

Какие функции и данные для логирования в PHP могут быть полезны?
trigger_error – для генерации исключения.
ini_set , error_reporting – для подавления вывода ошибок
set_error_handler – для установки собственной функции «перехвата» ошибок
register_shutdown_function – для отлова критических ошибок (которая работает, как нужно, начиная с php 5.2)
error_get_last – функция получения последней ошибки, произошедшей в скрипте, очень полезна в случае прерывания скрипта из за возникновения критической ошибки
$_SERVER – суперглобальный массив – источник информации – где и когда и какая ошибка произошла
getenv – функция для получения значения переменных среды окружения сервера, в том случае, если какие-то данные в суперглобальном массиве $_SERVER отсутствуют
Давайте напишем свой перехватичк исключительных ситуаций для дальнейшего логирования данных об ошибках. Суть заключается в следующем, мы переопределим вывод отладочной информации с помощью собственной функции в текстовый файл в определенной папке /errors, чтобы его не смогли прочесть извне, присвоим ему расширение .php (можно и при помощи запрета прямого доступа извне к этой папке, но данный метод более универсален для любых серверов и не требует дополнительной настройки окружения сервера) и добавим первой строчкой , название же зададим ему, совпадающее с текущей датой, чтобы было проще ориентироваться. В итоге один файл ошибок – одна дата.Также мы добавим «перехват» критических ошибок. И при помощи функции trigger_error для логирования непредвиденных ситуаций и генерации собственных ошибок типа E_USER:

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

<?php
error_reporting(0);
ini_set('error_reporting','0');
ini_set('display_errors', '0');
ini_set('display_startup_errors', '0');
ini_set('ignore_repeated_errors', '1');
define ('ROOT_PATH', dirname(dirname(__FILE__))."/");
//запретить/разрешить вывод ошибок
define('_ERR_HANDLING',true);
//где будем хранить файлы ошибок
define('_ERR_DIR',ROOT_PATH.'errs/');
function error_reporting_log($error_num, $error_var=null,
$error_file=null, $error_line=null) {
$error_desc= '';
$error_desc  = 'Error';
switch ($error_num){
case E_WARNING:
$error_desc = 'E_WARNING';
break;
case E_USER_WARNING:
$error_desc = 'E_USER_WARNING';
break;
case E_NOTICE:
$error_desc = 'E_NOTICE';
break;
case E_USER_NOTICE:
$error_desc = 'E_USER_NOTICE';
break;
case E_USER_ERROR:
$error_desc = 'E_USER_ERROR';
break;
case E_ERROR:
$error_desc = 'E_USER_ERROR';
break;
default:
$error_desc  = 'E_ALL';
break;
}
$date_file = date('y-m-d H:I:S');
$logfile= LOG_FILE;
$url = $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
$date_time = date('d.m.y - H:i:s');
$ip= isset($_SERVER['REMOTE_ADDR']) &&
!empty($_SERVER['REMOTE_ADDR']) ?
$_SERVER['REMOTE_ADDR']
: getenv('REMOTE_ADDR ') ;
$from= isset($_SERVER['HTTP_REFERRER'])&&
!empty($_SERVER['HTTP_REFERRER'])?
$_SERVER['HTTP_REFERRER']
:getenv('HTTP_REFERRER');
$errortext = $error_desc.': '.$error_var."\t".'
Line: '.$error_line."\t".'
File: '.$error_file."\t".'
Link: '.$url."\t".'
Date: '.$date_time."\t".'IP: '.$ip."\t".'
FROM:'.$from."\n";
unset($from,$error_desc,
$error_var,$error_line,
$error_file,$url,$date_time,$error_write);
$secuire= '<?php die("Forbidden."); ?>';
if(is_file($logfile)&&is_writeable($logfile)){
$fp = fopen($logfile,'r');
if($fp && is_resource($fp)){
$strings= fgets($fp);
if(isset($strings)&&!empty($strings)
&&strpos($strings,$secuire)===false){
unlink($logfile);
}
fclose($fp);
};
unset($fp);
}
if(!is_file($logfile)){
$dir= dirname($logfile);
if(is_dir($dir)&&is_writable($dir)){
$fp     = fopen($logfile,'w+');
if(is_resource($fp)){
flock($fp,LOCK_EX);
fwrite($fp,$secuire."\n");
flock($fp,LOCK_UN);
fclose($fp);
$fp= null;
}
unset($dir,$fp);
}
}
unset($secuire);
if(is_file($logfile)&&!is_writable($logfile)){
chmod($logfile,0775);
}
if(is_file($logfile)&&is_writeable($logfile)){
$fp     = fopen($logfile,'a+');
if(is_resource($fp)){
flock($fp,LOCK_EX);
fwrite($fp,$errortext);
flock($fp,LOCK_UN);
fclose($fp);
$fp= null;
unset($fp);
}
}
unset($logfile);
                return true;
}
function err_handler(){
if(_ERR_HANDLING){
$error_reporting= '';
$error_reporting= ini_get('error_reporting');
$error_reporting= $error_reporting?$error_reporting:E_ALL;
error_reporting(E_ERROR);
$date_file = date('dmY').'.php';
$dir= _ERR_DIR;
$path= $dir.$date_file;
$logfile= '';
if(!is_dir($dir) || !is_writable($dir)){
if(is_dir($dir)&&!is_writable($dir)){
chmod($dir,0775);
} else if(!is_dir($dir)){
$isdir= false;
$isdir= mkdir($dir,0775);
}
if(!$isdir&&!is_writable($dir)){
$dir= ROOT_PATH;
$path= $date_file;
}
}
if(is_dir($dir) && is_writable($dir)){
if(!is_file($path)){
$fp= fopen($path,'w+');
if($fp && is_resource($fp)){
$secuire= '<?php die("Forbidden."); ?>';
flock($fp,LOCK_EX);
                fwrite($fp,$secuire."\n");
                flock($fp,LOCK_UN);
                fclose($fp);
                $fp= null;
unset($secuire);
}
}
if(is_file($path) && !is_writable($path)){
chmod($path,0775);
}
if(is_file($path) && is_writable($path)){
ini_set('display_errors',0);
set_error_handler('error_reporting_log', (E_ALL & ~E_NOTICE));
$logfile= $path;
define('LOG_FILE',$logfile);
}
unset($date_file,$dir,$path,$logfile);
}
error_reporting($error_reporting);
unset($error_reporting);
}
}
function critical_error(){
$error = error_get_last();
if(is_array($error) && sizeof($error) && isset($error['type'])
&& $error['type'] == E_ERROR && !empty($error['message'])
&& !empty($error['file']) && !empty($error['line'])){
error_reporting_log($error['type'],
$error['message'],$error['file'],$error['line']);
}
}
if(function_exists('register_shutdown_function')
&& function_exists('error_get_last')
&& _ERR_HANDLING){
register_shutdown_function('critical_error');
}
err_handler();

Что здесь происходит? При помощи функции ini_set и error_reporting мы подавляем вывод ошибок в браузер посетителя, константами определяем местоположение нашей директории для хранения логов ошибок define(‘_ERR_DIR’,ROOT_PATH.’errs/’);. Определенная в самом начале константа (bool) _ERR_HANDLING – при значении true разрешит перенаправление всех наших ошибок в файлы, при значении false дальнейшие инструкции выполняться не будут. Для перехвата самих ошибок используется функция error_reporting_log, которая принимает тип ошибки, сообщение об ошибке, имя файла, где произошла ошибка, и строку, в которой она произошла. В ней мы собираем кроме этой информации, и другие полезные данные. А именно:

$_SERVER[‘HTTP_REFERRER’] – откуда пришел посетитель, с какой страницы?
$_SERVER[‘REMOTE_ADDR’] – ip посетителя,
$_SERVER[‘HTTP_HOST’]. $_SERVER[ ‘REQUEST_URI’] – полный адрес страницы вместе со строкой запроса, на которой возникла ошибка
date(‘y-m-d H:I:S’) – текущее время возникновения ошибки.
Зачастую, подобной информации хватит с головой и для логирования и исправления ошибок, которые были не замечены на этапе разработки, так и для отлова злоумышленников. Но есть один тип ошибок, который не будет перехвачен. Это критические ошибки PHP E_ERROR, которые приводят к прерыванию php скрипта . Начиная с версии PHP 5.2 для перехвата критических ошибок можно определить собственную функцию при мопощи register_shutdown_function. Мы создаем собственную функцию для этих целей critical_error, в которой при помощи функции error_get_last получаем информацию о текущей ошибке. Так как данная функция будет всегда выполняться после завершения php скрипта, мы логируем информацию только об ошибках с типом E_ERROR.

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

Давайте на примере все того же скрипта с mysql рассмотрим принцип логирования mysql ошибок (впрочем, по такому же принципу можно построить логирование любых внештатных ситуаций):

<?php
$id         = abs((int)$_GET['id']);
$limit      = abs((int)$_GET['limit']);
$limit      = $limit ? $limit : 10;
$limitfrom  = abs((int)$_GET['limitfrom ']);
$sql        = 'SELECT * FROM news '
.($id && !empty($id) ?
'WHERE category_id='. $id
: '')
.( $limitfrom && !empty($limitfrom)
? '  LIMIT '. $limitfrom.', '. $limit.''
: '  LIMIT '. $limit.'');
$resource   = mysql_query($sql);
If($resource && is_resource(resource)){
    //обработка данных
} else {
  $string                 = print_f(
'Ошибка mysql при получении новостей из категории с ID: %d,'
  . 'на странице:  %d , '
  . 'информация о запросе:  %s,'
  . ' информация об ошибке: %s',
  $id,$limitfrom, $sql, mysql_error());
   trigger_error($string,E_USER_ERROR);
   die();
}
?>

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

Примечание автора: также для генерации и перехвата ошибок Вы можете использовать конструкции try{}catch(Exeption $e){ thrown(throw new Exception("$name contains the word name");}

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

Сер 282013
 

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

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

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

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

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

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

Сер 282013
 

Итак, продолжаем разбираться с построением магазина на Virtuemart 2.0 и Joomla 2.5. На очереди веселый глюк с русскими инвойсами и pdf в Virtuemart 2.0. Дело в том, что когда происходит формирование заказа, virtuemart выставляет счет в pdf (invoice), только вот все русские буквы заменяются на знаки вопросов. Согласитесь, что это не слишком информативно для Ваших покупателей?

Сделаем маленькое отступление. Если на этапе оформления заказа Ваш Virtuemart 2.0 начинает глючить, то показывая страницу 404, то зависая, то выдавая множество ошибок, переставьте для начала пакет Virtuemart 2.0 all in one (aio), который идет в стандартной поставке с самим магазином.

Virtuemart 2.0 pdf инвойсы и русские буквы.
Итак, оказывается шрифты, которые используются для формирования счетов в самом virtuemart попросту не поддерживают кириллицу (кириллические символы). Программисты Virtuemart здесь кивают в сторону TCPDF, библиотеки, которую использует и Virtuemart для формирования pdf документов (вид для печати в pdf, virtuemart 2.0 invoce pdf и т.п.).

Итак, приведем порядок действий, как же «добавить» поддержку кириллицы в invoice pdf virtuemart 2.0.

Первое, после установки virtuemart 2.0 в папке /libraries создает подпапку /libraries/tcpdf/fonts/

Нам нужно добавить необходимые шрифты, поддерживающие кириллицу, в эту подпапку. Для этого переходим на сайт разработчиков в раздел download, качаем последнюю библиотеку для php 5 (joomla версии 2.5 больше не поддерживает php4). После того, как скачали, открываем архив, в нем папку tcpdf и папку fonts из нее копируем по пути /libraries/tcpdf (только папку шрифтов!), соглашаясь о замене всех существующих файлов. После этого мы сможем использовать следующие кириллические шрифты, например: freesans или times.

Перейдем теперь к изменению файлов virtuemart для добавления поддержки кириллицы в pdf invoice в virtuemart 2.0.

Открываем файл:

/components/com_virtuemart/controllers/invoice.php

Ищем следующее значение:

$pdf->setHeaderFont(Array('helvetica', '', 8));

Изменяем на:

$pdf->setHeaderFont(Array('freesans', '', 8));

Ищем значение:

$pdf->setFooterFont(Array('helvetica', '', 10));

Изменяем на:

$pdf->setFooterFont(Array('freesans', '', 10));

Ищем значение:

$pdf->SetFont('helvetica', '', 8, '', true);

Изменяем на:

$pdf->SetFont('freesans', '', 8, '', true);

Ищем значение:

$this->SetFont('helvetica', 'I', 8);

Изменяем на:

$this->SetFont('freesans', 'I', 8);

Дальше открываем файл

/components/com_virtuemart/helpers/vmpdf.php

Ищем следующее значение:

$this->setHeaderFont(Array('helvetica', '', 8));

Меняем на:

$this->setHeaderFont(Array('freesans', '', 8));

Ищем следующее значение:

$this->setFooterFont(Array('helvetica', '', 10));

Меняем на:

$this->setFooterFont(Array('freesans', '', 10));

Ищем следующее значение:

$this->SetFont('helvetica', '', 8, '', true);

Меняем на:

$this->SetFont('freesans', '', 8, '', true);

Ищем следующее значение:

$this->SetFont('helvetica', 'I', 8);

Меняем на:

$this->SetFont('freesans', 'I', 8);

Данная инструкция верна для virtuemart 2.0.16 + 2.0.18 a по замене кода в указанных файлах. Но что же делать, если код и версия вашего Virtuemart 2.0 отличаются?

Нужно сделать следующее: найти поиском по файлам рекурсивно в папках /components/com_virtuemart все строки, где встречается helvatica, и заменить их на freesans, или другой шрифт в формате tcpdf из папки /libraries/tcpdf/fonts (название шрифта в папке и будет названием шрифта для замены), поддерживающий кириллицу (русские символы). Можно также дополнительно осуществить поиск по методам самого класса tcpdf – SetFont,setHeaderFont,setFooterFont, и изменить шрифт, указанный в этих методах. А еще в этих методах Вы можете кроме начертания шрифтов изменить их размер. Для этого третьим параметром каждого метода можно передать целое число (высоту шрифта). Рекомендую для freesans увеличить на 1-2 пункта это значение по сравнению с helvatica. Шрифт будет крупнее и читабельнее. После всех изменений сделайте заказ и проверьте, как выглядит invoce в pdf.