14 Январь 2013

Кошерный подход к программированию для WordPress

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

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

Создадим контроллер

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

Очевидно где-то должна быть форма авторизации, куда пользователь введет свой логин и пароль. HTML-код формы авторизации приведен в листинге 1.

Листинг 1. HTML-код формы авторизации

<form action="/morkovin_controller.php?action=login" method="post">
	<label for="user_name">Логин:</label>
	<input id="user_name" type="text" name="log" />
	<label for="user_password">Пароль:</label>
	<input id="user_password" type="password" name="pwd" />
	<input type="submit" name="morkovin_sbmt_auth_btn" value="Войти" />
</form>

Данный код будет интепретирован браузером в самую элементарную форму, представленную на рис. 1. Остается лишь вставить в нужное место HTML-код (в сайдбар, пост или куда угодно).

Интерпретация формы авторизации браузером
Рис. 1. Форма авторизации. Интепретация кода листинга 1.

Как видно из строки 1 листинга 1 форма отправит введенный пользователем логин и пароль по адресу /morkovin_controller.php?action=login. Т.е. в корне сайта должен лежать файл morkovin_controller.php, который примет данные от пользователя и проведет авторизацию.

Файл morkovin_controller.php — это ни что иное как наш самодельный контроллер. В корне сайта я создал данный файл (его имя может быть любым, главное чтобы оно не пересекалось со стандартными URL WordPress).

Теперь перейдем к рассмотрению содержимого нашего контроллера.

В начале необходимо подключить все функции и методы WordPress.

Листинг 2. Подключение стандартных методов WordPress в контроллере

require_once(__DIR__.'/wp-load.php');

Данная инструкция подключит файл wp-load.php и мы сможем использовать методы WordPress для выполнения стандартных операций.

Волшебная константа __DIR__ будет содержать путь к корню сайта поскольку файл morkovin_controller.php, внутри которого мы используем константу __DIR__, сам расположен в корне. А файл wp-load.php лежит именно в корне сайта.

Далее, в зависимости от значения поля action, переданного контроллеру через URL в глобальном массиве GET, будут выполняться те или иные операции. Например, для того, чтобы выполнить авторизацию я передам login в качестве значения поля action. Посмотрите на первую строку листинга 1 — контроллеру передается глобальный массив GET с полем action = login. Остается извлечь значение поля и выполнить соответствующее ему действие.

Листинг 3. Выполнение функции, соответствующей извлеченному из GET значению поля action

$act_function = 'morkovin_action_' . morkovin_arr_get($_GET, 'action', 'login');
if (function_exists($act_function)) $act_function();
exit;

Рассмотрим первую строку листинга 3. В переменную $act_function попадет текст morkovin_action_ (именно таким префиксом будут начинаться все функции моего контроллера). Далее, посредством дополнительно написанной функции morkovin_arr_get (рассмотрим ее ниже) выполняется извлечение из массива GET значения поля action; значение объединяется с префиксом.

Во второй строке происходит выполнение функции с названием, сформированным в переменной $act_function, если такая функция существует.

Давайте познакомимся с кодом функции morkovin_arr_get().

Листинг 4. Функция, извлекающая значение из массива по заданному имени поля

function morkovin_arr_get($array, $key, $default='')
{
	$keyParts = explode('.', $key);

	$result = $array;
	foreach ($keyParts as $item)
	{
		if (isset($result[$item])) {
			$result = $result[$item];
		} else {
			$result = $default;
			break;
		}

	}

	return $result;
}

Это очень хитрая функция с заделом на будущее. Понятно, что в качестве аргумента $array функции передается массив из которого извлекается значение поля с именем $key, если поле $key в массиве нет, то будет использовано значение переданное по умолчанию аргументом $default. Но если в поле $key хранится вложенный массив, то вы сможете извлечь значение этого массива передав в качестве $key следующее — key1.key2. Такой аргумент извлечет из массива $array значение поля key1 (в поле key1 снова ассоциативный массив) и из него значение поля key2.

Благодаря листингу 3 и 4, при обращении по адресу  /morkovin_controller.php?action=login контроллер запустит функцию morkovin_action_login(). С содержимым данной функции мы познакомимся в листинге 5.

Листинг 5. Функция авторизации morkovin_action_login()

function morkovin_action_login()
{
	if (!morkovin_isPressed('morkovin_sbmt_auth_btn'))
	{
		status_header(404);
		nocache_headers();
		include( get_404_template() );
		exit;
	}

	$creds = array();
	$creds['user_login']    = morkovin_arr_get($_POST, 'log');
	$creds['user_password'] = morkovin_arr_get($_POST, 'pwd');
	$creds['remember']      = (bool) morkovin_arr_get($_POST, 'rememberme');
	$user = wp_signon($creds, FALSE);

	if (is_wp_error($user))
	{
		$data = array('title'=>'Вход', 'error'=>'wrong_password');
		morkovin_show_page('message1', $data);
	}
	else
	{
		wp_redirect(home_url()); exit;
	}
}

В 3-й строке выполняется функция morkovin_isPressed, которая проверяет была ли нажата кнопка name = "morkovin_sbmt_auth_btn". Именно нажатием кнопки morkovin_sbmt_auth_btn инициализируется процесс авторизации в штатном режиме (см. листинг 1). С функцией morkovin_isPressed мы познакомимся в листинге 6.

Если кнопка morkovin_isPressed нажата не была, то я формирую заголовок 404 и открываю шаблон страницы 404.

Команда exit в строке 8 прекращает дальнейшее выполнение кода.

Если же кнопка morkovin_isPressed была нажата, то программа не выполняя инструкции в строках 3 — 9 сразу перейдет к заполнению массива $creds авторизационными данными пользователя, которые извлекутся из массива POST (переданного формой рис. 1) уже знакомой нам функцией morkovin_arr_get() (листинг 4).

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

Если в процессе авторизации возникли ошибки (неправильно введен логин или пароль) строка 17 отловит их возникновение. Строки 19—20 отобразят соответствующую страницу с описанием ошибки. С кодом функции, отображающей вид страницы morkovin_show_page() мы разберемся ниже.

Если же ошибок в процессе авторизации не возникло, 24-я строка выполнит редирект на главную страницу и пользователь будет авторизован.

Познакомимся с функцией morkovin_isPressed без лишних пояснений.

Листинг 6. Функция morkovin_isPressed()

function morkovin_isPressed($buttonName)
{
	if (!isset($_POST[$buttonName]))
		return FALSE;

	// проверка referer
	if (!isset($_SERVER['HTTP_REFERER']))
		return TRUE;

	if (!preg_match('/^https?\:\/\/([^\/]+)\/.*$/i', $_SERVER['HTTP_REFERER'], $matches))
		return FALSE;

	if ($matches[1] != $_SERVER['HTTP_HOST'])
		return FALSE;

	return TRUE;
}

Отображение вида

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

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

Листинг 7. Отображение внешнего вида функцией morkovin_show_page()

function morkovin_show_page($pagename, $data=array())
{
	$__templatefile = ABSPATH.'/myviews/'.$pagename.'.php';
	if (!is_readable($__templatefile))
		throw new Exception('Template '.htmlspecialchars($__templatefile).' is not found.');

	extract($data);

	ob_start();

	try
	{
		require $__templatefile;
	}
	catch (Exception $e)
	{
		ob_end_clean();
		throw $e;
	}

	$content = ob_get_clean();

	header('Content-Type: text/html; charset=' . get_option('blog_charset'));
	require(TEMPLATEPATH.'/header.php');
	echo $content;
	require(TEMPLATEPATH.'/sidebar.php');
	require(TEMPLATEPATH.'/footer.php');

	exit;
}

В 3-й строке в переменной $__templatefile формируется путь к файлу, используемому для отображения внешнего вида. Как можно видеть файл расположен в папке myviews корня сайта, а его имя задается аргументом $pagename.

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

В 4-й строке осуществляется проверка наличия файла вида с указанным именем. Если его нет, то строка 5 поднимет исключение. О том к чему это приведет мы поговорим чуть позже.

В 7-й строке, благодаря функции extract, произойдет следующее. Поля массива $data превратятся в самостоятельные переменные. Именем переменной будет имя поля, значением — значение поля массива.

Далее от строки 9 до строки 21 включается буферизация. Это означает, что любой вывод, выполняемый между этими строками, не будет отображен в браузере, а будет направлен в буфер. Строка 21 поместит содержимое буфера в переменную $content.

Буферизация нужна вот зачем. В файле вида (например, /myviews/message.php) используются переменные, изъятые из массива $data в строке 7 листинга 7. В переменную $content попадет готовая к отображению средняя часть страницы, описанная в файле вида, переменные там заменены значениями изъятыми из массива $data. Но ведь нам нужно подключить шапку и подвал, а между ними выводить контент. Поэтому мы тормозим отображение контента в браузер до подключения шапки (строки 24 — 27).

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

Листинг 8. Файл внешнего вида myviews/message.php

	<div class="post">
		<h1><?php echo $title?></h1>
		
<?php isset($error) or $error = ''?>
<?php if($error){?>
		<div class="errormessage">
<?php if($error == 'wrong_password'){?>
			<p>Ошибка входа: Вы указали неправильный логин или пароль.</p>
<?php }?>
		</div><!-- .errormessage -->
<?php }?>

		
<?php isset($message) or $message = ''?>
<?php if($message){?>
		<div class="infomessage">
<?php if($message == 'registration_completed'){?>
			<p>Регистрация успешно завершена. На Ваш email отправлен пароль для входа на сайт.</p>
<?php }?>
<?php if($message == 'email_confirmcode_sent'){?>
			<p>Письмо с проверочной ссылкой отправлено на указанный адрес эл. почты. Для продолжения покупки перейдите по указанной в письме ссылке.</p>
<?php }?>
		</div><!-- .infomessage -->
<?php }?>
		
	</div>
	

Непосредственное отношение к авторизации имеют лишь строки 1 — 11. Но для примера отображения других информационных сообщений я оставил строки 13 — 24.

Итак, отображается шапка, потом содержимое переменной $content, потом подключается сайдбар и футер. Результат на рис. 2.

Результат подключения файла вида
Рис. 2. Отображение сообщения об ошибке авторизации.

Работа с исключениями

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

WordPress отловил исключение
Рис. 3. Отображение сообщения об ошибке. Не найден запрашиваемый шаблон myviews/message1.php.

Из текста на рис. 3. становится понятно, что WordPress отловил незнакомое ему исключение, в тексте пояснения которого написано «Template /Users/amorkovin/Sites/wordpress2/www//myviews/message1.php is not found». Это как раз тот текст, который мы подготовили в строке 5 листинга 7.

Инструкции try и catch в строках 11 и 15 листинга 7 проверяют не возникло ли при формировании шаблона исключение. Если не возникло, то подключается шаблон $__templatefile и его содержимое попадает в буфер. Если исключение возникло — выключается буферизация и отображается информация об исключении аналогичным рис. 3 образом.

Итог

Мы все сделали. Единственное замечание: функции листингов 4 и 6 не имеют прямого отношения к контроллеру. Поэтому более правильно будет вынести их в отдельный файл. Пусть этим отдельным файлом будет myfunctions.php в корне блога.

Не забудем подключить файл myfunctions.php аналогичным листингу 2 образом:

Листинг 9. Подключение myfunctions.php

require_once(ABSPATH.'myfunctions.php');

Переменная ABSPATH вернет путь к корню блога.

Листинги 2, 3, 5, 7, 9 помещаем в файл morkovin_controller.php корня блога.

Скачать исходники (*.zip, 7 кБайт).

С уважением, Андрей Морковин.

MAXCACHE: 0.87MB/0.00029 sec