14 December 2009

Magento. Пишем свой модуль. Добавляем CAPTCHA и дополнительные поля в регистрацию

PHP
Хочу поделится опытом программирования модулей для Magento. Большое спасибо пользователю jeje за приглашение.
В статье подробно описано создание модуля, реализующего допольнительные функции регистрации клиентов. Цели — дать представление о разработке под Magento на конкретном примере от начала до конца, показать основные подходы, организацию кода, указать на некоторые особенности. Статья ориентирована скорее на новичков, но и знакомые с Magento могут вынести что-то полезное для себя. Конечно в одной статье всего описать невозможно, но если тема окажется востребованной, то это может стать началом цикла статей.
Затронуты следующие моменты:
  • создание модуля
  • работа с блоками, шаблонами и разметкой (layout)
  • переопределение контроллера
  • скрипты инсталляции модуля

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



Страница регистрации

Чего задумали


Есть страница регистрации /magento/customer/account/create. Мы хотим добавить проверку CAPTCHA, а также спрашивать у клиентов группу и код приглашения. Полученные данные сохранять в базу. По умолчанию в Magento определены три группы клиентов General, Wholesale и Retailer. Из них мы и предложим выбирать будущему клиенту. Про код приглашения Magento ничего не знает — создадим новый атрибут.

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

Поиск


Ищем где лежат файлы шаблонов, где и как сохраняются данные регистрации.
  1. Шаблоны формы регистрации клиента
    /magento/app/design/frontend/default/default/template/customer/form/register.phtml — анкета регистрации
  2. Атрибуты добавляют в инсталляторе модуля, пример:
    /magento/app/code/core/Mage/Customer/sql/customer_setup/mysql4-upgrade-0.8.7-0.8.8.php
    подробности будут ниже, когда будем писать инсталятор для нашего модуля.
  3. Обнаруживаем описание изменяемых полей (аттрибутов) в конфигурации модуля:
    /magento/app/code/core/Mage/Customer/etc/config.xml
    внутри тэгов <fieldsets>
  4. Форма регистрации постит данные по адресу:
    http://localhost/magento/customer/account/createpost/

    Соответсвенно запрос обрабатывается контроллером Mage_Customer_AccountController метод createPostAction (line #234)


Реализация


Вариант с изменением найденых фалов не рассматриваем. Создадим свою тему и свой модуль.
С темой всё просто:
  1. создаем новую папку в /magento/app/design/frontend/default/mytheme
    default — имя интерфейса
    mytheme — имя нашей темы
  2. копируем файлы с сохранением структуры директорий в новую тему
  3. редактируем/создаем шаблоны по своему усмотрению

Одноименные шаблоны нашей темы перекроют шаблоны стандартной темы. Таким образом мы не вносим изменений в "родные" файлы Magento.

С модулем немного интересней:
Создаем новый модуль — CustomerMod. Структура папок у всех модулей одинаковая:
magento/app/code/local/
	Examples
		CustomerMod
			- Block
			- etc
			- Helper
			- Model
			- sql


не забываем добавить файл с описанием наших модулей:
magento/app/etc/modules/Examples_All.xml
<config>
    <modules>
        <Examples_CustomerMod>
            <active>true</active>
            <codePool>local</codePool>
        </Examples_CustomerMod>
    </modules>
</config>


Поля на странице регистрации


Начнем с добавления на страницу регистрации поля выбора группы («General», «Wholesale», «Retailer»). Для вывода информации Magento оперирует блоками и шаблонами. Блок можно назвать рендерером шаблона. Каждая страница состоит из блоков, для каждого блока указан шаблон.
Мы добавим свой блок со своим шаблоном внутрь блока регистрационной формы. Другими словами, наш блок будет дочерним к блоку формы.

1. Собственно новый шаблон


Шаблон выбора группы в отдельном файле:
/magento/app/design/frontend/default/example_theme/template/customer/form/register/groupselect.phtml

с нехитрым содержанием:

<?php $customer_groups = Mage::helper('customer')->getGroups()->toOptionArray(); ?>

<fieldset class="group-select">
<h4 class="legend"><?php echo $this->__('Customer Group') ?></h4>
<ul>
<?php foreach($customer_groups as $cg) { ?>
	<li class="f-left" style="margin: 2px 4px;" >
		<input type="radio" name="group_id" id="group_id" 
			value="<?php echo $cg['value']; ?>" 
			<?php if ($cg['value']==1) echo 'checked=1' ?>  />
		<label><?php echo $cg['label']; ?></label>
	</li>
<?php } //end foreach?>
</ul>
</fieldset>


Обратите внимание имя поля «group_id» должно соответсвовать имени аттрибута.

2. Корректировка layout


Взаимное расположение блоков и привязка к шаблонам описываются в layout. Это xml-файлы, которые лежат в папке magento/app/design/ИМЯ_ИНТЕРФЕЙСА/ИМЯ_ТЕМЫ/layout.

В нашем случае будет:
/magento/app/design/frontend/default/example_theme/layout/customermod.xml
<layout version="0.1.0">
	<customer_account_create>					
		<reference name="customer_form_register">			
			<block type="core/template" name="customergroups-select" template="customer/form/register/groupselect.phtml" />
			<block type="captcha/recaptcha" name="captcha" />			
		</reference>		
	</customer_account_create>		
</layout>


Мы ссылаемся на блок customer_form_register. Описываем внутри него наш дочерний блок:
type — это класс блока, который в конечном итоге рендерит шаблон. В данном случае «core/template», что значит Mage_Core_Block_Template.
name — имя может быть любым. Оно нужно, чтобы обращатся к блоку, например в reference.
template — шаблон блока
Не забываем указать обновления layout в конфигах модуля, чтобы Magento учитывала изменения указанные в customermod.xml:

/magento/app/code/local/Examples/CustomerMod/etc/config.xml
<frontend>
<layout>
      <updates>
          <customermod>
              <file>customermod.xml</file>
          </customermod>
      </updates>
 </layout>
</frontend>


3. Вывод блока родительским блоком.


Наш блок пока ещё не видно, потому что мы должны «вызвать» дочерний блок в родительском с помощью метода getChildHtml(ИМЯ_БЛОКА).
Копируем register.phtml в нашу тему из default темы и добавляем необходимый вызов перед секцией Login Information:

/magento/app/design/frontend/default/example_theme/template/customer/form/register.phtml
...
<?php echo $this->getChildHtml('customergroups-select') ?>
   
 <fieldset class="group-select wide">
        <h4 class="legend"><?php echo $this->__('Login Information') ?></h4>
...


4. Сохранение данных


Теперь в регистрационной форме должен появится выбор группы.
«Но постойте, покупатель регистрируется, указывая „Retailer“ — а в панеле админа он всё равно в General группе!» — да, поле group_id не сохраняется.
Чтобы введенные поля сохранялись нужно указать их в fieldsets в конфигурации:

/magento/app/code/local/Examples/CustomerMod/etc/config.xml
    <fieldsets>
            <customer_account>
                <group_id><create>1</create><update>1</update></group_id>	                
            </customer_account>
        </fieldsets>


Почему — спросите разработчиков Magento, — но так устроен Customer контроллер. Кому интересно — смотрите /magento/app/code/core/Mage/Customer/controllers/AccountController.php метод createPostAction.

Новый аттрибут


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

1. Инсталляционные скрипты


Все действия по модификации модели производятся в инсталляционных скриптах, хранящихся в папке sql внутри каждого модуля.
/magento/app/code/core/Mage/Customer/sql/customer_setup
Скрипты бывают двух видов — setup и upgrade. Magento прогоняет соответсвующий скрипт один раз при установке или обновлении модуля. После установки в таблице core_resource появляется запись, например:
'customermod_setup', '0.1.0'

Иными словами скрипты прогоняются по двум причинам:
  1. отсутсвие записи о модуле в таблице core_resource
  2. версия модуля указанная в core_resource ниже, чем в config.xml модуля


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

Скрипты исполняются инсталлятором. По-умолчанию класс инсталлятора Mage_Core_Model_Resource_Setup, но он не содержит методов работы с EAV моделью (а мы аттрибуты собираемся создавать), поэтому в нашем случае нужен Mage_Eav_Model_Entity_Setup.

setup class hierarchy

2. Скрипт создания атрибута


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

$installer = $this;

/* @var $installer Mage_Customer_Model_Entity_Setup */
$installer->startSetup();

$installer->addAttribute('customer', 'invitation_code', array(
	'type' => 'varchar',
	'input' => 'text',
	'label' => 'Invitation Code',
	'global' => 1,
	'visible' => 1,
	'required' => 1,
	'user_defined' => 1,
	'default' => null,
	'visible_on_front' => 1
));

$installer->endSetup();

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

3. Обновляем шаблон


В шаблон формы регистрации добавляем новое поле:

/magento/app/design/frontend/default/example_theme/template/customer/form/register.phtml
<div class="input-box">
	<label for="invitation_code"><?php echo $this->__('Invitation Code') ?> <span class="required">*</span></label><br/>
	<input type="text" name="invitation_code" id="invitation_code" title="<?php echo $this->__('Invitation Code') ?>" class="required-entry input-text" />
</div>


4. Обновляем конфигурацию


Добавляем описание установочного скрипта:
/magento/app/code/local/Examples/CustomerMod/etc/config.xml
<global>
	<resources>
		<customermod_setup>
			<setup>
				<module>Examples_CustomerMod</module>
				<class>Mage_Eav_Model_Entity_Setup</class>
			</setup>
			<connection><use>core_setup</use></connection>
		</customermod_setup>
		<customermod_write><connection><use>core_write</use></connection></customermod_write>
		<customermod_read><connection><use>core_read</use></connection></customermod_read>
	</resources>

И не забываем указать новое поле в fieldsets
<fieldsets>
	<customer_account>
		<group_id><create>1</create><update>1</update></group_id>
		<invitation_code><create>1</create><update>1</update></invitation_code>				                
	</customer_account>
</fieldsets>

Теперь в форме регистрации есть поле Invitation Code, значение которого сохраняется в атрибуте invitation_code для каждого клиента. Также новое поле появилось в панели администратора на странице аккаунта клиента в закладке Account Information.

Admin Customer Account Screenshot

CAPTCHA



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

На мой взгляд лучше оформить в отдельном модуле. Шаги по созданию нового модуля, отдельного шаблона, модификации разметки (layout) такие же, что были описаны выше. Остановимся на том, что ещё не было затронуто: контроллер и хелпер.
Значения с формы регистрации передаются в контроллер Mage_Customer_AccountController В Magento предусмотрен механизм переопределения контроллеров (начиная с версии 1.3 механизм несколько изменился). Вместо изменений в стандартном контроллере, создадим новый, унаследованный от стандартный.

Итак по-порядку.

Скачиваем библиотеку recaptcha-php и копируем в папку /magento/lib.

1. Контроллер



В конфигурации модуля CustomerMod описываем контроллер:
<config>
    ...
    <frontend>
    ...
     <routers>
         <customer>
            <args>
               <modules>
                  <Examples_CustomerMod before="Mage_Customer">Examples_CustomerMod</Examples_CustomerMod>
               </modules>
            </args>
         </customer>
      </routers>
    ...


Собственно сам контроллер.
/magento/app/code/local/Examples/CustomerMod/controllers/AccountController.php
require_once("Mage/Customer/controllers/AccountController.php");
require_once('recaptcha/recaptchalib.php');

class Examples_CustomerMod_AccountController extends Mage_Customer_AccountController {
		
	public function createPostAction() {		
		$request = $this->getRequest();		
		$captchaIsValid =  Mage::helper('captcha')->captchaIsValid($request);
		
		if ($captchaIsValid) {
			parent::createPostAction();
		} else {
			$this->_getSession()->setCustomerFormData($this->getRequest()->getPost());
			$this->_getSession()->addError($this->__('Verification code was not correct. Please try again.'));
			$this->_redirectError(Mage::getUrl('*/*/create', array('_secure'=>true)));
		}
	}
}


В отличие от всех других классов, определяемых в Magento, для контроллеров необходимо явно указывать на файл содержащий родительский класс и сторонние библиотеки, поэтому и нужны два require_once. Код минимален, используем стандартную функцию из recaptchalib. Но сама проверка введенной каптчи вынесена в отдельный класс-хелпер. Если понадобиться добавить такую же проверку в другие контроллеры, то всё сведется к проверке результата Mage::helper('captcha')->captchaIsValid($request).
Здесь ещё можно добавить, например, подлинность кода приглашения.

2. Хелпер


Хелпер в Magento — это класс синглетон, содержащий как правило набор вспомогательных методов. Обращение к хелперу производится с помощью метода Mage::helper() с именем модуля в качестве параметра. В нашем случае Examples_Captcha_Helper_Data будет содержать функции проверки каптчи.

/magento/app/code/local/Examples/Captcha/Helper/Data.php
require_once('recaptcha/recaptchalib.php');
class Examples_Captcha_Helper_Data extends Mage_Core_Helper_Abstract
{	
	const CAPTCHA_PUBLIC_KEY = "public-key-for-the-website";
	const CAPTCHA_PRIVATE_KEY = "private-key-for-the-website";
	
	public function captchaIsValid(Mage_Core_Controller_Request_Http $request) {	
		if ($request) {
			$resp = recaptcha_check_answer (self::CAPTCHA_PRIVATE_KEY,
		                           $_SERVER["REMOTE_ADDR"],
		                           $request->getParam("recaptcha_challenge_field"),
		                           $request->getParam("recaptcha_response_field") );		
			return $resp->is_valid;
		} 
		return false;						
	}
	
	public function captchaGetError(Mage_Core_Controller_Request_Http $request) {
		if ($request) {
			$resp = recaptcha_check_answer (self::CAPTCHA_PRIVATE_KEY,
			                           $_SERVER["REMOTE_ADDR"],
			                           $request->getParam("recaptcha_challenge_field"),
			                           $request->getParam("recaptcha_response_field") );
			return $resp->error;
		}
		return false;
		
	}
	
	public function getPublicKey() { return  Examples_Captcha_Helper_Data::CAPTCHA_PUBLIC_KEY; }
	
}


3. Блок CAPTCHA


Не плохо бы вывести картинку самой каптчи на страницу. Есть для этого функция recaptcha_get_html(). Не смотря на то, что функцию можно вызвать из шаблона (phtml), мы последуем идеям и архитектуре Magento — cоздадим новый тип блока, заодно узнаем как блок может быть и без шаблона. Для этого опишем класс Examples_Captcha_Block_Recaptcha. Вызов функции recaptcha_get_html() внесем в метод _toHtml. Этот метод вызывается при отрисовке блока в HTML. (см. /magento/app/code/core/Mage/Core/Block/Abstract.php line #643)

/magento/app/code/local/Examples/Captcha/Block/Recaptcha.php
require_once('recaptcha/recaptchalib.php');
class Examples_Captcha_Block_Recaptcha extends Mage_Core_Block_Abstract {		
	
	public function _toHtml() {
		$html = recaptcha_get_html( Mage::helper('captcha')->getPublicKey() );
		return $html;
	}
	
}


Добавляем новый блок в layout. Шаблон для этого блока не нужен, он и так выводит готовую каптчу.

/magento/app/design/frontend/default/example_theme/layout/customermod.xml
<?xml version="1.0"?>
<layout version="0.1.0">
<customer_account_create>					
		<reference name="customer_form_register">			
			<block type="core/template" name="customergroups-select" template="customer/form/register/groupselect.phtml" />
			<block type="captcha/recaptcha" name="captcha" />			
		</reference>		
	</customer_account_create>
</layout>


4. Конфигурация


Осталось только указать в конфигурации модуля Captcha, что он содержит блок и хелпер

/magento/app/code/local/Examples/Captcha/etc/config.xml
<?xml version="1.0" encoding="UTF-8"?>
<config>	
	<modules>
	   <Examples_Captcha>
	       <version>0.1.0</version>
	   </Examples_Captcha>
	</modules>
	
	<global>        		
		<blocks>
			<captcha><class>Examples_Captcha_Block</class></captcha>
		</blocks>				
		<helpers>
			<captcha>
				<class>Examples_Captcha_Helper</class>
			</captcha>
		</helpers>				
	</global>	
</config>


И каптча готова.

CAPTCHA screenshot

Заключение



Спасибо дочитавшим до конца! Надеюсь вам было интересно или хотя бы полезно :). Можете скачать готовый пример. Для написания статьи использовалась Magento версии 1.3.2.4.
Если затронутая тема интересна на хабре, то я с радостью выслушаю пожелания по новой статье.
Есть идеи и кое-какие материалы для статей на тему:
  • Обзор Magento пряники и грабли
  • События и слушатели в Magento на примере добавления email-уведомления.
  • Debugging в Magento (про тестовую страницу, про Firebug, про Mage::log)
  • Модели в Magento. Добавляем свои сущности и атрибуты
  • Как я добавлял блоки в email-шаблоны Magento
  • PDF в Magento. Горькая правда
  • Работа с коллекциями в Magento на примере создания отчета
  • выбор/настройка IDE для разработки в Magento

Tags:magentomagento ecommercecmsphpмодули magento
Hubs: PHP
+18
16.4k 63
Comments 19