Category Archives: php

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) за этот и другие классные проекты, а также вас, уважаемый читатель, за внимание и стремление к новым знаниям!

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

PHP: простой класс для ведения логов

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

Вот как он выглядит:

class LOG
{
// use STATIC rendering
	static private $fplog;	// file handler for logging
 
	// open logging file for writing
	static public function start($flogname = 'log.txt') {
		self::$fplog = fopen($flogname,'ab');
	}
 
	static public function stop() {
		fclose(self::$fplog);
	}
 
	static public function write($s, $usedate = true) {
	// пишем в лог-файл строку $s,
	// $usedate - вставлять ли в лог дату/время текущие
		if($usedate) 
			$tim = '['.date('Y-m-d H:i:s').'] ';
		else
			$tim = '';
		fwrite(self::$fplog,$tim.$s."\n");
	}
}
 
LOG::start();	// start default logging

Замететь, что после создания этого статичного класса мы сразу стратуем журнал лога через LOG::start() метод. После этого методами LOG::write() мы можем писать в лог всё что нам вздумается.

Да, и сразу отвечаю на пару вопросов.
1. Используем статичные методы для того, чтобы вести логи без создания экземпляра класса, через статический метод LOG::write(сообщение для лога). По умолчанию в логе пишется время перед сообщением в формате [YYYY-mm-dd H:i:s], но если мы во втором параметре передадим false, то время писаться для данного сообщения не будет. Естественно, это поведение можно изменить заменив в методе write() второй параметр с $usedate = true на $usedate = false.

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

Из любых функций, методов мы пишем LOG::write() и наш лог заполнен.

Лог пишется в файл log.txt в текущей директории, но мы можем при вызове метода start передать другое имя для файла лога.

Чтобы в Apache environment закрыть доступ к логу со стороны внешних запросов можно использовать такое простое правило Apache‘а:

<Files log.txt>
Deny from all 
</Files>

Кинув это правило в файл .htaccess в папке с log.txt, мы оградим наш лог от внешних любопытных взглядов.

Пока всё, на сим раскланиваюсь, с наступающим Новым Годом вас и православным Рождеством.

Отличного настроения, здоровья и ясного сознания. Keep you mind clear. Keep your mind open.

До встречи, друзья!

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" }

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

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

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

Хачим jquery.easyBox

Представляем EasyBox

Недавно нашёл неплохой lightbox-плагин для jQuery – EasyBox (https://code.google.com/p/easybox/)

Вот его описание и главные характеристики:
EasyBox это клон lightbox’а базирующийся на скрипте Slimbox2. Он предлагает множество различных дополнений и возможностей, как то:

  • отображение видео YouTube, Vimeo и DailyMotion
  • отображение строчного контента
  • отображение iframe’ов
  • улучшенная анимация
  • поддержка тем

И ещё:
Что делает EasyBox отличным?

  • распознает пропорции и разеры видео
  • отображает сообщения об ошибке если рисунок или ссылка недоступны
  • сжимает контент до размера страницы
  • сохраняет события JavaScript на строчных элементах
  • поддерживает управление колесиком мыши
  • поддерживает слайдшоу
  • поддерживает Drag & Drop

Неплохо, да?

Подключаем EasyBox к нашему проекту

Подключаем его так (самый простой вариант):

Скачиваем его отсюда например: http://easybox.googlecode.com/files/easybox-v1.4.zip
(если вы читаете это не в 2014-начале 2015 года, возможно, появится новая версия, можно пошариться на его родном сайте, хотя я не знаю что там можно ещё улучшить).

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

В head страницы добавляем ссылки на CSS с нужной темой:

<link rel="stylesheet" href="/easybox/styles/default/easybox.min.css" type="text/css" media="screen" />

В данном случае это тема default, но можно поиграться и с другими темами.

Подключим jquery, например так:

<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>

А затем ссылки на сам easybox, либо так:

<!-- объединенный -->
<script type="text/javascript" src="/easybox/distrib.min.js"></script>

Или три ссылки:

<script type="text/javascript" src="/easybox/easybox.min.js"></script>
<script type="text/javascript" src="/easybox/handlers.min.js"></script>
<script type="text/javascript" src="/easybox/extras/autoload.min.js"></script>

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

<a href="images/diplom.jpg" title="Красный диплом института искусств им. П. А. Серебрякова" class="lightbox">
  <img src="images/diplom_thumb.jpg" class="white shadow" alt="Диплом">
</a>

В данном случае на странице отображается небольшой рисунок диплома (images/diplom_thumb.jpg), а по щелчку на нем открывается лайтбокс с изображением увеличенного диплома (images/diplom.jpg).

Аналогично можно добавить ссылки на YouTube или iframe, так чтобы они открывались в лайтбоксе:

<a href="http://www.youtube.com/watch?v=VIDEO_ID" title="Video caption" class="lightbox">Моё видео на Ютубе!</a>
<a href="http://www.example.com" title="Example page" class="lightbox" data-width="320" data-height="240">Мой iframe в лайтбоксе!</a>

Вместо VIDEO_ID подставляем естественно ид видео на Ютубе. Для iframe мы также указали его размеры (это делать не обязательно).

Хачим EasyBox

Тут вы спросите резонно – но если этот самый EasyBox так крут, что там можно хачить?
А я так же резонно отвечу – хачить есть всегда что, идеальных продуктов наверное не бывает.

Несмотря на то, что в описании EasyBox‘а помимо всего прочего написано: EasyBox’ look is fully customizable, на самом деле это конечно не так.

Что нам нужно?

  1. Панель управления с описание и кнопкой закрытия (крестиком) вверху окна лайтбокса.
  2. Чтобы верхняя часть содержимого была на виду (если не помещается по вышине, то должна быть у границы верхнего viewport’а браузера, а не выезажть за её границу).

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

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

Что делать?
Берём девелоперскую версию easybox.js (не minified) с github’а и работаем с ней.
Версия: Easybox v1.4

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

В пределах 70-ой строки находим такой код:

/*
	Initialization
*/
$(function() {
	$(doc.body).append(
		$([
			overlay = $('<div id="easyOverlay" />').click(userClose)[0],
			center = $('<div id="easyCenter" />').append([
				container = $('<div id="easyContainer" />')[0],
				loadingIndicator = $('<div id="easyLoadingIndicator" />')[0],
				bottom = $('<div id="easyBottom" />').append([
					navLinks = $('<div id="easyNavigation" />').append([
						prevLink = $('<a id="easyPrevLink" href="#" />').click(previous)[0],
						nextLink = $('<a id="easyNextLink" href="#" />').click(next)[0]
					])[0],
					closeLink = $('<a id="easyCloseLink" href="#" />').click(userClose)[0],
					slideLink = $('<a id="easySlideLink" href="#" />').click(toggleSlide)[0],
					caption = $('<div id="easyCaption" />')[0],
					number = $('<div id="easyNumber" />')[0]
				])[0]
			])[0]
		]).css("display", "none")
	);
});

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

Блок center в свою очередь содержит три части – container – контейнер для содержимого, loadingIndicator – индикатор загрузки, прячущайси в итоге под содержимым, и bottom – так у EasyBox неприхотливо названа панель управления. Само название bottom (с англ.: низ, дно) свидетельнствует о том, что автор изначально помещает его под содержимым.

А нам надо над. И мы берем строку с container, вырезаем её и переносим под bottom, убрав после него запятую (разделяющую элементы массива). А после bottom соотв. запятую убираем, иначе JavaScript парсер будет жестко ругаться.

В итоге получаем такой код:

	/*
		Initialization
	*/
	$(function() {
		$(doc.body).append(
			$([
				overlay = $('<div id="easyOverlay" />').click(userClose)[0],
				center = $('<div id="easyCenter" />').append([
					loadingIndicator = $('<div id="easyLoadingIndicator" />')[0],
					bottom = $('<div id="easyBottom" />').append([
						navLinks = $('<div id="easyNavigation" />').append([
							prevLink = $('<a id="easyPrevLink" href="#" />').click(previous)[0],
							nextLink = $('<a id="easyNextLink" href="#" />').click(next)[0]
						])[0],
						closeLink = $('<a id="easyCloseLink" href="#" />').click(userClose)[0],
						slideLink = $('<a id="easySlideLink" href="#" />').click(toggleSlide)[0],
						caption = $('<div id="easyCaption" />')[0],
						number = $('<div id="easyNumber" />')[0]
					])[0],
					container = $('<div id="easyContainer" />')[0]
				])[0]
			]).css("display", "none")
		);
	});

Красота! Один пункт задачи мы выполинили, переходим к следующему.

Парсим глазами скрипт easybox.js далее, ищем место где лайтбокс появляется на экране (EasyBox использует шикарную анимацию).

Мы помним, что основной блок лайтбокса называется center, поэтому ищем что-то связанное с ним (я просто искал по целому слову center).

Нам надо найти изменение CSS-свойства top – ведь мы хотим разместить лайтбокс вверху окна, если он не влезает во viewport, и поэтому нам нужна вертикальная координата.

Но не находим ничего похожего по смыслу. Зато наш взгляд упирается в функцию animateCenter (начало её на строке 260). Судя по всему, эта функция как раз отвечает за анимирование и позиционирование лайтбокса в окне браузера. Наш пристальный и жесткий взгляд упирается в эту строчку:

// resize center
if ((center.offsetHeight != size[1]) || (center.offsetWidth != size[0]))
  p = {height: size[1], marginTop: -size[1]/2, width: size[0], marginLeft: -size[0]/2};

Да, именно здесь происходит позиционирование лайтбокса. Оказывается, easyBox использует не CSS-свойство top, а CSS-свойство margin-top (или marginTop в JS, что то же самое).

Значение marginTop: -size[1]/2 помещает лайтбокс в центр окна по вертикали (size[1] – размер лайтбокса по высоте).
Именно его нам надо подобратьь таким образом, чтобы лайтбокс начинался от верха окна, если он слишком высок.

Начинаем экспериментрировать. Пишем здесь marginTop: 0 и тестируем появление лайтбокса. Ууупс, он появился с верхом по центру окна. Значит, нам надо задать значение, равное половине высоты окна со знаком минус (чтобы лайтбокс ушел вверх, а не вниз от центра). В jquery высота viewport’а браузера находится простым методом $(window).height().

Значит, мы задаем значение так: marginTop: -($( window ).height() / 2)

Сохраняем скрипт, тестируем страничку – все получилось, вроде это то, что нам нужно!

Задание готово, получаем гонорар и идём спать! 🙂 Или читать интересные книги.

Да, итоговая функция animateCenter станет выглядеть так:

	function animateCenter(size, opacity, duration) {
		centerSize = size.slice();
		var p = {};
 
		// resize center и
		// подгоняем его к верхней границе browser's viewport
		if ((center.offsetHeight != size[1]) || (center.offsetWidth != size[0]))
			p = {height: size[1], marginTop: -($( window ).height() / 2), width: size[0], marginLeft: -size[0]/2};
		if (opacity > -1)
			p.opacity = opacity;
		$(center).animate(p, duration, options.resizeEasing);
	}

До встречи, друзья, вкусного чёрного чая с имбирем в сахаре!

Запуск Youtube или onclick на iframe

Цели

Сегодня встала задача – при начале воспроизведения YouTube-ролика, вставленно через iframe, запустить таймер.

Попытка №1 – videojs

Можно обернуть youtube ролик videojs и используя плагин videojs-youtube (https://github.com/eXon/videojs-youtube) наверное отлавливать событие типа onplay или как оно там называется.

Но мне это показалось геморрно, тем более на страничке с плагином videojs-youtube (по ссылке выше) написано:

Install

You can use bower (bower install videojs-youtube), npm (npm install videojs-youtube) or the source and build it using grunt. Then, the only file you need is dist/vjs.youtube.js.

Интересно, в каком мире живет автор этого плагина? Какого хера, я сижу в Windows 8.1, где мне взять этот гребанный vjs.youtube.js? Я полазил по папкам плагина здесь же, на github’е, но не нашел. Задним умом я понимаю что поиском через минуту он окажется у меня, но злость моя затмила мне разум, и я послал этот плагин далеко.

Итак, videojs при всём уважении к авторам отпадает.

Способ №2 – YouTube Player API Reference for iframe Embeds

У гугловодов есть два вида API посвященный процессингу воспроизведения роликов с Youtube.

Первый – YouTube JavaScript Player API Reference предназаначен для проигрывателей встраиваемых в страницу через object тэг (Flash Player 10.1 или более поздней версии) или через Chromeless Player.

Но этот способ мне не подходжил, так как я имел видео через iframe.

И тут же вижу прекрасную ссылку на YouTube Player API Reference for iframe Embeds и радостно потираю руки – https://developers.google.com/youtube/iframe_api_reference#Loading_a_Video_Player

Как раз то что мне нужно, вроде бы! Беглый обзор этого API позволил создать тестовый прообраз player’а, через который можно было перехватывать событие onStateChange, чтобы в итоге перехватить событие onplay.

Но ни хера! При производстве ролика с ид M7lc1UVf-VE всё было ОК, все функции API работали как заявлено в руководстве, но стоило мне поменять ид видео на 9kyNeSNO8Ck, как событие onStateChange переставало перехватываться, о чём свидетельствовали вызовы console.log в нужных местах.

Используя Fireox Firebug панель, я увидел такую ошибку:

"NetworkError: 404 Not Found - http://www.youtube.com/get_video?noflv=1&video_id=9kyNeSNO8Ck&cpn=0OwdBht95fxY5Vtu&el=embedded&referrer&eurl=http%3A%2F%2Fgoloslogos.ru%2Fgoloslad.html&fmt=18&ptk=youtube_none"

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

Беглый обзор гуглопоиска показал эту проблему – https://code.google.com/p/gdata-issues/issues/detail?id=6529 – и то что она до сих пор не решена. Похоже корни этой проблемы лежат в настройках автора YouTube видео, то есть является ли он партнёром YouTube ли нет, но дальше копать не было времени и желания, какого хрена я за гугль должен решать их проблему, они мне миллиардов не платят.

Поэтому YouTube Player API – в топку!

Способ №3: iframe.onclick

И тут меня по голове тюкнуло – мне нужно перехватить только запуск видео, который происходит по щелчку в любом месте прогрывателя, спрятанного в iframe. Но проблема в том, что обычный jQuery $(window).click(function(){}) не ловит это событие внутри iframe.

Поиск показал ложное решение этой проблемы:

document.getElementById("iframe_id").contentWindow.document.body.onclick = 
function() {
  alert("iframe clicked");
}

Дело в том, что этот в принципе верный способ не работает при кросс-доменных запросах, а youtube.com и даже youtu.be никак не относятся к моему домену.

Ниже я нашел это (вольный перевод с английского):

“Можно использовать решение, основанное на событиях blur и mouseover/mouseout. Плагин господина Vince iframeTracker jQuery plugin разработан как раз для детекта кликов по iframe’ам : https://github.com/finalclap/iframeTracker-jquery”

Итак, плагин iframeTracker-jquery, очень интересно и звучит заманчиво. Я его подключил к нашему проекту, написал такой прмерно код:

jQuery(document).ready(function($){
// id моего iframe с YouTube - player
    $('#player').iframeTracker({
        blurCallback: function(){
        // здесь происходит событие клик на iframe
            startMyTimer();
        }
    });
});

И всё получилось! При клике на ролике YouTube он запускался, и также запускалась наша функция startMyTimer()!

Благодарности

Спасибо автору этого замечательного jQuery плагина, низкий ему поклон за работу.

Моя задача оказалась успешно решенной, я получил свои очередные $1100 (тысячу сто баксов) и пошёл заниматься своими делами, коих у меня невпроворот.

Чего и Вам искренне советую, дорогие друзья!

До встречи на Альбионе.

PHP-торт на день Рождения!

С Днём Рождения?!

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

Планируем, собираем ресурсы

Итак, пишем простейший PHP скрипт для вывода надписи поверх картинки.

Торт наш будет выглядеть так –

Наш праздничный торт ко Дню Рождения!

Используем формат PNG с прозрачным цветом. Мы его немного затемнили в редакторе, чтобы надпись поверх выглядела более ярко. Сохраним его в папке images под именем bd_cake.png.

Шрифт. Использовать будем TTF-шрифт Fita_Poluustav (fitpustv.ttf – взять здесь, бесплатный).

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

Итак, торт готов, шрифт готов, теперь пишем небольшой PHP-скриптик, используя старую добрую GD библиотеку (доп. пояснения к коду даны ниже).

Сразу замечу только, что имя скрипту задаётся через GET параметр name, если он не указан используем имя любимой девушки или жены, например, Светлана. Длина имени не должна превышать 13 символов.

Кодировка скрипта и имени должна быть UTF-8.

// Файл bd_cake.php
 
// пробуем получить имя из GET-параметра name
$name = htmlspecialchars(urldecode(@$_GET['name']));
if(trim($name) == '')
	$name = 'Светлана';
 
// ограничим имя 13 символами, иначе оно будет вылезать за пределы экрана
// используем mb_ функции, так как UTF8 мультибайтовая кодировка
if(mb_strlen($name,'utf-8') > 13)
	$name = mb_substr($name,0,13,'utf-8');
 
// путь по умолчанию для шрифта - текущий каталог
putenv('GDFONTPATH=' . realpath('.')); 
$font = 'fitpustv'; // название шрифта без расширения ttf
 
$im = imagecreatefrompng('images/bd_cake.png'); // наш торт
$green = imagecolorallocate($im,0x0,0xFF,0x34); // цвет зелёный
 
// Выкл. alpha blending и вкл. флаг alpha (см. ниже)
imagealphablending($im, false);
imagesavealpha($im, true);
imagealphablending($im, true);
 
$text = 'поздравляем!';
$x = getX(27,0, $text);
imagettftext($im, 27, 0, $x, 255, $green, $font, $text);
 
$text = $name;
$sz = 36;
$x = getX($sz,16, $text);
$y = getY($sz,16, $text);
imagettftext($im, $sz, 16, $x, $y, $green, $font, $text);
 
 
header('Content-Type: image/png');
imagepng($im);
die;
 
// центруем текст по X
function getX($size, $angle, $text)
{
	global $font, $im;
 
	$ts = imagettfbbox($size,$angle,$font,$text);
	$dx = abs($ts[2] - $ts[0]);
	return (imagesx($im) - $dx) / 2;
}
 
// центруем текст по Y
function getY($size, $angle, $text)
{
	global $font, $im;
 
	$ts = imagettfbbox($size,$angle,$font,$text);
	$dy = abs($ts[5] - $ts[3]);
	return (imagesy($im) - $dy) / 2 + $dy;
}

Тестируем

В итоге по умолчанию получаем такое:

С Днём Рождения!

Имя:

Для теста используем поле ввода и кнопку “Обновить” так:

<input id="text" size="30" maxlength="20">
<button onclick="document.getElementById('cake').src=
   'http://atzar.ru/bd_cake.php?name='+document.getElementById('text').value">Обновить
</button>

Доп. пояснения к скрипту

Такой код:

// Выкл. alpha blending и вкл. флаг alpha (см. ниже)
imagealphablending($im, false);
imagesavealpha($im, true);
imagealphablending($im, true);

выглядит странно, но он работает как надо.

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

Функции getX и getY позволяют определять координаты X и Y для центрирования надпись по рисунку по соотв. осям. Имя мы центрируем по обоим осям, а слово “поздравляем!” только по оси X, Y-координату подпираем эксперименталдьным путём где-то в нижней части торта.

В качестве аргументов они принимают размер шрифта, угол наклона и сам текст. Используют глобальные переменные рисунка ($im) и имени шрифта ($font).

В принципе всё. GD-функция imagettftext выводит надпись на рисунке по расчитанным координатам. Имя мы выводим под углом 16 градусов (снизу вверх).

Остальные GD-функции обычные, imagecreatefrompng – получаем рисунок из готово файла PNG, imagecolorallocate – готовим цвет в формате RGB, imagepng – выводим рисунок в браузер или файл (если мы укажем второй параметр).

Если мы выводим PNG-рисунок прямиком в браузер, мы должны установить HTTP-заголовок Content-Type: image/png, и конечно не должны смешивать вывод с текстом.

Всё, благодарю за ваше внимание, успехов и до новых встреч!

Допиливаем VKontakteApi (WP-плагин)

Всё отлично!

Используем неплохой плагин для WordPress’а от Забродского Евгения (kowack): VKontakte API.

Плагин позволяет наряду с обычными комментариями вставлять и видеть комментарии VKontakte и Facebook.

Бонусом идёт также возможность добавлять кнопки соц.сетей – PlusOne button, Tweet button, Mail.ru+Ok.ru button, Ya.ru button.

Для Вконтакте имеется также возможность добавлять кнопки Мне нравится и Поделиться.

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

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

Что не так?

Плагин выводит комменты VK,FB и WP блоками, если один блок виден, то другие скрыты, появление блока происходит с медленным эффектом, что немного имхо раздражает.

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

Мы хотели бы, чтобы блоки были все сразу видны, то есть идёт первым VKontakte блок с комментариями, под ним – Facebook’чный блок комментариев, и уже под ним – стандартный WP (Word Press’овский блок с комментариями).

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

За работу!

Итак, заходим в папку wp-content/plugins/vkontakte-api.

Это родная папка плагина. Откроем файл js/callback.js и сразу видим блок Comments switcher с функциями showVK(),showFB(),showWP().

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

Нам достаточно закомментировать внутренность этих функций (но не сами функции конечно), чтобы комментарии были все видны. Наша задача выполнена? Да, почти, но по нажатию кнопок Vkontakte, Facebook и Site ничего не происходит.

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

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

Для VKontakte #vkapi, для Facebook #fb-comments (этого ид изнчально нету, ниже написано, как его добавить), для Site #comments.

Таким образом весь модернизированный блок будет выглядеть так:

// Comments switcher
function showVK(Tshow, Thide) {
/*    if (!Tshow && Tshow != 0) Tshow = 1000;
    if (!Thide && Thide != 0) Thide = 1500;
    jQuery("#vkapi").show(Tshow);
    jQuery(".fb-comments").hide(Thide);
    jQuery("#comments").hide(Thide);
    jQuery("#respond").hide(Thide);
*/
    if(Tshow == 1) window.location.hash = 'vkapi';
 
}
function showFB(Tshow, Thide) {
/*    if (!Tshow && Tshow != 0) Tshow = 1000;
    if (!Thide && Thide != 0) Thide = 1500;
    jQuery(".fb-comments").show(Tshow);
    jQuery("#vkapi").hide(Thide);
    jQuery("#comments").hide(Thide);
    jQuery("#respond").hide(Thide);
*/
    window.location.hash = 'fb-comments';
}
function showWP(Tshow, Thide) {
/*    if (!Tshow && Tshow != 0) Tshow = 1000;
    if (!Thide && Thide != 0) Thide = 1500;
    jQuery("#comments").show(Tshow);
    jQuery("#respond").show(Tshow);
    jQuery("#vkapi").hide(Thide);
    jQuery(".fb-comments").hide(Thide);
*/
    window.location.hash = 'comments';
}

Для чего для контакта мы добавили условие if(Tshow == 1)?

Дело в том, что при заходе на страницу вызывается первый раз функция showVK() (если включен показ сначала комментов VK, а не FB). И тогда из-за исправленого скрипта происходил бы перескок сразу на комменты VK, что не есть хорошо, так как по умолчанию читатель ещё не читал статью/пост.

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

Как говорилось выше, нет хэша #fb-comments. Чтобы его добавить, откроем файл vkapi.php в папке плагина, найдём там функцию add_fb_comments() и перед строкой class=’fb-comments’ (или после неё, не важно) добавим строку id=’fb-comments’.

Таким образом функция add_fb_comments() станет выглядеть так:

function add_fb_comments()
{
		$width = get_option('vkapi_comm_width');
		$limit = get_option('vkapi_comm_limit');
		$url = get_permalink();
		echo "
	<div style='background:white'
			 id='fb-comments'
			 class='fb-comments'
			 data-href='{$url}'
			 data-num-posts='{$limit}'
			 data-width='{$width}'
			 colorscheme='light'>
	</div>";
}

Сохранить, Upload, Refresh – вуаля, всё работает.

А руссификация этих кнопок и надписи Comments?

Языковость у этого плагина происходит так:

$text = __('Comments:', $this->plugin_domain);

Комментируем соотв. строку и добавляем под ней свою такую:

$text = 'Комментарии: &nbsp;&nbsp;';

А &nbsp;&nbsp; я уже отсебя добавил, это отсебятина какая-то.

Это мы находим в функции add_tabs_button_start().

Аналогичные действия проделаем для функций add_tabs_button_vk(), add_tabs_button_fb(), add_tabs_button_wp() и называем кнопки как хотим. Я назвал ВКонтакте, Facebook и Сайт соответственно.

Всё, конец. Всё работает? Да!

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

До свидания, друзья!