Category Archives: куски кода

Защищаемся от спама по добавлению ip в чёрный список

Иногда боты достают конкретно, можно банить их по ip.

Для этого имеем в виду то, что ip в таблице со счётчиком num==0 нормален, а всё что выше – нет, и мы его откидываем.

Итак, строим такую таблицу:

CREATE TABLE `ips_blocked` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `dtm` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `ip` varchar(44) NOT NULL COMMENT 'ip адрес',
  `ua` tinytext NOT NULL COMMENT 'User agent',
  `num` int(11) NOT NULL DEFAULT '0' COMMENT 'кол-во попаданий',
  `rem` tinytext NOT NULL COMMENT 'примечание',
  PRIMARY KEY (`id`),
  UNIQUE KEY `ip` (`ip`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

ip туда добавляем вручную, т.к. этот процесс редкий, если нет – пишите скрипт для добавления туда ip.

По умолчанию num равен 0.

Итак, перед записью заявки проверяем ip по этой таблице, и если он есть и его номер num больше 0, откидываем заявку:

$ip = empty($_SERVER['REMOTE_ADDR']) ? '' : htmlspecialchars($_SERVER['REMOTE_ADDR']);
 
if(DB::columnIntPrepared('SELECT 1 FROM ips_blocked WHERE ip=? AND num>0', array($ip))) {
	DB::execute("UPDATE ips_blocked SET num=num+1 WHERE ip=?", array($ip));
	report('Ваш ip находится в чёрном списке. Пришлите вашу заявку нам на email.');
}

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

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

На этом всё, до свидания!

P.S. Для ip версии 4 (IPv4) наиболее эффективно наверное использовать тип INT(11),
и при этом каждый раз при добавлении/выборке его в/из таблицы надо использовать соотв. функции, например так:

Добавление: INSERT INTO ips_blocked VALUES (…, INET_ATON(?), …)
Выборка: SELECT INET_NTOA(ip) AS ip

Здесь вместо знака вопроса при добавлении вставляем ip в подготовленное SQL-выражение.

Но я не стал этого делать, а сделал тип поля VARCHAR и длину поля вообще 44, чтобы при случае иметь возможность добавлять IPv6, не знаю уже, когда этот момент настанет. ))))

Собственная капча на PHP своими руками за 5 минут

Приветствую вас!

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

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

Итак, в этом посте мы напишем собственный гибкий капча-механизм на PHP, который мы сможем поставить на свои формы за 5-10 минут.

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

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

Создадим папку fonts и закинем туда парочку бесплатных TTF-шрифтов, например, liber-mono.ttf и liber-sans.ttf. Сразу замечу, что полный комплект файлов – скрипты и шрифты вы можете скачать по ссылке: https://beotiger.com/download/jcaptcha.

Напишем наш серверный скрипт, который будет создавать и отображать капчу, назовём его скажем, jcaptcha.php

Вот этот скрипт с подробными комментариями:

// зададим имя куки для сохранения в ней кода капчи
define('CAPTCHA_COOKIE', 'imgcaptcha_');
 
/*
 
	Инициализируем генератор случайных чисел.
	Хотя в руководстве по PHP написано, что это делается автоматически
	каждый раз при запуске сценария, но... я им не верю 0_0
 
*/
 
mt_srand(time());
 
/*
 
	Определим путь к папке со шрифтами
	и список имен файлов со шрифтами в ней -
	из этого списка каждый раз будем выбирать случайный шрифт
 
*/
 
define('PATH_TTF', 'fonts/');
$fonts = array('liber-mono.ttf', 'liber-sans.ttf');
 
/*
 
	Основные параметры капчи.
 
	Для поддержки разных параметров капчи здесь можно	создать
	многомерный массив и обращаться к нему по индексу.
 
*/
 
$par = array(
	// ширина капчи
	'WIDTH' => 120,
	// высота капчи
	'HEIGHT' => 32,
	// размер шрифта на капче
	'FONT_SIZE' => 14,
 
	// кол-во символов на капче
	'CHARS_COUNT' => 5,
	// разрешенные символы капчи
	'ALLOWED_CHARS' => 'ABCDEFGHJKLMNPQRSTUVWXYZ23458',
 
	// фоновый цвет капчи - белый в нашем случае
	'BG_COLOR' => '#FFFFFF',
	// кол-во линий на капче
	'LINES_COUNT' => 3,
	// толщина линий
	'LINES_THICKNESS' => 2
);
 
/*
	Общие парметры капчи
*/
 
// цвета символов
define('CODE_CHAR_COLORS', '#880000,#008800,#000088,#888800,#880088,#008888,#000000');
// цвета линий
define('CODE_LINE_COLORS', '#880000,#008800,#000088,#888800,#880088,#008888,#000000');
 
// получаем цвета линий и символов в массивы для случайной выборки позднее
$line_colors = preg_split('/,\s*?/', CODE_LINE_COLORS);
$char_colors = preg_split('/,\s*?/', CODE_CHAR_COLORS);
 
// создаем пустой рисунок и заполняем его белым фоном
$img = imagecreatetruecolor($par['WIDTH'], $par['HEIGHT']);
imagefilledrectangle($img, 0, 0, $par['WIDTH'] - 1, $par['HEIGHT'] - 1, gd_color($par['BG_COLOR']));
 
// устанавливаем толщину линий и выводим их на капчу
imagesetthickness($img, $par['LINES_THICKNESS']);
 
for ($i = 0; $i < $par['LINES_COUNT']; $i++)
    imageline($img,
        mt_rand(0, $par['WIDTH'] - 1),
        mt_rand(0, $par['HEIGHT'] - 1),
        mt_rand(0, $par['WIDTH'] - 1),
        mt_rand(0, $par['HEIGHT'] - 1),
        gd_color($line_colors[mt_rand(0, count($line_colors) - 1)])
    );
 
// Переменная для хранения кода капчи
$code = '';
 
// Зададим координату по центру оси Y 
$y = ($par['HEIGHT'] / 2) + ($par['FONT_SIZE'] / 2);
 
// Выводим символы на капче
for ($i = 0; $i < $par['CHARS_COUNT']; $i++) {
		// выбираем случайный цвет из доступного набора
    $color = gd_color($char_colors[mt_rand(0, count($char_colors) - 1)]);
    // определяем случайный угол наклона символа от -45 до 45 градусов
    $angle = mt_rand(-45, 45);
    // выбираем случайный символ из доступного набора
    $char = substr($par['ALLOWED_CHARS'], mt_rand(0, strlen($par['ALLOWED_CHARS']) - 1), 1);
    // выбираем случайный шрифт из доступного набора
    $font = PATH_TTF . $fonts[mt_rand(0, count($fonts) - 1)];
    // вычислим координату текущего символа по оси X
    $x = (intval(($par['WIDTH'] / $par['CHARS_COUNT']) * $i) + ($par['FONT_SIZE'] / 2));
 
    // выводим символ на капчу
    imagettftext($img, $par['FONT_SIZE'], $angle, $x, $y, $color, $font, $char);
 
    // сохраняем код капчи
    $code .= $char;
}
 
// сохраним капчу в куках для дальнейшей проверки
setcookie(CAPTCHA_COOKIE, md5($code));
 
/*
 
	Посылаем сформированный рисунок в браузер и избавляемся от него, 
	хотя сборщик мусора обычно это делает за нас
 
*/
 
header("Content-Type: image/png");
imagepng($img);
imagedestroy($img);
 
// Преобразуем HTML 6-символьный цвет в GD цвет 
function gd_color($html_color)
{
  return preg_match('/^#?([\dA-F]{6})$/i', $html_color, $rgb)
    ? hexdec($rgb[1]) : false;
}

Вот как выглядит сформированная данным скриптом капча (щёлкните на ней для смены кода):

Капча

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

<form action="go.php" method="post">
	Введите имя: <input name="name"><br>
	Введите email: <input name="email"><br>
	Введите код с картинки: <input name="captcha">
	<img title="Щёлкните для нового кода" alt="Капча" src="jcaptcha.php" style="border: 1px solid black" onclick="this.src='jcaptcha.php?id=' + (+new Date());"><br>
	<input type="submit" value="Отправить!">
</form>

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

 
	// зададим имя куки для получения из неё кода капчи,
	// оно конечно же должно совпадать с соотв. именем в jcaptcha.php
	define('CAPTCHA_COOKIE', 'imgcaptcha_');
	// заметим: поле `captcha` обязательно для заполнения
	if(empty($_POST['captcha']) || md5($_POST['captcha']) != @$_COOKIE[CAPTCHA_COOKIE])
		die('Неверный код с картинки. Вернитесь и повторите попытку.');

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

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

Итак, вы можете скачать полный комплект файлов данного примера – скрипты и шрифты по ссылке: https://beotiger.com/download/jcaptcha

А теперь – пока, пока.
До свидания, до новых встреч, друзья!

GifCreator: создаём динамические GIF-ки своими руками на PHP

Введение

Здравствуйте.

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

Но на GIF’ке с несколькими кадрами как мы текст напишем? Надо доставить каждый кадр, обновлять на нём текст и собирать кадры в GIF-ку снова.

Вот к примеру дана нам такая GIF-ка всего с 2-мя кадрами, закольцованными, меняющимися примерно каждые 2-3 секунды:

openprice.gif

Внизу вы видите текст:

До окончания акции: 3 дня
Осталось: 5 курсов

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

Что же делать?

Как это сделать, спросите вы? GifCreator.php нам в руку, отвечу я. Проект на GitHub’е здесь: https://github.com/Sybio/GifCreator

С помощью этой небольшой либы можно создавать GIF-ки на PHP своими руками.

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

Основы создания новой GIF-ки

Итак, процесс создания GIF-ки с помощью GifCreator таков:

Определяем массив, содержащий в себе пути к файлам рисунков, GD Image-ресурсам (созданным с помощью таких функций как imagecreatefromjpeg и т.п.), а также сырыми (raw) данными содержимого рисунков.
К примеру:

$frames = array(
    imagecreatefrompng('images/pic1.png'), // ресурс GD Image 
    'images/pic2.png', // путь к рисунку на диске 
    file_get_contents('images/pic3.jpg'), // сырое содержимое рисунка  
    'https://mydom.ru/images/pic4.jpg', // URL-путь к рисунку в сети
);

Далее определяем массив с продолжительностью кадров в миллисекундах.

$durations = array(40, 80, 40, 20);

Далее создаём саму GIF-ку

$gc = new GifCreator\GifCreator();
$gc->create($frames, $durations, 5);

Третий параметр метода .create() определяет количество повторов кадров GIF-ки. В данном случае мы повторяем кадры GIF-ки 5 раз. Можно задать 0 для бесконечного повтора.

И наконец получаем результат:

$gif = $gc->getGif();

Теперь мы можем сохранить нашу GIF-ку на диске:

file_put_contents('images/pic.gif', $gif);

или вывести прямиком в браузер:

header('Content-type: image/gif');
die($gif);

Важные особенности

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

Практика

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

Первое, разбираем GIF-ку на фреймы. Как это сделать, спросите вы. Без понятия, отвечу я. Гоголь или Яндекс вам в помощь.

Получаем наши заветные фрэймы:

Frame0.gif 
Frame0.gif  
Frame1.gif
Frame1.gif

В любом тектсовом, ой, графическом редакторе очищаем нужные области от текста, и сохраняем результат в файлики Frame0.png и Frame1.png.

Результат может выглядеть так:

Frame0.png   Frame1.png

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

// Подключаем класс создания GIF-ок
include('GifCreator.php');
 
// Определим сколько дней осталось до окончания акции
 
// сейчас
$datetime1 = new DateTime();
 
// до какого числа: выберем случайный промежуток 0 - 7 дней
$datetime2 = new DateTime(date('Y-m-d', time() + rand(80000, 604800)));
 
$interval = $datetime1->diff($datetime2);
$days_left = $interval->format('%d') + 1;
 
// определим окончание для слова ДЕНЬ (дня/дней/день)	
$ln = substr($days_left, -1, 1); // последняя цифра числа
if($days_left < 21 && $days_left > 4)
	$days = 'дней';
elseif($ln >= 2 && $ln < 5)
	$days = 'дня';
elseif($ln == 1)
	$days = 'день';
else
	$days = 'дней';
 
// сколько курсов осталось
mt_srand(time());
$num = toZero(15 - mt_rand(0, 15));
 
// готовим наши динамические тексты, функцию Suffix смотрите внизу
$text1 = "До окончания акции: $days_left $days";
$text2 = "Осталось: $num курс" . Suffix($num);
 
/*
 
	Для Кириллицы - используем TTF-шрифты,
	Times New Roman Bold для данного примера.
	Путь по умолчанию для шрифта - в текущем каталоге.
 
*/
 
putenv('GDFONTPATH=' . realpath('.')); 
$font = 'timesbd'; // название шрифта без расширения ttf
 
/*
 
 Размер символов ($size) и начальная координата по вертикали ($y),
	подбираются опытным путём
 
*/
 
$size = 11;
$y = 310;
 
// Открываем первый фрэйм и добавляем тексты по центру внизу друг под другом чёрным цветом
$frame0 = imagecreatefrompng('gif/Frame0.png');
$black = imagecolorallocate($frame0, 0, 0, 0);
 
$x1 = getX($size, 0, $text1, $font, $frame0);
$x2 = getX($size, 0, $text2, $font, $frame0);
imagettftext($frame0, $size, 0, $x1, $y, $black, $font, $text1);
imagettftext($frame0, $size, 0, $x2, $y + 20, $black, $font, $text2);
 
// Тоже самое проделываем для второго фрэйма, координата по X нам уже известна
// Помним, что фрэймы у нас должны быть всегда одного размера
$frame1 = imagecreatefrompng('gif/Frame1.png');
$black = imagecolorallocate($frame1, 0, 0, 0);
imagettftext($frame1, $size, 0, $x1, $y, $black, $font, $text1);
imagettftext($frame1, $size, 0, $x2, $y + 20, $black, $font, $text2);
 
 
/*
 
	Итак, добавляем созданные нами кадры в массив и определяем
	продолжительность задержки между кадрами в 2 секунды
 
*/
 
$frames = array($frame0, $frame1);
$durations = array(200, 200);
 
/*
	Создаём GIF-ку, анимация зациклена (3-ий параметр в create() - 0)
*/
 
$gc = new GifCreator\GifCreator();
$gc->create($frames, $durations, 0);
 
// Получаем GIF-ку и выводим её в браузер
$gif = $gc->getGif();
 
header('Content-type: image/gif');
die($gif);
 
/* ********************************
 
	Вспомогательные функции
 
******************************** */
 
/*
 
	getX($size, $angle, $text, $font, $im)
	Центруем текст по оси X (горизонтали) на рисунке
 
	Вход:
		@size - размер символов текста
		@angle - угол поворота текста
		@text - сам текст
		@font - название шрифта
		@im - ресурс GD Image
 
	Выход: нужная координата по оси X
 
*/
 
function getX($size, $angle, $text, $font, $im)
{
	$ts = imagettfbbox($size, $angle, $font, $text);
	$dx = abs($ts[2] - $ts[0]);
	return (imagesx($im) - $dx) / 2;
}
 
/*
 
	Возвращает 0, если аргумент меньше нуля, иначе - сам аргумент
 
*/
 
function toZero($val)
{
  return ($val > 0 ? $val : 0);
}
 
/*
 
	Вернуть правильное окончание слова `курс`
	в зависимости	от переданного числа
 
	Правильно для числа от 0 до 110 (> 100 надо дорабатывать)
 
	@returns {String} окончание
 
*/
 
function Suffix($num)
{
	if($num > 10 && $num < 20)
		return 'ов';
 
	// смотрим последнюю цифру
  $n = intval(substr(strval($num), -1, 1));
 
 	return $n == 1 ? '' : ($n < 5 && $n > 0 ? 'а' : 'ов');
}

Файл GifCreator.php с классом GifCreator можно скачать с GitHub’а – ссылку я давал выше, либо взять с архива с примерами – ссылку я даю ниже. 😉

Также при создании картинок с текстом необходимо подключать нужные шрифты, в данном примере мы подключили шрифт Times New Roman Bold из файла timesbd.ttf. Путь к файлу шрифта надо задавать либо напрямую в имени шрифта, либо через переменную окружения GDFONTPATH, как и показано в нашем примере. В Windows тут надо проявлять большую осмотрительность и осторожность.

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

Результат

Вот результат работы нашего скрипта.

GifCreate

Заметьте, что при каждом обновлении странички надпись будет меняться.

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

Скачивайте полный рабочий архив со всеми файликами ресурсов и скриптами отсюда:
https://beotiger.com/download/gifcreate, заваривайте крепкий чай с вишнёвым вареньем и наслаждайтесь жизнью и классной погодой за окном.

А мне остаётся только раскланяться с вами, поздравить с наступающими праздниками и началом чемпионата мира по футболу.

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

Комментарии VKontakte/Facebook – перехватываем и отправляем нам на почту

Добавляем виджеты комментариев VK/FB на свою страничку

Создаём приложения

Добавить комменты VK/FB на свой сайт относительно просто – создаём соотв. приложения в VK/FB, получаем их id. Вот адреса страничек для создания приложений: https://vk.com/dev/widgets_for_sites для ВК и https://developers.facebook.com/apps/ для Фэйсбук.

Подробно на этой теме останавливаться нет смысла, так как у них меняется всё очень часто, я имею в виду всякую воду и интерфейс, и каждый раз вникать в одно и тоже под другим соусом не особенно хочется. Но принцип видимо остаётся одним и тем же, и созданное нами несколько лет назад приложения продолжают работать. Честно говоря, назвать приложением это от FB/VK у меня язык с трудом поворачивается, скорее это надстройка над виджетами, контроллер виджетов точнее. Главное – они обеспечивают связь нашего сайта с многотысячной армией пользователей этих популярных сетей.
Continue reading

Читаем/пишем бинарники на JavaScript (JScript) для Win Wscript

Да, проблема в том, что JavaScript пытается преобразовать полученные байты в юникод (unicode), и портит большинство байт, которые больше 0x7F. Поэтому при чтении/записи бинарников получается не то, что мы хотим.

Пошарив какое-то время в инете, я нашёл табличку преобразований символов больше либо равным 0x80, но она в итоге оказалась не полной, и мои бинарники читались и писались с ошибками.

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

Идея такая – создать бинарный файл, состоящий из последовательности байт от 0 до 255, то есть размером 256 байт, а потом распрасить его JavaScript’ом и проанализировать полученные символы.

Пишем краткий сценрий на PHP, ибо PHP всегда под рукой, что в Виньде ,что в Линуксе, что на серверах.

<?php
/* create file of bytes from 0 to 255 */
	$s = '';
	for($i = 0; $i < 256; $i++)
		$s .= chr($i);
 
	file_put_contents('256.dat', $s);

В итоге, мы имеем файл 256.dat, который проанализируем JavaScript’ и составим табличку преобразований на лету.

var name = '256.dat', i; // путь к нашему файлу и переменная д/цикла
 
/* ******************************************
* Используем API AkelPad's Scripts плагина  *
******************************************* */
var bytes = AkelPad.ReadFile(name, 0);	// binary
log('bytes.length=' + bytes.length); // должно быть 256
 
var abytes = bytes.split('');
log('abytes.length=' + abytes.length); // должно быть 256
 
var ascii = '', fromAscii = '', c;
for(i = 0; i < abytes.length; i++) {
        // table asciiCodeAt
        if(i > 127)
	  ascii += 'case 0x' + bin2hex(abytes[i]) +
            ': return 0x' + i.toString(16) + '; break;\n'
	// REVERSE TABLE fromAscii
	c = bin2hex(abytes[i]);
	if(c.length == 3)
		c = '0' + c;
	else
	if(c.length == 2)
		c = '00' + c;
	// count only high values
	if(i > 127)
	  fromAscii += 'case 0x' + i.toString(16) + ': s += \'\\u' +
            c.toUpperCase() + '\'; break;\n'
}
 
log('// ASCII:');
log(ascii)
log('// fromAscii:');
log(fromAscii);
 
function bin2hex(s) {
  //  discuss at: http://phpjs.org/functions/bin2hex/
  // original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
  // bugfixed by: Onno Marsman
  // bugfixed by: Linuxworld
  // improved by: ntoniazzi (http://phpjs.org/functions/bin2hex:361#comment_177616)
  //   example 1: bin2hex('Kev');
  //   returns 1: '4b6576'
  //   example 2: bin2hex(String.fromCharCode(0x00));
  //   returns 2: '00'
 
  var i, l, o = '',
    n;
 
  s += '';
 
  for (i = 0, l = s.length; i < l; i++) {
    n = s.charCodeAt(i)
      .toString(16);
    o += n.length < 2 ? '0' + n : n;
  }
 
  return o;
}
 
// Логируем текст с помощью AkelPad'овского Log плагина
function log(text)
{
  AkelPad.Call("Log::Output", 4, text + '\n');
}

Потом копируем полученный вывод в отдельный файл, добавляем необохдимую разметку и получаем в итоге полные таблицы преобразований to ASCII и from Ascii Code для JavaScript:

/*
	Needed for reading from binary files
 	See actionUtils.getImageSize method in emmet-app.js
*/
String.prototype.asciiCodeAt = function(i) {
    // charCodeAt returns some bytes translated to unicode characters. 
    // this function means to counteract that.
    switch(this.charCodeAt(i)) {
        case 0x402: return 0x80; break;
				case 0x403: return 0x81; break;
				case 0x201a: return 0x82; break;
				case 0x453: return 0x83; break;
				case 0x201e: return 0x84; break;
				case 0x2026: return 0x85; break;
				case 0x2020: return 0x86; break;
				case 0x2021: return 0x87; break;
				case 0x20ac: return 0x88; break;
				case 0x2030: return 0x89; break;
				case 0x409: return 0x8a; break;
				case 0x2039: return 0x8b; break;
				case 0x40a: return 0x8c; break;
				case 0x40c: return 0x8d; break;
				case 0x40b: return 0x8e; break;
				case 0x40f: return 0x8f; break;
				case 0x452: return 0x90; break;
				case 0x2018: return 0x91; break;
				case 0x2019: return 0x92; break;
				case 0x201c: return 0x93; break;
				case 0x201d: return 0x94; break;
				case 0x2022: return 0x95; break;
				case 0x2013: return 0x96; break;
				case 0x2014: return 0x97; break;
				case 0x98: return 0x98; break;
				case 0x2122: return 0x99; break;
				case 0x459: return 0x9a; break;
				case 0x203a: return 0x9b; break;
				case 0x45a: return 0x9c; break;
				case 0x45c: return 0x9d; break;
				case 0x45b: return 0x9e; break;
				case 0x45f: return 0x9f; break;
				case 0xa0: return 0xa0; break;
				case 0x40e: return 0xa1; break;
				case 0x45e: return 0xa2; break;
				case 0x408: return 0xa3; break;
				case 0xa4: return 0xa4; break;
				case 0x490: return 0xa5; break;
				case 0xa6: return 0xa6; break;
				case 0xa7: return 0xa7; break;
				case 0x401: return 0xa8; break;
				case 0xa9: return 0xa9; break;
				case 0x404: return 0xaa; break;
				case 0xab: return 0xab; break;
				case 0xac: return 0xac; break;
				case 0xad: return 0xad; break;
				case 0xae: return 0xae; break;
				case 0x407: return 0xaf; break;
				case 0xb0: return 0xb0; break;
				case 0xb1: return 0xb1; break;
				case 0x406: return 0xb2; break;
				case 0x456: return 0xb3; break;
				case 0x491: return 0xb4; break;
				case 0xb5: return 0xb5; break;
				case 0xb6: return 0xb6; break;
				case 0xb7: return 0xb7; break;
				case 0x451: return 0xb8; break;
				case 0x2116: return 0xb9; break;
				case 0x454: return 0xba; break;
				case 0xbb: return 0xbb; break;
				case 0x458: return 0xbc; break;
				case 0x405: return 0xbd; break;
				case 0x455: return 0xbe; break;
				case 0x457: return 0xbf; break;
				case 0x410: return 0xc0; break;
				case 0x411: return 0xc1; break;
				case 0x412: return 0xc2; break;
				case 0x413: return 0xc3; break;
				case 0x414: return 0xc4; break;
				case 0x415: return 0xc5; break;
				case 0x416: return 0xc6; break;
				case 0x417: return 0xc7; break;
				case 0x418: return 0xc8; break;
				case 0x419: return 0xc9; break;
				case 0x41a: return 0xca; break;
				case 0x41b: return 0xcb; break;
				case 0x41c: return 0xcc; break;
				case 0x41d: return 0xcd; break;
				case 0x41e: return 0xce; break;
				case 0x41f: return 0xcf; break;
				case 0x420: return 0xd0; break;
				case 0x421: return 0xd1; break;
				case 0x422: return 0xd2; break;
				case 0x423: return 0xd3; break;
				case 0x424: return 0xd4; break;
				case 0x425: return 0xd5; break;
				case 0x426: return 0xd6; break;
				case 0x427: return 0xd7; break;
				case 0x428: return 0xd8; break;
				case 0x429: return 0xd9; break;
				case 0x42a: return 0xda; break;
				case 0x42b: return 0xdb; break;
				case 0x42c: return 0xdc; break;
				case 0x42d: return 0xdd; break;
				case 0x42e: return 0xde; break;
				case 0x42f: return 0xdf; break;
				case 0x430: return 0xe0; break;
				case 0x431: return 0xe1; break;
				case 0x432: return 0xe2; break;
				case 0x433: return 0xe3; break;
				case 0x434: return 0xe4; break;
				case 0x435: return 0xe5; break;
				case 0x436: return 0xe6; break;
				case 0x437: return 0xe7; break;
				case 0x438: return 0xe8; break;
				case 0x439: return 0xe9; break;
				case 0x43a: return 0xea; break;
				case 0x43b: return 0xeb; break;
				case 0x43c: return 0xec; break;
				case 0x43d: return 0xed; break;
				case 0x43e: return 0xee; break;
				case 0x43f: return 0xef; break;
				case 0x440: return 0xf0; break;
				case 0x441: return 0xf1; break;
				case 0x442: return 0xf2; break;
				case 0x443: return 0xf3; break;
				case 0x444: return 0xf4; break;
				case 0x445: return 0xf5; break;
				case 0x446: return 0xf6; break;
				case 0x447: return 0xf7; break;
				case 0x448: return 0xf8; break;
				case 0x449: return 0xf9; break;
				case 0x44a: return 0xfa; break;
				case 0x44b: return 0xfb; break;
				case 0x44c: return 0xfc; break;
				case 0x44d: return 0xfd; break;
				case 0x44e: return 0xfe; break;
				case 0x44f: return 0xff; break;
 
        default: return this.charCodeAt(i);
    }
}
 
/*
	Needed for writing to binary files.
 	See base64.decode method in emmet-app.js
*/
String.fromAsciiCode = function() {
    // reverse for asciiCodeAt() method
    var s = '', i, c = arguments.length;
 
    for(i = 0; i < c; i++)
    	switch(arguments[i]) {
				case 0x80: s += '\u0402'; break;
				case 0x81: s += '\u0403'; break;
				case 0x82: s += '\u201A'; break;
				case 0x83: s += '\u0453'; break;
				case 0x84: s += '\u201E'; break;
				case 0x85: s += '\u2026'; break;
				case 0x86: s += '\u2020'; break;
				case 0x87: s += '\u2021'; break;
				case 0x88: s += '\u20AC'; break;
				case 0x89: s += '\u2030'; break;
				case 0x8a: s += '\u0409'; break;
				case 0x8b: s += '\u2039'; break;
				case 0x8c: s += '\u040A'; break;
				case 0x8d: s += '\u040C'; break;
				case 0x8e: s += '\u040B'; break;
				case 0x8f: s += '\u040F'; break;
				case 0x90: s += '\u0452'; break;
				case 0x91: s += '\u2018'; break;
				case 0x92: s += '\u2019'; break;
				case 0x93: s += '\u201C'; break;
				case 0x94: s += '\u201D'; break;
				case 0x95: s += '\u2022'; break;
				case 0x96: s += '\u2013'; break;
				case 0x97: s += '\u2014'; break;
				//case 0x98: s += '\u0098'; break;
				case 0x99: s += '\u2122'; break;
				case 0x9a: s += '\u0459'; break;
				case 0x9b: s += '\u203A'; break;
				case 0x9c: s += '\u045A'; break;
				case 0x9d: s += '\u045C'; break;
				case 0x9e: s += '\u045B'; break;
				case 0x9f: s += '\u045F'; break;
				//case 0xa0: s += '\u00A0'; break;
				case 0xa1: s += '\u040E'; break;
				case 0xa2: s += '\u045E'; break;
				case 0xa3: s += '\u0408'; break;
				//case 0xa4: s += '\u00A4'; break;
				case 0xa5: s += '\u0490'; break;
				//case 0xa6: s += '\u00A6'; break;
				//case 0xa7: s += '\u00A7'; break;
				case 0xa8: s += '\u0401'; break;
				//case 0xa9: s += '\u00A9'; break;
				case 0xaa: s += '\u0404'; break;
				//case 0xab: s += '\u00AB'; break;
				//case 0xac: s += '\u00AC'; break;
				//case 0xad: s += '\u00AD'; break;
				//case 0xae: s += '\u00AE'; break;
				case 0xaf: s += '\u0407'; break;
				//case 0xb0: s += '\u00B0'; break;
				//case 0xb1: s += '\u00B1'; break;
				case 0xb2: s += '\u0406'; break;
				case 0xb3: s += '\u0456'; break;
				case 0xb4: s += '\u0491'; break;
				//case 0xb5: s += '\u00B5'; break;
				//case 0xb6: s += '\u00B6'; break;
				//case 0xb7: s += '\u00B7'; break;
				case 0xb8: s += '\u0451'; break;
				case 0xb9: s += '\u2116'; break;
				case 0xba: s += '\u0454'; break;
				//case 0xbb: s += '\u00BB'; break;
				case 0xbc: s += '\u0458'; break;
				case 0xbd: s += '\u0405'; break;
				case 0xbe: s += '\u0455'; break;
				case 0xbf: s += '\u0457'; break;
				case 0xc0: s += '\u0410'; break;
				case 0xc1: s += '\u0411'; break;
				case 0xc2: s += '\u0412'; break;
				case 0xc3: s += '\u0413'; break;
				case 0xc4: s += '\u0414'; break;
				case 0xc5: s += '\u0415'; break;
				case 0xc6: s += '\u0416'; break;
				case 0xc7: s += '\u0417'; break;
				case 0xc8: s += '\u0418'; break;
				case 0xc9: s += '\u0419'; break;
				case 0xca: s += '\u041A'; break;
				case 0xcb: s += '\u041B'; break;
				case 0xcc: s += '\u041C'; break;
				case 0xcd: s += '\u041D'; break;
				case 0xce: s += '\u041E'; break;
				case 0xcf: s += '\u041F'; break;
				case 0xd0: s += '\u0420'; break;
				case 0xd1: s += '\u0421'; break;
				case 0xd2: s += '\u0422'; break;
				case 0xd3: s += '\u0423'; break;
				case 0xd4: s += '\u0424'; break;
				case 0xd5: s += '\u0425'; break;
				case 0xd6: s += '\u0426'; break;
				case 0xd7: s += '\u0427'; break;
				case 0xd8: s += '\u0428'; break;
				case 0xd9: s += '\u0429'; break;
				case 0xda: s += '\u042A'; break;
				case 0xdb: s += '\u042B'; break;
				case 0xdc: s += '\u042C'; break;
				case 0xdd: s += '\u042D'; break;
				case 0xde: s += '\u042E'; break;
				case 0xdf: s += '\u042F'; break;
				case 0xe0: s += '\u0430'; break;
				case 0xe1: s += '\u0431'; break;
				case 0xe2: s += '\u0432'; break;
				case 0xe3: s += '\u0433'; break;
				case 0xe4: s += '\u0434'; break;
				case 0xe5: s += '\u0435'; break;
				case 0xe6: s += '\u0436'; break;
				case 0xe7: s += '\u0437'; break;
				case 0xe8: s += '\u0438'; break;
				case 0xe9: s += '\u0439'; break;
				case 0xea: s += '\u043A'; break;
				case 0xeb: s += '\u043B'; break;
				case 0xec: s += '\u043C'; break;
				case 0xed: s += '\u043D'; break;
				case 0xee: s += '\u043E'; break;
				case 0xef: s += '\u043F'; break;
				case 0xf0: s += '\u0440'; break;
				case 0xf1: s += '\u0441'; break;
				case 0xf2: s += '\u0442'; break;
				case 0xf3: s += '\u0443'; break;
				case 0xf4: s += '\u0444'; break;
				case 0xf5: s += '\u0445'; break;
				case 0xf6: s += '\u0446'; break;
				case 0xf7: s += '\u0447'; break;
				case 0xf8: s += '\u0448'; break;
				case 0xf9: s += '\u0449'; break;
				case 0xfa: s += '\u044A'; break;
				case 0xfb: s += '\u044B'; break;
				case 0xfc: s += '\u044C'; break;
				case 0xfd: s += '\u044D'; break;
				case 0xfe: s += '\u044E'; break;
				case 0xff: s += '\u044F'; break;
 
        default: s += String.fromCharCode(arguments[i]);
    }
    return s;
}

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

Например, используем эти таблички для base64 кодирования/декодирования бинарных данных (методы из emmet-app.js)

/**
 * @author Sergey Chikuyonok (serge.che@gmail.com)
 * @link http://chikuyonok.ru
 */
emmet.define('base64', function(require, _) {
	var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
 
	return {
		/**
		 * Encodes data using base64 algorithm
		 * @author Tyler Akins (http://rumkin.com)
		 * @param {String} input
		 * @returns {String}
		 * @memberOf emmet.base64
		 */
		encode : function(input) {
			var output = [];
			var chr1, chr2, chr3, enc1, enc2, enc3, enc4, cdp1, cdp2, cdp3;
			var i = 0, il = input.length, b64 = chars;
 
			while (i < il) {
 
				// call .asciiCodeAt() instead of .charCodeAt()
				// see AkelEmmet.js in Scripts folder for details
				cdp1 = input.asciiCodeAt(i++);
				cdp2 = input.asciiCodeAt(i++);
				cdp3 = input.asciiCodeAt(i++);
 
				chr1 = cdp1 & 0xff;
				chr2 = cdp2 & 0xff;
				chr3 = cdp3 & 0xff;
 
				enc1 = chr1 >> 2;
				enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
				enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
				enc4 = chr3 & 63;
 
				if (isNaN(cdp2)) {
					enc3 = enc4 = 64;
				} else if (isNaN(cdp3)) {
					enc4 = 64;
				}
 
				output.push(b64.charAt(enc1) + b64.charAt(enc2) + b64.charAt(enc3) + b64.charAt(enc4));
			}
 
			return output.join('');
		},
 
		/**
		 * Decodes string using MIME base64 algorithm
		 * 
		 * @author Tyler Akins (http://rumkin.com)
		 * @param {String} data
		 * @return {String}
		 */
		decode : function(data) {
			var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, tmpArr = [];
			var b64 = chars, il = data.length;
 
			if (!data) {
				return data;
			}
 
			data += '';
 
			do { // unpack four hexets into three octets using index points in b64
				h1 = b64.indexOf(data.charAt(i++));
				h2 = b64.indexOf(data.charAt(i++));
				h3 = b64.indexOf(data.charAt(i++));
				h4 = b64.indexOf(data.charAt(i++));
 
				bits = h1 << 18 | h2 << 12 | h3 << 6 | h4;
 
				o1 = bits >> 16 & 0xff;
				o2 = bits >> 8 & 0xff;
				o3 = bits & 0xff;
 
				if (h3 == 64) {
					tmpArr[ac++] = String.fromAsciiCode(o1); // String.fromCharCode(o1);
				} else if (h4 == 64) {
					tmpArr[ac++] = String.fromAsciiCode(o1, o2); //String.fromCharCode(o1, o2);
				} else {
					tmpArr[ac++] = String.fromAsciiCode(o1, o2, o3); //String.fromCharCode(o1, o2, o3);
				}
			} while (i < il);
 
			return tmpArr.join('');
		}
	};
});

Крепкого Вам чая в бокале, сладкого лимона и главное конечно, я знаю, что банален, но желаю хорошего настроения. От чего оно зависит?

PHP: HTML::render – грамотно рендерим свои странички

Здравствуйте.

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

Это мощный рендерер занимает всего строчек 40, очень удобный и быстрый. Я им пользуюсь уже 5 лет и ни разу не пожалел об этом. Всегда используйте этот рендерер, даже в демо-проектах, нечего сваливать PHP-код и HTML-разметку в кучу.

HTML::render

Вот его полный код:

///////////////////////////////////////////////
//////////// PARSE HTML FUNCTIONS ////////////
/////////////////////////////////////////////
// use STATIC rendering - используем статический рендеринг
class HTML
{
        // deafult folder for html pages (templates)
	static private $folder = 'html';
	// change default folder for html templates
	static public function changeFolder($folder) {
		self::$folder = $folder;
	}
 
	static public function render($template, $data = array()) {
		$content = file_get_contents(self::$folder."/{$template}.html");
		$content = self::design_render_text($content, $data);
		return $content;
	}
 
	static private function design_render_text($content, $data = array()) {
		$content = self::design_parse_function($content, $data);
		$content = self::design_parse($content, $data);
		return $content;
	}
	static private function design_parse_function($content, $data = array()) {
		preg_match_all('/\<\<(.*?)\>\>/is', $content, $res);
		if (@$res[1])
			foreach ($res[1] as $el) {
				$middle = self::design_parse($el, $data);
				$middle = '$result = '.$middle.';';
				eval($middle);
 
				$content = str_ireplace('<<'.$el.'>>', $result, $content);
			}
		return $content;
	}
	static private function design_parse($content, $data) {
		preg_match_all('/\%\%(.*?)\%\%/si', $content, $res);
		if (@$res[1])
			foreach ($res[1] as $el) $content = str_ireplace('%%'.$el.'%%', $data[$el], $content);
		return $content;
	}
}

Достаточно включить этот класс в свой проект и вы можете легко и просто рендерить странички. Как он работает?

Синтаксис:
HTML::render( template [ , array ] );

template – имя шаблона веб-странички. Используется без расширения. По умолчанию шаблоны расположены в папке html (её можно поменять вызовом HTML::changeFolder(‘tpl’), чтобы использовать папку tpl вместо html, к примеру). Шаблоны в папке должны иметь расширение html. Можно использовать любое кол-во вложенных подпапок, естественно.

array – ассоциативный массив, все ключи которого сопоставляются с вхождениями %%…%% в шаблоне и с заменой их значениями. Можно не использовать, если на страничке нет таких элементов.

Например:

$html = array('TITLE' => 'Добро пожаловать!');
$page = HTML::render('index', $html);
die($page);

и браузер выдаст нам отрендеренную страничку. Если в шаблоне html/index.html будет присутствовать вхождение %%TITLE%%, оно будет заменено в данном случае на Добро пожаловать!

Странички состоят из простейших шаблонов – HTML-код + вставка переменных и любых PHP-конструкций.
%%VAR%% – используем подстановку для VAR. VAR – ключ ассоциативного массива, который идёт вторым параметром в вызове HTML::render() (см. выше).
<<PHP_code>> – выполнение PHP кода и отображение на страничке его результата. Можно использовать любые доступные при вызове рендерера функции, и супер-глобальные переменные, например <<$_SERVER[‘HTTP_HOST’]>>
Также может быть использовано для логического ветвления в шаблоне, когда рендерер отобразит особую часть из шаблона при выполнении определённого условия. Пример смотрите ниже.

Пример

Но всё слова-слова, но без примера не всё так понятно, верно.

Вот наш шаблон:

<!DOCTYPE html>
<html lang="ru">
 
<head>
	<meta charset="utf-8">
	<title>Ошибка соединения с БД</title>
</head>
 
<body>
<h1 style="color: blue">Ошибка соединения с БД.<br>
Повторите попытку через минуту!</h1>
<<('%%DEBUG_MODE%%')?'
<div class="info">
%%DB_ERR%%
</div>':''>>
</body>
</html>

Использование:

$dsn = 'mysql:dbname=testdb;host=127.0.0.1';
$user = 'dbuser';
$password = 'dbpass';
 
try {
    $dbh = new PDO($dsn, $user, $password);
    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
    die(HTML::render('err/db', 
      array('DEBUG_MODE' => $_DEBUGGING, 'DB_ERR' => $e->getMessage())))
}

В этом примере делается попытка соединиться с БД, и если она не удалась, выводится страница из шаблона, находящегося в файле html/err/db.html. %%DB_ERR%% заменяется значением $e->getMessage(), и оно будет включено в страничку, только если %%DEBUG_MODE%%, который преобразуется в true/false согласно переменной $_DEBUGGING, будет равен true.

Этот класс можно применять не только для рендеринга цельных (оконечных) страничек, но и для обработки любых HTML-кусков. Да, возможно не хватает выполнения циклических блоков, как в Smarty, например, но для 40-ка строчек PHP-кода, я полагаю, и это совсем неплохо. Любую нужную функциональность уже добавляем по мере необходимости и по прихоти клиентов.

Удачи на даче! И крепкого чая, крепкой любви и крепкого имбиря.

Немного философии или крутой класс-обертка над PHP PDO (MySQL, SQlite etc.)

Здравствуйте.

Читатель моего блока наверное недоумевает – блог называется “Философия программирования”, но вот философии в нем что-то не наблюдается. Я хотел написать пост о программистах странных, но позже напишу. Сейчас хочу представить класс-обёртку для PHP/PDO.

Класс-обёртка для PHP/PDO

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

А как же русская народная пословица (мудрость?) “Простота хуже воровства”, спросите вы и поставите меня в тупик.
Когда я думал об этой пословице, я долго бил себя кулаком в грудь, и пытался сам себе доказать, что пословица неверна, что я прав, и лучше всё упрощать, чем усложнять. Но червь сомнения всё равно селился в моей душе и грыз меня изнутри, доводя до слёз.

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

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

Что касаемо кода (программирования), то чем проще и понятней код, чем его меньше, тем проще его поддерживать и отлаживать, верно? Тут нет никаких сомнений или исключений.

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

Хотя изумительно сложные по виду и разнообразные фракталы (например, классический фрактал Мандельброта), как вы знаете, рисуются буквально десятком (или менее) строчек кода, надо просто знать соотв. уравнения.

ОК, может хватить философии и займёмся делом?

Класс-обёртка для PHP/PDO – теперь уже точно! ))

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

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

Преимущества оберток – значительное упрощение вычислений и кода.

Например, случай PHP PDO.

Да, в PDO есть все методы для работы с базами данных, это универсальная обертка над различными драйверами БД. В нем есть такие методы, как PDO::query, PDO::prepare, PDO::exec и т.д.

Также надо знать и учитывать класс PDOStatement, который позволяет подготавливать и вычислять различные запросы к БД и получать из них результаты.

Общие случаи работы с PDO таков:

Создаём экземпляр класса PDO:

try
{
	$db = new PDO(
		"mysql:host=localhost;dbname=my_database;charset=utf8",
		'username',
		'password',
		array(PDO::MYSQL_ATTR_INIT_COMMAND=>'SET NAMES UTF8')
	);
}
catch (PDOException $e)
{
	die('Сайт перегружен. Повторите попытку через 5 секунд'); 
}

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

Например:

$prep = $db->prepare("SELECT id, name, active FROM partners WHERE email=? AND pass=? LIMIT 1");
$prep->execute(array($email, $pass));
$ar = $prep->fetch(PDO::FETCH_ASSOC);
if(!count($ar)) {
  echo 'Неверное имя или пароль';
  return false;
}
// ... продолжение кода

Таким образом, используя $prep как экземпляр PDOStatement, мы получаем в $ar ассоциативный массив выборки из таблицы partners открытой БД. Теперь в $ar[‘id’], $ar[‘name’, $ar[‘active’] содержат нужные значения.

Используя же обертку, мы можем написать примерно так:

$prep = DB::execute("SELECT id, name, active FROM partners WHERE email=? AND pass=? LIMIT 1", array($email, $pass)); 
$ar = $prep->fetch(PDO::FETCH_ASSOC);
if(!count($ar)) {
  echo 'Неверное имя или пароль';
  return false;
}
// ... продолжение кода

В нашей обёртке все обращения к БД осуществляются через статический класс DB, таким образом нам не нужно следить, чтобы указатель на открытую БД всегда находился в пределах видимости функций работы с ним. Это уже большой плюс.
Кроме того, обертка как мы видим в предыдущем примере упрощает ряд вызовов, например вместо двух вызовов с созданием промежуточной переменной $prep типа PDOStatement мы в данном случае используем один.

Класс DB (static)

Итак, привожу полный пример класса DB, а после – краткое описание методов класса и примеры работы с ним.

//////////////////////////////////////////////////////////
//////////////// PDO DB wrapper	/////////////////////////
////////////////////////////////////////////////////////
class DB
{
// use STATIC rendering
	static private $db;	// db handler
 
	static public function init() {
		try {
			self::$db = new PDO(
				"mysql:host=localhost;dbname=my_database;charset=utf8",
				'username', 'password',
				array(PDO::MYSQL_ATTR_INIT_COMMAND=>'SET NAMES utf8')
			);
			self::$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
			//self::$db->exec("SET NAMES utf8"); -- не нужно если сработает PDO::MYSQL_ATTR_INIT_COMMAND?
		}
		catch (PDOException $e)
		{
			die('<h3 style="color: blue">Ошибка соединения с БД. Повторите попытку через полминуты</h3>');
		}
	}
 
	static public function query($sql) {
		return self::$db->query($sql);
	}
 
	static public function exec($sql) {
		return self::$db->exec($sql);
	}
 
	// one column result
	static public function column($sql) {
		return self::$db->query($sql)->fetchColumn();
	}
 
	// intval one column result
	static public function columnInt($sql) {
		return intval(self::$db->query($sql)->fetchColumn());
	}
 
	static public function prepare($sql) {
		return self::$db->prepare($sql);
	}
 
	static public function lastInsertId() {
		return self::$db->lastInsertId();
	}
 
	// prepares and execute one SQL
	static public function execute($sql, $ar) {
		return self::$db->prepare($sql)->execute($ar);
	}
 
	// returns error info on db handler (not stmt handler)
	static public function error() {
		$ar = self::$db->errorInfo();
		return $ar[2] . ' (' . $ar[1] . '/' . $ar[0] . ')';
	}
 
	// returns one row fetched in FETCH_ASSOC mode
	static public function fetchAssoc($sql) {
		return self::$db->query($sql)->fetch(PDO::FETCH_ASSOC);
	}
 
	// returns one row fetched in FETCH_NUM mode
	static public function fetchNum($sql) {
		return self::$db->query($sql)->fetch(PDO::FETCH_NUM);
	}
 
}

Для запуска БД надо написать:

DB::init();

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

Единственная не универсальность, это:

catch (PDOException $e)
{
  die('<h3 style="color: blue">Ошибка соединения с БД. Повторите попытку через полминуты</h3>');
}

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

Описание методов класса DB

Итак, вот описание всех методов класса:

DB::query($sql) – выполнить один запрос к БД с возвращением результата PDOStatement. его следует использовать только в том случае, если запрос не содержит необработанные данные, например из внешних источников. При использовании данных в запросе от внешних источников всегда следует применять подготовленные запросы (prepared statements) – смотрите ниже – в целях предотвращения SQL Injection (инъекции SQL запроса) и уверенности в целостности конечного запроса.

DB::exec($sql) – выполнение запроса, не требующего возврата результата. К нему относится тоже примечание, что и для DB::query. Так же его всегда следует использовать вместо DB::query, если запрос НЕ возвращает результата (обычно это UPDATE или DELETE), так как он, как указано, работает быстрее.
Возвращает кол-во затронутых строк.

DB::column($sql) – первая вкусность, которая упрощает жизнь. Выбор только одного значения из БД, соотв. запросу.
DB::columnInt($sql) – тоже самое, только возвращается intval значение поля выборки, что бывает полезно.

DB::prepare($sql) – подготовка запроса, возвращает PDOStatement.
DB::lastInsertId() – получение lastInsertId от последнего запроса INSERT.

DB::execute($sql, $ar) – подготавливает и сразу выполняет запрос. Каждый ? или :var в теле запроса должен соотв. значению из массива $ar. Пример смотрите выше. Возвращает true/false.

DB::error – возвращает строку последней ошибки над DB вместе с кодами в скобках. Помните, что при использования PDOSatement’ов ошибки, связанные именно с их исполнением, следует обрабатывать над ними, а не над общей DB.

И, наконец, две последние вкусности:
DB::fetchAssoc($sql)
DB::fetchNum($sql)

Возвращают результат выполнения $sql, в первом случае как PDO::FETCH_ASSOC, во втором – как PDO::FETCH_NUM.
Заметьте, что запрос НЕ подготовлен, поэтому можно использовать только с проверенными данными (НЕ из внешних источников). Но так как в большинстве случаев мы пишем запросы с корректными данными, эти функции часто бывают очень полезны и сокращают код программы.

Пример их использования

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

$id = intval($id); // обычно это уже сделано, то есть $id - у нас целая переменная,
// и не может содержать строки или ещё какой-нибудь мусор
$ar = DB::fetchAssoc("SELECT name,surname,email FROM cleints WHERE id=$id");
extract($ar);
// теперь в переменных $name, $surname, $email у нас необходимые данные клиента.

Ещё пример:

$id = intval($id); // обычно это уже сделано, то есть $id - у нас целая переменная,
// и не может содержать строки или ещё какой-нибудь мусор
 
list($name, $dtm) = DB::fetchNum("SELECT CONCAT(name, ' ', surname), DATE(dk_dtend) 
			FROM clients
            WHERE DATE(dk_dtend)>'2007-01-01' AND id=$id");

Этот прием можно было использовать и в предыдущем примере, всё зависит от стиля, настроения и желания. Мы выбрали из таблицы два поля – имя ($name) и дату ($dtm) одним запросом. Здесь мы также уверенны, что подставляемая в запрос переменная $id не может содержать мусор или непроверенные данные.

Заключение

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

Но ничего не обещаю. Ещё Linux – компиляция программ с GCC/G++, make, cmake – ох, это чудо. GNU Utils, я люблю Вас! Сам не знаю за что.

До новых встреч – вкусного горячего кофе, удобной позы в работе, а самое главное – я не устаю это повторять, хотя в зубах завязло – отличного настроения! Это главное. Сейчас – отличное настроение. Не потом, не завтра, не в обед пятницы, не через год, на Гавайях, а сейчас. Отличное настроение – это бодрость духа – решает всё. ИМХО, конечно, по моему скромному мнению.

Удачи на даче!

Video пропорции 16:9 – ищем целые размеры

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

Для этого мне надо знать ширину и соотв. высоту видео для этой пропорции в разумных пределах, ну например от 16 до 1024. Где взять эти числа? Минутный гугло-поиск (а на поиск не хотел больше тратить по теме) не дал ничего.

Поэтому за пару минут была сваяна и запущена такая PHP-программа:

<?php
$prop = 16/9;                           
$x=16;
 
while($x <= 1024) {
	for($y = 9; $y <= 576; $y++)
		if($x / $y == $prop)
			print('Found: ' . $x . ':' . $y . "\n");
	$x++;
}

16:9 и 1024:576 берём как проверенные константы. Если нужно больше – возможна константа 1280:720.

Итак, сохраняем под именем prop16_9.php и запускаем наше творение:

php prop16_9.php

и танцуем с бубном в руках! У нас всё получилось. Спасибо тебе, PHP мой дорогой!

Да, а вывод программы таков:

Found: 16:9             
Found: 32:18            
Found: 48:27            
Found: 64:36            
Found: 80:45            
Found: 96:54            
Found: 112:63           
Found: 128:72           
Found: 144:81           
Found: 160:90           
Found: 176:99           
Found: 192:108          
Found: 208:117          
Found: 224:126          
Found: 240:135          
Found: 256:144          
Found: 272:153          
Found: 288:162          
Found: 304:171          
Found: 320:180          
Found: 336:189          
Found: 352:198
Found: 368:207
Found: 384:216
Found: 400:225
Found: 416:234
Found: 432:243
Found: 448:252
Found: 464:261
Found: 480:270
Found: 496:279
Found: 512:288
Found: 528:297
Found: 544:306
Found: 560:315
Found: 576:324
Found: 592:333
Found: 608:342
Found: 624:351
Found: 640:360
Found: 656:369
Found: 672:378
Found: 688:387
Found: 704:396
Found: 720:405
Found: 736:414
Found: 752:423
Found: 768:432
Found: 784:441
Found: 800:450
Found: 816:459
Found: 832:468 
Found: 848:477 
Found: 864:486 
Found: 880:495 
Found: 896:504 
Found: 912:513 
Found: 928:522 
Found: 944:531 
Found: 960:540 
Found: 976:549 
Found: 992:558 
Found: 1008:567
Found: 1024:576

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

Удачи!

С Новым 2015 Годом! или Добавляем гирлянды и снег на наши странички

С Новым 2015 Годом!

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

Пример такой странички Вы можете наблюдать здесь – http://goloslogos.ru/ny2015.html. Только там гирлянды раскалываются без звука, потому что меня попросили его убрать, и ниже я покажу Вам как это сделать.

Начнём

Итак, гирляндочки и снег придумал не я, а Скот Шиллер. Родная страничка снежного шторма (snowstorm) здесь – http://www.schillmania.com/projects/snowstorm/

Она посвящена снегу, а гирляндочки идут бонусом!

Итак, заходим на https://github.com/scottschiller/snowstorm и скачиваем проект как ZIP-архив (кнопка справа Download ZIP) ну или естесно клонируем её:

git clone https://github.com/scottschiller/Snowstorm.git

Из всего этого проекта нам нужна папочка lights (это виджет с гирляндами) и скрипт snowstorm.js.

Чтобы все это у меня лежало в порядке в одном месте, я закидываю скриптик snowstorm.js в папку lights, а саму папку lights мы закидываем в наш проект.

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

  <link rel="stylesheet" media="screen" href="lights/lights.css" />
  <script src="lights/soundmanager2-nodebug-jsmin.js"></script>
  <script src="http://yui.yahooapis.com/combo?2.6.0/build/yahoo-dom-event/yahoo-dom-event.js&2.6.0/build/animation/animation-min.js"></script>
  <script src="lights/christmaslights.js?2015"></script>
 
  <script src="lights/snowstorm.js"></script>

Гирляндочки тянут за собой soundmanager2 (крутая либа от того же автора) – для звука взрывающихся гирлянд, и yahoo-dom-event – либу от Yahoo для удобного манипулирования домом. Заметьте, если Вам нужен только снег без гирляндочек, все эти файлики не нужны, надо подключить лишь snowstorm.js и всё!

Но какой Новый Год без гирляндочек, верно? Поэтому нам придётся повозиться.

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

<div id="lights">
<!-- Гирлянды будут здесь -->
</div>

В принципе и всё. Можно перезагрузить страницу и наслаждаться гирляндами и снегом! Ура, с праздником вас!

Но! jQuery?

Для работы снега и гирлянд jQuery не нужен.

Но. Но если мы используем jQuery и ествественно везде у нас стоит знак доллара, то lights будут ругаться. Дело в том, что автор скрипта додумался использовтаь этот знак для обертки функции getElementById. Открываем файлик christmaslights.js и в самом верху видим такое:

function $(sID) {
  return document.getElementById(sID);
}

Ну что за… Меняем здесь $ на что-то нейтральное, например, на __gid, а затем меняем на этой же страничке ниже все вхождения $ на __gid (их всего три что ли, не помню точно, но мало, можно было вообще с оберткой не заморачиваться имхо).

Всё, после этого jQuery заработает параллельно с lights!

Убираем звук

После того, как я поставил эти виджеты на страничку, мне позвонили и сказали – А что, там гирлянды со звуком взрываются? Я – Да, это же прикольно! Мне – Убери его, он пугает.

Кого может напугать этот милый чудесный звук лопающихся гирлянд?

Но хозяин – барин, и мне пришлось скрепя сердце убрать звук. Как? Не скажу, чтобы вы тоже этого не делали (шутка).

В том же самом файлике christmaslights.js находим (примерно на 318 строке) такой кусок кода:

if (soundManager && soundManager.ok()) {
  soundManager.play(self.soundID,{pan:self.pan});
  // soundManager.sounds[self.soundID].play({pan:self.pan});
  // if (self.bonusSound != null) window.setTimeout(self.smashBonus,1000);
}

(здесь комментарии не мои, а автора).

Как нетрудно догадаться, почесав репу пару минут (а может и пару часов), здесь происходит создание звука. Теперь просто закомментируем строчку soundManager.play(self.soundID,{pan:self.pan});, сохраним файлик, аплодим его, обновляем HTML – и вуаля. Звука нет!

И это ещё не всё!

Внимательный читатель заметит на страничке http://www.schillmania.com/projects/snowstorm/ описание целого API для управления снегом. Не буду перечислять все возможности этого мощного API, но кое-что отмечу.

Мы использовали такой код в придачу при старте снега (читайте комментарии того, что делает каждая строка):

snowStorm.excludeMobile = false; // включаем поддержку на мобильных устройствах
snowStorm.autoStart = false; // при загрузке страницы не стартуем сразу снег
snowStorm.flakesMaxActive = 50; // максимально кол-во одновременно активных снежинок
snowStorm.freezeOnBlur = true; // останавливать эффект снега при деактивации странички (наверное, полезно всем)
setInterval(snowStorm.toggleSnow, 15000); // вкл/выкл. снег каждые 15 секунд - это уже моё великое изобретение :)

Ещё пара интересных имхо свойств и методов этого замечательного API:

Свойства

snowStorm.followMouse = true; – заставим снег следовать за горизонтальным перемещением указателя мыши.
snowStorm.snowColor = ‘#fff’; – цвет снега – белый, он и так белый, не жёлтый же!
snowStorm.useTwinkleEffect = true; – позволим снегу мерцать периодически во время падения

Методы

snowStorm.freeze(); – заморозим снег
snowStorm.resume(); – разбудим снег из замороженного состояния
snowStorm.toggleSnow(); – вкл/выкл эффект снега
snowStorm.stop(); – полностью убрать эффект снега со странички. Чтобы не замерзнуть совсем…

Всё, на сим раскланиваюсь, надо FTP дорабатывать, я встал что-то на нём.

Благодарю автора снега Скотта Шиллера (Scott Schiller) за этот и другие классные проекты, а также вас, уважаемый читатель, за внимание и стремление к новым знаниям!

Удачи, успеха и удовольствия от работы!
С наступающими праздниками вас, друзья – всех с Великим Новым Годом, и православных – с Великим Рождеством!

JavaScript: обход атрибутов элемента

Методы JavaScript DOM .getAttribute() и jquery .attr() позволяют получить только один атрибут элемента по его имени. А что делать если нам нужно обойти все заданные атрибуты элемента, чтобы, например, определить какие-то параметры, и нам не важно точное имя атрибута?

Здесь показан обход аттрибутов методом JavaScript DOM и заполнение массивов nodes и values их именами и значениями соответственно (с http://stackoverflow.com/questions/2048720/get-all-attributes-from-a-html-element-with-javascript-jquery, ответ Roland Bouman, который получил всех больше плюсов):

var nodes=[], values=[],
    el = document.getElementById('someId');
for (var attr, i = 0, attrs = el.attributes, n = attrs.length; i < n; i++){
    attr = attrs[i];
    nodes.push(attr.nodeName);
    values.push(attr.nodeValue);
}

А если мы используем jquery, можно получить атрибуты так (c http://stackoverflow.com/questions/14645806/get-all-attributes-of-an-element-using-jquery, красивый ответ pimvdb) :

$(this).each(function() {
  $.each(this.attributes, function() {
    if(this.specified) {
      // Теперь this.name - имя атрибута
      // this.value - его значение (JavaScript DOM)
    }
  });
});

Примечание: в стрых IE мы получаем все возможные атрибуты, не только те, которые представлены в элементе. Поэтому мы используем свойство specified.

Автор ответа тут же указал дополнение к jquery, добавляющее вызов метода .attr() без параметров к любому элементу, который возвращает объект со всеми атрибутами этого элемента:

(function(old) {
  $.fn.attr = function() {
    if(arguments.length === 0) {
      if(this.length === 0) {
        return null;
      }
 
      var obj = {};
      $.each(this[0].attributes, function() {
        if(this.specified) {
          obj[this.name] = this.value;
        }
      });
      return obj;
    }
 
    return old.apply(this, arguments);
  };
})($.fn.attr);
 
// Использование:
var $div = $("<div data-a='1' id='b'>");
$div.attr();  // { "data-a": "1", "id": "b" }

Красиво, да? Автору ответа – большой плюс.

Правда сам на практике я ещё не проверял эти методы (кроме первого, он прекрасно работает).

До встречи, друзья, отличного настроения, крепкого сна и чистого неба над головой!