📜 ⬆️ ⬇️

Development of modules for Magento 1.x - big guide + video



Hi, Habr! Despite the long-released Magento 2, the first version of Magento is still alive and alive and is not going to leave us yet. The Magento team will support the first version of the product for 3 years from the release date of version 2, i.e. until about November 2018. The market is replete with the broadest selection of themes, modules and services, sharpened for Magento 1.x version. And a large number of sites that are now on Magento 1.x, do not rush to update. A lot of work - little exhaust. So, the development of the first versions for Magento is still relevant and will be for several years.

But not about the prospects for the development of e-commerce solutions will be discussed in this article. Then I decided to put together a kind of guide for creating modules for Magento 1.x (hereinafter referred to as simply Magento). But not a simple guide, in which you just need to follow the instructions, but with some explanations “why we write this way and not otherwise.” I tried to find a middle ground between brevity and sufficiency. First and foremost, the guide bears the benefit of beginners in the development of modules for Magento. But more experienced users of this material can benefit.

Actually, I tried to make each part self-sufficient, i.e. if you are interested only in a single moment, then you can take all the necessary information from a specific section and not run around the whole guide. And if you have already implemented some sections from the section, you can skip them. The same attitude to the video. Only video lessons are enough for work, but you can also do without video, there is an order of actions and listings with comments. Although some things are better to look at the video, because there, besides coding, there are still demonstrations of efficiency. Yes, and I just could miss something. So there may be some undocumented moments in the video, and in the text version there may be add-ons that are not in the video. It was not inevitable, because everything was done at different times.
')

Training


It all starts with the preparation of the workplace, and in our case - the server with the installed test store.

If your environment is ready, you can proceed to the next section.

Server on Ubuntu 16.04 LTS


Download the distribution Ubuntu 16.04 , configure the "virtual". And install Ubuntu on our virtual machine. The installation process is generally simple and does not require documentation, but the entire installation and configuration process can be found in the video below.

Video: Installing UBUNTU 16.04 - Nginx + php7-fpm + mysql + samba


Install and configure the necessary software.

sudo su apt-get install && apt-get upgrade 

We put the file manager, editor and task manager

 apt-get install mc nano htop 

Configure a static IP address (in principle, you can not do this, and assign a static address on the side of the router):

 nano /etc/network/interfaces 

Setup Example:

 iface eth0 inet static address 192.168.0.100 netmask 255.255.255.0 gateway 192.168.0.1 dns-nameservers 192.168.0.1 8.8.8.8 auto eth0 

where eth0 is the network interface. You can see it by writing ifconfig
Nginx webserver:

 apt-get install nginx 

PHP 7.0 FPM:

 apt-get install php-fpm php-xdebug php-soap php-gd php-mbstring php-mcrypt php-curl php-xml 

MySQL 5.7 and phpMyAdmin:

 apt-get install mysql-server-5.7 phpmyadmin 

Change the owner and rights to the folder where the store files will be:

 chown -R dev:dev /var/www chmod -R 777 /var/www 

dev: dev is the name and group of the user. I used this name when installing Ubuntu.
Now you need to configure the installed software.

Nginx


I made 3 config for Nginx: dynamic domain, config for Magento 2 (useful), config for phpMyAdmin. The concept of the action of the so-called config with dynamic domains is simple.


dynamic.conf
  server { listen 80; server_name $http_host; root /var/www/$http_host; location / { index index.html index.php; try_files $uri $uri/ @handler; expires 30d; } location /. { return 404; } location @handler { rewrite / /index.php; } location ~ .php/ { rewrite ^(.*.php)/ $1 last; } location ~ .php$ { if (!-e $request_filename) { rewrite / /index.php last; } expires off; fastcgi_pass unix:/run/php/php7.0-fpm.sock; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param SCRIPT_NAME $document_root$fastcgi_script_name; fastcgi_param MAGE_RUN_TYPE store; include fastcgi_params; } } 


m2.conf
  # Magento Vars # # Example configuration: upstream fastcgi_backend { server unix:unix:/run/php/php7.0-fpm.sock; } server { set $MAGE_ROOT /var/www/m2.dev; set $MAGE_MODE default; # or production or developer listen 80; server_name m2.dev; root /var/www/m2.dev/pub; index index.php; autoindex off; charset off; add_header 'X-Content-Type-Options' 'nosniff'; add_header 'X-XSS-Protection' '1; mode=block'; location /setup { root $MAGE_ROOT; location ~ ^/setup/index.php { fastcgi_pass fastcgi_backend; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } location ~ ^/setup/(?!pub/). { deny all; } location ~ ^/setup/pub/ { add_header X-Frame-Options "SAMEORIGIN"; } } location /update { root $MAGE_ROOT; location ~ ^/update/index.php { fastcgi_split_path_info ^(/update/index.php)(/.+)$; fastcgi_pass fastcgi_backend; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; include fastcgi_params; } # deny everything but index.php location ~ ^/update/(?!pub/). { deny all; } location ~ ^/update/pub/ { add_header X-Frame-Options "SAMEORIGIN"; } } location / { try_files $uri $uri/ /index.php?$args; } location /pub { location ~ ^/pub/media/(downloadable|customer|import|theme_customization/.*\.xml) { deny all; } alias $MAGE_ROOT/pub; add_header X-Frame-Options "SAMEORIGIN"; } location /static/ { if ($MAGE_MODE = "production") { expires max; } location ~* \.(ico|jpg|jpeg|png|gif|svg|js|css|swf|eot|ttf|otf|woff|woff2)$ { add_header Cache-Control "public"; add_header X-Frame-Options "SAMEORIGIN"; expires +1y; if (!-f $request_filename) { rewrite ^/static/(version\d*/)?(.*)$ /static.php?resource=$2 last; } } location ~* \.(zip|gz|gzip|bz2|csv|xml)$ { add_header Cache-Control "no-store"; add_header X-Frame-Options "SAMEORIGIN"; expires off; if (!-f $request_filename) { rewrite ^/static/(version\d*/)?(.*)$ /static.php?resource=$2 last; } } if (!-f $request_filename) { rewrite ^/static/(version\d*/)?(.*)$ /static.php?resource=$2 last; } add_header X-Frame-Options "SAMEORIGIN"; } location /media/ { try_files $uri $uri/ /get.php?$args; location ~ ^/media/theme_customization/.*\.xml { deny all; } location ~* \.(ico|jpg|jpeg|png|gif|svg|js|css|swf|eot|ttf|otf|woff|woff2)$ { add_header Cache-Control "public"; add_header X-Frame-Options "SAMEORIGIN"; expires +1y; try_files $uri $uri/ /get.php?$args; } location ~* \.(zip|gz|gzip|bz2|csv|xml)$ { add_header Cache-Control "no-store"; add_header X-Frame-Options "SAMEORIGIN"; expires off; try_files $uri $uri/ /get.php?$args; } add_header X-Frame-Options "SAMEORIGIN"; } location /media/customer/ { deny all; } location /media/downloadable/ { deny all; } location /media/import/ { deny all; } location ~ cron\.php { deny all; } location ~ (index|get|static|report|404|503)\.php$ { try_files $uri =404; fastcgi_pass fastcgi_backend; fastcgi_param PHP_FLAG "session.auto_start=off \n suhosin.session.cryptua=off"; fastcgi_param PHP_VALUE "memory_limit=256M \n max_execution_time=600"; fastcgi_read_timeout 600s; fastcgi_connect_timeout 600s; fastcgi_param MAGE_MODE $MAGE_MODE; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } } 


phpmyadmin.conf
  server { listen 80; server_name pma myadmin; root /usr/share/phpmyadmin/; index index.php; location /setup/index.php { deny all; } location ~ .php$ { if (!-e $request_filename) { rewrite / /index.php last; } expires off; fastcgi_pass unix:/run/php/php7.0-fpm.sock; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param SCRIPT_NAME $document_root$fastcgi_script_name; fastcgi_param MAGE_RUN_TYPE store; include fastcgi_params; } include fastcgi_params; } 

Put the configs in the / etc / nginx / sites-useable / folder and make symlinks on them in the / etc / nginx / sites-enabled / folder. Or simply add them to the / etc / nginx / sites-enabled / folder

PHP 7.0 FPM


Edit /etc/php/7.0/fpm/php.ini. We are concerned only with some parameters, which in principle can be customized to your taste.

  max_execution_time = 300 max_input_time = 160 memory_limit = 512M display_errors = On log_errors = On html_errors = On date.timezone = (   ) 

Samba server
I like working through samba, mount a network drive to myself and quietly copy files. But you may not need it. The taste and color, as they say ... My config is:

smb.conf
 [global] workgroup = WORKGROUP server string = %h server (Samba, Ubuntu) dns proxy = no log file = /var/log/samba/log.%m max log size = 1000 syslog = 0 panic action = /usr/share/samba/panic-action %d server role = standalone server passdb backend = tdbsam obey pam restrictions = yes unix password sync = yes passwd program = /usr/bin/passwd %u passwd chat = *Enter\snew\s*\spassword:* %n\n *Retype\snew\s*\spassword:* %n\n *password\supdated\ssuccessfully* . pam password change = yes map to guest = bad user null passwords = Yes guest account = www-data [www] path = /var/www/ comment = WWW folder guest ok = yes browseable = yes read only = no locking = no force user = www-data force group = www-data 



Installing a test store


The installation process is simple and does not require any special skills. But for clarification, I will leave the video instruction hidden under the spoiler.

Video: Installing Magento Test Store


But there is one important point.
Magento does not work on PHP 7.
To start it, you can use the following fix: github.com/Inchoo/Inchoo_PHP7

Module creation



Structure and configuration


Video: Structure and configuration of the Magento module


The structure of the module IGN_Siteblocks-1.zip created in the lesson

We will learn to create modules using the example of a module for displaying blocks on the store pages (its frontend parts). And first of all we invent the name of the module. The name should be short and carry meaning. And we need to choose a namespace (usually the name of the developer’s company or his full name). And the final name takes the form Namespace_Modulename . In our case, I called IGN_Siteblocks .

Create a registration XML file:

app / etc / modules / IGN_Siteblocks.xml
  <?xml version="1.0" ?> <config> <modules> <IGN_Siteblocks> <active>true</active> <!--   --> <codePool>local</codePool> </IGN_Siteblocks> </modules> </config> 

Let's talk about codePool. There are 3 of them: local , community , core .

And we will immediately decide that we will not change anything in the core, the base files of the system are there and if they need to be changed, then there are other ways besides their direct editing.
We can safely use local and community (Actually, it's better to take the community right away, but in this example it will be local ).

Go to the admin of the store, in the System> Configuration> Advanced> Disable Modules Output section and see our IGN_Siteblocks.

Create folders for our module:

app / code / local / IGN / Siteblocks /
  1. Block - block classes that are responsible for rendering pages.
  2. controllers - controllers accept requests
  3. etc - there are all sorts of configuration files
  4. Helper - additional classes assistants
  5. Model - models
  6. sql - installation scripts

Modules in Magento implement the MVC pattern. We have models, views (blocks, templates and layouts) and controllers. In folder etc create config.xml

app / code / local / IGN / Siteblocks / etc / config.xml
  <?xml version="1.0" ?> <config> <modules> <IGN_Siteblocks> <version>1.0.0</version> </IGN_Siteblocks> </modules> <global> <!--   , , , ,   --> </global> <frontend> <!--   frontend  : , , ,  --> </frontend> <admin> <!--   admin  : , , --> </admin> <adminhtml> <!--   admin  : , ,  --> </adminhtml> <defalut> <!--   admin  : , ,  --> </defalut> </config> 


Here we will declare our blocks, models, controllers, helpers, observers, rewright, layouts, translations, and standard values ​​of some module settings.

Debugging XDEBUG + PHPSTORM Code


Video: Debugging XDEBUG + PHPSTORM Code


Here I would still recommend to look at the video instruction. First, configure the server:

 apt-get install php-xdebug 

Edit the settings in php.ini or xdebug.ini

/etc/php/7.0/conf.d/20-xdebug.ini
 zend_extension = xdebug.so xdebug.idekey = "PHPSTORM" xdebug.remote_autostart = 1 xdebug.remote_connect_back = 1 xdebug.remote_enable = 1 xdebug.remote_port = 9000 


We save and do not forget to restart the service php7.0-fpm restart service . In PHPSTORM we create a new Remote Debug config.

We add the server with the corresponding address and port. In the IDE key field, enter the word PHPSTORM.

Models, collections. Work with the database.


Video: Models, collections. Work with Magento Database


The structure of the module IGN_Siteblocks-2.zip created in the lesson

Models are classes for working with data and data only. No subtleties with the method of storing this data in the database. No code associated with rendering this data. In Magento these are: Customer, Product, Order, and so on.

To make our module use models, you need to configure config.xml
Let me remind you that models, blocks and helpers are added to the global section. The config.xml takes the following form:

app / code / local / IGN / Siteblocks / etc / config.xml
  <?xml version="1.0" ?> <config> <modules> <IGN_Siteblocks> <version>1.0.0</version> </IGN_Siteblocks> </modules> <global> <models> <siteblocks> <!--    namespace_modulename   modulename --> <class>IGN_Siteblocks_Model</class> <resourceModel>siteblocks_resource</resourceModel> </siteblocks> <siteblocks_resource> <class>IGN_Siteblocks_Resource</class> <entities> <block> <!--   --> <table>ign_siteblock</table> <!--      ""  --> </block> </entities> </siteblocks_resource> </models> <resources> <siteblocks_setup> <!--         install  upgrade  --> <setup> <module>IGN_Siteblocks</module> </setup> </siteblocks_setup> </resources> </global> </config> 


It is important to determine the name of the prefix (I don’t know which term is better here). I chose siteblocks. This is an arbitrary name and is usually formed from the namespace and the name of the module or only the name of the module. Well, or to obfuscate developers, you can choose a completely arbitrary string by purchasing the amulet from curses in advance.

Choose clearly and preferably without the use of uppercase characters. One typo, and you will dig for a long time, look for the problem. Model name and table binding. The model name corresponds to the model file name. The name of the table in the database is arbitrary. In my case, to refer to the model, you need to write this:

 Mage::getModel('siteblocks/block'); 

Now you can add models. Create a block model. For each model associated with a table, you need to create 3 files: a model, a resource model, a collection model. The model abstracts from working with the base, the resource models are at a lower level. There we implement the logic of filtering, sorting, data processing before they are saved and after loading from the database.
Model code Block.php:

app / code / local / IGN / Siteblocks / Model / Block.php
  <?php class IGN_Siteblocks_Model_Block extends Mage_Core_Model_Abstract { public function _construct() { parent::_construct(); $this->_init('siteblocks/block'); //      config.xml  } } 


Models inherited from Mage_Core_Model_Abstract . Resource models are saved in the Model / Resource folder.

app / code / local / IGN / Siteblocks / Model / Resource / Block.php
  <?php class IGN_Siteblocks_Model_Resource_Block extends Mage_Core_Model_Resource_Db_Abstract { public function _construct() { $this->_init('siteblocks/block','block_id'); //block_id   PRIMARY KEY  ,   entity_id } } 


app / code / local / IGN / Siteblocks / Model / Resource / Block / Collection.php
  <?php class IGN_Siteblocks_Model_Resource_Block_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract { public function _construct() { parent::_construct(); $this->_init('siteblocks/block'); } } 


Our classes are empty, but they already implement the necessary minimum of inheritance functionality.
We will add code to them as needed. And if we want to add more models, then add another model binding to the table and new 3 files. You can add as many models as you like that are not tied to the table (just to implement some functionality), then you simply add a new file, it is not necessary to inherit from Mage_Core_Model_Abstract .

Do not forget to create an installation script that will create a table for our model.

app / code / local / IGN / Siteblocks / sql / siteblocks_setup / install-1.0.0.sql
  <?php /** @var Mage_Core_Model_Resource_Setup $installer */ $installer = $this; $installer->startSetup(); $table = $installer->getConnection() ->newTable($this->getTable('siteblocks/block')) ->addColumn('block_id',Varien_Db_Ddl_Table::TYPE_INTEGER,null,array( 'identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true )) ->addColumn('title',Varien_Db_Ddl_Table::TYPE_VARCHAR,null,array( 'nullable' => false )) ->addColumn('content',Varien_Db_Ddl_Table::TYPE_TEXT,null,array( 'nullable' => false )) ->addColumn('block_status',Varien_Db_Ddl_Table::TYPE_TINYINT,null,array( 'nullable' => false )) ->addColumn('created_at',Varien_Db_Ddl_Table::TYPE_DATETIME,null,array( 'nullable' => false )); $installer->getConnection()->createTable($table); //  $installer->run(" CREATE TABLE IF NOT EXISTS `{$this->getTable('siteblocks/block')}` ( `block_id` int(11) NOT NULL AUTO_INCREMENT, `title` varchar(500) NOT NULL, `content` text NOT NULL, `block_status` tinyint(4) NOT NULL, `created_at` datetime NOT NULL, PRIMARY KEY (`block_id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ; "); $installer->endSetup(); 


IMPORTANT MOMENT!

If you have already tried to log in to the admin area, with the module installed, when there was no installation script yet. Most likely your install script will never run again. In this case, you need to find and delete the siteblocks_setup entry from the core_resource table in the store database.

When upgrading the module version. We specify the new version in the config.xml , for example: 1.0.1 . And create an upgrade script: upgrade-1.0.0-1.0.1.php . And in the same vein with subsequent upgrades.

Speaking of models and collections, it is impossible not to mention the most basic methods of these classes.

Some examples of using models.
  //    block_id = 1 $block = Mage::getModel('siteblocks/block')->load(1); //  $block->delete(); //C $block->save(); //        Mage::getModel('siteblocks/block')->setId(1)->delete(); //     $blocks = Mage::getModel('siteblocks/block')->getCollection(); //   block_id = 1, 2  3 $blocks->addFieldToFilter('block_id',array('in'=>array(1,2,3))) ; echo $blocks->getSelect(); //  SQL  //    $blocks = Mage::getResourceModel('siteblocks/block_collection'); 




Controllers and routing


Video: Controllers and routing in Magento.


The structure of the module IGN_Siteblocks-3.zip created in the lesson

The controllers, according to the MVC pattern, are responsible for processing requests. Take the so-called input signal in the form of an HTTP request. Followed the link - the corresponding controller worked.

Before creating controllers, configure routing in config.xml . Routing for frontend and admin parts is configured separately. So add routers to the frontend and admin section.

config.xml takes the form:

app / code / local / IGN / Siteblocks / etc / config.xml
  <?xml version="1.0" ?> <config> <modules> <IGN_Siteblocks> <version>1.0.0</version> </IGN_Siteblocks> </modules> <global> <models> <siteblocks> <class>IGN_Siteblocks_Model</class> <resourceModel>siteblocks_resource</resourceModel> </siteblocks> <siteblocks_resource> <class>IGN_Siteblocks_Resource</class> <entities> <block> <table>ign_siteblock</table> </block> </entities> </siteblocks_resource> </models> <resources> <siteblocks_setup> <setup> <module>IGN_Siteblocks</module> </setup> </siteblocks_setup> </resources> </global> <frontend> <routers> <siteblocks> <use>standard</use> <args> <module>IGN_Siteblocks</module> <frontName>siteblocks</frontName><!--  ,      --> </args> </siteblocks> </routers> </frontend> <admin> <routers> <adminhtml> <args> <modules> <siteblocks after="Mage_Adminhtml">IGN_Siteblocks_Adminhtml</siteblocks> </modules> </args> </adminhtml> </routers> </admin> <default> </default> </config> 


Now you can create your own controllers in the controllers folder of our module. The controller class for the frontend part must inherit from the Mage_Core_Controller_Front_Action class.

Let's create a test controller TestController.php

app / code / local / IGN / Siteblocks / controllers / TestController.php
 <?php class IGN_Siteblocks_TestController extends Mage_Core_Controller_Front_Action { public function mytestAction() { die('test'); } } 


If you now go to a URL like example.com/siteblocks/test/mytest . You will see a white screen labeled "test". If this does not happen, then an error has occurred at some stage.

Recheck the code and read the logs. URL consists of router ( siteblocks ) / controller ( Test Controller) / action ( mytest Action)

GET parameters can be transferred in 2 ways:


Controllers for admin panel are created in the controllers / Adminhtml folder. The controller class for the frontend part must be inherited from the Mage_Adminhtml_Controller_Action class.

Let's create a test controller TestController.php:

app / code / local / IGN / Siteblocks / controllers / Adminhtml / TestController.php
 <?php class IGN_Siteblocks_Adminhtml_TestController extends Mage_Adminhtml_Controller_Action { public function mytestAction() { die('admin'); } } 


You can access it at the URL: example.com/admin/test/mytest - where admin is your way to the admin area.

And here there is a nuance: such a URL may already be occupied by another module. Exit here 2: change the name of the controller to a deliberately non-conflict (for example, IgntestController.php) or add the controllers to a subfolder.

app / code / local / IGN / Siteblocks / controllers / Adminhtml / Siteblocks / TestController.php
 <?php class IGN_Siteblocks_Adminhtml_Siteblocks_TestController extends Mage_Adminhtml_Controller_Action { public function mytestAction() { die('admin'); } } 


Now our URL takes the form: example.com/admin/siteblocks_test/mytest


Helpers


Video: Helpers in Magento


The structure of the module IGN_Siteblocks-4.zip created in the lesson

The helper classes in Magento are used as additional classes. They should implement third-party logic that does not fit into the functionality of models, blocks or controllers. But the module needs at least one class of Data.php helper.

This helper is used by default for translating text (labels, menu items, etc.) and other logic.

In the helper it is recommended to declare methods for reading settings from the config. Helpers must inherit from the Mage_Core_Helper_Abstract class.

app / code / loca / IGN / Siteblocks / Helper / Data.php
 <?php class IGN_Siteblocks_Helper_Data extends Mage_Core_Helper_Abstract { } 


There is a __ () method for translating text in a helper, and its use is as follows:

 echo Mage::helper('siteblocks')->__('Some text') 

Translation files we declare in config.xml.

app / code / local / IGN / Siteblocks / etc / config.xml
 <?xml version="1.0" ?> <config> <modules> <IGN_Siteblocks> <version>1.0.0</version> </IGN_Siteblocks> </modules> <global> <models> <siteblocks> <class>IGN_Siteblocks_Model</class> <resourceModel>siteblocks_resource</resourceModel> </siteblocks> <siteblocks_resource> <class>IGN_Siteblocks_Resource</class> <entities> <block> <table>ign_siteblock</table> </block> </entities> </siteblocks_resource> </models> <resources> <siteblocks_setup> <setup> <module>IGN_Siteblocks</module> </setup> </siteblocks_setup> </resources> <helpers> <siteblocks> <class>IGN_Siteblocks_Helper</class> </siteblocks> </helpers> </global> <frontend> <routers> <siteblocks> <use>standard</use> <args> <module>IGN_Siteblocks</module> <frontName>siteblocks</frontName> </args> </siteblocks> </routers> <translate> <modules> <IGN_Siteblocks> <files> <default>IGN_Siteblocks.csv</default> </files> </IGN_Siteblocks> </modules> </translate> </frontend> <admin> <routers> <adminhtml> <args> <modules> <siteblocks after="Mage_Adminhtml">IGN_Siteblocks_Adminhtml</siteblocks> </modules> </args> </adminhtml> </routers> </admin> <defalut> </defalut> </config> 


And the file IGN_Siteblocks.csv is created in the folder app / locale / en_US /. Content type: "Some text", "Some text" .

We try to display text using our helper, and in this case, the module localization into different languages ​​is simplified.

It is enough to copy the translation file to the appropriate locale and translate the second column and there is no need to dig into the code.


Module configuration in admin panel


Video: Module configuration in Magento admin panel


The structure of the module IGN_Siteblocks-5.zip created in the lesson

To give the module flexibility, we will create a page with the settings of the module. This is done exclusively through xml files. We need to create 2 files:

system.xml - where fields will be added
adminhtml.xml - where sections and access rights will be specified

And we can specify the default settings in the default section in the config.xml file.

app / code / local / IGN / Siteblocks / etc / config.xml
 <?xml version="1.0" ?> <config> <modules> <IGN_Siteblocks> <version>1.0.0</version> </IGN_Siteblocks> </modules> <global> <models> <siteblocks> <class>IGN_Siteblocks_Model</class> <resourceModel>siteblocks_resource</resourceModel> </siteblocks> <siteblocks_resource> <class>IGN_Siteblocks_Resource</class> <entities> <block> <table>ign_siteblock</table> </block> </entities> </siteblocks_resource> </models> <resources> <siteblocks_setup> <setup> <module>IGN_Siteblocks</module> </setup> </siteblocks_setup> </resources> <helpers> <siteblocks> <class>IGN_Siteblocks_Helper</class> </siteblocks> </helpers> </global> <frontend> <routers> <siteblocks> <use>standard</use> <args> <module>IGN_Siteblocks</module> <frontName>siteblocks</frontName> </args> </siteblocks> </routers> <translate> <modules> <IGN_Siteblocks> <files> <default>IGN_Siteblocks.csv</default> </files> </IGN_Siteblocks> </modules> </translate> </frontend> <admin> <routers> <adminhtml> <args> <modules> <siteblocks after="Mage_Adminhtml">IGN_Siteblocks_Adminhtml</siteblocks> </modules> </args> </adminhtml> </routers> </admin> <defalut> <siteblocks> <settings> <enabled>1</enabled> <block_count>10</block_count> </settings> </siteblocks> </defalut> </config> 


app / code / local / IGN / Siteblocks / etc / adminhtml.xml
 <?xml version="1.0"?> <config> <acl> <resources> <admin> <children> <system> <children> <config> <children> <siteblocks translate="title" module="siteblocks"> <title>Siteblocks</title> </siteblocks> </children> </config> </children> </system> </children> </admin> </resources> </acl> </config> 


app / code / local / IGN / Siteblocks / etc / system.xml
 <?xml version="1.0"?> <config> <tabs> <ign translate="label" module="siteblocks"> <!--      --> <label>IGN</label> <sort_order>2</sort_order> </ign> </tabs> <sections> <siteblocks module="siteblocks" translate="label"> <label>Siteblocks</label> <tab>ign</tab> <!--       --> <frontend>text</frontend> <sort_order>1</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> <show_in_store>1</show_in_store> <groups> <settings module="siteblocks" translate="label"> <label>Settings</label> <expanded>1</expanded> <sort_order>1</sort_order> <show_in_default>1</show_in_default> <show_in_Website>1</show_in_Website> <show_in_store>1</show_in_store> <fields> <enabled translate="label comment" module="siteblocks"> <label>Enabled</label> <frontend_type>select</frontend_type> <!--       lib/Varien/Data/Form/Element --> <source_model>siteblocks/source_status</source_model> <!--     --> <sort_order>1</sort_order> <show_in_default>1</show_in_default> <show_in_Website>1</show_in_Website> <show_in_store>1</show_in_store> <comment>Is module enabled</comment> </enabled> <blocks_count> <label>Blocks on page</label> <frontend_type>text</frontend_type> <sort_order>2</sort_order> <show_in_default>1</show_in_default> <show_in_Website>1</show_in_Website> <show_in_store>1</show_in_store> <depends><enabled>1</enabled></depends> <!--         --> </blocks_count> <raw_text> <label>Raw text</label> <frontend_type>textarea</frontend_type> <sort_order>3</sort_order> <show_in_default>1</show_in_default> <show_in_Website>1</show_in_Website> <show_in_store>1</show_in_store> <depends><enabled>1</enabled></depends> </raw_text> </fields> </settings> </groups> </siteblocks> </sections> </config> 


In our settings, a dropdown is displayed with options, and a custom model is used for these options:

app / code / local / IGN / Siteblocks / Model / Source / Status.php
 <?php class IGN_Siteblocks_Model_Source_Status { const ENABLED = '1'; const DISABLED = '0'; /** * Options getter * * @return array */ public function toOptionArray() { return array( array('value' => self::ENABLED, 'label'=>Mage::helper('siteblocks')->__('Enabled')), array('value' => self::DISABLED, 'label'=>Mage::helper('siteblocks')->__('Disabled')), ); } /** * Get options in "key-value" format * * @return array */ public function toArray() { return array( self::DISABLED => Mage::helper('siteblocks')->__('Disabled'), self::ENABLED => Mage::helper('siteblocks')->__('Enabled'), ); } 




Frontend blocks. Layouts. Templates


Video: Frontend blocks. Layouts. Magento Templates


The structure of the module IGN_Siteblocks-6.zip, created in the lesson

. And, as it is not difficult to guess from the title, we will use 3 types of files: blocks, layouts and templates.

Blocks are the classes responsible for preparing and displaying information. Blocks use templates for output, but not always. If a template is used, then it is simply included in the fetchView method:



Therefore, from the template, we call the block in $ this .

app / code / local / IGN / Siteblocks / Block / List.php
 <?php class IGN_Siteblocks_Block_List extends Mage_Core_Block_Template { public function getBlocks() { //return Mage::getResourceModel('siteblocks/block_collection'); return Mage::getModel('siteblocks/block')->getCollection() ->addFieldToFilter('block_status',array('eq'=>IGN_Siteblocks_Model_Source_Status::ENABLED)); } } 


The block is inherited from the Mage_Core_Block_Template class . But it depends on what our block will output. For example, when displaying a list of products, it is desirable to inherit from the Mage_Catalog_Block_Product_List block . Layouts are used to build the page structure, what elements to display on the page and in what order.

Create a layout file:

app / design / frontend / base / default / layout / siteblocks.xml
 <?xml version="1.0"?> <layout version="1.0.0"> <siteblocks_index_index> <!--   URL example.com/siteblocks/index/index --> <reference name="head"> <action method="setTitle"> <title>My Siteblocks</title> </action> </reference> <reference name="content"> <block name="siteblocks.list" as="siteblocks" type="siteblocks/list" template="siteblocks/list.phtml"/> </reference> </siteblocks_index_index> <catalog_category_default> <!--    handle            --> <reference name="left"> <block name="siteblocks.list" as="siteblocks" type="siteblocks/list" template="siteblocks/list.phtml"/> </reference> <reference name="right"> <block name="siteblocks.list" as="siteblocks" type="siteblocks/list" template="siteblocks/list.phtml"/> </reference> </catalog_category_default> <catalog_product_view> <!--        --> <reference name="product.info.extrahint"> <!--        catalog.xml          --> <block name="siteblocks.list" before="-" as="siteblocks" type="siteblocks/list" template="siteblocks/list.phtml"/> </reference> </catalog_product_view> </layout> 


In the layout, we can add js, css files to head, we can add or remove a block in on some page of interest to us. The theme of the layouts is quite extensive and from the top I have brought a minimally simple layout that will add our block in several places on the site.

Alternatively (without a layout) you can output HTML code in the controller:

 $html = Mage::app()->getLayout()->createBlock('siteblocks/list')->setTemplate('siteblocks/list.phtml')->toHtml() $this->getResponse()->setBody($html); 

And the HTML code of this block will be displayed. This is often necessary, for example when using AJAX requests.

In the layout, we have the siteblocks / list.phtml file mentioned. It can be omitted if the template indicates it by default.

 class IGN_Siteblocks_Block_List extends Mage_Core_Block_Template { protected $_template = 'siteblocks/list.phtml'; } 

Create a template:

app / design / base / default / template / siteblocks / list.phtml
 <?php foreach($this->getBlocks() as $block):?> <div class="siteblock"> <div class="block-title"><?php echo $block->getTitle()?></div> <div class="block-content"><?php echo $block->getContent()?></div> </div> <?php endforeach;?> 


As you can see in the code, we call the getBlocks block method, which returns a collection of records, which we output. Rename TestController or create a new one. IndexController

app / code / local / IGN / Siteblocks / controllers / IndexController.php
 <?php class IGN_Siteblocks_IndexController extends Mage_Core_Controller_Front_Action { public function indexAction() { $this->loadLayout(); #  $this->renderLayout(); # html } } 


The URL by which we will see the output looks like: example.com/siteblocks/index/index or example.com/siteblocks , index / index can be omitted.

And the handle in the layout will be used like this: siteblocks_index_index . To look at the output of records, you must add them directly to the database or go to the next step of the development of the editing form.


Admin interface. Grid. Editing form.


Video: Admin interface. Grid. Magento Editing Form


The structure of the IGN_Siteblocks-7.zip module created in the lesson

The process of creating an Admin interface consists of several steps:


Add items to the menu:

app / code / local / IGN / Siteblocks / etc / adminhtml.xml
 <?xml version="1.0"?> <config> <acl> <resources> <admin> <children> <system> <children> <config> <children> <siteblocks translate="title" module="siteblocks"> <title>Siteblocks</title> </siteblocks> </children> </config> </children> </system> <cms> <children> <siteblocks translate="title" module="siteblocks"> <title>Siteblocks</title> </siteblocks> </children> </cms> </children> </admin> </resources> </acl> <menu> <cms> <!--        --> <children> <siteblocks translate="title" module="siteblocks"> <title>Siteblocks</title> <action>adminhtml/siteblocks</action> <!--       , index      --> <sort_order>20</sort_order> </siteblocks> </children> </cms> </menu> </config> 


The correct section code (in the cms example) can be found in adminhtml.xml files of standard Magento modules. There and see how to create your section. Do not forget to duplicate the information in the acl block .

Let's create a controller and 1 action to begin with.

app / code / local / IGN / Siteblocks / controllers / Adminhtml / SiteblocksController.php
 <?php class IGN_Siteblocks_Adminhtml_SiteblocksController extends Mage_Adminhtml_Controller_Action { public function indexAction() { $this->loadLayout(); $this->_addContent($this->getLayout()->createBlock('siteblocks/adminhtml_siteblocks')); $this->renderLayout(); } } 


We could create a layout for the admin panel, but the necessary blocks can be added directly in the controller. Here we have added our page to the content. Index action will display the page with grid entries.

Now, you can proceed to the creation of blocks.

app / code / local / IGN / Siteblocks / Block / Adminhtml / Siteblocks.php
 <?php class IGN_Siteblocks_Block_Adminhtml_Siteblocks extends Mage_Adminhtml_Block_Widget_Grid_Container { public function __construct() { $this->_controller = 'adminhtml_siteblocks'; $this->_blockGroup = 'siteblocks'; $this->_headerText = Mage::helper('siteblocks')->__('Siteblocks'); $this->_addButtonLabel = Mage::helper('siteblocks')->__('Add New Block'); parent::__construct(); } } 


Why we have prescribed such property values, now we will see in the method of the class Mage_Adminhtml_Block_Widget_Grid_Container:



Thus, the block type of the grid block is formed.

app / code / local / IGN / Siteblocks / Block / Adminhtml / Siteblocks / Grid.php
 <?php class IGN_Siteblocks_Block_Adminhtml_Siteblocks_Grid extends Mage_Adminhtml_Block_Widget_Grid { public function __construct() { parent::__construct(); $this->setId('cmsBlockGrid'); $this->setDefaultSort('block_identifier'); $this->setDefaultDir('ASC'); } protected function _prepareCollection() { $collection = Mage::getModel('siteblocks/block')->getCollection(); /* @var $collection Mage_Cms_Model_Mysql4_Block_Collection */ $this->setCollection($collection); return parent::_prepareCollection(); } protected function _prepareColumns() { $this->addColumn('title', array( 'header' => Mage::helper('siteblocks')->__('Title'), 'align' => 'left', 'index' => 'title', )); $this->addColumn('block_status', array( 'header' => Mage::helper('cms')->__('Status'), 'align' => 'left', 'type' => 'options', 'options' => Mage::getModel('siteblocks/source_status')->toArray(), 'index' => 'block_status' )); $this->addColumn('created_at', array( 'header' => Mage::helper('siteblocks')->__('Created At'), 'index' => 'created_at', 'type' => 'date', )); return parent::_prepareColumns(); } protected function _prepareMassaction() { $this->setMassactionIdField('block_id'); $this->getMassactionBlock()->setIdFieldName('block_id'); $this->getMassactionBlock() ->addItem('delete', array( 'label' => Mage::helper('siteblocks')->__('Delete'), 'url' => $this->getUrl('*/*/massDelete'), 'confirm' => Mage::helper('siteblocks')->__('Are you sure?') ) ) ->addItem('status', array( 'label' => Mage::helper('siteblocks')->__('Update status'), 'url' => $this->getUrl('*/*/massStatus'), 'additional' => array('block_status'=> array( 'name' => 'block_status', 'type' => 'select', 'class' => 'required-entry', 'label' => Mage::helper('siteblocks')->__('Status'), 'values' => Mage::getModel('siteblocks/source_status')->toOptionArray() ) ) ) ); return $this; } /** * Row click url * * @return string */ public function getRowUrl($row) { return $this->getUrl('*/*/edit', array('block_id' => $row->getId())); } } 


Grid block in our case takes this form. In principle, by the name of the methods and properties, one can understand how the columns are added, the URL to the edit page is formed, and the collection of records is prepared for display in the table.

It is important to note that the default column types and the principles of their construction can be viewed in the app / code / core / Mage / Adminhtml / Block / Widget / Grid / Column / Renderer / folder .

The edit page will also consist of 2 blocks: a container block and a form block.

app / code / local / IGN / Siteblocks / Block / Adminhtml / Siteblocks / Edit.php
 <?php class IGN_Siteblocks_Block_Adminhtml_Siteblocks_Edit extends Mage_Adminhtml_Block_Widget_Form_Container { public function __construct() { $this->_objectId = 'block_id'; $this->_controller = 'adminhtml_siteblocks'; $this->_blockGroup = 'siteblocks'; parent::__construct(); $this->_updateButton('save', 'label', Mage::helper('siteblocks')->__('Save Block')); $this->_updateButton('delete', 'label', Mage::helper('siteblocks')->__('Delete Block')); $this->_addButton('saveandcontinue', array( 'label' => Mage::helper('adminhtml')->__('Save and Continue Edit'), 'onclick' => 'saveAndContinueEdit()', 'class' => 'save', ), -100); $this->_formScripts[] = " function saveAndContinueEdit(){ editForm.submit($('edit_form').action+'back/edit/'); } "; } /** * Get edit form container header text * * @return string */ public function getHeaderText() { if (Mage::registry('siteblocks_block')->getId()) { return Mage::helper('siteblocks')->__("Edit Block '%s'", $this->escapeHtml(Mage::registry('siteblocks_block')->getTitle())); } else { return Mage::helper('siteblocks')->__('New Block'); } } } 


And then I adjust the property values ​​to the method of the parent class, which would result in a block type siteblocks / adminhtml_siteblocks_edit_form Form

class:

app / code / local / IGN / Siteblocks / Block / Adminhtml / Siteblocks / Edit / Form.php
 <?php class IGN_Siteblocks_Block_Adminhtml_Siteblocks_Edit_Form extends Mage_Adminhtml_Block_Widget_Form { /** * Init form */ public function __construct() { parent::__construct(); $this->setId('block_form'); $this->setTitle(Mage::helper('siteblocks')->__('Block Information')); } protected function _prepareForm() { $model = Mage::registry('siteblocks_block'); $form = new Varien_Data_Form( array( 'id' => 'edit_form', 'action' => $this->getUrl('*/*/save',array('block_id'=>$this->getRequest()->getParam('block_id'))), 'method' => 'post' ) ); $form->setHtmlIdPrefix('block_'); $fieldset = $form->addFieldset('base_fieldset', array('legend'=>Mage::helper('siteblocks')->__('General Information'), 'class' => 'fieldset-wide')); if ($model->getBlockId()) { $fieldset->addField('block_id', 'hidden', array( 'name' => 'block_id', )); } $fieldset->addField('title', 'text', array( 'name' => 'title', 'label' => Mage::helper('siteblocks')->__('Block Title'), 'title' => Mage::helper('siteblocks')->__('Block Title'), 'required' => true, )); $fieldset->addField('block_status', 'select', array( 'label' => Mage::helper('siteblocks')->__('Status'), 'title' => Mage::helper('siteblocks')->__('Status'), 'name' => 'block_status', 'required' => true, 'options' => Mage::getModel('siteblocks/source_status')->toArray(), )); $fieldset->addField('content', 'textarea', array( 'name' => 'content', 'label' => Mage::helper('siteblocks')->__('Content'), 'title' => Mage::helper('siteblocks')->__('Content'), 'style' => 'height:36em', 'required' => true, )); $form->setValues($model->getData()); $form->setUseContainer(true); $this->setForm($form); return parent::_prepareForm(); } } 


Fields are added in a simple way and with a clear set of options, and the types of standard fields can be viewed in the lib / Varien / Data / Form / Element / folder. Now let's see why here we have an instance of the siteblock model $ model = Mage :: registry ('siteblocks_block'); and add the remaining actions to the controller. We need actions, editing, saving, deleting records. Also, we will have added actions for mass deletion and status change, when a user in the table can mark several lines and click the delete button for these marked entries.

The controller takes the following form:

app / code / local / IGN / Siteblocks / controllers / Adminhtml / SiteblocksController.php
 <?php class IGN_Siteblocks_Adminhtml_SiteblocksController extends Mage_Adminhtml_Controller_Action { public function indexAction() { $this->loadLayout(); $this->_addContent($this->getLayout()->createBlock('siteblocks/adminhtml_siteblocks')); $this->renderLayout(); } public function newAction() { $this->_forward('edit'); } public function editAction() { $id = $this->getRequest()->getParam('block_id'); Mage::register('siteblocks_block',Mage::getModel('siteblocks/block')->load($id)); $blockObject = (array)Mage::getSingleton('adminhtml/session')->getBlockObject(true); if(count($blockObject)) { Mage::registry('siteblocks_block')->setData($blockObject); } $this->loadLayout(); $this->_addContent($this->getLayout()->createBlock('siteblocks/adminhtml_siteblocks_edit')); $this->renderLayout(); } public function saveAction() { try { $id = $this->getRequest()->getParam('block_id'); $block = Mage::getModel('siteblocks/block')->load($id); /*$block ->setTitle($this->getRequest()->getParam('title')) ->setContent($this->getRequest()->getParam('content')) ->setBlockStatus($this->getRequest()->getParam('block_status')) ->save();*/ $block ->setData($this->getRequest()->getParams()) ->setCreatedAt(Mage::app()->getLocale()->date()) ->save(); if(!$block->getId()) { Mage::getSingleton('adminhtml/session')->addError('Cannot save the block'); } } catch(Exception $e) { Mage::logException($e); Mage::getSingleton('adminhtml/session')->addError($e->getMessage()); Mage::getSingleton('adminhtml/session')->setBlockObject($block->getData()); return $this->_redirect('*/*/edit',array('block_id'=>$this->getRequest()->getParam('block_id'))); } Mage::getSingleton('adminhtml/session')->addSuccess('Block was saved successfully!'); $this->_redirect('*/*/'.$this->getRequest()->getParam('back','index'),array('block_id'=>$block->getId())); } public function deleteAction() { $block = Mage::getModel('siteblocks/block') ->setId($this->getRequest()->getParam('block_id')) ->delete(); if($block->getId()) { Mage::getSingleton('adminhtml/session')->addSuccess('Block was deleted successfully!'); } $this->_redirect('*/*/'); } public function massStatusAction() { $statuses = $this->getRequest()->getParams(); try { $blocks= Mage::getModel('siteblocks/block') ->getCollection() ->addFieldToFilter('block_id',array('in'=>$statuses['massaction'])); foreach($blocks as $block) { $block->setBlockStatus($statuses['block_status'])->save(); } } catch(Exception $e) { Mage::logException($e); Mage::getSingleton('adminhtml/session')->addError($e->getMessage()); return $this->_redirect('*/*/'); } Mage::getSingleton('adminhtml/session')->addSuccess('Blocks were updated!'); return $this->_redirect('*/*/'); } public function massDeleteAction() { $blocks = $this->getRequest()->getParams(); try { $blocks= Mage::getModel('siteblocks/block') ->getCollection() ->addFieldToFilter('block_id',array('in'=>$blocks['massaction'])); foreach($blocks as $block) { $block->delete(); } } catch(Exception $e) { Mage::logException($e); Mage::getSingleton('adminhtml/session')->addError($e->getMessage()); return $this->_redirect('*/*/'); } Mage::getSingleton('adminhtml/session')->addSuccess('Blocks were deleted!'); return $this->_redirect('*/*/'); } } 


Now, in our module, you can edit the records and display them on the frontend part.


Events and listeners


Video: Events and listeners in Magento


The structure of the module IGN_Siteblocks-8.zip created in the lesson

In Magento, you can use the template "event-listener". That allows in our module to catch some specific moments of the site. Adds agility, flexibility and more automation.

And standard events in Magento implemented a lot. Search Magento for the text "Mage :: dispatchEvent". Or take a look at the link . This is from explicit events, there are still events occurring with each model, each block, or a controller action. As a rule, this is a pre and post event.

model_save_before, model_save_after, controller_action_predispatch, controller_action_postdispatch, core_block_abstract_to_html_before, core_block_abstract_to_html_after

Plus, events using event_prefix of your classes or your route name controllers (siteblocks_save_before, controller_action_predispatch_siteblocks ...) There

are a lot of variations and thanks to this system, you can easily “catch” the desired event.

You can directly create an event anywhere in the code:

 Mage::dispatchEvent('some_event_name',array('myparam' => $someVar)); 

Listeners are declared in config.xml. And there are 3 options: global , adminhtml , frontend . Accordingly, this is just a division where we want our listener to work. Our config takes the following form:

app / code / local / IGN / Siteblocks / etc / config.xml
 <?xml version="1.0" ?> <config> <modules> <IGN_Siteblocks> <version>1.0.0</version> </IGN_Siteblocks> </modules> <global> <blocks> <siteblocks> <class>IGN_Siteblocks_Block</class> </siteblocks> </blocks> <models> <siteblocks> <class>IGN_Siteblocks_Model</class> <resourceModel>siteblocks_resource</resourceModel> </siteblocks> <siteblocks_resource> <class>IGN_Siteblocks_Model_Resource</class> <entities> <block> <table>ign_siteblock</table> </block> </entities> </siteblocks_resource> </models> <resources> <siteblocks_setup> <setup> <module>IGN_Siteblocks</module> </setup> </siteblocks_setup> </resources> <helpers> <siteblocks> <class>IGN_Siteblocks_Helper</class> </siteblocks> </helpers> </global> <frontend> <events> <checkout_cart_product_add_after> <!--      --> <observers> <siteblocks> <class>siteblocks/observer</class> <method>checkout_cart_product_add_after</method> <!--         --> <type>model</type> </siteblocks> </observers> </checkout_cart_product_add_after> </events> <layout> <updates> <siteblocks module="siteblocks"> <file>siteblocks.xml</file> </siteblocks> </updates> </layout> <routers> <siteblocks> <use>standard</use> <args> <module>IGN_Siteblocks</module> <frontName>siteblocks</frontName> </args> </siteblocks> </routers> <translate> <modules> <IGN_Siteblocks> <files> <default>IGN_Siteblocks.csv</default> </files> </IGN_Siteblocks> </modules> </translate> </frontend> <admin> <routers> <adminhtml> <args> <modules> <siteblocks after="Mage_Adminhtml">IGN_Siteblocks_Adminhtml</siteblocks> </modules> </args> </adminhtml> </routers> </admin> <default> <siteblocks> <settings> <enabled>1</enabled> <block_count>10</block_count> </settings> </siteblocks> </default> </config> 


We have added 1 listener to the event of adding goods to the basket. Now you need to create a listener class. You can do without this class and add logic to any model. But it is bad form. Therefore, Observer.php

app / code / local / IGN / Siteblocks / Model / Observer.php
 <?php class IGN_Siteblocks_Model_Observer { /** * @param $bserver Varien_Event_Observer */ //   camelCase  .  :   =   //     , : checkoutCartProductAddAfter public function checkout_cart_product_add_after($observer) { var_dump($observer->getEvent()->getData('quote_item')->getData());die; } } 


In our method, we can produce all the necessary manipulations. Now we just print the contents of the item from the cart. (Comment out this code later, otherwise you will not be able to add products to the cart).


Crown and scheduled tasks


Video: Cron and Scheduled Tasks in Magento


The structure of the module IGN_Siteblocks-9.zip created in the lesson

Another controversy in automating the workflow of our module, and the work of the store is the ability to create tasks on a schedule.

First of all, it will be necessary to configure the launch of the Magento crown, and the already launched Magento file itself will distribute when what task to launch. Configuring Magento cron in the console:

 crontab -e * * * * * php /var/www/magento.dev/cron.php 

More information here: help.ubuntu.ru/wiki/cron . Or you can not configure, but run cron, when you need it, simply by clicking on a link like example.com/cron.php

We declare our tasks in config.xml in a separate crontab block. And the updated file view:

app / code / local / IGN / Siteblocks / etc / config.xml
 <?xml version="1.0" ?> <config> <modules> <IGN_Siteblocks> <version>1.0.0</version> </IGN_Siteblocks> </modules> <global> <blocks> <siteblocks> <class>IGN_Siteblocks_Block</class> </siteblocks> </blocks> <models> <siteblocks> <class>IGN_Siteblocks_Model</class> <resourceModel>siteblocks_resource</resourceModel> </siteblocks> <siteblocks_resource> <class>IGN_Siteblocks_Model_Resource</class> <entities> <block> <table>ign_siteblock</table> </block> </entities> </siteblocks_resource> </models> <resources> <siteblocks_setup> <setup> <module>IGN_Siteblocks</module> </setup> </siteblocks_setup> </resources> <helpers> <siteblocks> <class>IGN_Siteblocks_Helper</class> </siteblocks> </helpers> </global> <frontend> <events> <controller_action_predispatch> </controller_action_predispatch> <checkout_cart_product_add_after> <observers> <siteblocks> <class>siteblocks/observer</class> <method>checkout_cart_product_add_after</method> <type>model</type> </siteblocks> </observers> </checkout_cart_product_add_after> </events> <layout> <updates> <siteblocks module="siteblocks"> <file>siteblocks.xml</file> </siteblocks> </updates> </layout> <routers> <siteblocks> <use>standard</use> <args> <module>IGN_Siteblocks</module> <frontName>siteblocks</frontName> </args> </siteblocks> </routers> <translate> <modules> <IGN_Siteblocks> <files> <default>IGN_Siteblocks.csv</default> </files> </IGN_Siteblocks> </modules> </translate> </frontend> <admin> <routers> <adminhtml> <args> <modules> <siteblocks after="Mage_Adminhtml">IGN_Siteblocks_Adminhtml</siteblocks> </modules> </args> </adminhtml> </routers> </admin> <default> <siteblocks> <settings> <enabled>1</enabled> <block_count>10</block_count> </settings> </siteblocks> </default> <crontab> <jobs> <siteblocks_clear_cache> <!--   --> <schedule> <cron_expr>*/10 * * * *</cron_expr> <!--  10  --> </schedule> <run> <model>siteblocks/cron::siteblocks_clear_cache</model> <!--   ,     --> </run> </siteblocks_clear_cache> </jobs> </crontab> </config> 


For the tasks we will use a separate Cron.php file.

app / code / local / IGN / Siteblocks / Model / Cron.php
 <?php class IGN_Siteblocks_Model_Cron { public function siteblocks_clear_cache() { //do something here Mage::app()->cleanCache(array('siteblocks_blocks')); } } 




Using renderers in admin panel


Video: Using Renderers in Magento Admin Panel


The structure of the IGN_Siteblocks-10.zip module created in the lesson

Often, standard elements are often not enough to implement the intended functionality. Therefore, you can create a renderer for the desired item and this process does not take much time.

Consider creating a renderer for a form element. We have admin form:

app / code / local / IGN / Siteblocks / Block / Adminhtml / Siteblocks / Edit / Form.php
 <?php class IGN_Siteblocks_Block_Adminhtml_Siteblocks_Edit_Form extends Mage_Adminhtml_Block_Widget_Form { /** * Init form */ public function __construct() { parent::__construct(); $this->setId('block_form'); $this->setTitle(Mage::helper('siteblocks')->__('Block Information')); } protected function _prepareForm() { $model = Mage::registry('siteblocks_block'); $form = new Varien_Data_Form( array( 'id' => 'edit_form', 'action' => $this->getUrl('*/*/save',array('block_id'=>$this->getRequest()->getParam('block_id'))), 'method' => 'post', 'enctype' => 'multipart/form-data' ) ); $form->setHtmlIdPrefix('block_'); $fieldset = $form->addFieldset('base_fieldset', array( 'legend'=>Mage::helper('siteblocks')->__('General Information'), 'class' => 'fieldset-wide') ); if ($model->getBlockId()) { $fieldset->addField('block_id', 'hidden', array( 'name' => 'block_id', )); } $fieldset->addField('title', 'text', array( 'name' => 'title', 'label' => Mage::helper('siteblocks')->__('Block Title'), 'title' => Mage::helper('siteblocks')->__('Block Title'), 'required' => true, )); #1           #       .../Block/Adminhtml/Siteblocks/Edit/Renderer/Myimage.php $fieldset->addType('myimage','IGN_Siteblocks_Block_Adminhtml_Siteblocks_Edit_Renderer_Myimage'); #       -,     lib/Varien/Data/Form/Element/Myimage.php $fieldset->addField('image', 'myimage', array( 'name' => 'image', 'label' => Mage::helper('siteblocks')->__('Image'), 'title' => Mage::helper('siteblocks')->__('Image'), 'required' => true, )); $fieldset->addField('block_status', 'select', array( 'label' => Mage::helper('siteblocks')->__('Status'), 'title' => Mage::helper('siteblocks')->__('Status'), 'name' => 'block_status', 'required' => true, 'options' => Mage::getModel('siteblocks/source_status')->toArray(), )); $fieldset->addField('content', 'textarea', array( 'name' => 'content', 'label' => Mage::helper('siteblocks')->__('Content'), 'title' => Mage::helper('siteblocks')->__('Content'), 'style' => 'height:36em', 'required' => true, )); $form->setValues($model->getData()); $form->setUseContainer(true); $this->setForm($form); return parent::_prepareForm(); } } 


In our form, there are 2 options for creating renderers and you can use any option, but I’m more impressed with the option of creating a file in the lib / Varien / Data / Form / Element / folder. Sincein this case, we will be able to use the same renderer and in the fields for system.xml we can quietly indicate <frontend_type> myimage </ frontend_type> by our example.

File Contents:

app / code / local / IGN / Siteblocks / Block / Adminhtml / Siteblocks / Edit / Renderer / Myimage.php
 <?php class IGN_Siteblocks_Block_Adminhtml_Siteblocks_Edit_Renderer_Myimage extends Varien_Data_Form_Element_Abstract { /** * Constructor * * @param array $data */ public function __construct($data) { parent::__construct($data); $this->setType('file'); } /** * Return element html code * * @return string */ public function getElementHtml() { $html = ''; if ((string)$this->getValue()) { $url = $this->_getUrl(); if( !preg_match("/^http\:\/\/|https\:\/\//", $url) ) { $url = Mage::getBaseUrl('media') . 'siteblocks' .DS.$url; } $html = '<a href="' . $url . '"' . ' onclick="imagePreview(\'' . $this->getHtmlId() . '_image\'); return false;">' . '<img src="' . $url . '" id="' . $this->getHtmlId() . '_image" title="' . $this->getValue() . '"' . ' alt="' . $this->getValue() . '" height="100" width="100" class="small-image-preview v-middle" />' . '</a> '; /*$additional = Mage::app()->getLayout()->createBlock('Mage_Adminhtml_Block_Template'); $additional->setTemplate('siteblocks/image.phtml') ->setImageUrl($url); $html = $additional->toHtml();*/ #       ,   html    ,      } $this->setClass('input-file'); $html .= parent::getElementHtml(); return $html; } /** * Return html code of hidden element * * @return string */ protected function _getHiddenInput() { return '<input type="hidden" name="' . parent::getName() . '[value]" value="' . $this->getValue() . '" />'; } /** * Get image preview url * * @return string */ protected function _getUrl() { return $this->getValue(); } /** * Return name * * @return string */ public function getName() { return $this->getData('name'); } } 



lib / Varien / Data / Form / Element / Myimage.php
 <?php class Varien_Data_Form_Element_Myimage extends Varien_Data_Form_Element_Abstract { /** * Constructor * * @param array $data */ public function __construct($data) { parent::__construct($data); $this->setType('file'); } /** * Return element html code * * @return string */ public function getElementHtml() { $html = ''; if ((string)$this->getValue()) { $url = $this->_getUrl(); if( !preg_match("/^http\:\/\/|https\:\/\//", $url) ) { $url = Mage::getBaseUrl('media') . 'siteblocks' .DS.$url; } $html = '<a href="' . $url . '"' . ' onclick="imagePreview(\'' . $this->getHtmlId() . '_image\'); return false;">' . '<img src="' . $url . '" id="' . $this->getHtmlId() . '_image" title="' . $this->getValue() . '"' . ' alt="' . $this->getValue() . '" height="150" width="150" class="small-image-preview v-middle" />' . '</a> '; /*$additional = Mage::app()->getLayout()->createBlock('Mage_Adminhtml_Block_Template'); $additional->setTemplate('siteblocks/image.phtml') ->setImageUrl($url); $html = $additional->toHtml();*/ #       ,   html    ,      } $this->setClass('input-file'); $html .= parent::getElementHtml(); return $html; } /** * Return html code of hidden element * * @return string */ protected function _getHiddenInput() { return '<input type="hidden" name="' . parent::getName() . '[value]" value="' . $this->getValue() . '" />'; } /** * Get image preview url * * @return string */ protected function _getUrl() { return $this->getValue(); } /** * Return name * * @return string */ public function getName() { return $this->getData('name'); } } 


I copied the contents of these files from the standard lib / Varien / Data / Form / Element / Image.php
and corrected the code to fit my needs.

Now let's create a renderer for the Grid column.

At the same time, I completed some additions in the module functional. It was necessary to make the functionality of loading and saving images.

app / code / local / IGN / Siteblocks / Block / Adminhtml / Siteblocks / Grid.php
 <?php class IGN_Siteblocks_Block_Adminhtml_Siteblocks_Grid extends Mage_Adminhtml_Block_Widget_Grid { public function __construct() { parent::__construct(); $this->setId('cmsBlockGrid'); $this->setDefaultSort('block_identifier'); $this->setDefaultDir('ASC'); } protected function _prepareCollection() { $collection = Mage::getModel('siteblocks/block')->getCollection(); /* @var $collection Mage_Cms_Model_Mysql4_Block_Collection */ $this->setCollection($collection); return parent::_prepareCollection(); } protected function _prepareColumns() { $this->addColumn('title', array( 'header' => Mage::helper('siteblocks')->__('Title'), 'align' => 'left', 'index' => 'title', )); $this->addColumn('image', array( 'header' => Mage::helper('siteblocks')->__('Image'), 'align' => 'left', 'index' => 'image', 'filter' => false, <!--      --> 'sortable' => false,<!--      --> 'renderer' => 'IGN_Siteblocks_Block_Adminhtml_Siteblocks_Grid_Renderer_Image', // 'renderer' => 'siteblocks/adminhtml_siteblocks_grid_renderer_image' #  )); $this->addColumn('block_status', array( 'header' => Mage::helper('cms')->__('Status'), 'align' => 'left', 'type' => 'options', 'options' => Mage::getModel('siteblocks/source_status')->toArray(), 'index' => 'block_status' )); $this->addColumn('created_at', array( 'header' => Mage::helper('siteblocks')->__('Created At'), 'index' => 'created_at', 'type' => 'date', )); return parent::_prepareColumns(); } protected function _prepareMassaction() { $this->setMassactionIdField('block_id'); $this->getMassactionBlock()->setIdFieldName('block_id'); $this->getMassactionBlock() ->addItem('delete', array( 'label' => Mage::helper('siteblocks')->__('Delete'), 'url' => $this->getUrl('*/*/massDelete'), 'confirm' => Mage::helper('siteblocks')->__('Are you sure?') ) ) ->addItem('status', array( 'label' => Mage::helper('siteblocks')->__('Update status'), 'url' => $this->getUrl('*/*/massStatus'), 'additional' => array('block_status'=> array( 'name' => 'block_status', 'type' => 'select', 'class' => 'required-entry', 'label' => Mage::helper('siteblocks')->__('Status'), 'values' => Mage::getModel('siteblocks/source_status')->toOptionArray() ) ) ) ); return $this; } /** * Row click url * * @return string */ public function getRowUrl($row) { return $this->getUrl('*/*/edit', array('block_id' => $row->getId())); } } 


app / code / local / IGN / Siteblocks / Block / Adminhtml / Siteblocks / Grid / Renderer / Image.php
 <?php class IGN_Siteblocks_Block_Adminhtml_Siteblocks_Grid_Renderer_Image extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract { public function render(Varien_Object $row) #       { if( ! $row->getImage()) { return ''; } $url = Mage::getBaseUrl('media') . 'siteblocks' .DS .$row->getImage(); $html = "<img src='$url' width='100' height='auto'>"; return $html; } } 


In the renderer, we create a $ src URL and output the html code of the image. Now we can see in the table pictures.

In order to upload pictures in the module, you need to make some additions.

1. Update version in config.xml to 1.0.1
2. Create file upgrade-1.0.0-1.0.1.php

app / code / local / IGN / Siteblocks / sql / siteblocks_setup / upgrade-1.0.0-1.0.1.php
 <?php /** @var Mage_Core_Model_Resource_Setup $installer */ $installer = $this; $installer->startSetup(); $installer->run(" ALTER TABLE `{$this->getTable('siteblocks/block')}` ADD `image` TEXT NOT NULL; "); $installer->endSetup(); 


3. In the controller, add the appropriate code:

app / code / local / IGN / Siteblocks / controllers / Adminhtml / SiteblocksController.php
 <?php class IGN_Siteblocks_Adminhtml_SiteblocksController extends Mage_Adminhtml_Controller_Action { public function indexAction() { $this->loadLayout(); $this->_addContent($this->getLayout()->createBlock('siteblocks/adminhtml_siteblocks')); $this->renderLayout(); } public function newAction() { $this->_forward('edit'); } public function editAction() { $id = $this->getRequest()->getParam('block_id'); Mage::register('siteblocks_block',Mage::getModel('siteblocks/block')->load($id)); $blockObject = (array)Mage::getSingleton('adminhtml/session')->getBlockObject(true); if(count($blockObject)) { Mage::registry('siteblocks_block')->setData($blockObject); } $this->loadLayout(); $this->_addContent($this->getLayout()->createBlock('siteblocks/adminhtml_siteblocks_edit')); $this->renderLayout(); } //    protected function _uploadFile($fieldName,$model) { if( ! isset($_FILES[$fieldName])) { return false; } $file = $_FILES[$fieldName]; if(isset($file['name']) && (file_exists($file['tmp_name']))){ if($model->getId()){ unlink(Mage::getBaseDir('media').DS.$model->getData($fieldName)); } try { $path = Mage::getBaseDir('media') . DS . 'siteblocks' . DS; $uploader = new Varien_File_Uploader($file); $uploader->setAllowedExtensions(array('jpg','png','gif','jpeg')); $uploader->setAllowRenameFiles(true); $uploader->setFilesDispersion(false); $uploader->save($path, $file['name']); $model->setData($fieldName,$uploader->getUploadedFileName()); return true; } catch(Exception $e) { return false; } } } public function saveAction() { try { $id = $this->getRequest()->getParam('block_id'); $block = Mage::getModel('siteblocks/block')->load($id); /*$block ->setTitle($this->getRequest()->getParam('title')) ->setContent($this->getRequest()->getParam('content')) ->setBlockStatus($this->getRequest()->getParam('block_status')) ->save();*/ $block ->setData($this->getRequest()->getParams()); $this->_uploadFile('image',$block); //    $block ->setCreatedAt(Mage::app()->getLocale()->date()) ->save(); if(!$block->getId()) { Mage::getSingleton('adminhtml/session')->addError('Cannot save the block'); } } catch(Exception $e) { Mage::logException($e); Mage::getSingleton('adminhtml/session')->addError($e->getMessage()); Mage::getSingleton('adminhtml/session')->setBlockObject($block->getData()); return $this->_redirect('*/*/edit',array('block_id'=>$this->getRequest()->getParam('block_id'))); } Mage::getSingleton('adminhtml/session')->addSuccess('Block was saved successfully!'); $this->_redirect('*/*/'.$this->getRequest()->getParam('back','index'),array('block_id'=>$block->getId())); } public function deleteAction() { $block = Mage::getModel('siteblocks/block') ->setId($this->getRequest()->getParam('block_id')) ->delete(); if($block->getId()) { Mage::getSingleton('adminhtml/session')->addSuccess('Block was deleted successfully!'); } $this->_redirect('*/*/'); } public function massStatusAction() { $statuses = $this->getRequest()->getParams(); try { $blocks= Mage::getModel('siteblocks/block') ->getCollection() ->addFieldToFilter('block_id',array('in'=>$statuses['massaction'])); foreach($blocks as $block) { $block->setBlockStatus($statuses['block_status'])->save(); } } catch(Exception $e) { Mage::logException($e); Mage::getSingleton('adminhtml/session')->addError($e->getMessage()); return $this->_redirect('*/*/'); } Mage::getSingleton('adminhtml/session')->addSuccess('Blocks were updated!'); return $this->_redirect('*/*/'); } public function massDeleteAction() { $blocks = $this->getRequest()->getParams(); try { $blocks= Mage::getModel('siteblocks/block') ->getCollection() ->addFieldToFilter('block_id',array('in'=>$blocks['massaction'])); foreach($blocks as $block) { $block->delete(); } } catch(Exception $e) { Mage::logException($e); Mage::getSingleton('adminhtml/session')->addError($e->getMessage()); return $this->_redirect('*/*/'); } Mage::getSingleton('adminhtml/session')->addSuccess('Blocks were deleted!'); return $this->_redirect('*/*/'); } } 


4. Don't forget to create the media / siteblocks / folder and assign the appropriate write permissions.

Do not forget about the display of pictures on the frontend.


Edit the template:

app / design / frontend / base / default / template / siteblocks / list.phtml
 <?php foreach($this->getBlocks() as $block):?> <div class="siteblock"> <div class="block-title"><?php echo $block->getTitle()?></div> <div class="block-image"> <?php if($block->getImage()):?> <img src="<?php echo $block->getImageSrc()?>" height="150" width="auto" alt="<?php $block->getTitle()?>" title="<?php $block->getTitle()?>"> <?php endif;?> </div> <div class="block-content"><?php echo $block->getContent() ?></div> </div> <?php endforeach;?> 


I added a new getImageSrc method to the model and here is its listing:

app / code / local / IGN / Siteblocks / Model / Block.php
 <?php /** * Class IGN_Siteblocks_Model_Block * @method getBlockStatus() * @method getContent() * @method getImage() */ class IGN_Siteblocks_Model_Block extends Mage_Core_Model_Abstract { protected $_eventPrefix = 'siteblocks_block'; public function _construct() { parent::_construct(); $this->_init('siteblocks/block'); } public function getImageSrc() { return Mage::getBaseUrl('media') . 'siteblocks' . DS . $this->getImage(); } } 


Displaying full-size downloaded images is not a good idea, but the main task now was a description of the renderers.


Using the WYSIWYG editor


Video: Using WYSIWYG editor in Magento admin panel


The structure of the module IGN_Siteblocks-11.zip created in the lesson

WYSIWYG - what you see is what you get (what you see is what you get). This is a handy editor for creating content. And in our module there is an application. But its inclusion is not as simple as expected. We come to the fact that we need to create a layout for the admin.

app / design / adminhtml / default / default / layout / siteblocks.xml
 <?xml version="1.0"?> <layout version="1.0.0"> <adminhtml_siteblocks_edit> <!--       --> <update handle="editor"/> <!--     handle      js  css   ,      cms.xml --> </adminhtml_siteblocks_edit> <adminhtml_system_config_edit> <update handle="editor"/> </adminhtml_system_config_edit> </layout> 


Now you need to update the edit form.

app / code / local / IGN / Siteblocks / Block / Adminhtml / Siteblocks / Edit / Form.php
 <?php class IGN_Siteblocks_Block_Adminhtml_Siteblocks_Edit_Form extends Mage_Adminhtml_Block_Widget_Form { /** * Init form */ public function __construct() { parent::__construct(); $this->setId('block_form'); $this->setTitle(Mage::helper('siteblocks')->__('Block Information')); } protected function _prepareForm() { $model = Mage::registry('siteblocks_block'); $form = new Varien_Data_Form( array( 'id' => 'edit_form', 'action' => $this->getUrl('*/*/save',array('block_id'=>$this->getRequest()->getParam('block_id'))), 'method' => 'post', 'enctype' => 'multipart/form-data' ) ); $form->setHtmlIdPrefix('block_'); $fieldset = $form->addFieldset('base_fieldset', array( 'legend'=>Mage::helper('siteblocks')->__('General Information'), 'class' => 'fieldset-wide') ); if ($model->getBlockId()) { $fieldset->addField('block_id', 'hidden', array( 'name' => 'block_id', )); } $fieldset->addField('title', 'text', array( 'name' => 'title', 'label' => Mage::helper('siteblocks')->__('Block Title'), 'title' => Mage::helper('siteblocks')->__('Block Title'), 'required' => true, )); //$fieldset->addType('myimage','IGN_Siteblocks_Block_Adminhtml_Siteblocks_Edit_Renderer_Myimage'); $fieldset->addField('image', 'myimage', array( 'name' => 'image', 'label' => Mage::helper('siteblocks')->__('Image'), 'title' => Mage::helper('siteblocks')->__('Image'), 'required' => true, )); $fieldset->addField('block_status', 'select', array( 'label' => Mage::helper('siteblocks')->__('Status'), 'title' => Mage::helper('siteblocks')->__('Status'), 'name' => 'block_status', 'required' => true, 'options' => Mage::getModel('siteblocks/source_status')->toArray(), )); #   $fieldset->addField('content', 'editor', array( 'name' => 'content', 'label' => Mage::helper('siteblocks')->__('Content'), 'title' => Mage::helper('siteblocks')->__('Content'), 'style' => 'height:36em', 'required' => true, 'config' => Mage::getSingleton('cms/wysiwyg_config')->getConfig() )); $form->setValues($model->getData()); $form->setUseContainer(true); $this->setForm($form); return parent::_prepareForm(); } //  ,       head,      protected function _prepareLayout() { parent::_prepareLayout(); if (Mage::getSingleton('cms/wysiwyg_config')->isEnabled()) { $this->getLayout()->getBlock('head')->setCanLoadTinyMce(true); } } } 


After these actions, instead of a boring textarea, we get a convenient editor field. And if we want to do the same for the field on the configuration page, then we need to create a new renderer, which, basically, will be a copy-paste of the standard Editor element.

lib / Varien / Data / Form / Element / Myeditor.php
 <?php class Varien_Data_Form_Element_Myeditor extends Varien_Data_Form_Element_Editor { public function __construct($attributes=array()) { parent::__construct($attributes); #      if (Mage::getSingleton('cms/wysiwyg_config')->isEnabled()) { Mage::app()->getLayout()->getBlock('head')->setCanLoadTinyMce(true); $this->setData('config',Mage::getSingleton('cms/wysiwyg_config')->getConfig()); } if($this->isEnabled()) { $this->setType('wysiwyg'); $this->setExtType('wysiwyg'); } else { $this->setType('textarea'); $this->setExtType('textarea'); } } } 


And system.xml now looks like this:

app / code / local / IGN / Siteblocks / etc / system.xml
 <?xml version="1.0"?> <config> <tabs> <ign translate="label" module="siteblocks"> <label>IGN</label> <sort_order>2</sort_order> </ign> </tabs> <sections> <siteblocks module="siteblocks" translate="label"> <label>Siteblocks</label> <tab>ign</tab> <frontend>text</frontend> <sort_order>1</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> <show_in_store>1</show_in_store> <groups> <settings module="siteblocks" translate="label"> <label>Settings</label> <expanded>1</expanded> <sort_order>1</sort_order> <show_in_default>1</show_in_default> <show_in_Website>1</show_in_Website> <show_in_store>1</show_in_store> <fields> <enabled translate="label comment" module="siteblocks"> <label>Enabled</label> <frontend_type>select</frontend_type> <source_model>siteblocks/source_status</source_model> <sort_order>1</sort_order> <show_in_default>1</show_in_default> <show_in_Website>1</show_in_Website> <show_in_store>1</show_in_store> <comment>Is module enabled</comment> </enabled> <blocks_count> <label>Blocks on page</label> <frontend_type>text</frontend_type> <sort_order>2</sort_order> <show_in_default>1</show_in_default> <show_in_Website>1</show_in_Website> <show_in_store>1</show_in_store> <depends><enabled>1</enabled></depends> </blocks_count> <raw_text> <label>Raw text</label> <frontend_type>myeditor</frontend_type> <!--    frontend_type --> <sort_order>3</sort_order> <show_in_default>1</show_in_default> <show_in_Website>1</show_in_Website> <show_in_store>1</show_in_store> <depends><enabled>1</enabled></depends> </raw_text> <myimage> <label>Image</label> <frontend_type>myimage</frontend_type> <sort_order>3</sort_order> <show_in_default>1</show_in_default> <show_in_Website>1</show_in_Website> <show_in_store>1</show_in_store> <depends><enabled>1</enabled></depends> </myimage> </fields> </settings> </groups> </siteblocks> </sections> </config> 


For our module, this is not the right field and is made for example. But in this way we will display the content on the frontend.

app / design / frontend / base / default / template / siteblocks / list.phtml
 <?php foreach($this->getBlocks() as $block):?> <div class="siteblock"> <div class="block-title"><?php echo $block->getTitle()?></div> <div class="block-image"> <?php if($block->getImage()):?> <img src="<?php echo $block->getImageSrc()?>" height="150" width="auto" alt="<?php $block->getTitle()?>" title="<?php $block->getTitle()?>"> <?php endif;?> </div> <div class="block-content"><?php echo $this->getBlockContent($block)?></div> </div> <?php endforeach;?> 


A new method getBlockContent was created in the block for this.

app / local / IGN / Siteblocks / Block / List.php
 <?php class IGN_Siteblocks_Block_List extends Mage_Core_Block_Template { public function getBlocks() { //return Mage::getResourceModel('siteblocks/block_collection'); $items = Mage::getModel('siteblocks/block')->getCollection() ->addFieldToFilter('block_status',array('eq'=>IGN_Siteblocks_Model_Source_Status::ENABLED)); return $items; } public function getBlockContent($block) { $processor = Mage::helper('cms')->getBlockTemplateProcessor(); $html = $processor->filter($block->getContent()); return $html; } } 




Using Rule Conditions


Video: Using Rule Conditions in Magento


The structure of the module IGN_Siteblocks-12.zip created in the lesson .

Next step, we will add conditions to our module. Same used in Magento Promotional Rules. And here 2 types of conditions are prepared. In the first, attributes of the goods are used, in the second the basket. The following recipe describes the first case, but their differences consist only in the substitution of several lines.

Why do we need conditions? We will use conditions to choose where the block will be displayed. For example, on the pages of products whose price is below $ 100 or all phones from a certain category, which have 16GB of memory and production date 2015. We’re not going to talk about use cases here.

Order of creation:

1. Update the module version and add the upgrade script so that a new column is added to the tableconditions_serialized type TEXT .

app / code / local / IGN / Siteblocks / sql / siteblocks_setup / upgrade-1.0.1-1.0.2.php
 <?php /** @var Mage_Core_Model_Resource_Setup $installer */ $installer = $this; $installer->startSetup(); $installer->run(" ALTER TABLE `{$this->getTable('siteblocks/block')}` ADD `conditions_serialized` TEXT NOT NULL; "); $installer->endSetup(); 


2. The model should inherit from Mage_Rule_Model_Abstract . And must declare 2 methods: getConditionsInstance and getActionInstance

app / code / local / IGN / Siteblocks / Model / Observer.php
 <?php /** * Class IGN_Siteblocks_Model_Block * @method getBlockStatus() * @method getContent() * @method getImage() */ class IGN_Siteblocks_Model_Block extends Mage_Rule_Model_Abstract { protected $_eventPrefix = 'siteblocks_block'; # ,      ,     public function getActionsInstance() { return Mage::getModel('catalogrule/rule_action_collection'); } public function getConditionsInstance() { return Mage::getModel('catalogrule/rule_condition_combine'); } public function _construct() { parent::_construct(); $this->_init('siteblocks/block'); } public function getImageSrc() { return Mage::getBaseUrl('media') . 'siteblocks' . DS . $this->getImage(); } } 


All focus on the getConditionsInstance method . Now we use conditions as in Catalog Price Rules, i.e. only the properties and attributes of the product. If we want conditions like in Shopping Cart Price Rules, then we need to use Mage :: getModel ('salesrule / rule_condition_combine');

And if you want to decide when to display the block based on the data in the basket, then we take the salesrule. And also, you can create your own model and implement any conditions in it.

3. You need to update the saveAction in our controller.

app / code / local / IGN / Siteblocks / controllers / Adminhtml / SiteblocksController.php
 <?php class IGN_Siteblocks_Adminhtml_SiteblocksController extends Mage_Adminhtml_Controller_Action { public function indexAction() { $this->loadLayout(); $this->_addContent($this->getLayout()->createBlock('siteblocks/adminhtml_siteblocks')); $this->renderLayout(); } public function newAction() { $this->_forward('edit'); } public function editAction() { $id = $this->getRequest()->getParam('block_id'); Mage::register('siteblocks_block',Mage::getModel('siteblocks/block')->load($id)); $blockObject = (array)Mage::getSingleton('adminhtml/session')->getBlockObject(true); if(count($blockObject)) { Mage::registry('siteblocks_block')->setData($blockObject); } $this->loadLayout(); $this->_addContent($this->getLayout()->createBlock('siteblocks/adminhtml_siteblocks_edit')); $this->renderLayout(); } protected function _uploadFile($fieldName,$model) { if( ! isset($_FILES[$fieldName])) { return false; } $file = $_FILES[$fieldName]; if(isset($file['name']) && (file_exists($file['tmp_name']))){ if($model->getId()){ unlink(Mage::getBaseDir('media').DS.$model->getData($fieldName)); } try { $path = Mage::getBaseDir('media') . DS . 'siteblocks' . DS; $uploader = new Varien_File_Uploader($file); $uploader->setAllowedExtensions(array('jpg','png','gif','jpeg')); $uploader->setAllowRenameFiles(true); $uploader->setFilesDispersion(false); $uploader->save($path, $file['name']); $model->setData($fieldName,$uploader->getUploadedFileName()); return true; } catch(Exception $e) { return false; } } } public function saveAction() { try { $id = $this->getRequest()->getParam('block_id'); /** @var IGN_Siteblocks_Model_Block $block */ $block = Mage::getModel('siteblocks/block')->load($id); /*$block ->setTitle($this->getRequest()->getParam('title')) ->setContent($this->getRequest()->getParam('content')) ->setBlockStatus($this->getRequest()->getParam('block_status')) ->save();*/ #      $data = $this->getRequest()->getParams(); if (isset($data['rule']['conditions'])) { $data['conditions'] = $data['rule']['conditions']; } unset($data['rule']); # setData  loadPost $block ->loadPost($data); $this->_uploadFile('image',$block); $block ->setCreatedAt(Mage::app()->getLocale()->date()) ->save(); if(!$block->getId()) { Mage::getSingleton('adminhtml/session')->addError('Cannot save the block'); } } catch(Exception $e) { Mage::logException($e); Mage::getSingleton('adminhtml/session')->addError($e->getMessage()); Mage::getSingleton('adminhtml/session')->setBlockObject($block->getData()); return $this->_redirect('*/*/edit',array('block_id'=>$this->getRequest()->getParam('block_id'))); } Mage::getSingleton('adminhtml/session')->addSuccess('Block was saved successfully!'); $this->_redirect('*/*/'.$this->getRequest()->getParam('back','index'),array('block_id'=>$block->getId())); } public function deleteAction() { $block = Mage::getModel('siteblocks/block') ->setId($this->getRequest()->getParam('block_id')) ->delete(); if($block->getId()) { Mage::getSingleton('adminhtml/session')->addSuccess('Block was deleted successfully!'); } $this->_redirect('*/*/'); } public function massStatusAction() { $statuses = $this->getRequest()->getParams(); try { $blocks= Mage::getModel('siteblocks/block') ->getCollection() ->addFieldToFilter('block_id',array('in'=>$statuses['massaction'])); foreach($blocks as $block) { $block->setBlockStatus($statuses['block_status'])->save(); } } catch(Exception $e) { Mage::logException($e); Mage::getSingleton('adminhtml/session')->addError($e->getMessage()); return $this->_redirect('*/*/'); } Mage::getSingleton('adminhtml/session')->addSuccess('Blocks were updated!'); return $this->_redirect('*/*/'); } public function massDeleteAction() { $blocks = $this->getRequest()->getParams(); try { $blocks= Mage::getModel('siteblocks/block') ->getCollection() ->addFieldToFilter('block_id',array('in'=>$blocks['massaction'])); foreach($blocks as $block) { $block->delete(); } } catch(Exception $e) { Mage::logException($e); Mage::getSingleton('adminhtml/session')->addError($e->getMessage()); return $this->_redirect('*/*/'); } Mage::getSingleton('adminhtml/session')->addSuccess('Blocks were deleted!'); return $this->_redirect('*/*/'); } } 


4. Update admin layout:

app / code / design / adminhtml / default / default / layout / siteblocks.xml
 <?xml version="1.0"?> <layout version="1.0.0"> <adminhtml_siteblocks_edit> <update handle="editor"/> <!--     js  --> <reference name="head"> <action method="setCanLoadExtJs"><flag>1</flag></action> <action method="setCanLoadRulesJs"><flag>1</flag></action> </reference> </adminhtml_siteblocks_edit> <adminhtml_system_config_edit> <update handle="editor"/> </adminhtml_system_config_edit> </layout> 


5. Edit the admin file of the form, where we will add the condition designer.

app / code / local / IGN / Siteblocks / Block / Adminhtml / Siteblocks / Edit / Form.php
 <?php class IGN_Siteblocks_Block_Adminhtml_Siteblocks_Edit_Form extends Mage_Adminhtml_Block_Widget_Form { /** * Init form */ public function __construct() { parent::__construct(); $this->setId('block_form'); $this->setTitle(Mage::helper('siteblocks')->__('Block Information')); } protected function _prepareForm() { $model = Mage::registry('siteblocks_block'); $form = new Varien_Data_Form( array( 'id' => 'edit_form', 'action' => $this->getUrl('*/*/save',array('block_id'=>$this->getRequest()->getParam('block_id'))), 'method' => 'post', 'enctype' => 'multipart/form-data' ) ); $form->setHtmlIdPrefix('block_'); $fieldset = $form->addFieldset('base_fieldset', array( 'legend'=>Mage::helper('siteblocks')->__('General Information'), 'class' => 'fieldset-wide') ); if ($model->getBlockId()) { $fieldset->addField('block_id', 'hidden', array( 'name' => 'block_id', )); } $fieldset->addField('title', 'text', array( 'name' => 'title', 'label' => Mage::helper('siteblocks')->__('Block Title'), 'title' => Mage::helper('siteblocks')->__('Block Title'), 'required' => true, )); //$fieldset->addType('myimage','IGN_Siteblocks_Block_Adminhtml_Siteblocks_Edit_Renderer_Myimage'); $fieldset->addField('image', 'myimage', array( 'name' => 'image', 'label' => Mage::helper('siteblocks')->__('Image'), 'title' => Mage::helper('siteblocks')->__('Image'), 'required' => true, )); $fieldset->addField('block_status', 'select', array( 'label' => Mage::helper('siteblocks')->__('Status'), 'title' => Mage::helper('siteblocks')->__('Status'), 'name' => 'block_status', 'required' => true, 'options' => Mage::getModel('siteblocks/source_status')->toArray(), )); $fieldset->addField('content', 'editor', array( 'name' => 'content', 'label' => Mage::helper('siteblocks')->__('Content'), 'title' => Mage::helper('siteblocks')->__('Content'), 'style' => 'height:36em', 'required' => true, 'config' => Mage::getSingleton('cms/wysiwyg_config')->getConfig() )); #    $model->getConditions()->setJsFormObject('block_conditions_fieldset'); $renderer = Mage::getBlockSingleton('adminhtml/widget_form_renderer_fieldset') ->setTemplate('promo/fieldset.phtml') ->setNewChildUrl($this->getUrl('*/promo_catalog/newConditionHtml/form/block_conditions_fieldset')); $conditionsFieldset = $form->addFieldset('conditions_fieldset', array( 'legend'=>Mage::helper('siteblocks')->__('Conditions'), 'class' => 'fieldset-wide') )->setRenderer($renderer); $conditionsFieldset->addField('conditions', 'text', array( 'name' => 'conditions', 'label' => Mage::helper('siteblocks')->__('Conditions'), 'title' => Mage::helper('siteblocks')->__('Conditions'), 'required' => true, ))->setRule($model)->setRenderer(Mage::getBlockSingleton('rule/conditions')); $form->setValues($model->getData()); $form->setUseContainer(true); $this->setForm($form); return parent::_prepareForm(); } protected function _prepareLayout() { parent::_prepareLayout(); if (Mage::getSingleton('cms/wysiwyg_config')->isEnabled()) { #    ,    2   $this->getLayout()->getBlock('head')->setCanLoadExtJs(true); $this->getLayout()->getBlock('head')->setCanLoadRulesJs(true); $this->getLayout()->getBlock('head')->setCanLoadTinyMce(true); } } } 


Pay attention to the line:

 $this->getUrl('*/promo_catalog/newConditionHtml/form/block_conditions_fieldset') 

if we use Shopping Cart Price Rules, we write:

 $this->getUrl('*/promo_quote/newConditionHtml/form/block_conditions_fieldset') 

Look at one more important point:

block_conditions_fieldset - where block_ should match $ form-> setHtmlIdPrefix ('block_');

And this is all that concerns the admin part. Now add validation conditions on the frontend. To do this, edit the List.php block.

app / code / local / IGN / Siteblocks / Block / List.php
 <?php class IGN_Siteblocks_Block_List extends Mage_Core_Block_Template { public function getBlocks() { //return Mage::getResourceModel('siteblocks/block_collection'); $items = Mage::getModel('siteblocks/block')->getCollection() ->addFieldToFilter('block_status',array('eq'=>IGN_Siteblocks_Model_Source_Status::ENABLED)); $filteredItems = $items; #      . if(Mage::registry('current_product') instanceof Mage_Catalog_Model_Product) { $filteredItems = array(); /** @var IGN_Siteblocks_Model_Block $item */ foreach ($items as $item) { #  validate    ,     if($item->validate(Mage::registry('current_product'))) { $filteredItems[] = $item; } } } return $filteredItems; } public function getBlockContent($block) { $processor = Mage::helper('cms')->getBlockTemplateProcessor(); $html = $processor->filter($block->getContent()); return $html; } } 


Using the validator is extremely simple. In our case, we ignore the conditions if the block output is not on the page of the product and there is nothing to validate in this case.


Using tabs on the edit page


Video: Using tabs on the edit page in Magento


The structure of the module IGN_Siteblocks-13.zip created in the lesson .

Tabs are convenient and useful to use when you have a lot of fields. You divide the fields into groups and each group creates its own tab. There are several options for adding tabs. First we need to create a tab class and add its output on the edit page. The class itself looks like this:

app / code / local / IGN / Siteblocks / Block / Adminhtml / Siteblocks / Edit / Tabs.php
 <?php class IGN_Siteblocks_Block_Adminhtml_Siteblocks_Edit_Tabs extends Mage_Adminhtml_Block_Widget_Tabs { public function __construct() { parent::__construct(); $this->setId('block_tabs'); $this->setDestElementId('edit_form'); $this->setTitle(Mage::helper('siteblocks')->__('Block Information')); } #      .       protected function _prepareLayout() { $this->addTab('main_tab',array( 'label' => $this->__('Main'), 'title' => $this->__('Main'), 'content' => $this->getLayout()->createBlock('siteblocks/adminhtml_siteblocks_edit_tab_main')->toHtml() )); /*$this->addTab('conditions_tab',array( 'label' => $this->__('Conditions'), 'title' => $this->__('Conditions'), 'content' => $this->getLayout()->createBlock('siteblocks/adminhtml_siteblocks_edit_tab_conditions')->toHtml() ));*/ $this->addTab('conditions_tab','siteblocks/adminhtml_siteblocks_edit_tab_conditions'); return parent::_prepareLayout(); } } 


Take a look at the implementation of the addTab method and see that you can input an array, an object, a string. And there are some differences. Here I recommend, look in the video, where I clearly demonstrate. But here I will put in a good word.

If we pass a string to the method, then the tab class must implement the Mage_Adminhtml_Block_Widget_Tab_Interface interface .

Otherwise, you will get an error. And the interface requires the implementation of 4 methods. Therefore, in the example we use 2 options for the demonstration. In practice, it is better to use the same methods for adding tabs.

Let's look at the contents of our tabs, which we copied from the Form.php source file.

app / code / local / IGN / Siteblocks / Block / Adminhtml / Siteblocks / Edit / Tab / Main.php
 <?php class IGN_Siteblocks_Block_Adminhtml_Siteblocks_Edit_Tab_Main extends Mage_Adminhtml_Block_Widget_Form { /** * Init form */ public function __construct() { parent::__construct(); $this->setId('main_form'); $this->setTitle(Mage::helper('siteblocks')->__('Block Information')); } protected function _prepareForm() { $model = Mage::registry('siteblocks_block'); $form = new Varien_Data_Form(); $form->setHtmlIdPrefix('main_'); $fieldset = $form->addFieldset('base_fieldset', array( 'legend'=>Mage::helper('siteblocks')->__('General Information'), 'class' => 'fieldset-wide') ); if ($model->getBlockId()) { $fieldset->addField('block_id', 'hidden', array( 'name' => 'block_id', )); } $fieldset->addField('title', 'text', array( 'name' => 'title', 'label' => Mage::helper('siteblocks')->__('Block Title'), 'title' => Mage::helper('siteblocks')->__('Block Title'), 'required' => true, )); //$fieldset->addType('myimage','IGN_Siteblocks_Block_Adminhtml_Siteblocks_Edit_Renderer_Myimage'); $fieldset->addField('image', 'myimage', array( 'name' => 'image', 'label' => Mage::helper('siteblocks')->__('Image'), 'title' => Mage::helper('siteblocks')->__('Image'), 'required' => true, )); $fieldset->addField('block_status', 'select', array( 'label' => Mage::helper('siteblocks')->__('Status'), 'title' => Mage::helper('siteblocks')->__('Status'), 'name' => 'block_status', 'required' => true, 'options' => Mage::getModel('siteblocks/source_status')->toArray(), )); $fieldset->addField('content', 'editor', array( 'name' => 'content', 'label' => Mage::helper('siteblocks')->__('Content'), 'title' => Mage::helper('siteblocks')->__('Content'), 'style' => 'height:36em', 'required' => true, 'config' => Mage::getSingleton('cms/wysiwyg_config')->getConfig() )); $form->setValues($model->getData()); $this->setForm($form); return parent::_prepareForm(); } protected function _prepareLayout() { parent::_prepareLayout(); if (Mage::getSingleton('cms/wysiwyg_config')->isEnabled()) { $this->getLayout()->getBlock('head')->setCanLoadTinyMce(true); } } } 


app / code / local / IGN / Siteblocks / Block / Adminhtml / Siteblocks / Edit / Tab / Conditions.php
 <?php class IGN_Siteblocks_Block_Adminhtml_Siteblocks_Edit_Tab_Conditions extends Mage_Adminhtml_Block_Widget_Form implements Mage_Adminhtml_Block_Widget_Tab_Interface { #,    public function getTabTitle() { return $this->__('Conditions'); } public function getTabLabel() { return $this->__('Conditions'); } public function canShowTab() { return true; } public function isHidden() { return false; } /** * Init form */ public function __construct() { parent::__construct(); $this->setId('conditions_form'); $this->setTitle(Mage::helper('siteblocks')->__('Block Conditions')); } protected function _prepareForm() { $model = Mage::registry('siteblocks_block'); $form = new Varien_Data_Form(); $form->setHtmlIdPrefix('block_'); $model->getConditions()->setJsFormObject('block_conditions_fieldset'); $renderer = Mage::getBlockSingleton('adminhtml/widget_form_renderer_fieldset') ->setTemplate('promo/fieldset.phtml') ->setNewChildUrl($this->getUrl('*/promo_catalog/newConditionHtml/form/block_conditions_fieldset')); $conditionsFieldset = $form->addFieldset('conditions_fieldset', array( 'legend'=>Mage::helper('siteblocks')->__('Conditions'), 'class' => 'fieldset-wide') )->setRenderer($renderer); $conditionsFieldset->addField('conditions', 'text', array( 'name' => 'conditions', 'label' => Mage::helper('siteblocks')->__('Conditions'), 'title' => Mage::helper('siteblocks')->__('Conditions'), 'required' => true, ))->setRule($model)->setRenderer(Mage::getBlockSingleton('rule/conditions')); $form->setValues($model->getData()); $this->setForm($form); return parent::_prepareForm(); } protected function _prepareLayout() { parent::_prepareLayout(); if (Mage::getSingleton('cms/wysiwyg_config')->isEnabled()) { $this->getLayout()->getBlock('head')->setCanLoadTinyMce(true); } } } 


We just copied the original Form.php file. Separated form elements. And do not forget to remove the $ form-> setUseContainer (true) flag ; . Accordingly, fields from the form source file can be deleted.

app / code / local / IGN / Siteblocks / Block / Adminhtml / Siteblocks / Edit / Form.php
 <?php class IGN_Siteblocks_Block_Adminhtml_Siteblocks_Edit_Form extends Mage_Adminhtml_Block_Widget_Form { /** * Init form */ public function __construct() { parent::__construct(); $this->setId('block_form'); $this->setTitle(Mage::helper('siteblocks')->__('Block Information')); } protected function _prepareForm() { $model = Mage::registry('siteblocks_block'); $form = new Varien_Data_Form( array( 'id' => 'edit_form', 'action' => $this->getUrl('*/*/save',array('block_id'=>$this->getRequest()->getParam('block_id'))), 'method' => 'post', 'enctype' => 'multipart/form-data' ) ); $form->setHtmlIdPrefix('block_'); $form->setValues($model->getData()); $form->setUseContainer(true); $this->setForm($form); return parent::_prepareForm(); } protected function _prepareLayout() { parent::_prepareLayout(); if (Mage::getSingleton('cms/wysiwyg_config')->isEnabled()) { $this->getLayout()->getBlock('head')->setCanLoadTinyMce(true); } } } 


How to make a tab block.

Method number 1 in the controller:

app / code / local / IGN / Siteblocks / controllers / Adminhtml / SiteblocksController.php
 <?php class IGN_Siteblocks_Adminhtml_SiteblocksController extends Mage_Adminhtml_Controller_Action { public function indexAction() { $this->loadLayout(); $this->_addContent($this->getLayout()->createBlock('siteblocks/adminhtml_siteblocks')); $this->renderLayout(); } public function newAction() { $this->_forward('edit'); } public function editAction() { $id = $this->getRequest()->getParam('block_id'); Mage::register('siteblocks_block',Mage::getModel('siteblocks/block')->load($id)); $blockObject = (array)Mage::getSingleton('adminhtml/session')->getBlockObject(true); if(count($blockObject)) { Mage::registry('siteblocks_block')->setData($blockObject); } $this->loadLayout(); #     $this->_addLeft($this->getLayout()->createBlock('siteblocks/adminhtml_siteblocks_edit_tabs')); $this->_addContent($this->getLayout()->createBlock('siteblocks/adminhtml_siteblocks_edit')); $this->renderLayout(); } protected function _uploadFile($fieldName,$model) { if( ! isset($_FILES[$fieldName])) { return false; } $file = $_FILES[$fieldName]; if(isset($file['name']) && (file_exists($file['tmp_name']))){ if($model->getId()){ unlink(Mage::getBaseDir('media').DS.$model->getData($fieldName)); } try { $path = Mage::getBaseDir('media') . DS . 'siteblocks' . DS; $uploader = new Varien_File_Uploader($file); $uploader->setAllowedExtensions(array('jpg','png','gif','jpeg')); $uploader->setAllowRenameFiles(true); $uploader->setFilesDispersion(false); $uploader->save($path, $file['name']); $model->setData($fieldName,$uploader->getUploadedFileName()); return true; } catch(Exception $e) { return false; } } } public function saveAction() { try { $id = $this->getRequest()->getParam('block_id'); /** @var IGN_Siteblocks_Model_Block $block */ $block = Mage::getModel('siteblocks/block')->load($id); /*$block ->setTitle($this->getRequest()->getParam('title')) ->setContent($this->getRequest()->getParam('content')) ->setBlockStatus($this->getRequest()->getParam('block_status')) ->save();*/ $data = $this->getRequest()->getParams(); if (isset($data['rule']['conditions'])) { $data['conditions'] = $data['rule']['conditions']; } unset($data['rule']); $block ->loadPost($data); $this->_uploadFile('image',$block); $block ->setCreatedAt(Mage::app()->getLocale()->date()) ->save(); if(!$block->getId()) { Mage::getSingleton('adminhtml/session')->addError('Cannot save the block'); } } catch(Exception $e) { Mage::logException($e); Mage::getSingleton('adminhtml/session')->addError($e->getMessage()); Mage::getSingleton('adminhtml/session')->setBlockObject($block->getData()); return $this->_redirect('*/*/edit',array('block_id'=>$this->getRequest()->getParam('block_id'))); } Mage::getSingleton('adminhtml/session')->addSuccess('Block was saved successfully!'); $this->_redirect('*/*/'.$this->getRequest()->getParam('back','index'),array('block_id'=>$block->getId())); } public function deleteAction() { $block = Mage::getModel('siteblocks/block') ->setId($this->getRequest()->getParam('block_id')) ->delete(); if($block->getId()) { Mage::getSingleton('adminhtml/session')->addSuccess('Block was deleted successfully!'); } $this->_redirect('*/*/'); } public function massStatusAction() { $statuses = $this->getRequest()->getParams(); try { $blocks= Mage::getModel('siteblocks/block') ->getCollection() ->addFieldToFilter('block_id',array('in'=>$statuses['massaction'])); foreach($blocks as $block) { $block->setBlockStatus($statuses['block_status'])->save(); } } catch(Exception $e) { Mage::logException($e); Mage::getSingleton('adminhtml/session')->addError($e->getMessage()); return $this->_redirect('*/*/'); } Mage::getSingleton('adminhtml/session')->addSuccess('Blocks were updated!'); return $this->_redirect('*/*/'); } public function massDeleteAction() { $blocks = $this->getRequest()->getParams(); try { $blocks= Mage::getModel('siteblocks/block') ->getCollection() ->addFieldToFilter('block_id',array('in'=>$blocks['massaction'])); foreach($blocks as $block) { $block->delete(); } } catch(Exception $e) { Mage::logException($e); Mage::getSingleton('adminhtml/session')->addError($e->getMessage()); return $this->_redirect('*/*/'); } Mage::getSingleton('adminhtml/session')->addSuccess('Blocks were deleted!'); return $this->_redirect('*/*/'); } } 


But we will give up this idea and use the method number 2 in the layout:

app / design / adminhtml / default / default / layout / siteblocks.xml
 <?xml version="1.0"?> <layout version="1.0.0"> <adminhtml_siteblocks_edit> <update handle="editor"/> <reference name="head"> <action method="setCanLoadExtJs"><flag>1</flag></action> <action method="setCanLoadRulesJs"><flag>1</flag></action> </reference> <!--       --> <reference name="left"> <block type="siteblocks/adminhtml_siteblocks_edit_tabs" name="siteblocks_tabs"> <!-- 2 c     --> <block name="conditions_tab" type="siteblocks/adminhtml_siteblocks_edit_tab_conditions"/> <action method="addTab"><name>my_conditions</name><block>conditions_tab</block></action> <action method="addTab"><name>my_conditions</name><block>siteblocks/adminhtml_siteblocks_edit_tab_conditions</block></action> </block> </reference> </adminhtml_siteblocks_edit> <adminhtml_system_config_edit> <update handle="editor"/> </adminhtml_system_config_edit> </layout> 


And one last tip: do not add tabs at once in 2 places. One in the layout, the other in the block. Add in one place or all in a layout or all in a block.


The output of the table (grid) of goods on the edit page and on the frontend.


Video: Using tabs on the edit page in Magento


The structure of the module IGN_Siteblocks-14.zip created in the lesson .

Now we will add the final feature to the module - the ability to mark products that will be displayed on the frontend together with the block.

A sort of alternative related products. In mind, dololno useful use cases of outputting a block with text and goods on product pages with suitable conditions for the block are added.

Add a new tab:

app / code / local / IGN / Siteblocks / Block / Adminhtml / Siteblocks / Edit / Tabs.php
 <?php class IGN_Siteblocks_Block_Adminhtml_Siteblocks_Edit_Tabs extends Mage_Adminhtml_Block_Widget_Tabs { public function __construct() { parent::__construct(); $this->setId('block_tabs'); $this->setDestElementId('edit_form'); $this->setTitle(Mage::helper('siteblocks')->__('Block Information')); } protected function _prepareLayout() { $this->addTab('main_tab',array( 'label' => $this->__('Main'), 'title' => $this->__('Main'), 'content' => $this->getLayout()->createBlock('siteblocks/adminhtml_siteblocks_edit_tab_main')->toHtml() )); $this->addTab('conditions_tab',array( 'label' => $this->__('Conditions'), 'title' => $this->__('Conditions'), 'content' => $this->getLayout()->createBlock('siteblocks/adminhtml_siteblocks_edit_tab_conditions')->toHtml() )); //     AJAX,         $this->addTab('products_tab','siteblocks/adminhtml_siteblocks_edit_tab_products'); return parent::_prepareLayout(); } } 


The tab uses AJAX. This can be seen in the code. The URL for requests is also indicated there.

app / code / local / IGN / Siteblocks / Block / Adminhtml / Siteblocks / Edit / Tab / Products.php
 <?php class IGN_Siteblocks_Block_Adminhtml_Siteblocks_Edit_Tab_Products extends Mage_Adminhtml_Block_Widget_Form implements Mage_Adminhtml_Block_Widget_Tab_Interface { public function getTabTitle() { return $this->__('Products'); } public function getTabLabel() { return $this->__('Products'); } public function canShowTab() { return true; } public function isHidden() { return false; } public function getClass() { return 'ajax'; } public function getTabClass() { return 'ajax'; } #URL  , ('_current'=>true)     ,     block_id    public function getTabUrl() { return $this->getUrl('*/*/products',array('_current'=>true)); } } 


Sincetab uses AJAX, you need to add actions to the controller. And, looking ahead, you can see what logic was added to the saveAction, so that the marked items would be saved.

app / code / local / IGN / Siteblocks / controllers / Adminhtml / SiteblocksController.php
 <?php class IGN_Siteblocks_Adminhtml_SiteblocksController extends Mage_Adminhtml_Controller_Action { public function indexAction() { $this->loadLayout(); $this->_addContent($this->getLayout()->createBlock('siteblocks/adminhtml_siteblocks')); $this->renderLayout(); } public function newAction() { $this->_forward('edit'); } public function editAction() { $id = $this->getRequest()->getParam('block_id'); Mage::register('siteblocks_block',Mage::getModel('siteblocks/block')->load($id)); $blockObject = (array)Mage::getSingleton('adminhtml/session')->getBlockObject(true); if(count($blockObject)) { Mage::registry('siteblocks_block')->setData($blockObject); } $this->loadLayout(); //$this->_addLeft($this->getLayout()->createBlock('siteblocks/adminhtml_siteblocks_edit_tabs')); $this->_addContent($this->getLayout()->createBlock('siteblocks/adminhtml_siteblocks_edit')); $this->renderLayout(); } protected function _uploadFile($fieldName,$model) { if( ! isset($_FILES[$fieldName])) { return false; } $file = $_FILES[$fieldName]; if(isset($file['name']) && (file_exists($file['tmp_name']))){ if($model->getId()){ unlink(Mage::getBaseDir('media').DS.$model->getData($fieldName)); } try { $path = Mage::getBaseDir('media') . DS . 'siteblocks' . DS; $uploader = new Varien_File_Uploader($file); $uploader->setAllowedExtensions(array('jpg','png','gif','jpeg')); $uploader->setAllowRenameFiles(true); $uploader->setFilesDispersion(false); $uploader->save($path, $file['name']); $model->setData($fieldName,$uploader->getUploadedFileName()); return true; } catch(Exception $e) { return false; } } } public function saveAction() { try { $id = $this->getRequest()->getParam('block_id'); /** @var IGN_Siteblocks_Model_Block $block */ $block = Mage::getModel('siteblocks/block')->load($id); /*$block ->setTitle($this->getRequest()->getParam('title')) ->setContent($this->getRequest()->getParam('content')) ->setBlockStatus($this->getRequest()->getParam('block_status')) ->save();*/ $data = $this->getRequest()->getParams(); #         $links = $this->getRequest()->getPost('links', array()); if (array_key_exists('products', $links)) { $selectedProducts = Mage::helper('adminhtml/js')->decodeGridSerializedInput($links['products']); $products = array(); foreach($selectedProducts as $product => $position) { $products[$product] = isset($position['position']) ? $position['position'] : $product; } $data['products'] = $products; } if (isset($data['rule']['conditions'])) { $data['conditions'] = $data['rule']['conditions']; } unset($data['rule']); $block ->loadPost($data); $this->_uploadFile('image',$block); $block ->setCreatedAt(Mage::app()->getLocale()->date()) ->save(); if(!$block->getId()) { Mage::getSingleton('adminhtml/session')->addError('Cannot save the block'); } } catch(Exception $e) { Mage::logException($e); Mage::getSingleton('adminhtml/session')->addError($e->getMessage()); Mage::getSingleton('adminhtml/session')->setBlockObject($block->getData()); return $this->_redirect('*/*/edit',array('block_id'=>$this->getRequest()->getParam('block_id'))); } Mage::getSingleton('adminhtml/session')->addSuccess('Block was saved successfully!'); $this->_redirect('*/*/'.$this->getRequest()->getParam('back','index'),array('block_id'=>$block->getId())); } public function deleteAction() { $block = Mage::getModel('siteblocks/block') ->setId($this->getRequest()->getParam('block_id')) ->delete(); if($block->getId()) { Mage::getSingleton('adminhtml/session')->addSuccess('Block was deleted successfully!'); } $this->_redirect('*/*/'); } public function massStatusAction() { $statuses = $this->getRequest()->getParams(); try { $blocks= Mage::getModel('siteblocks/block') ->getCollection() ->addFieldToFilter('block_id',array('in'=>$statuses['massaction'])); foreach($blocks as $block) { $block->setBlockStatus($statuses['block_status'])->save(); } } catch(Exception $e) { Mage::logException($e); Mage::getSingleton('adminhtml/session')->addError($e->getMessage()); return $this->_redirect('*/*/'); } Mage::getSingleton('adminhtml/session')->addSuccess('Blocks were updated!'); return $this->_redirect('*/*/'); } public function massDeleteAction() { $blocks = $this->getRequest()->getParams(); try { $blocks= Mage::getModel('siteblocks/block') ->getCollection() ->addFieldToFilter('block_id',array('in'=>$blocks['massaction'])); foreach($blocks as $block) { $block->delete(); } } catch(Exception $e) { Mage::logException($e); Mage::getSingleton('adminhtml/session')->addError($e->getMessage()); return $this->_redirect('*/*/'); } Mage::getSingleton('adminhtml/session')->addSuccess('Blocks were deleted!'); return $this->_redirect('*/*/'); } #2     AJAX  public function productsAction() { $this->loadLayout() ->renderLayout(); } public function productsgridAction() { $this->loadLayout() ->renderLayout(); } } 


From the code in the controller it is clear that you need to update the layout.

app / design / adminhtml / default / default / layout / adminhtml.xml
 <?xml version="1.0"?> <layout version="1.0.0"> <adminhtml_siteblocks_edit> <update handle="editor"/> <reference name="head"> <action method="setCanLoadExtJs"><flag>1</flag></action> <action method="setCanLoadRulesJs"><flag>1</flag></action> </reference> <reference name="left"> <block type="siteblocks/adminhtml_siteblocks_edit_tabs" name="siteblocks_tabs"> <!-- <block name="conditions_tab" type="siteblocks/adminhtml_siteblocks_edit_tab_conditions"/> <action method="addTab"><name>my_conditions</name><block>conditions_tab</block></action>--> <!--<action method="addTab"><name>my_conditions</name><block>siteblocks/adminhtml_siteblocks_edit_tab_conditions</block></action>--> </block> </reference> </adminhtml_siteblocks_edit> <adminhtml_system_config_edit> <update handle="editor"/> </adminhtml_system_config_edit> <!--    ,     ,        --> <adminhtml_siteblocks_products> <block type="core/text_list" name="root" output="toHtml"> <block type="siteblocks/adminhtml_siteblocks_edit_tab_products_grid" name="siteblocks_products"/> <block type="adminhtml/widget_grid_serializer" name="siteblocks_products_serializer"> <reference name="siteblocks_products_serializer"> <action method="initSerializerBlock"> <grid_block_name>siteblocks_products</grid_block_name> <data_callback>getSelectedBlockProducts</data_callback> <hidden_input_name>links[products]</hidden_input_name> <reload_param_name>siteblocks_products</reload_param_name> </action> <action method="addColumnInputName"> <input_name>position</input_name> </action> </reference> </block> </block> </adminhtml_siteblocks_products> <!--       --> <adminhtml_siteblocks_productsgrid> <block type="core/text_list" name="root" output="toHtml"> <block type="siteblocks/adminhtml_siteblocks_edit_tab_products_grid" name="block_products"/> </block> </adminhtml_siteblocks_productsgrid> </layout> 


Watch carefully for correct block naming. For your project, you will rename it. Rename simultaneously in all places.

The final element in the admin interface will be the table class.

app / code / local / IGN / Siteblocks / Block / Adminhtml / Siteblocks / Edit / Tab / Products / Grid.php
 <?php class IGN_Siteblocks_Block_Adminhtml_Siteblocks_Edit_Tab_Products_Grid extends Mage_Adminhtml_Block_Widget_Grid { protected $_block; /** * Set grid params * */ public function __construct() { parent::__construct(); $this->setId('siteblocks_product_grid'); $this->setDefaultSort('entity_id'); $this->setUseAjax(true); if ($this->_getBlock()->getId()) { $this->setDefaultFilter(array('in_products'=>1)); } if ($this->isReadonly()) { $this->setFilterVisibility(false); } } protected function _getBlock() { if(!$this->_block) { $this->_block = Mage::getModel('siteblocks/block')->load($this->getRequest()->getParam('block_id')); } return $this->_block; } protected function _addColumnFilterToCollection($column) { // Set custom filter for in product flag if ($column->getId() == 'in_products') { $productIds = $this->_getSelectedProducts(); if (empty($productIds)) { $productIds = 0; } if ($column->getFilter()->getValue()) { $this->getCollection()->addFieldToFilter('entity_id', array('in'=>$productIds)); } else { if($productIds) { $this->getCollection()->addFieldToFilter('entity_id', array('nin'=>$productIds)); } } } else { parent::_addColumnFilterToCollection($column); } return $this; } /** * Checks when this block is readonly * * @return boolean */ public function isReadonly() { return $this->_getBlock()->getUpsellReadonly(); } protected function _prepareCollection() { #        $collection = Mage::getResourceModel('catalog/product_collection') ->addAttributeToSelect('*'); if ($this->isReadonly()) { $productIds = $this->_getSelectedProducts(); if (empty($productIds)) { $productIds = array(0); } $collection->addFieldToFilter('entity_id', array('in'=>$productIds)); } $this->setCollection($collection); return parent::_prepareCollection(); } /** * Add columns to grid * * @return Mage_Adminhtml_Block_Widget_Grid */ protected function _prepareColumns() { #       if (!$this->_getBlock()->getUpsellReadonly()) { $this->addColumn('in_products', array( 'header_css_class' => 'a-center', 'type' => 'checkbox', 'name' => 'in_products', 'values' => $this->_getSelectedProducts(), 'align' => 'center', 'index' => 'entity_id' )); } $this->addColumn('entity_id', array( 'header' => Mage::helper('catalog')->__('ID'), 'sortable' => true, 'width' => 60, 'index' => 'entity_id' )); $this->addColumn('name', array( 'header' => Mage::helper('catalog')->__('Name'), 'index' => 'name' )); $this->addColumn('type', array( 'header' => Mage::helper('catalog')->__('Type'), 'width' => 100, 'index' => 'type_id', 'type' => 'options', 'options' => Mage::getSingleton('catalog/product_type')->getOptionArray(), )); $sets = Mage::getResourceModel('eav/entity_attribute_set_collection') ->setEntityTypeFilter(Mage::getModel('catalog/product')->getResource()->getTypeId()) ->load() ->toOptionHash(); $this->addColumn('set_name', array( 'header' => Mage::helper('catalog')->__('Attrib. Set Name'), 'width' => 130, 'index' => 'attribute_set_id', 'type' => 'options', 'options' => $sets, )); $this->addColumn('status', array( 'header' => Mage::helper('catalog')->__('Status'), 'width' => 90, 'index' => 'status', 'type' => 'options', 'options' => Mage::getSingleton('catalog/product_status')->getOptionArray(), )); $this->addColumn('visibility', array( 'header' => Mage::helper('catalog')->__('Visibility'), 'width' => 90, 'index' => 'visibility', 'type' => 'options', 'options' => Mage::getSingleton('catalog/product_visibility')->getOptionArray(), )); $this->addColumn('sku', array( 'header' => Mage::helper('catalog')->__('SKU'), 'width' => 80, 'index' => 'sku' )); $this->addColumn('price', array( 'header' => Mage::helper('catalog')->__('Price'), 'type' => 'currency', 'currency_code' => (string) Mage::getStoreConfig(Mage_Directory_Model_Currency::XML_PATH_CURRENCY_BASE), 'index' => 'price' )); $this->addColumn('position', array( 'header' => Mage::helper('catalog')->__('Position'), 'name' => 'position', 'type' => 'number', 'width' => 60, 'validate_class' => 'validate-number', 'index' => 'position', 'editable' => true )); return parent::_prepareColumns(); } # URL       public function getGridUrl() { return $this->_getData('grid_url') ? $this->_getData('grid_url') : $this->getUrl('*/*/productsgrid', array('_current'=>true)); } protected function _getSelectedProducts() { return array_keys($this->getSelectedBlockProducts()); } public function getSelectedBlockProducts() { $selected = $this->getRequest()->getParam('siteblocks_products'); $products = array(); foreach ($this->_getBlock()->getProducts() as $product => $position) { $products[$product] = array('position' => $position); } foreach ($selected as $product) { if(!isset($products[$product])) { $products[$product] = array('position'=>$product); } } return $products; } } 


In order for us to successfully save the goods, we need to update the version and create a new upgrade script, in which we will add a new column.

app / code / local / IGN / Siteblocks / sql / siteblocks_setup / upgrade-1.0.2-1.0.3.php
 <?php /** @var Mage_Core_Model_Resource_Setup $installer */ $installer = $this; $installer->startSetup(); $installer->run(" ALTER TABLE `{$this->getTable('siteblocks/block')}` ADD `products` TEXT NOT NULL; "); $installer->endSetup(); 


And small transformations in the model.

app / code / local / IGN / Siteblocks / Model / Block.php
 <?php /** * Class IGN_Siteblocks_Model_Block * @method getBlockStatus() * @method getContent() * @method getImage() */ class IGN_Siteblocks_Model_Block extends Mage_Rule_Model_Abstract { protected $_eventPrefix = 'siteblocks_block'; public function getActionsInstance() { return Mage::getModel('catalogrule/rule_action_collection'); } public function getConditionsInstance() { return Mage::getModel('catalogrule/rule_condition_combine'); } public function _construct() { parent::_construct(); $this->_init('siteblocks/block'); } public function getImageSrc() { return Mage::getBaseUrl('media') . 'siteblocks' . DS . $this->getImage(); } #      protected function _beforeSave() { parent::_beforeSave(); if(is_array($this->getData('products'))) { $this->setData('products',json_encode($this->getData('products'))); } } #      protected function _afterLoad() { parent::_beforeSave(); if(!empty($this->getData('products'))) { $this->setData('products',(array)json_decode($this->getData('products'))); } } # ,      public function getProducts() { if(!is_array($this->getData('products'))) { $this->setData('products',(array)json_decode($this->getData('products'))); } return $this->getData('products'); } } 


Products can be assigned. Now it is necessary to display them correctly on the frontend. For these purposes, I created a new template, which I copied from upsells and edited to fit my needs:

app / design / frontend / base / default / template / siteblocks / product / list.php
 <?php if(count($this->getLoadedProductCollection()->getItems())): ?> <div class="box-collateral box-up-sell"> <h2><?php echo $this->__('You may also like') ?></h2> <ul class="products-grid products-grid--max-4-col" id="upsell-product-table"> <?php foreach ($this->getLoadedProductCollection()->getItems() as $_link): ?> <li> <a href="<?php echo $_link->getProductUrl() ?>" title="<?php echo $this->escapeHtml($_link->getName()) ?>" class="product-image"> <img src="<?php echo $this->helper('catalog/image')->init($_link, 'small_image')->resize(280) ?>" alt="<?php echo $this->escapeHtml($_link->getName()) ?>" /> </a> <h3 class="product-name"><a href="<?php echo $_link->getProductUrl() ?>" title="<?php echo $this->escapeHtml($_link->getName()) ?>"><?php echo $this->escapeHtml($_link->getName()) ?></a></h3> <?php echo $this->getPriceHtml($_link, true, '-upsell') ?> </li> <?php endforeach; ?> </ul> </div> <?php endif ?> 


Also update the list.phtml block output template:

app / design / frontend / base / default / template / siteblocks / list.php
 <?php foreach($this->getBlocks() as $block):?> <div class="siteblock"> <div class="block-title"><?php echo $block->getTitle()?></div> <div class="block-image"> <?php if($block->getImage()):?> <img src="<?php echo $block->getImageSrc()?>" height="150" width="auto" alt="<?php $block->getTitle()?>" title="<?php $block->getTitle()?>"> <?php endif;?> </div> <div class="block-content"><?php echo $this->getBlockContent($block)?></div> <div class="block-product-list"> <?php echo $this->getProductsList($block)?> </div> </div> <?php endforeach;?> 


And the required changes in the List.php block:

app / code / local / IGN / Siteblocks / Block / List.php
 <?php class IGN_Siteblocks_Block_List extends Mage_Core_Block_Template { public function getBlocks() { //return Mage::getResourceModel('siteblocks/block_collection'); $items = Mage::getModel('siteblocks/block')->getCollection() ->addFieldToFilter('block_status',array('eq'=>IGN_Siteblocks_Model_Source_Status::ENABLED)); $filteredItems = $items; if(Mage::registry('current_product') instanceof Mage_Catalog_Model_Product) { $filteredItems = array(); /** @var IGN_Siteblocks_Model_Block $item */ foreach ($items as $item) { if($item->validate(Mage::registry('current_product'))) { $filteredItems[] = $item; } } } return $filteredItems; } public function getBlockContent($block) { $processor = Mage::helper('cms')->getBlockTemplateProcessor(); $html = $processor->filter($block->getContent()); return $html; } //      public function getProductsList($block) { $products = $block->getProducts(); asort($products); $collection = Mage::getResourceModel('catalog/product_collection') ->addFieldToFilter('entity_id',array('in'=>array_keys($products))) ->addAttributeToSelect('*'); /** @var Mage_Catalog_Block_Product_List $list */ $list = $this->getLayout()->createBlock('catalog/product_list'); $list->setCollection($collection); $list->setTemplate('siteblocks/product/list.phtml'); return $list->toHtml(); } } 


We could also create our own block for goods, but we can use a standard one for our tasks.

So we got a module that can display blocks in some places of the site. The output of blocks on the product page is carried out with checking conditions (Rule Conditions). For content input we use a convenient WYSIWYG editor.

As well as with the unit, we can withdraw several products. The module, which is easy to find a real use with some modifications for themselves. Public repository with the created module. And this guide would not be complete if we had not considered the process of creating our own payment method and delivery method.



Creating a Payment Method Module


Video: Developing a Payment Method Module for Magento


Public repository: bitbucket.org/dvman8bit/ign_payment

This will be a payment method that will be able to pay for the order by entering a secret code. Let's imagine that this is entering some details to pay for the order. The topic can be developed and made a full form. Our task is to understand the minimum of actions to create the basis for the future full-fledged method of payment.

Payment method includes several files: 2 blocks, 2 templates, 2 xml files and 1 model.
Let's start with system.xml, in it we will add a new section in the existing Payment Methods tab.

app / code / community / IGN / Payment / etc / system.xml
 <?xml version="1.0"?> <config> <sections> <payment> <groups> <ignpayment translate="label"> <label>IGN Payment</label> <frontend_type>text</frontend_type> <sort_order>30</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> <show_in_store>1</show_in_store> <fields> <active translate="label"> <label>Enabled</label> <frontend_type>select</frontend_type> <source_model>adminhtml/system_config_source_yesno</source_model> <sort_order>1</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> <show_in_store>0</show_in_store> </active> <order_status translate="label"> <label>New Order Status</label> <frontend_type>select</frontend_type> <source_model>adminhtml/system_config_source_order_status_newprocessing</source_model> <sort_order>2</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> <show_in_store>0</show_in_store> </order_status> <payment_action translate="label"> <label>Automatically Invoice All Items</label> <frontend_type>select</frontend_type> <source_model>payment/source_invoice</source_model> <sort_order>3</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> <show_in_store>0</show_in_store> <depends> <order_status separator=",">processing,processed_ogone</order_status> </depends> </payment_action> <sort_order translate="label"> <label>Sort Order</label> <frontend_type>text</frontend_type> <sort_order>100</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> <show_in_store>0</show_in_store> <frontend_class>validate-number</frontend_class> </sort_order> <title translate="label"> <label>Title</label> <frontend_type>text</frontend_type> <sort_order>1</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> <show_in_store>1</show_in_store> </title> <allowspecific translate="label"> <label>Payment from Applicable Countries</label> <frontend_type>allowspecific</frontend_type> <sort_order>50</sort_order> <source_model>adminhtml/system_config_source_payment_allspecificcountries</source_model> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> <show_in_store>0</show_in_store> </allowspecific> <specificcountry translate="label"> <label>Payment from Specific Countries</label> <frontend_type>multiselect</frontend_type> <sort_order>51</sort_order> <source_model>adminhtml/system_config_source_country</source_model> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> <show_in_store>0</show_in_store> <can_be_empty>1</can_be_empty> </specificcountry> <min_order_total translate="label"> <label>Minimum Order Total</label> <frontend_type>text</frontend_type> <sort_order>98</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> <show_in_store>0</show_in_store> </min_order_total> <max_order_total translate="label"> <label>Maximum Order Total</label> <frontend_type>text</frontend_type> <sort_order>99</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> <show_in_store>0</show_in_store> </max_order_total> <secret_code translate="label"> <label>Secret Code</label> <frontend_type>text</frontend_type> <sort_order>99</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> <show_in_store>0</show_in_store> </secret_code> </fields> </ignpayment> </groups> </payment> </sections> </config> 


In system.xml, almost all fields are standard. We have added only 1 new field where we will enter the secret code.

app / code / community / IGN / Payment / etc / config.xml
 <?xml version="1.0"?> <config> <modules> <IGN_Payment> <version>1.0.0</version> </IGN_Payment> </modules> <global> <models> <ignpayment> <class>IGN_Payment_Model</class> </ignpayment> </models> <resources> <payment_setup> <setup> <module>IGN_Payment</module> </setup> </payment_setup> </resources> <blocks> <ignpayment> <class>IGN_Payment_Block</class> </ignpayment> </blocks> <helpers> <ignpayment> <class>IGN_Payment_Helper</class> </ignpayment> </helpers> </global> <frontend> <translate> <modules> <IGN_Payment> <files> <default>IGN_Payment.csv</default> </files> </IGN_Payment> </modules> </translate> </frontend> <adminhtml> <translate> <modules> <IGN_Payment> <files> <default>IGN_Payment.csv</default> </files> </IGN_Payment> </modules> </translate> </adminhtml> <default> <payment> <ignpayment> <active>1</active> <model>ignpayment/method</model> <!--      --> <order_status>pending</order_status> <title>Secret Code</title> <allowspecific>0</allowspecific> <sort_order>1</sort_order> <group>offline</group> </ignpayment> </payment> </default> </config> 


We now turn to the most important part: the Method.php model.

app / code / community / IGN / Payment / Model / Method.php
 <?php class IGN_Payment_Model_Method extends Mage_Payment_Model_Method_Abstract { //     protected $_code = 'ignpayment'; // block type protected $_formBlockType = 'ignpayment/form'; protected $_infoBlockType = 'ignpayment/info'; //      ,         public function validate() { $code = Mage::app()->getRequest()->getParam('secret_code'); if($code != $this->getConfigData('secret_code')) { Mage::throwException(Mage::helper('ignpayment')->__("This code doesn't work!")); } return parent::validate(); } } 


Be sure to inherit from the class Mage_Payment_Model_Method_Abstract . If you look inside this class, you will see a bunch of properties with default values ​​and methods. Properties and methods carry quite speaking names, so if something is particularly important to us, we copy it into our class and indicate the value that corresponds to our needs.

We remember that the model implements the methods:

order (), capture (), void (), refund (), and so on. And if our payment method must “communicate” with the payment service servers, then we copy the methods into our class and add the corresponding scripts to them.

Now we will take care of the output of our method on the frontend part. And here we create 2 classes. Form.php is used when displaying the payment method in the checkout block.

app / code / community / IGN / Payment / Block / Form.php
 <?php /** * Payment method form base block */ class IGN_Payment_Block_Form extends Mage_Payment_Block_Form { public function _construct() { parent::_construct(); // ,    ,        $this->setTemplate('ignpayment/form.phtml'); } } 


This block is displayed in the info block on the order page.

app / code / community / IGN / Payment / Block / Info.php
 <?php class IGN_Payment_Block_Info extends Mage_Payment_Block_Info { protected function _construct() { parent::_construct(); $this->setTemplate('ignpayment/info.phtml'); } } 


And the corresponding block templates:

app / design / frontend / base / default / template / ignpayment / form.phtml
 <!--   id,      payment_form_,    -  --> <div id="payment_form_ignpayment" style="display: none"> <input type="text" name="secret_code" autocomplete="off"> <!--          --> </div> 


The contents of the info.phtml file is standard, but we can change it to fit your needs.

app / design / frontend / base / default / template / ignpayment / info.phtml
 <p><strong><?php echo $this->escapeHtml($this->getMethod()->getTitle()) ?></strong></p> <?php if ($_specificInfo = $this->getSpecificInformation()):?> <table> <tbody> <?php foreach ($_specificInfo as $_label => $_value):?> <tr> <th><strong><?php echo $this->escapeHtml($_label)?>:</strong></th> </tr> <tr> <td><?php echo nl2br(implode($this->getValueAsArray($_value, true), "\n"))?></td> </tr> <?php endforeach; ?> </tbody> </table> <?php endif;?> <?php echo $this->getChildHtml()?> 


This is the basis of our payment method. Further edits strongly rest on the work of a particular payment service. You may well need a controller, which will knock on the payment service, passing the details of the transaction. And the creation of controllers is described above, as is the creation of the helper, which I dropped here.


Shipping Method Module


Video: Shipping Method Development for Magento


Public repository: bitbucket.org/dvman8bit/ign_shipment

Let's see what actions are needed to create your own delivery method. Our module will work with Belpochta. SinceI myself am from the Republic of Belarus and it is quite relevant to me. Belpost has no public API. And there is no captcha, so it’s easy for us to ask for a price.

For the delivery method to work, you need at least 3 files. 2 xml and one model, we still use the helper. Total 4.

app / code / community / IGN / Shipment / etc / config.xml
 <?xml version="1.0"?> <config> <modules> <IGN_Shipment> <version>1.0.0</version> </IGN_Shipment> </modules> <global> <models> <ignshipment> <class>IGN_Shipment_Model</class> </ignshipment> </models> <helpers> <ignshipment> <class>IGN_Shipment_Helper</class> </ignshipment> </helpers> </global> <adminhtml> <translate> <modules> <IGN_Shipment> <files> <default>IGN_Shipment.csv</default> </files> </IGN_Shipment> </modules> </translate> </adminhtml> <frontend> <translate> <modules> <IGN_Shipment> <files> <default>IGN_Shipment.csv</default> </files> </IGN_Shipment> </modules> </translate> </frontend> <default> <carriers> <ignshipment> <active>1</active> <sallowspecific>0</sallowspecific> <model>ignshipment/carrier</model> <!--    --> <name>IGN Shipment</name> <price>5.00</price> <title>IGN Shipment</title> <type>I</type> <specificerrmsg>This shipping method is currently unavailable. If you would like to ship using this shipping method, please contact us.</specificerrmsg> <handling_type>F</handling_type> <packet_max_weight>2000</packet_max_weight> </ignshipment> </carriers> </default> </config> 



app / code / community / IGN / Shipment / etc / system.xml
 <?xml version="1.0"?> <config> <sections> <carriers> <groups> <ignshipment translate="label"> <label>IGN Shipping</label> <frontend_type>text</frontend_type> <sort_order>2</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> <show_in_store>1</show_in_store> <fields> <active translate="label"> <label>Enabled</label> <frontend_type>select</frontend_type> <source_model>adminhtml/system_config_source_yesno</source_model> <sort_order>1</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> <show_in_store>0</show_in_store> </active> <name translate="label"> <label>Method Name</label> <frontend_type>text</frontend_type> <sort_order>3</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> <show_in_store>1</show_in_store> </name> <price translate="label"> <label>Price</label> <frontend_type>text</frontend_type> <validate>validate-number validate-zero-or-greater</validate> <sort_order>5</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> <show_in_store>0</show_in_store> </price> <handling_type translate="label"> <label>Calculate Handling Fee</label> <frontend_type>select</frontend_type> <source_model>shipping/source_handlingType</source_model> <sort_order>7</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> <show_in_store>0</show_in_store> </handling_type> <handling_fee translate="label"> <label>Handling Fee</label> <frontend_type>text</frontend_type> <validate>validate-number validate-zero-or-greater</validate> <sort_order>8</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> <show_in_store>0</show_in_store> </handling_fee> <sort_order translate="label"> <label>Sort Order</label> <frontend_type>text</frontend_type> <sort_order>100</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> <show_in_store>0</show_in_store> </sort_order> <title translate="label"> <label>Title</label> <frontend_type>text</frontend_type> <sort_order>2</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> <show_in_store>1</show_in_store> </title> <type translate="label"> <label>Type</label> <frontend_type>select</frontend_type> <source_model>adminhtml/system_config_source_shipping_flatrate</source_model> <sort_order>4</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> <show_in_store>0</show_in_store> </type> <sallowspecific translate="label"> <label>Ship to Applicable Countries</label> <frontend_type>select</frontend_type> <sort_order>90</sort_order> <frontend_class>shipping-applicable-country</frontend_class> <source_model>adminhtml/system_config_source_shipping_allspecificcountries</source_model> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> <show_in_store>0</show_in_store> </sallowspecific> <specificcountry translate="label"> <label>Ship to Specific Countries</label> <frontend_type>multiselect</frontend_type> <sort_order>91</sort_order> <source_model>adminhtml/system_config_source_country</source_model> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> <show_in_store>0</show_in_store> <can_be_empty>1</can_be_empty> </specificcountry> <showmethod translate="label"> <label>Show Method if Not Applicable</label> <frontend_type>select</frontend_type> <sort_order>92</sort_order> <source_model>adminhtml/system_config_source_yesno</source_model> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> <show_in_store>0</show_in_store> </showmethod> <specificerrmsg translate="label"> <label>Displayed Error Message</label> <frontend_type>textarea</frontend_type> <sort_order>80</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> <show_in_store>1</show_in_store> </specificerrmsg> <packet_max_weight> <label>Packet Max Weight</label> <frontend_type>text</frontend_type> <sort_order>80</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> <show_in_store>1</show_in_store> </packet_max_weight> </fields> </ignshipment> </groups> </carriers> </sections> </config> 


Now you can create a model Carrier.php

app / code / community / IGN / Shipment / Model / Carrier.php
 <?php class IGN_Shipment_Model_Carrier extends Mage_Shipping_Model_Carrier_Abstract implements Mage_Shipping_Model_Carrier_Interface { protected $_code = 'ignshipment'; public function collectRates(Mage_Shipping_Model_Rate_Request $request) { /** @var Mage_Shipping_Model_Rate_Result $result */ $result = Mage::getModel('shipping/rate_result'); $weight = $request->getPackageWeight(); /** @var Mage_Shipping_Model_Rate_Result_Method $method */ $method = Mage::getModel('shipping/rate_result_method'); $method->setCarrier($this->_code); $method->setCarrierTitle($this->getConfigData('title')); //           if($weight > $this->getConfigData('packet_max_weight')) { $this->_getBoxMethod($weight,$method); } else { $this->_getPacketMethod($weight,$method); } $result->append($method); return $result; } protected function _getPacketMethod($weight,$method) { $method->setMethod('packet'); $method->setMethodTitle('Packet belpost'); $sum = Mage::helper('ignshipment')->getPacketCost($weight); $method->setPrice($sum/19050); } protected function _getBoxMethod($weight,$method) { $method->setMethod('box'); $method->setMethodTitle('Box belpost'); $sum = Mage::helper('ignshipment')->getBoxCost($weight); $method->setPrice($sum/19050); } //        API public function isTrackingAvailable() { return false; } public function getAllowedMethods() { //    2  .   2000 ,   return array( 'packet' => 'Packet belpost', 'box' => 'Box belpost' ); } } 


We inherit the model from the class Mage_Shipping_Model_Carrier_Abstract . Interface implementation is not required. In our logic, the possibility of counting the number of boxes is still not used, which will also affect the cost of delivery. But in this case, you will have to count each box by its weight and sum up the cost. We accept that all goods fit in one common box.

I rendered the logic of “communication” with the belpochta into a helper. In technical terms, the HTTP request and the price are simply made, and there is nothing to do with this code in the model.

app / code / community / IGN / Shipment / Helper / Data.php
 <?php class IGN_Shipment_Helper_Data extends Mage_Core_Helper_Abstract { public function getPacketCost($weight) { $request = new Zend_Http_Client(); $request->setUri('http://tarifikator.belpost.by/forms/international/packet.php'); $request->setParameterPost(array( 'who'=>'ur', 'type'=>'registered', 'priority'=>'priority', 'to'=>'other', 'weight'=>$weight )); $response = $request->request(Zend_Http_Client::POST); $html = $response->getBody(); $tag_regex = "/<blockquote>(.*)<\/blockquote>/im"; $sum_reqex = "/(\d+)/is"; preg_match_all($tag_regex, $html, $matches, PREG_PATTERN_ORDER); if(isset($matches[1]) && isset($matches[1][0])) { preg_match($sum_reqex,$matches[1][0],$matches); if(isset($matches[0])) { return (float)$matches[0]; } } //   ,       //          return Mage::getStoreConfig('carriers/ignshipment/price'); } public function getBoxCost($weight) { $request = new Zend_Http_Client(); $request->setUri('http://tarifikator.belpost.by/forms/international/ems.php'); $request->setParameterPost(array( 'who'=>'ur', 'type'=>'goods', 'to'=>'n10', //  .           , ..  Magento  US, NZ, AU,     n1,n2,n3  . 'weight'=>$weight )); $response = $request->request(Zend_Http_Client::POST); $html = $response->getBody(); $tag_regex = "/<blockquote>(.*)<\/blockquote>/im"; $sum_reqex = "/(\d+)/is"; preg_match_all($tag_regex, $html, $matches, PREG_PATTERN_ORDER); if(isset($matches[1]) && isset($matches[1][0])) { preg_match($sum_reqex,$matches[1][0],$matches); if(isset($matches[0])) { return $matches[0]; } } //   ,       //          return Mage::getStoreConfig('carriers/ignshipment/price'); } } 


Perhaps you have questions for my regulars. I also have questions for them, but let's leave it according to the principle “it works - don't touch”.

We can not go into the process of "recognizing" prices. All this is given only for example. In the production version of this code does not fit. And in general, it is worth developing as a service, plus adding caching, and also, it would be good to calculate the cost calculation formula. Otherwise, there will be problems if the mail server is unavailable or when they update the design. You can search the formula for calculating the cost somewhere on the site or ask by mail to some friendly mail employee.

Summarize. The delivery method is able to calculate the cost based on the total weight. Weight is taken from the standard attribute of the goods. And if the administrator is not too lazy to specify each product, then it will work.

In conclusion, I wish you all success. And for errors, which, probably, a lot, write preferably in the LAN.

ps I can not take advantage of the moment and do not popiarit your little youtube channel . Come in, there are stremchiki and not only for Magento. And soon we will take up the analysis of Magento 2.

All the best!

Source: https://habr.com/ru/post/312322/


All Articles