Category Archives: функции

PHP несколько полезных функций дат

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

Стандартный формат даты MySQL: YYYY-mm-dd.

То есть сперва идут четыре цифры года, дефис, 2 цифры месяца с ведущим нулём, если он есть, дефис, и 2 цифры дня также с ведущим нулём, если он есть.

Напишем функции преобразования даты из формата MySQL в формат дат, принятых на територии России, т.е. такой: dd.mm.YYYY

Тут понятно, две цифры дня, две цифры месяца после точки, и 4 цифры года также после точки.

// преобразуем из MySql date-формата в наш формат: dd.mm.yyyy (d.m.Y)
// 1970-08-22 --> 22.08.1970
function ToOurDate($thedate) { 
	$d = substr($thedate, 8, 2);	// день
	$m = substr($thedate, 5, 2);	// месяц
	$y = substr($thedate, 0, 4);  // год
	return "$d.$m.$y";
}

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

// преобразуем нашего формата (dd.mm.yyyy (d.m.Y)) в MySql DATE-формат
//   22.08.1970 --> 1970-08-22
function ToMySqlDate($thedate) { 
	$d = substr($thedate, 0, 2);	// день
	$m = substr($thedate, 3, 2);	// месяц
	$y = substr($thedate, 6, 4);  // год
	return "$y-$m-$d";
}

Следующая функия ShowMyDate – показать дату (формат MySQL) в удобочитаемом виде.
На вход передаём дату в формате MySQL, например “2020-04-05“, а на выходе получаем строку типа “5 апреля 2020 года“. Или просто “5 апреля“, если не укажем флаг ifyear – показывать год.

Флаг ifyear имеет тройное значение: 0 – не показывать год совсем, 1 – показать год со словом “года”, и 2 – показать просто цифры года.

/**
	Вернуть дату из формата MySQL YYYY-MM-DD (mydate)
	в удобочитаемом виде
	ifmonth - добавить к дате месяц, ifyear - добавить год
**/
function ShowMyDate($mydate, $ifmonth = 1, $ifyear = 0)
{
	$mon = array('', 'января', 'февраля', 'марта', 'апреля', 'мая', 'июня',
						'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря');
	$m = 0;
	$y = 0;
	$day = substr($mydate, 8, 2);//день
	if($day{0} == '0') $day = $day{1}; // обрежем ведущий нолик, если есть
	if($ifmonth) {
		$m = intval(substr($mydate, 5, 2));
		if($m < 1 || $m > 12) $m = 0;
	}
 
 
	$r = $day;
	if($m) $r .= ' ' . $mon[$m];//добавим месяц письмом
 
	if($ifyear)
		$y = intval(substr($mydate, 0, 4));
 
	if($ifyear == 1)
		$r .= " $y года";
	elseif($ifyear == 2)
		$r .= " $y";
 
	return $r;
}//ShowMyDate

И наконец функция ShowFullDates. На входе передём две даты в формате MySQL, на выходе получаем строку типа “15 декабря-16 мая“,
или “15-20 марта“, или “15 марта 2020 года-20 мая 2021 года“.
Флаг ifyear – показывать год, имеет то же значение, что и для функции ShowMyDate.
Функция распознаёт разные месяцы и разные года, и при необходимости строит соотв. строки.

///////////////////////////////////////////////////////////////////////////////
/**
	Вернуть две даты (формата MySQL)
	в виде 1-2 января 2014 года
**/
function ShowFullDates($d1, $d2, $ifyear = 0)
{
	if($d1 == $d2) return ShowMyDate($d1, 1, $ifyear);
 
	$y1 = intval(substr($d1, 0, 4));
	$y2 = intval(substr($d2, 0, 4));
 
	// если разные года
	if($y1 != $y2) return ShowMyDate($d1, 1, $ifyear) . '-' . ShowMyDate($d2, 1, $ifyear);
 
	$m1 = intval(substr($d1, 5, 2));
	$m2 = intval(substr($d2, 5, 2));
 
	if($m1 == $m2) return ShowMyDate($d1, 0, 0) . '-' . ShowMyDate($d2, 1, $ifyear);
 
	return ShowMyDate($d1, 1, 0) . '-' . ShowMyDate($d2, 1, $ifyear);
}//ShowFullDates

Пока всё, хватит с нас функций. Эти функции я написал лет 12-15 назад, и до сих пор они служат мне верой и правдой во всём. Это legacy функции, функции наследства.

До свидания. Любви вам, это главное.

Emmet: balance tag исправляем

Здравствуйте.
На страничке http://docs.emmet.io/actions/match-pair/ у Сергея написано следующее (перевод мой с английского):
********************************************************************************

Balance

“Balance Outward” (Ctrl+D)

“Balance Inward (Shift+Ctrl+D)

Известная балансировка тэгов: поиск границ тэгов и выбор их от текущей позиции каретки. Она может быть расширенной (outward balancing) или суженной (inward balancing) при вызове несколько раз подряд. Не каждый редактор поддерживает одновременно суженную и расширенную балансировку.

Балансировка тэгов Emmet достаточно уникальна. В отличие от остальных, она ищет границы тегов от текущей позиции каретки, а не от начала документа. Это означает, что она может работать не только в HTML документах.
********************************************************************************

Да, классная штука, правда, в реальной жизни нас скорее интересует текущий тег – просмотр границ и переход в начало конец этого тэга (для этого у Emmet‘а есть действие matching pair).

Но если она (балансировка) есть, хотелось бы использовать её на полную мощь. Так как я добавил поддержку Emmet для редактора AkelPad (http://akelpad.sourceforge.net/forum/viewtopic.php?p=30266#30266), мне захотелось подробнее разобраться в вопросе, а именно меня задели слова, что “Не каждый редактор поддерживает одновременно суженную и расширенную балансировку.“.

Экспериментируя с Emmet‘ом в AkelPad‘e, я заметил недоделанность эту (отсутствие балансировки вглубь, в частности), и решил посмотреть, что можно сделать.

Итак, нас интересует такой участок кода в файле emmet-app.js (я добавлял поддержку Emmet’а для AkelPad’а, используя плагин для Komodo Edit‘а, а потом проанализировал подобный файл от плагина к Notepad++):

/**
 * HTML pair matching (balancing) actions
 * @constructor
 * @memberOf __matchPairActionDefine
 * @param {Function} require
 * @param {Underscore} _
 */
emmet.exec(function(require, _) {
	/** @type emmet.actions */
	var actions = require('actions');
	var matcher = require('htmlMatcher');
	var lastMatch = null;
 
	/**
	 * Find and select HTML tag pair
	 * @param {IEmmetEditor} editor Editor instance
	 * @param {String} direction Direction of pair matching: 'in' or 'out'. 
	 * Default is 'out'
	 */
	function matchPair(editor, direction) {
		direction = String((direction || 'out').toLowerCase());
		var info = require('editorUtils').outputInfo(editor);
 
		var range = require('range');
		/** @type Range */
		var sel = range.create(editor.getSelectionRange());
		var content = info.content;
 
		// validate previous match
		if (lastMatch && !lastMatch.range.equal(sel)) {
			lastMatch = null;
		}
 
		if (lastMatch && sel.length()) {
			if (direction == 'in') {
				// user has previously selected tag and wants to move inward
				if (lastMatch.type == 'tag' && !lastMatch.close) {
					// unary tag was selected, can't move inward
					return false;
				} else {
					if (lastMatch.range.equal(lastMatch.outerRange)) {
						lastMatch.range = lastMatch.innerRange;
					} else {
						var narrowed = require('utils').narrowToNonSpace(content, lastMatch.innerRange);
						lastMatch = matcher.find(content, narrowed.start + 1);
						if (lastMatch && lastMatch.range.equal(sel) && lastMatch.outerRange.equal(sel)) {
							lastMatch.range = lastMatch.innerRange;
						}
					}
				}
			} else {
				if (
						!lastMatch.innerRange.equal(lastMatch.outerRange) 
						&& lastMatch.range.equal(lastMatch.innerRange) 
						&& sel.equal(lastMatch.range)) {
					lastMatch.range = lastMatch.outerRange;
				} else {
					lastMatch = matcher.find(content, sel.start);
					if (lastMatch && lastMatch.range.equal(sel) && lastMatch.innerRange.equal(sel)) {
						lastMatch.range = lastMatch.outerRange;
					}
				}
			}
		} else {
			lastMatch = matcher.find(content, sel.start);
		}
 
		if (lastMatch && !lastMatch.range.equal(sel)) {
			editor.createSelection(lastMatch.range.start, lastMatch.range.end);
			return true;
		}
 
		lastMatch = null;
		return false;
	}
 
	...
 
}

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

		// validate previous match
		if (lastMatch && !lastMatch.range.equal(sel)) {
			lastMatch = null;
		}

Вон оно что. Тут у нас статическая переменная lastMatch. Дело в том, что если JavaScript скрипт каждый раз подключается заново, статические переменные теряют смысл, так как они каждый раз заново инициализируются.

В браузерах после загрузки страницы все скрипты остаются в памяти и статические переменные работают как надо. В Notepad++ также подключение скрипта emmet-app.js происходит только один раз при первом вызове (используется Python-wrapper для JavaScript), поэтому там также всё работает более-менее нормально.

А в AkelPad‘е каждый раз мы запускаем скрипт заново, то есть он не висит в памяти, занимая пространство и время редактора, поэтому в нашем случае эта статическая переменная бесполезна.

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

То есть мы в любом случае ищем как сначала, переставив местами части.

Вот что в итоге получается:

/**
 * HTML pair matching (balancing) actions
 * @constructor
 * @memberOf __matchPairActionDefine
 * @param {Function} require
 * @param {Underscore} _
 */
emmet.exec(function(require, _) {
	/** @type emmet.actions */
	var actions = require('actions');
	var matcher = require('htmlMatcher');
	var lastMatch = null;
 
	/**
	 * Find and select HTML tag pair
	 * @param {IEmmetEditor} editor Editor instance
	 * @param {String} direction Direction of pair matching: 'in' or 'out'. 
	 * Default is 'out'
	 */
	function matchPair(editor, direction) {
		direction = String((direction || 'out').toLowerCase());
		var info = require('editorUtils').outputInfo(editor);
 
		var range = require('range');
		/** @type Range */
		var sel = range.create(editor.getSelectionRange());
		var content = info.content;
 
		// we do not have static lastMatch here
		lastMatch = matcher.find(content, sel.start);
		// try for in balancing
		if(!lastMatch)
			lastMatch = matcher.find(content, sel.start + 1);
 
		if (lastMatch && sel.length()) {
			if (direction == 'in') {
				// user has previously selected tag and wants to move inward
				if (lastMatch.type == 'tag' && !lastMatch.close) {
					// unary tag was selected, can't move inward
					return false;
				} else {
					if (lastMatch.range.equal(lastMatch.outerRange)) {
						lastMatch.range = lastMatch.innerRange;
					} else {
						var narrowed = require('utils').narrowToNonSpace(content, lastMatch.innerRange);
						lastMatch = matcher.find(content, narrowed.start + 1);
						if (lastMatch && lastMatch.range.equal(sel) && lastMatch.outerRange.equal(sel)) {
							lastMatch.range = lastMatch.innerRange;
						}
					}
				}
			} else {
				if (
						!lastMatch.innerRange.equal(lastMatch.outerRange) 
						&& lastMatch.range.equal(lastMatch.innerRange) 
						&& sel.equal(lastMatch.range)) {
					lastMatch.range = lastMatch.outerRange;
				} else {
					lastMatch = matcher.find(content, sel.start);
					if (lastMatch && lastMatch.range.equal(sel) && lastMatch.innerRange.equal(sel)) {
						lastMatch.range = lastMatch.outerRange;
					}
				}
			}
		}
 
		if (lastMatch && !lastMatch.range.equal(sel)) {
			editor.createSelection(lastMatch.range.start, lastMatch.range.end);
			return true;
		}
 
		return false;
	}
 
	...
 
}

Для balancing inward я добавил такую фичу:

		// try for in balancing
		if(!lastMatch)
			lastMatch = matcher.find(content, sel.start + 1);

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

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

Но сам движок Сергея по поиску HTML/XML тэгов (matcher.find) нуждается в доработке, так он вне HTML может спотыкаться при использовании экранированных кавычек и ряда других случаях. Но этим, насколько я мог заметить, грешат практически все наверное подобные алгоритмы, так как найти matching tag в определённых случаях бывает довольно тяжело.

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

Благодарю Вас за чтение и Господа за еду.

Немного философии или крутой класс-обертка над 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, я люблю Вас! Сам не знаю за что.

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

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

JavaScript тест на Flash и на мобильный девайс – пара полезных проверок

JavaScript тест на Flash и мобильный девайс iOS/Android

Тест на поддержку Adobe Flash

Для программирования под JavaScript иногда бывает нужно узнать, поддерживает ли браузер читателя технологию Adobe Flash.
Вот простоая функция, которую я одолжил у audio.js Колбера (kolber.github.io/audiojs/)

/**
	Test UA for Adobe Flash support 
	Borrowed from audio.js by kolber
	kolber.github.io/audiojs/
*/
var	uaHasFlash = function () {
	if (navigator.plugins && navigator.plugins.length && navigator.plugins["Shockwave Flash"])
		return true;
	else if (navigator.mimeTypes && navigator.mimeTypes.length) {
		var b =	navigator.mimeTypes["application/x-shockwave-flash"];
		return b && b.enabledPlugin;
	} else
		try {
			new ActiveXObject("ShockwaveFlash.ShockwaveFlash");
			return true;
		} catch (a) {}
 
	return false;
}

Тест на мобильный девайс – смартфон/планшет с iOS/Android

А чтобы опрделить, что у читателя Вашего сайта смартфон/планшет Andriod или от Apple (на iOS, то есть iPad или iPhone или iPod), достаточно такой наверное простейшей проверки:

if(/(ipod|iphone|ipad|Android)/i.test(navigator.userAgent)) {
// да, у читателя устройство с iOS/Android
}

Пока всё, пока-пока, до связи!

P.S. Вкусного чая с имбирём в сахаре.

Хачим 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);
	}

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

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, и конечно не должны смешивать вывод с текстом.

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

Быстрая валидация форм на javascript

Чтобы быстро валидировать форму на JavaScript достаточного такого кода:
Note: используем jquery.

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

var tryFormSubmitfunction = function ()
{
// проверяем валидность ввода данных
	var res = true,	// результат вовзрата по умолчанию
		f = this;	// контекст поиска полей в форме
	$('.req', f).removeClass('need-req');
	$('.req', f).each(function(index) {
		if(res && (
			$.trim($(this).val()) == '' || ($(this).hasClass('mail') 
&& !$(this).val().match(
/^[\+A-Za-z0-9][\+A-Za-z0-9\._-]*[\+A-Za-z0-9_]*@([A-Za-z0-9]+([A-Za-z0-9-]*[A-Za-z0-9]+)*\.)+[A-Za-z]+$/
))
		)) {
			$(this).addClass('need-req').get(0).select();
 
			setTimeout(
				function() { 
                        $('.req', f).removeClass('need-req'); },3000);
			res = false;
		}
	});
 
	return res;
}

Описание

Смысл кода такой: проверяем все поля имеющие класс req, и если оно пустое, прерываем сабмит (отправку на сервер) формы, наделяем такой элемент классом need-req и фокусируемся на нём, чтобы пользователь ввёл всё-таки какие-нибудь данные.

Кроме того, класс need-req удаляется с этого элемента через 3 секунды (см. строку с функцией setTimeout), чтобы не отвлекать внимание.

И ещё один плюсик – если элемент ввода имеет класс mail, то мы проводим доп.проверку на наличия в нём корректного адреса электронной почты (email) – см. функцию match().

То есть в нашем случае мы используем классы req и mail не для украшательств (хотя их можно и украсить), а для подсказки JavaScript-коду. Класс need-req мы используем как раз для украшательств, то есть подсказки пользователю, что этот элемент требует его внимания (см. ниже).

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

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

$('form').submit(tryFormSubmit);

В самой форме для всех полей, требующих заполнения хотя бы одним символом указываем класс req, а для поля, в котром нужно иметь корректный email адрес добавляем ещё класс mail, примерно так:

          <div class="item">
            <label for="name">Ваше имя</label>
            <input class="text req" name="name" value="" />
          </div>
          <div class="item">
            <label for="surname">Ваша фамилия</label>
            <input class="text req" name="surname" value="" />
          </div>
          <div class="item">
            <label for="email">Укажите Ваш E-mail</label>
            <input class="text req mail" name="email" value="" />
          </div>
          <div class="item">
            <label for="comment">Ваши вопросы</label>
            <textarea name="comment"></textarea>
          </div>

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

Для выделения ошибочного поля (за раз выделяется только одно поле – мы же пишем быструю простую функцию) используем класс need-req в CSS файле и опишем его к примеру так:

/* класс при выделении неверного элемента формы */
.need-req
{
	outline: double red 5px;
}

Всё, полная валидация форм готова практически за 5 минут.
Благодаря jQuery, конечно.

Всё работает и работает так как надо, так как мы хотим.

Удачи и счастья!

PHP: Групповая обработка файлов

Давече встала передо мной проблема – перевести весь сайт на UTF-8 с Windows-1251. Для этого нам надо (помимо возможно нового парсинга значений из БД) просто перекодировать все страницы сайта.
Вручную я за десять секунд могу перекодировать текстовый файл в Notepad++, но если таких файлов десятки или сотни, что нормально для стандартного сайта, это может встать геморроем.

Конечно, тут нужна групповая обработка файлов. Что делать? Юзать утилиту iconv – но она опять таки предназанчена для конвертации одного файла, а для групповой обработки надо изощряться через всякий find’ы, пайпы и т.п. Я не настолько пока силён в Unix-командах, чтобы быстро и без труда сделать всё как надо.

Для Windows нашлось (опять таки при поверхностном гуглении, так как времени для долгого изыскания нету) пара утилит, но они все какие-то корявые оказались и тупые.

Что делать? Писать самому! Тем более, что у нас под рукой есть такой простой и мощный язык как PHP Hypertext Preprocessor.

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

Сразу определим параметры этой функции –

function processFiles($dir, $callback, $mask = '/.*/', $recursive = true)
{ ... }

Параметры:
$dir – имя каталога для начала обхода.

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

$mask – PCRE – маска файлов. Функция $callback будет вызываться только для тех файлов, для которых preg_match($mask, полное
имя файла)
функция вернёт true.
Благодаря этому параметру мы можем отобрать только те файлы, которые нам действительно нужны, а не перекладывать эту функцию на вызываемую функцию (блин, что за каламбур получился? ладно, пусть будет).
По умолчанию (это не обязательный параметр) мы выбираем все файлы, кроме скрытых (то есть те, имя которых начинается с точки – по Unix соглашению).

$recursive – также необязательный параметр (по умолчанию true) – вызывать ли рекурсивно обработку файлов в подпапках текущей папки.
Обычно нам так и нужно, поэтому дефолтное значение – да, вызывать.

Вот полной код этой функции обхода дерева каталогов на PHP:

// рекурсивный (по умолчанию) обход папок
// recursive walking directories
function processFiles($dir, $callback, $mask = '/.*/', $recursive = true)
{
	$dh = opendir($dir);
	if(!$dh)
		return;
 
	$curDir = getcwd();
	chdir($dir);
 
	while (($entry = readdir($dh)) !== false) {
		if(substr($entry,0,1) == '.')
		// hidden entry
			continue;
		$fullName = $dir.'/'.$entry;
		if(is_dir($entry)) {
			if ($recursive)
				processFiles($entry, $callback, $mask);
			continue;
		}
		if(preg_match($mask,$fullName))
			call_user_func($callback,$entry);
	}
 
	chdir($curDir);
}

В этом куске кода нет ничего сложного, все PHP-функции довольно стары и стандартны. Можно отметить только, что пропуская скрытые файлы – hidden entry (по Unix соглашению – то есть файлы и папки, начинающиеся с точки), мы параллельно пропускаем `.` и `..` вхождения, которые также включаются функцией readdir в листинг файлов.

Теперь осталось дело за малым – написать callback-функцию для обработки файлов.

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

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

Вот как может выглядеть такая функция:

// просто выведем полное имя файла (с текущим путём)
function listFiles($name)
{
    echo 'File: '.getcwd().'/'.$name.' ('.file_exists($name).')<br>';
}

Теперь попробуем применить наши новые две функции с маской для файлов – выберем только текстовые файлы с расширениями php,html,js,css или всех тех, что находятся в папке doc (и её подпапках):

processFiles('.', 'listFiles', '#\.php$|\.html$|\.js$|\.css$|doc/#');

Изучив в браузере (я запускаю свои скрипты через браузер для текущего сервера, но вы можете конечно использовать CLI, для этого только в выводе вместо ‘<br>’ нужно использовать “\n”) вывод и убедившись, что отбираются именно нужные нам файлы, можно приступить к конечной цели нашей задачи – переконвертация файлов из кодировки Windows-1251 в кодировку UTF-8.

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

function reEncode($name)
{
// конвертируем файл из Windows-1251 в UTF-8
	$in = file_get_contents($name);
	$out = iconv('windows-1251','utf-8',$in);
	file_put_contents($name, $out);
}

Благодаря мощной PHP-функции iconv конвертация не вызывает никаких проблем, если мы знаем названия кодировок, с которыми работаем, и iconv тоже их знает.

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

file_put_contents($name, iconv('windows-1251','utf-8',file_get_contents($name)));

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

Итак, наша задача успешно решена – вызываем обработку наших файлов примерно так:

processFiles('.', 'reEncode', '#\.php$|\.html$|\.js$|\.css$|doc/#');

Запускаем её для текущего каталога, и через какое-то время (зависит от кол-во файлов и папок) все наши текстовые файлы стали UTF-8!

Красота! Но на этом мы не останавливаемся. Нашу замечательную функцию processFiles мы можем использовать в самых разнообразных целях, какие только нам нужны.

Например, конвертим все наши документы в Unix-EOL (\n вместо \r\n на концах строк):

function toUnixEOL($name)
{
// конвертим концы строк из стандарта Windows (\r\n) в стандарт Unix (\n)
	$in = file_get_contents($name);
	$out = str_replace("\r\n","\n",$in);
	file_put_contents($name, $out);
}

Так же мы можем запустить групповую обработку для изображений – создадим для всех наших изображений миниатюры размером к примеру 150*150 точек в формате PNG черно-белого варианта (см. ниже).

Благодря PHP эта задача кажется легче чем думается, и нам не нужны никакие сторонние утилиты, платные или бесплатные – мы всё можем сделать своими руками, точнее своим мозгом, а что может быть приятнее?

Итак, вызов инструкций:

// полный путь для папки с миниатюрами (thumbs)
$targetDir = getcwd().'/inc/thumbs';
// если папка не существует, создадим её
if(!@opendir($targetDir))
	mkdir($targetDir);
processFiles('./inc/img', 'processImage', '/\.jpg$|\.jpeg$|\.png$|\.gif$/');

сделает из изображений в папке ./inc/img миниатюры 150*150 пикселей и поместит их в папку ./inc/thumbs

А вот как может выглядеть функция processImage:

/* ***********************************************
	Обработка изображений
	Вход -
		$name - имя изображения в текущей папке
********************************************** */
function processImage($name) {
// уменьшаем картинку до 150*150 пикселей,
// преобразуем в чёрно-белое 
// и сохраняем как PNG в отдельной папке
	global $targetDir;
	// имя для нового изображения
	$pathInfo = pathinfo($name);
	$fileName = $targetDir.'/'.$pathInfo['filename'].'.png';
 
        // распознаём только JPEG (JPG), PNG и GIF
	$him = @imagecreatefromjpeg($name);
	if(!$him) {
		$him = @imagecreatefromgif($name);
		if(!$him) {
			$him = @imagecreatefrompng($name);// может это PNG?
			if(!$him)
				return false;
		}
	}
	// удалим файл, если есть случайно уже
	if(file_exists($fileName))
            unlink($fileName);
 
	resizeImage($him,150,$fileName);	// сохраняем оригинально изображение
	// чистим память (поможем PHP)
	imagedestroy($him);
	unset($him);
 
	return true;
}
 
/*
	преобразуем изображение -
	изменим размеры до 150*150 пикселей,
	превратим в черно-белое и сохраним в формате PNG
 
	Вход:
		$originalImage - GD-дескриптор оригинального изображения
		$newWidth - новая ширина (и высота в нашем случае)
		$fileName - путь для сохранения обработанного изображения
*/
function resizeImage($originalImage,$newWidth,$fileName){
    // получим оригинальные размеры
    $width  = imagesx($originalImage);
    $height = imagesy($originalImage);
 
    // для сохранения ASPECT RATIO можно воспользоваться
    // след. закомментированной формулой
    // $newHeight = round(($height * $newWidth) / $width);
 
    // но мы делаем высоту изображения равной его ширине
    $newHeight = $newWidth;	// make it square
 
    // resize the original image
    $newImage = imagecreatetruecolor($newWidth, $newHeight);
    imagecopyresampled($newImage, $originalImage, 0, 0, 0, 0, 
                          $newWidth, $newHeight, $width, $height);
    // преобразуем в черно-белое (зачем? - просто для примера)
    imagefilter($newImage, IMG_FILTER_GRAYSCALE);
    // сохраняем новое изображение
    imagepng($newImage, $fileName);
    imagedestroy($newImage);
    unset($newImage);
}

Что ещё можно делать с файлами в групповой обработке? Переименовывать! Это уже задача для 1-го класса Школы PHP в Хогвардсе!

Удачи и счастливого кодинга.