📜 ⬆️ ⬇️

Step-by-step module creation in Magento - novice developer’s guide

How much about Magento do not write, but still a lot of questions;) © jeje


When studying the Magento e-shop, I have met many articles describing the creation of a module using examples, but almost all of them give step-by-step instructions “do this, do this and get it”, without explaining why this or that step is being taken. As a result, when an error occurs, it is rather difficult to determine what is done wrong, and it is also more difficult to customize the module to fit your needs.

In this article, I will try to show the creation of a module step by step with explanations of each change using the example of the news module DS News, where DS is the Namespace (Namespace) and News is the name of the module. This module naming scheme is quite convenient in order not to be afraid of name conflicts in the module names. I will try to emphasize on the values ​​used in the configuration file - the names of the nodes and the place where they are used. I myself use this manual constantly when creating a new module, since remember where data comes from, which classes need to be inherited, etc ... it is simply impossible physically. And here everything is in one article.
')
I will not describe the installation of Magento and filling with goods, considering that the system is already working. However, you should make sure that caching is disabled (the cache can be disabled in the admin area on the System / Cache Management page) - this is necessary in order to immediately see the changes being made.

Step 1. Registering the module in the system
In the first step, we will create a minimum module that will not do anything, but will be taken into account in the system (and shown in the admin panel):

  1. Create directory / app / code / local / DS / News
  2. Create etc / config.xml file
    <?xml version="1.0" ?> <config> <modules> <DS_News> <version>0.0.1</version> </DS_News> </modules> </config> 

  3. Create DS_News.xml file in the / app / etc / modules / directory
     <?xml version="1.0" ?> <config> <modules> <DS_News> <active>true</active> <codePool>local</codePool> </DS_News> </modules> </config> 



As a result of creating this directory and file structure, the DS_News module will be registered in the system. This module can be seen in the list of modules in the admin panel on the System / Configuration / Advanced / Advanced / Disable Modules Output path.

In paragraph 1, the directory / app / code / local / DS / News is created , in which the main module code is stored: models, controllers, helpers, etc ... In paragraph 2, the directory etc is created in which the module configuration files are stored, and the main configuration file config.xml is created , in which so far only the version of the module is indicated. In paragraph 3, the module is registered in the system.

In fact, for the task of registering a module, point 3 is the most important - the module will be shown in the admin area even if there is no module directory. The name of the DS_News.xml file in the / app / etc / modules / directory is also optional - the system parses all the XML files from this directory when it is loaded. This can be convenient when developing in the case when several interconnected modules are installed - all modules can be specified in one XML file.

The DS_News module names in xml files consist of two parts: [name of the namespace directory] _ [name of the module directory] . In connection with this naming approach, it is desirable to compile the names of directories only from letters, and it is also important to take case into account — the name of the module in the configuration files must exactly match the name of the directories for correct operation on * nix systems.

The file /app/etc/modules/DS_News.xml uses two tags: active and codePool . The first tag is responsible for enabling / disabling the module, and the second is for the location in the / app / code / directory: core (system core), community (modules developed by the Magento community) or local (modules developed by other developers).



Step 2. Database initialization
For the news module to work, you need to create a table in which the news will be stored. Magento allows you to create / update tables when installing a module.

  1. Create a file sql / dsnews_setup / install-0.0.1.php
     <?php die('DS News module setup'); $installer = $this; $installer->startSetup(); $installer->run("CREATE TABLE ds_news_entities ( `news_id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, `title` VARCHAR(255) NOT NULL, `content` TEXT NOT NULL, `created` DATETIME, PRIMARY KEY (`news_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;"); $installer->endSetup(); 

  2. Add the section with the resource for installation into the etc / config.xml configuration file:
     <?xml version="1.0" ?> <config> <modules> ... </modules> <global> <resources> <dsnews_setup> <setup> <module>DS_News</module> </setup> </dsnews_setup> </resources> </global> </config> 



After the changes are made, you just need to open the site in the browser. If DS News module setup appears, then everything is fine. A little later, it will be possible to comment out the line with die in the file install-0.0.1.php , and update the browser window, after which the module will be registered in the core_resource table, and the ds_news_entities table will be created in the database.

If the caption does not appear, then most likely the module has already been installed on the system, and the installation requires removing the dsnews_setup resource record from the core_resource table in the database using any SQL manager, and then refresh the browser window with the site again:

 DELETE FROM `core_resource` WHERE `code` = 'dsnews_setup'; 


The dsnews_setup node name in config.xml can be anything. The main thing is that the name of this node coincides with the name of the directory where the files for installation will be located. Also, the name of this node must be unique in the system, since This name is used as the primary key of the core_resource table, in which all installed resources are stored.

The names of files in the installation directory should correspond to a certain structure. For installation files, this is install- [version]. [Php | sql] , and for upgrade files, this upgrade- [version-from] is [version-to]. [Php | sql] . For more information about the process of setting resources, naming files and directories, see the Mage_Core_Model_Resource_Setup class.



Step 3. Getting the table name from the config.xml
Since in the future in this module, work with the database will occur through models and collections, table names should be stored in the configuration file, and not in the installation file. To do this, you need to change the file etc / config.xml , as well as the installation file sql / dsnews_setup / install-0.0.1.php

  1. Create a model node config / global / models / in the file etc / config.xml
     <?xml version="1.0" ?> <config> ... <global> <models> <dsnews> <resourceModel>dsnews_resource</resourceModel> </dsnews> <dsnews_resource> <entities> <table_news> <table>ds_news_entities</table> </table_news> </entities> </dsnews_resource> </models> <resources> ... </resources> </global> </config> 

  2. Edit the sql / dsnews_setup / install-0.0.1.php file
     <?php $installer = $this; $tableNews = $installer->getTable('dsnews/table_news'); die($tableNews); $installer->startSetup(); $installer->getConnection()->dropTable($tableNews); $table = $installer->getConnection() ->newTable($tableNews) ->addColumn('news_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( 'identity' => true, 'nullable' => false, 'primary' => true, )) ->addColumn('title', Varien_Db_Ddl_Table::TYPE_TEXT, '255', array( 'nullable' => false, )) ->addColumn('content', Varien_Db_Ddl_Table::TYPE_TEXT, null, array( 'nullable' => false, )) ->addColumn('created', Varien_Db_Ddl_Table::TYPE_DATETIME, null, array( 'nullable' => false, )); $installer->getConnection()->createTable($table); $installer->endSetup(); 



If the browser displays the name of the ds_news_entities table , then everything is done correctly - you can delete the line with die and refresh the window to complete the installation of the module. Otherwise, you should delete the module resource record from the core_resource table using any SQL manager:

 DELETE FROM `core_resource` WHERE `code` = 'dsnews_setup'; 


In order for us not to have SQL errors in case this table was created in the previous step, the code for preliminary deletion of an already existing table was added to the installation file. Also, the code for creating the same table is now changed to abstract: the table is created using an abstract connection class, which will allow you to further maintain databases other than MySQL.

The dsnews model node name, as well as the dsnews_resource model-model node name, located in config / global / models / , can be any name. The only requirement is the uniqueness of the node name among other models / resources of the system.

Inside the installer code in clause 2, the table name is queried using the $ installer-> getTable ('dsnews / table_news') function , which takes a string as argument
[model node name] / [resource entities node name] . The model node itself does not store the names of the tables, since the low-level interaction with the database, according to the logic of Magento, must occur with the help of resource models. For this reason, the model stores only the link to the resource-model node in the resourceModel tag.



Step 4. Accessing the module via FrontEnd
Now we will display the inscription “Hello World” on the site.

  1. Add frontend section to the configuration file etc / config.xml
     <?xml version="1.0" ?> <config> <modules> ... </modules> <frontend> <routers> <dsnews> <use>standard</use> <args> <module>DS_News</module> <frontName>news</frontName> </args> </dsnews> </routers> </frontend> <global> ... </global> </config> 

  2. Create controllers / IndexController.php file
     <?php class DS_News_IndexController extends Mage_Core_Controller_Front_Action { public function indexAction() { echo '<h1>News</h1>'; } } 



Now at the address http://site.com/news/ will be displayed the inscription "News".

The name of the dsnews node located in the config / frontend / routers / section can be any; the only requirement is the uniqueness of the name among other routers of the system. More important is the value of the frontName node - this value will be used to determine the desired module during the processing of the request.

The path is determined as follows: http: // [site] / [router] / [controller] / [action] . When prompted, you can omit [action] or the [controller] / [action] pair, then instead of the missing parameters, the default value index will be used. Thus, the paths http://site.com/news/ , http://site.com/news/index/ , http://site.com/news/index/index are equivalent - refer to the DS News module, the controller IndexController and action indexAction .



Step 5. Using the template for data output
Information can be generated directly in the controller, or a special template can be used to separate the display logic from the program logic.

  1. Create a template file / app/design/frontend/[package mine/[theme mine/ template/ ds_news/index.phtml
     <h1>Template ds_news/index.phtml</h1> 

  2. Create a page layout configuration file /app/design/frontend/[package//[theme//layout/ds_news.xml
     <?xml version="1.0" ?> <layout> <dsnews_index_index> <reference name="content"> <block type="core/template" template="ds_news/index.phtml" /> </reference> </dsnews_index_index> </layout> 

  3. Add the layout section to the etc / config.xml configuration file
     <?xml version="1.0" ?> <config> <modules> ... </modules> <frontend> <layout> <updates> <dsnews> <file>ds_news.xml</file> </dsnews> </updates> </layout> <routers> ... </routers> </frontend> <global> ... </global> </config> 

  4. Change controller controllers / IndexController.php
     <?php class DS_News_IndexController extends Mage_Core_Controller_Front_Action { public function indexAction() { $this->loadLayout(); $this->renderLayout(); } } 



Now, on the way http://site.com/news/ , the site page with all the blocks (header, footer, sidebar) should open, and the inscription “Template ds_news / index.phtml” will be displayed in place of the page content.

All templates are stored in the template directory, and page layout settings are in the current theme directory / app / design / frontend / [package] / [theme] . When working with Magento, you need to create your own themes, without touching the contents of the base themes / app / design / frontend / base / and / app / design / frontend / default / due to the fact that the contents of the base themes can be overwritten when you update the version of Magento.

When you initialize the page layout of $ this-> loadLayout () in the controller action, the layout layout handle of the page is alternately loaded, which can change each other. By default, the handle default is loaded first, and the last one is the handle with the name [router] _ [controller] _ [action] , where [router] is the name of the config / frontend / routers / [router] node.

Paragraph 2 defines the layout handle for our controller / action: dsnews_index_index , inside of which a block of the built-in type core / template is created and the dsnews / index.phtml template created in paragraph 1. In paragraph 3, the config / frontend / layout / section is added to the configuration file updates , which indicates the update file to be applied when initializing a theme.

If there are not so many changes in the theme layout and the module is not planned to be implemented in other projects, then the layout handle can be placed in the file /app/design/frontend/[package[/[theme//layout/local.xml ,
which is always loaded after all updates - this file is not necessary to be written in the configuration file of the module config.xml

In case of difficulty with determining the correct name of the layout handle , you can see the list of used handle- s in the controller:
 <?php class DS_News_IndexController extends Mage_Core_Controller_Front_Action { public function indexAction() { $this->loadLayout(); $layoutHandles = $this->getLayout()->getUpdate()->getHandles(); echo '<pre>' . print_r($layoutHandles, true) . '</pre>'; } } 

As a result, you can see the output, like this:
 Array
 (
     [0] => default
     [1] => STORE_default
     [2] => THEME_frontend_ [package] _ [theme]
     [3] => dsnews_index_index
     [4] => customer_logged_out
 )




Step 6. Show news directly from the database
At this stage, you can try to display news in a template, using data directly from the database. To do this, you first need to add some test news to the database:

  1. Add test news to the database for output:
     INSERT INTO `ds_news_entities` VALUES (NULL, 'News 1', 'News 1 Content', '2013-10-16 17:45'), (NULL, 'News 2', 'News 2 Content', '2013-11-07 04:12'), (NULL, 'News 3', 'News 3 Content', '2014-01-12 15:55'); 

  2. Change Template / app/ design/ frontend/[package mine/[theme mine/ template/ dsnews/index.phtml
     <h1>News</h1> <?php $news = Mage::registry('news'); foreach ($news as $item) { echo '<h2>' . $item['title'] . '</h2>'; } 

  3. Change controller controllers / IndexController.php
     <?php class DS_News_IndexController extends Mage_Core_Controller_Front_Action { public function indexAction() { $resource = Mage::getSingleton('core/resource'); $read = $resource->getConnection('core_read'); $table = $resource->getTableName('dsnews/table_news'); $select = $read->select() ->from($table, array('news_id', 'title', 'content', 'created')) ->order('created DESC'); $news = $read->fetchAll($select); Mage::register('news', $news); $this->loadLayout(); $this->renderLayout(); } } 



This example shows the option of directly requesting data from the database, as well as using the registry Mage :: registry to transfer news from the controller to the template. The registry is available globally in any part of the code and, in rare cases, it is the most optimal solution in difficult situations.

Using the registry, as well as using global variables, is considered a sign of poor-quality code. Direct access to the database is also not recommended, as this is a potential problem of compatibility and code portability. It is better to use the opportunities provided by the system itself for working with objects - models and collections, which will be discussed in the next section.



Step 7. Creating and using models
According to the logic of Magento (and many other OOP systems), data should be processed at the level of objects / models and collections, not taking into account the methods of obtaining and saving data. The operations of receiving and saving data are left for resource models, as lower-level ones - then, in the event of a change in the way data is stored (databases, files, etc ...), there is no need to change the processing logic, but only need to change the resource model, involved in obtaining and storing data. Let us redo the output of news using models, and also add the output of news content by ID:

  1. Add class node to model node in configuration file:
     <?xml version="1.0" ?> <config> <modules> ... </modules> <frontend> ... </frontend> <global> <models> <dsnews> <class>DS_News_Model</class> <resourceModel>dsnews_resource</resourceModel> </dsnews> <dsnews_resource> <class>DS_News_Model_Resource</class> <entities> <table_news> <table>ds_news_entities</table> </table_news> </entities> </dsnews_resource> </models> <resources> ... </resources> </global> </config> 

  2. Create a model news file Model / News.php
     <?php class DS_News_Model_News extends Mage_Core_Model_Abstract { public function _construct() { parent::_construct(); $this->_init('dsnews/news'); } } 

  3. Create a resource file of the Model / Resource / News.php news model .
     <?php class DS_News_Model_Resource_News extends Mage_Core_Model_Mysql4_Abstract { public function _construct() { $this->_init('dsnews/table_news', 'news_id'); } } 

  4. Create a resource file for the Model / Resource / News / Collection.php collection
     <?php class DS_News_Model_Resource_News_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract { public function _construct() { parent::_construct(); $this->_init('dsnews/news'); } } 

  5. Change controller controllers / IndexController.php
     <?php class DS_News_IndexController extends Mage_Core_Controller_Front_Action { public function indexAction() { $news = Mage::getModel('dsnews/news')->getCollection()->setOrder('created', 'DESC'); $viewUrl = Mage::getUrl('news/index/view'); echo '<h1>News</h1>'; foreach ($news as $item) { echo '<h2><a href="' . $viewUrl . '?id=' . $item->getId() . '">' . $item->getTitle() . '</a></h2>'; } } public function viewAction() { $newsId = Mage::app()->getRequest()->getParam('id', 0); $news = Mage::getModel('dsnews/news')->load($newsId); if ($news->getId() > 0) { echo '<h1>' . $news->getTitle() . '</h1>'; echo '<div class="content">' . $news->getContent() . '</div>'; } else { $this->_forward('noRoute'); } } } 



Now the link http://site.com/news opens a list of news in the form of links, when you click on it, the page with the contents of the news will open.

In paragraph 1, the class node is added to the configuration file, in which the basic class prefixes for the DS_News_Model model and the DS_News_Model_Resource resource are written.

When requesting a model in the controller (item 5) Mage :: getModel ('dsnews / news') , the getModel function takes a string of type [model] / [class] from which the model class name is formed, where [model] is the name of the config node / global / models / [model] , from which the value of the class node is taken - DS_News_Model , and the value of [class] is added to this class prefix (the first letter in each word [class] is converted to the capital letter). For example, the DS_News_Model_News class is obtained from the dsnews / news line , and the DS_News_Model_News_Gallery class is obtained from the dsnews / news_gallery line .

In paragraph 2, the base news model DS_News_Model_News is created , in the constructor of which the resource $ this -> _ init ('dsnews / news') is initialized: the function takes the [model] / [class] line as parameters, where [model] is the name of the node config / global / models / [model] , and [class] is the name of the class. However, unlike the model, the initialization of the resource class uses the value of the config / global / models / [resourceModel] / class of the resource referenced by the model in the resourceModel node as a class prefix. As a result, when initializing the $ this -> _ init ('dsnews / news') resource, the DS_News_Model_Resource_News class will be initialized.

In clause 3, the DS_News_Model_Resource_News resource class is created, in which the $ this -> _ init table is initialized ('dsnews / table_news', 'news_id') : the first parameter is the path to the name of the desired table, and the second is the field used as the primary key ( PRIMARY KEY) table.

In clause 4, the object collection class is initialized, in the constructor of which the initial model DS_News_Model_News is initialized.

In paragraph 5, there is a change in the receipt of data from the database. This time models and collections are used. In the indexAction action, all news is requested by receiving the $ news = Mage :: getModel ('dsnews / news') -> getCollection () collection . The class name of the collection is calculated from the resource class name of the DS_News_Model_Resource_News + _Collection model.

In the viewAction action, the news is loaded by id received in the request. If the news with this ID exists, then the name of the news and content will be displayed. Otherwise, page 404 will be generated.



Step 8. Creating and using blocks
To output data to Magento, special blocks are used - these are code objects that are responsible for rendering a particular section of code. In step 5: Using the template for data output, the standard core / template block was used. In this step, blocks will be created for the news module to display the list and content of the news.

  1. Add node config / global / blocks
     <?xml version="1.0" ?> <config> ... <global> <blocks> <dsnews> <class>DS_News_Block</class> </dsnews> </blocks> ... </global> </config> 

  2. Create block class news block Block / News.php
     <?php class DS_News_Block_News extends Mage_Core_Block_Template { public function getNewsCollection() { $newsCollection = Mage::getModel('dsnews/news')->getCollection(); $newsCollection->setOrder('created', 'DESC'); return $newsCollection; } } 

  3. Create class block news block content View / View.php
     <?php class DS_News_Block_View extends Mage_Core_Block_Template { } 

  4. Update the configuration file / app/design/frontend/[package mine/[theme mine/layout/ds_news.xml
     <layout> <dsnews_index_index> <reference name="content"> <block type="dsnews/news" template="ds_news/index.phtml" /> </reference> </dsnews_index_index> <dsnews_index_view> <reference name="content"> <block type="dsnews/view" name="news.content" template="ds_news/view.phtml" /> </reference> </dsnews_index_view> </layout> 

  5. Modify the template file / app/design/frontend/[package mine/[theme mine/ template/ ds_news/index.phtml
     <?php $news = $this->getNewsCollection(); $newsViewUrl = Mage::getUrl('news/index/view'); ?> <h1>News</h1> <?php foreach ($news as $item): ?> <h2> <a href="<?php echo $newsViewUrl; ?>?id=<?php echo $item->getId(); ?>"> <?php echo $item->getTitle(); ?> </a> </h2> <?php endforeach; ?> 

  6. Create a template file / app/design/frontend/[package mine/[theme mine/ template/ ds_news/view.phtml
     <h1><?php echo $newsItem->getTitle(); ?></h1> <div class="content"><?php echo $newsItem->getContent(); ?></div> 

  7. Change controller controllers / IndexController.php
     <?php class DS_News_IndexController extends Mage_Core_Controller_Front_Action { public function indexAction() { $this->loadLayout(); $this->renderLayout(); } public function viewAction() { $newsId = Mage::app()->getRequest()->getParam('id', 0); $news = Mage::getModel('dsnews/news')->load($newsId); if ($news->getId() > 0) { $this->loadLayout(); $this->getLayout()->getBlock('news.content')->assign(array( "newsItem" => $news, )); $this->renderLayout(); } else { $this->_forward('noRoute'); } } } 



When you open the http://site.com/news/ page, the list of news will now be displayed as the contents of the page, and clicking on the news link will open the page with the news contents.

The block mechanism allows the use of display logic regardless of the controller. As you can see in step 3, the indexAction action is now only concerned with loading and displaying templates. The logic of receiving news now lies on the DS_News_Block_News block, and the display logic is on the /app/design/frontend/[package_///thetheme/template/dsnews/index.phtml template. Thus, for example, if the latest news list should appear on the sidebar of any other page, then it will be enough to connect the necessary block to the page without using the controller.

$this , / - .

1 , Magento . , (type) .xml . type [module]/[block] , [module]config/global/blocks/[module] , class ; a [block] — . type=«dsnews/news» DS_News_Block_News .

4 <reference name=«content» . , , . layout handle dsnews_index_view , <block type=«dsnews/view» name=«news.content» template=«ds_news/view.phtml» /> . viewAction assign , . .



Step 9. Creating an admin for the module
, , - . .

  1. controllers/Adminhtml/NewsController.php
     <?php class DS_News_Adminhtml_NewsController extends Mage_Adminhtml_Controller_Action { public function indexAction() { echo '<h1>News Module: Admin section</h1>'; } } 

  2. etc/config.xml
     <?xml version="1.0" ?> <config> ... <admin> <routers> <dsnews_admin> <use>admin</use> <args> <module>DS_News</module> <frontName>dsnews_admin</frontName> </args> </dsnews_admin> </routers> </admin> ... </config> 

  3. , etc/config.xml
     <?xml version="1.0" ?> <config> ... <adminhtml> <menu> <dsnews module="dsnews"> <title>News</title> <sort_order>77</sort_order> <action>dsnews_admin/adminhtml_news</action> </dsnews> </menu> </adminhtml> ... </config> 

  4. - Helper/Data.php
     <?php class DS_News_Helper_Data extends Mage_Core_Helper_Abstract { } 

  5. etc/config.xml
     <?xml version="1.0" ?> <config> ... <global> ... <helpers> <dsnews> <class>DS_News_Helper</class> </dsnews> </helpers> ... </global> ... </config> 



, , News , «News Module: Admin section» .

1 , 2 — config/admin/routers/[router] . , http://site.com/index.php/[frontName]/[controller]/index/ , [frontName]config/admin/routers/[router]/args/[frontName] , [controller] — , DS_News + [controller] + Controller . adminhtml_news DS_News_Adminhtml_NewsController . http://site.com/index.php/dsnews_admin/adminhtml_news/index/ .

, Magento - . , . 3 , .

4 5, , «Warning: include(Mage\DS\News\Helper\Data.php): failed to open stream: No such file or directory...» . Magento , module config/adminhtml/menu/[menu] . config/global/helpers/[helper] , . 4 5 -, .

, — frontName config/admin/routers config/frontend/routers .
( ), , HTTPS .



Step 10. Using blocks in admin panel
. .

  1. controllers/Adminhtml/NewsController.php
     <?php class DS_News_Adminhtml_NewsController extends Mage_Adminhtml_Controller_Action { public function indexAction() { $this->loadLayout(); $this->_setActiveMenu('dsnews'); $contentBlock = $this->getLayout()->createBlock('dsnews/adminhtml_news'); $this->_addContent($contentBlock); $this->renderLayout(); } } 

  2. Block/Adminhtml/News.php
     <?php class DS_News_Block_Adminhtml_News extends Mage_Adminhtml_Block_Abstract { public function _toHtml() { return '<h1>News Module: Admin section</h1>'; } } 



Magento , frontend ( ), backend () . .xml / ( 5: « »), , - Magento, . HTML « » . , /app/design/adminhtml/default/default/ , , .. .

1 $this->loadLayout() . $this->getLayout() : // . createBlock [module]/[block] , <block type="[module]/[block]" ; .



Step 11. Displaying the list in the Data Grid
, .

  1. Block/Adminhtml/News.php
     <?php class DS_News_Block_Adminhtml_News extends Mage_Adminhtml_Block_Widget_Grid_Container { protected function _construct() { parent::_construct(); $helper = Mage::helper('dsnews'); $this->_blockGroup = 'dsnews'; $this->_controller = 'adminhtml_news'; $this->_headerText = $helper->__('News Management'); $this->_addButtonLabel = $helper->__('Add News'); } } 

  2. Block/Adminhtml/News/Grid.php
     <?php class DS_News_Block_Adminhtml_News_Grid extends Mage_Adminhtml_Block_Widget_Grid { protected function _prepareCollection() { $collection = Mage::getModel('dsnews/news')->getCollection(); $this->setCollection($collection); return parent::_prepareCollection(); } protected function _prepareColumns() { $helper = Mage::helper('dsnews'); $this->addColumn('news_id', array( 'header' => $helper->__('News ID'), 'index' => 'news_id' )); $this->addColumn('title', array( 'header' => $helper->__('Title'), 'index' => 'title', 'type' => 'text', )); $this->addColumn('created', array( 'header' => $helper->__('Created'), 'index' => 'created', 'type' => 'date', )); return parent::_prepareColumns(); } } 



, 1 DS_News_Block_Adminhtml_News : [_blockGroup]/[_controller]_grid , _blockGroupconfig/global/blocks/[_blockGroup] . «dsnews/adminhtml_news_grid» .

_prepareColumns , . addColumn , — , index — , type — . Mage_Adminhtml_Block_Widget_Grid_Column .



Step 12. Bulk operations in Data Grid


  1. Block/Adminhtml/News/Grid.php _prepareMassaction
     <?php class DS_News_Block_Adminhtml_News_Grid extends Mage_Adminhtml_Block_Widget_Grid { protected function _prepareCollection(){ ... } protected function _prepareColumns(){ ... } protected function _prepareMassaction() { $this->setMassactionIdField('news_id'); $this->getMassactionBlock()->setFormFieldName('news'); $this->getMassactionBlock()->addItem('delete', array( 'label' => $this->__('Delete'), 'url' => $this->getUrl('*/*/massDelete'), )); return $this; } } 

  2. massDeleteAction controllers/Adminhtml/NewsController.php
     <?php class DS_News_Adminhtml_NewsController extends Mage_Adminhtml_Controller_Action { public function indexAction(){ ... } public function massDeleteAction() { $news = $this->getRequest()->getParam('news', null); if (is_array($news) && sizeof($news) > 0) { try { foreach ($news as $id) { Mage::getModel('dsnews/news')->setId($id)->delete(); } $this->_getSession()->addSuccess($this->__('Total of %d news have been deleted', sizeof($news))); } catch (Exception $e) { $this->_getSession()->addError($e->getMessage()); } } else { $this->_getSession()->addError($this->__('Please select news')); } $this->_redirect('*/*'); } } 



. , Actions Delete Submit .

1 _prepareMassaction , id- news_id , , id-. — , , . : . , . Mage_Adminhtml_Block_Catalog_Product_Grid .



Step 13. CRUD: Add, Edit, and Delete
,

  1. controllers/Adminhtml/NewsController.php
     <?php class DS_News_Adminhtml_NewsController extends Mage_Adminhtml_Controller_Action { public function indexAction() { $this->loadLayout()->_setActiveMenu('dsnews'); $this->_addContent($this->getLayout()->createBlock('dsnews/adminhtml_news')); $this->renderLayout(); } public function newAction() { $this->_forward('edit'); } public function editAction() { $id = (int) $this->getRequest()->getParam('id'); Mage::register('current_news', Mage::getModel('dsnews/news')->load($id)); $this->loadLayout()->_setActiveMenu('dsnews'); $this->_addContent($this->getLayout()->createBlock('dsnews/adminhtml_news_edit')); $this->renderLayout(); } public function saveAction() { if ($data = $this->getRequest()->getPost()) { try { $model = Mage::getModel('dsnews/news'); $model->setData($data)->setId($this->getRequest()->getParam('id')); if(!$model->getCreated()){ $model->setCreated(now()); } $model->save(); Mage::getSingleton('adminhtml/session')->addSuccess($this->__('News was saved successfully')); Mage::getSingleton('adminhtml/session')->setFormData(false); $this->_redirect('*/*/'); } catch (Exception $e) { Mage::getSingleton('adminhtml/session')->addError($e->getMessage()); Mage::getSingleton('adminhtml/session')->setFormData($data); $this->_redirect('*/*/edit', array( 'id' => $this->getRequest()->getParam('id') )); } return; } Mage::getSingleton('adminhtml/session')->addError($this->__('Unable to find item to save')); $this->_redirect('*/*/'); } public function deleteAction() { if ($id = $this->getRequest()->getParam('id')) { try { Mage::getModel('dsnews/news')->setId($id)->delete(); Mage::getSingleton('adminhtml/session')->addSuccess($this->__('News was deleted successfully')); } catch (Exception $e) { Mage::getSingleton('adminhtml/session')->addError($e->getMessage()); $this->_redirect('*/*/edit', array('id' => $id)); } } $this->_redirect('*/*/'); } public function massDeleteAction(){ ... } } 

  2. getRowUrl Block/Adminhtml/News/Grid.php
     <?php class DS_News_Block_Adminhtml_News_Grid extends Mage_Adminhtml_Block_Widget_Grid { protected function _prepareCollection(){ ... } protected function _prepareColumns(){ ... } protected function _prepareMassaction(){ ... } public function getRowUrl($model) { return $this->getUrl('*/*/edit', array( 'id' => $model->getId(), )); } } 

  3. Block/Adminhtml/News/Edit.php
     <?php class DS_News_Block_Adminhtml_News_Edit extends Mage_Adminhtml_Block_Widget_Form_Container { protected function _construct() { $this->_blockGroup = 'dsnews'; $this->_controller = 'adminhtml_news'; } public function getHeaderText() { $helper = Mage::helper('dsnews'); $model = Mage::registry('current_news'); if ($model->getId()) { return $helper->__("Edit News item '%s'", $this->escapeHtml($model->getTitle())); } else { return $helper->__("Add News item"); } } } 

  4. Block/Adminhtml/News/Edit/Form.php
     <?php class DS_News_Block_Adminhtml_News_Edit_Form extends Mage_Adminhtml_Block_Widget_Form { protected function _prepareForm() { $helper = Mage::helper('dsnews'); $model = Mage::registry('current_news'); $form = new Varien_Data_Form(array( 'id' => 'edit_form', 'action' => $this->getUrl('*/*/save', array( 'id' => $this->getRequest()->getParam('id') )), 'method' => 'post', 'enctype' => 'multipart/form-data' )); $this->setForm($form); $fieldset = $form->addFieldset('news_form', array('legend' => $helper->__('News Information'))); $fieldset->addField('title', 'text', array( 'label' => $helper->__('Title'), 'required' => true, 'name' => 'title', )); $fieldset->addField('content', 'editor', array( 'label' => $helper->__('Content'), 'required' => true, 'name' => 'content', )); $fieldset->addField('created', 'date', array( 'format' => Mage::app()->getLocale()->getDateFormat(Mage_Core_Model_Locale::FORMAT_TYPE_SHORT), 'image' => $this->getSkinUrl('images/grid-cal.gif'), 'label' => $helper->__('Created'), 'name' => 'created' )); $form->setUseContainer(true); if($data = Mage::getSingleton('adminhtml/session')->getFormData()){ $form->setValues($data); } else { $form->setValues($model->getData()); } return parent::_prepareForm(); } } 



Data Grid , Add News — .

1 new , edit , save delete , . new edit , new edit . , ( , ), . save
$model->setData($data)->setId(...) — , .. , setData . , .

2 Data Grid — . 3 , , [_blockGroup]/[_controller]_[_mode]_form , dsnews/adminhtml_news_edit_form ( _mode edit ).

. , new Varien_Data_Form id HTML id , .. JavaScript-, . , name .



Step 14. Using Tabs
, TAB-.

  1. controllers/Adminhtml/NewsController.php
     <?php class DS_News_Adminhtml_NewsController extends Mage_Adminhtml_Controller_Action { public function indexAction(){ ... } public function newAction(){ ... } public function editAction() { $id = (int) $this->getRequest()->getParam('id'); $model = Mage::getModel('dsnews/news'); if($data = Mage::getSingleton('adminhtml/session')->getFormData()){ $model->setData($data)->setId($id); } else { $model->load($id); } Mage::register('current_news', $model); $this->loadLayout()->_setActiveMenu('dsnews'); $this->_addLeft($this->getLayout()->createBlock('dsnews/adminhtml_news_edit_tabs')); $this->_addContent($this->getLayout()->createBlock('dsnews/adminhtml_news_edit')); $this->renderLayout(); } public function saveAction(){ ... } public function deleteAction() { ... } public function massDeleteAction(){ ... } } 

  2. Block/Adminhtml/News/Edit/Tabs.php
     <?php class DS_News_Block_Adminhtml_News_Edit_Tabs extends Mage_Adminhtml_Block_Widget_Tabs { public function __construct() { $helper = Mage::helper('dsnews'); parent::__construct(); $this->setId('news_tabs'); $this->setDestElementId('edit_form'); $this->setTitle($helper->__('News Information')); } protected function _prepareLayout() { $helper = Mage::helper('dsnews'); $this->addTab('general_section', array( 'label' => $helper->__('General Information'), 'title' => $helper->__('General Information'), 'content' => $this->getLayout()->createBlock('dsnews/adminhtml_news_edit_tabs_general')->toHtml(), )); $this->addTab('custom_section', array( 'label' => $helper->__('Custom Fields'), 'title' => $helper->__('Custom Fields'), 'content' => $this->getLayout()->createBlock('dsnews/adminhtml_news_edit_tabs_custom')->toHtml(), )); return parent::_prepareLayout(); } } 

  3. Block/Adminhtml/News/Edit/Form.php
     <?php class DS_News_Block_Adminhtml_News_Edit_Form extends Mage_Adminhtml_Block_Widget_Form { protected function _prepareForm() { $form = new Varien_Data_Form(array( 'id' => 'edit_form', 'action' => $this->getUrl('*/*/save', array( 'id' => $this->getRequest()->getParam('id') )), 'method' => 'post', 'enctype' => 'multipart/form-data' )); $form->setUseContainer(true); $this->setForm($form); return parent::_prepareForm(); } } 

  4. General Block/Adminhtml/News/Edit/Tabs/General.php
     <?php class DS_News_Block_Adminhtml_News_Edit_Tabs_General extends Mage_Adminhtml_Block_Widget_Form { protected function _prepareForm() { $helper = Mage::helper('dsnews'); $model = Mage::registry('current_news'); $form = new Varien_Data_Form(); $fieldset = $form->addFieldset('general_form', array( 'legend' => $helper->__('General Information') )); $fieldset->addField('title', 'text', array( 'label' => $helper->__('Title'), 'required' => true, 'name' => 'title', )); $fieldset->addField('content', 'editor', array( 'label' => $helper->__('Content'), 'required' => true, 'name' => 'content', )); $fieldset->addField('created', 'date', array( 'format' => Mage::app()->getLocale()->getDateFormat(Mage_Core_Model_Locale::FORMAT_TYPE_SHORT), 'image' => $this->getSkinUrl('images/grid-cal.gif'), 'label' => $helper->__('Created'), 'name' => 'created' )); $form->setValues($model->getData()); $this->setForm($form); return parent::_prepareForm(); } } 

  5. Custom
    Block/Adminhtml/News/Edit/Tabs/Custom.php
     <?php class DS_News_Block_Adminhtml_News_Edit_Tabs_Custom extends Mage_Adminhtml_Block_Widget { protected function _toHtml() { return '<h2>Custom Fields</h2>'; } } 



. 2 — , — . , General, .

, , . 2 - , _prepareLayout . . , General $form = new Varien_Data_Form() , , — .



Step 15. Adding an image loading field
.

  1. General Block/Adminhtml/News/Edit/Tabs/General.php
     <?php class DS_News_Block_Adminhtml_News_Edit_Tabs_General extends Mage_Adminhtml_Block_Widget_Form { protected function _prepareForm() { $helper = Mage::helper('dsnews'); $model = Mage::registry('current_news'); $form = new Varien_Data_Form(); $fieldset = $form->addFieldset('general_form', array('legend' => $helper->__('General Information'))); $fieldset->addField('title', 'text', array( 'label' => $helper->__('Title'), 'required' => true, 'name' => 'title', )); $fieldset->addField('content', 'editor', array( 'label' => $helper->__('Content'), 'required' => true, 'name' => 'content', )); $fieldset->addField('image', 'image', array( 'label' => $helper->__('Image'), 'name' => 'image', )); $fieldset->addField('created', 'date', array( 'format' => Mage::app()->getLocale()->getDateFormat(Mage_Core_Model_Locale::FORMAT_TYPE_SHORT), 'image' => $this->getSkinUrl('images/grid-cal.gif'), 'label' => $helper->__('Created'), 'name' => 'created' )); $formData = array_merge($model->getData(), array('image' => $model->getImageUrl())); $form->setValues($formData); $this->setForm($form); return parent::_prepareForm(); } } 

  2. Helper/Data.php
     <?php class DS_News_Helper_Data extends Mage_Core_Helper_Abstract { public function getImagePath($id = 0) { $path = Mage::getBaseDir('media') . '/ds_news'; if ($id) { return "{$path}/{$id}.jpg"; } else { return $path; } } public function getImageUrl($id = 0) { $url = Mage::getBaseUrl(Mage_Core_Model_Store::URL_TYPE_MEDIA) . 'ds_news/'; if ($id) { return $url . $id . '.jpg'; } else { return $url; } } } 

  3. Model/News.php
     <?php class DS_News_Model_News extends Mage_Core_Model_Abstract { protected function _construct() { parent::_construct(); $this->_init('dsnews/news'); } protected function _afterDelete() { $helper = Mage::helper('dsnews'); @unlink($helper->getImagePath($this->getId())); return parent::_afterDelete(); } public function getImageUrl() { $helper = Mage::helper('dsnews'); if ($this->getId() && file_exists($helper->getImagePath($this->getId()))) { return $helper->getImageUrl($this->getId()); } return null; } } 

  4. controllers/Adminhtml/NewsController.php
     <?php class DS_News_Adminhtml_NewsController extends Mage_Adminhtml_Controller_Action { public function indexAction(){ ... } public function newAction(){ ... } public function editAction(){ ... } public function saveAction() { $id = $this->getRequest()->getParam('id'); if ($data = $this->getRequest()->getPost()) { try { $helper = Mage::helper('dsnews'); $model = Mage::getModel('dsnews/news'); $model->setData($data)->setId($id); if (!$model->getCreated()) { $model->setCreated(now()); } $model->save(); $id = $model->getId(); if (isset($_FILES['image']['name']) && $_FILES['image']['name'] != '') { $uploader = new Varien_File_Uploader('image'); $uploader->setAllowedExtensions(array('jpg', 'jpeg')); $uploader->setAllowRenameFiles(false); $uploader->setFilesDispersion(false); $uploader->save($helper->getImagePath(), $id . '.jpg'); // Upload the image } else { if (isset($data['image']['delete']) && $data['image']['delete'] == 1) { @unlink($helper->getImagePath($id)); } } Mage::getSingleton('adminhtml/session')->addSuccess($this->__('News was saved successfully')); Mage::getSingleton('adminhtml/session')->setFormData(false); $this->_redirect('*/*/'); } catch (Exception $e) { Mage::getSingleton('adminhtml/session')->addError($e->getMessage()); Mage::getSingleton('adminhtml/session')->setFormData($data); $this->_redirect('*/*/edit', array( 'id' => $id )); } return; } Mage::getSingleton('adminhtml/session')->addError($this->__('Unable to find item to save')); $this->_redirect('*/*/'); } public function deleteAction(){ ... } public function massDeleteAction(){ ... } } 



JPG, . /media/ds_news , , . — URL , , — . , — image : $formData = array_merge($model->getData(), array('image' => $model->getImageUrl())) .



Step 16. Connect JavaScript / CSS files in admin panel


  1. /skin/adminhtml/default/default/ds_news/adminhtml/scripts.js
     console.log('DS News admin'); 

  2. /skin/adminhtml/default/default/ds_news/adminhtml/styles.css
     #general_form label { color: #FF0000; font-weight: bold; } 

  3. controllers/Adminhtml/NewsController.php
     <?php class DS_News_Adminhtml_NewsController extends Mage_Adminhtml_Controller_Action { public function indexAction(){ ... } public function newAction(){ ... } public function editAction() { $id = (int) $this->getRequest()->getParam('id'); $model = Mage::getModel('dsnews/news'); if ($data = Mage::getSingleton('adminhtml/session')->getFormData()) { $model->setData($data)->setId($id); } else { $model->load($id); } Mage::register('current_news', $model); $this->loadLayout()->_setActiveMenu('dsnews'); $this->getLayout()->getBlock('head')->addItem('skin_js', 'ds_news/adminhtml/scripts.js'); $this->getLayout()->getBlock('head')->addItem('skin_css', 'ds_news/adminhtml/styles.css'); $this->_addLeft($this->getLayout()->createBlock('dsnews/adminhtml_news_edit_tabs')); $this->_addContent($this->getLayout()->createBlock('dsnews/adminhtml_news_edit')); $this->renderLayout(); } public function saveAction(){ ... } public function deleteAction(){ ... } public function massDeleteAction(){ ... } } 



DS News admin , .

, , , : JS, . — /skin/adminhtml/default/default/ /js/ — . , , /js/ , — .

, /js/ /js/
 $this->getLayout()->getBlock('head')->addItem('skin_js', 'ds_news/adminhtml/scripts.js'); $this->getLayout()->getBlock('head')->addItem('skin_css', 'ds_news/adminhtml/styles.css'); 
on
 $this->getLayout()->getBlock('head')->addJs('ds_news/adminhtml/scripts.js'); $this->getLayout()->getBlock('head')->addItem('js_css', 'ds_news/adminhtml/styles.css'); 




Step 17. Update module: add categories for news
— : . , .

  1. sql/dsnews_setup/upgrade-0.0.1-0.0.2.php
     <?php echo '<h1>Upgrade DS News to version 0.0.2</h1>'; exit; $installer = $this; $tableCategories = $installer->getTable('dsnews/table_categories'); $tableNews = $installer->getTable('dsnews/table_news'); $installer->startSetup(); $installer->getConnection()->dropTable($tableCategories); $table = $installer->getConnection() ->newTable($tableCategories) ->addColumn('category_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( 'identity' => true, 'nullable' => false, 'primary' => true, )) ->addColumn('title', Varien_Db_Ddl_Table::TYPE_TEXT, '255', array( 'nullable' => false, )); $installer->getConnection()->createTable($table); $installer->getConnection()->addColumn($tableNews, 'category_id', array( 'comment' => 'News Category', 'default' => '0', 'nullable' => false, 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, )); $installer->endSetup(); 

  2. etc/config.xml
     <?xml version="1.0" ?> <config> <modules> <DS_News> <version>0.0.2</version> </DS_News> </modules> ... <global> ... <models> <dsnews> <class>DS_News_Model</class> <resourceModel>dsnews_resource</resourceModel> </dsnews> <dsnews_resource> <class>DS_News_Model_Resource</class> <entities> <table_categories> <table>ds_news_categories</table> </table_categories> <table_news> <table>ds_news_entities</table> </table_news> </entities> </dsnews_resource> </models> ... </global> </config> 

  3. Model/Category.php
     <?php class DS_News_Model_Category extends Mage_Core_Model_Abstract { protected function _construct() { parent::_construct(); $this->_init('dsnews/category'); } protected function _afterDelete() { $newsCollection = Mage::getModel('dsnews/news')->getCollection() ->addFieldToFilter('category_id', $this->getId()); foreach($newsCollection as $news){ $news->setCategoryId(0)->save(); } return parent::_afterDelete(); } } 

  4. Block/Adminhtml/News/Edit/Tabs/General.php
     <?php class DS_News_Block_Adminhtml_News_Edit_Tabs_General extends Mage_Adminhtml_Block_Widget_Form { protected function _prepareForm() { ... $fieldset->addField('category_id', 'select', array( 'label' => $helper->__('Category'), 'name' => 'category_id', 'values' => $helper->getCategoriesOptions(), )); ... } } 

  5. Helper/Data.php ,
     <?php class DS_News_Helper_Data extends Mage_Core_Helper_Abstract { public function getImagePath($id = 0){ ... } public function getImageUrl($id = 0){ ... } public function getCategoriesList() { $categories = Mage::getModel('dsnews/category')->getCollection()->load(); $output = array(); foreach($categories as $category){ $output[$category->getId()] = $category->getTitle(); } return $output; } public function getCategoriesOptions() { $categories = Mage::getModel('dsnews/category')->getCollection()->load(); $options = array(); $options[] = array( 'label' => '', 'value' => '' ); foreach ($categories as $category) { $options[] = array( 'label' => $category->getTitle(), 'value' => $category->getId(), ); } return $options; } } 

  6. Block/News/Grid.php
     <?php class DS_News_Block_Adminhtml_News_Grid extends Mage_Adminhtml_Block_Widget_Grid { protected function _prepareCollection(){ ... } protected function _prepareColumns() { $helper = Mage::helper('dsnews'); $this->addColumn('news_id', array( 'header' => $helper->__('News ID'), 'index' => 'news_id', 'width' => '100px', )); $this->addColumn('title', array( 'header' => $helper->__('Title'), 'index' => 'title', 'type' => 'text', )); $this->addColumn('category', array( 'header' => $helper->__('Category'), 'index' => 'category_id', 'options' => $helper->getCategoriesList(), 'type' => 'options', 'width' => '150px', )); $this->addColumn('created', array( 'header' => $helper->__('Created'), 'index' => 'created', 'type' => 'date', )); return parent::_prepareColumns(); } protected function _prepareMassaction(){ ... } public function getRowUrl($model){ ... } } 

  7. etc/config.xml
     <?xml version="1.0" ?> <config> ... <adminhtml> <menu> <dsnews module="dsnews"> <title>News</title> <sort_order>77</sort_order> <children> <dsnews_news translate="title" module="dsnews"> <title>News</title> <sort_order>10</sort_order> <action>dsnews_admin/adminhtml_news</action> </dsnews_news> <dsnews_category translate="title" module="dsnews"> <title>Categories</title> <sort_order>20</sort_order> <action>dsnews_admin/adminhtml_category</action> </dsnews_category> </children> </dsnews> </menu> </adminhtml> ... </config> 



«Upgrade DS News to version 0.0.2», — . core_resourcedsnews_setup 0.0.1 . , , comment , .

, , . , . , , . / — Magento ( /var/cache ), .. Magento !



Step 18. Displaying the Data Grid in the tab
,

  1. Model/Category.php
     <?php class DS_News_Model_Category extends Mage_Core_Model_Abstract { protected function _construct(){ ... } protected function _afterDelete() { foreach($this->getNewsCollection() as $news){ $news->setCategoryId(0)->save(); } return parent::_afterDelete(); } public function getNewsCollection() { $collection = Mage::getModel('dsnews/news')->getCollection(); $collection->addFieldToFilter('category_id', $this->getId()); return $collection; } } 

  2. Block/Adminhtml/Category/Edit/Tabs/News.php
     <?php class DS_News_Block_Adminhtml_Category_Edit_Tabs_News extends Mage_Adminhtml_Block_Widget_Grid { public function __construct() { parent::__construct(); $this->setId('categoryNewsGrid'); $this->setUseAjax(true); } protected function _prepareCollection() { $collection = Mage::registry('current_category')->getNewsCollection(); $this->setCollection($collection); return parent::_prepareCollection(); } protected function _prepareColumns() { $helper = Mage::helper('dsnews'); $this->addColumn('ajax_grid_news_id', array( 'header' => $helper->__('News ID'), 'index' => 'news_id', 'width' => '100px', )); $this->addColumn('ajax_grid_title', array( 'header' => $helper->__('Title'), 'index' => 'title', 'type' => 'text', )); $this->addColumn('ajax_grid_created', array( 'header' => $helper->__('Created'), 'index' => 'created', 'type' => 'date', )); return parent::_prepareColumns(); } public function getGridUrl() { return $this->getUrl('*/*/news', array('_current' => true)); } } 

  3. Block/Adminhtml/Category/Edit/Tabs.php
     <?php class DS_News_Block_Adminhtml_Category_Edit_Tabs extends Mage_Adminhtml_Block_Widget_Tabs { public function __construct(){ ... } protected function _prepareLayout() { $helper = Mage::helper('dsnews'); $this->addTab('general_section', array( 'label' => $helper->__('General Information'), 'title' => $helper->__('General Information'), 'content' => $this->getLayout()->createBlock('dsnews/adminhtml_category_edit_tabs_general')->toHtml(), )); $this->addTab('news_section', array( 'class' => 'ajax', 'label' => $helper->__('News'), 'title' => $helper->__('News'), 'url' => $this->getUrl('*/*/news', array('_current' => true)), )); return parent::_prepareLayout(); } } 

  4. controllers/Adminhtml/CategoryController.php ,
     <?php class DS_News_Adminhtml_CategoryController extends Mage_Adminhtml_Controller_Action { public function indexAction(){ ... } public function newAction(){ ... } public function editAction(){ ... } public function saveAction(){ ... } public function deleteAction(){ ... } public function newsAction() { $id = (int) $this->getRequest()->getParam('id'); $model = Mage::getModel('dsnews/category')->load($id); Mage::register('current_category', $model); if (Mage::app()->getRequest()->isAjax()) { $this->loadLayout(); echo $this->getLayout()->createBlock('dsnews/adminhtml_category_edit_tabs_news')->toHtml(); } } } 



, «News». , . , , , 3:
 <?php class DS_News_Block_Adminhtml_Category_Edit_Tabs extends Mage_Adminhtml_Block_Widget_Tabs { public function __construct(){ ... } protected function _prepareLayout() { $helper = Mage::helper('dsnews'); $category = Mage::registry('current_category'); $this->addTab('general_section', array( 'label' => $helper->__('General Information'), 'title' => $helper->__('General Information'), 'content' => $this->getLayout()->createBlock('dsnews/adminhtml_category_edit_tabs_general')->toHtml(), )); if($category->getId()){ $this->addTab('news_section', array( 'class' => 'ajax', 'label' => $helper->__('News'), 'title' => $helper->__('News'), 'url' => $this->getUrl('*/*/news', array('_current' => true)), )); } return parent::_prepareLayout(); } } 


, , 2 $this->setId('categoryNewsGrid') , JavaScript , , JavaScript (, ).

ajax_grid_ 2. , , .

, - , ajax ( ) /, . getGridUrl -. array('_current' => true) , , , id .



Step 19. Saving Selected Records in Data Grid
, / . , . , -, . , - . Mage_Adminhtml_Block_Widget_Grid_Serializer .

  1. Block/Adminhtml/Category/Edit/Tabs/News.php
     <?php class DS_News_Block_Adminhtml_Category_Edit_Tabs_News extends Mage_Adminhtml_Block_Widget_Grid { public function __construct() { parent::__construct(); $this->setDefaultFilter(array('ajax_grid_in_category' => 1)); $this->setId('categoryNewsGrid'); $this->setSaveParametersInSession(false); $this->setUseAjax(true); } protected function _prepareCollection() { $collection = Mage::getModel('dsnews/news')->getCollection(); $this->setCollection($collection); return parent::_prepareCollection(); } protected function _prepareColumns() { $helper = Mage::helper('dsnews'); $this->addColumn('ajax_grid_in_category', array( 'align' => 'center', 'header_css_class' => 'a-center', 'index' => 'news_id', 'type' => 'checkbox', 'values' => $this->getSelectedNews(), )); $this->addColumn('ajax_grid_news_id', array( 'header' => $helper->__('News ID'), 'index' => 'news_id', 'width' => '100px', )); $this->addColumn('ajax_grid_title', array( 'header' => $helper->__('Title'), 'index' => 'title', 'type' => 'text', )); $this->addColumn('ajax_grid_created', array( 'header' => $helper->__('Created'), 'index' => 'created', 'type' => 'date', )); return parent::_prepareColumns(); } protected function _addColumnFilterToCollection($column) { if ($column->getId() == 'ajax_grid_in_category') { $collection = $this->getCollection(); $selectedNews = $this->getSelectedNews(); if ($column->getFilter()->getValue()) { $collection->addFieldToFilter('news_id', array('in' => $selectedNews)); } elseif (!empty($selectedNews)) { $collection->addFieldToFilter('news_id', array('nin' => $selectedNews)); } } else { parent::_addColumnFilterToCollection($column); } return $this; } public function getGridUrl() { return $this->getUrl('*/*/news', array('_current' => true, 'grid_only' => 1)); } public function getSelectedNews() { if (!isset($this->_data['selected_news'])) { $selectedNews = Mage::app()->getRequest()->getParam('selected_news', null); if(is_null($selectedNews) || !is_array($selectedNews)){ $category = Mage::registry('current_category'); $selectedNews = $category->getNewsCollection()->getAllIds(); } $this->_data['selected_news'] = $selectedNews; } return $this->_data['selected_news']; } } 

  2. controllers/Adminhtml/CategoryController.php
     <?php class DS_News_Adminhtml_CategoryController extends Mage_Adminhtml_Controller_Action { public function indexAction(){ ... } public function newAction(){ ... } public function editAction(){ ... } public function saveAction() { $categoryId = $this->getRequest()->getParam('id'); if ($data = $this->getRequest()->getPost()) { try { $helper = Mage::helper('dsnews'); $model = Mage::getModel('dsnews/category'); $model->setData($data)->setId($categoryId); $model->save(); $categoryId = $model->getId(); $categoryNews = $model->getNewsCollection()->getAllIds(); if ($selectedNews = $this->getRequest()->getParam('selected_news', null)) { $selectedNews = Mage::helper('adminhtml/js')->decodeGridSerializedInput($selectedNews); } else { $selectedNews = array(); } $setCategory = array_diff($selectedNews, $categoryNews); $unsetCategory = array_diff($categoryNews, $selectedNews); foreach($setCategory as $id){ Mage::getModel('dsnews/news')->setId($id)->setCategoryId($categoryId)->save(); } foreach($unsetCategory as $id){ Mage::getModel('dsnews/news')->setId($id)->setCategoryId(0)->save(); } Mage::getSingleton('adminhtml/session')->addSuccess($this->__('Category was saved successfully')); Mage::getSingleton('adminhtml/session')->setFormData(false); $this->_redirect('*/*/'); } catch (Exception $e) { Mage::getSingleton('adminhtml/session')->addError($e->getMessage()); Mage::getSingleton('adminhtml/session')->setFormData($data); $this->_redirect('*/*/edit', array( 'id' => $categoryId )); } return; } Mage::getSingleton('adminhtml/session')->addError($this->__('Unable to find item to save')); $this->_redirect('*/*/'); } public function deleteAction(){ ... } public function newsAction() { $id = (int) $this->getRequest()->getParam('id'); $model = Mage::getModel('dsnews/category')->load($id); $request = Mage::app()->getRequest(); Mage::register('current_category', $model); if ($request->isAjax()) { $this->loadLayout(); $layout = $this->getLayout(); $root = $layout->createBlock('core/text_list', 'root', array('output' => 'toHtml')); $grid = $layout->createBlock('dsnews/adminhtml_category_edit_tabs_news'); $root->append($grid); if (!$request->getParam('grid_only')) { $serializer = $layout->createBlock('adminhtml/widget_grid_serializer'); $serializer->initSerializerBlock($grid, 'getSelectedNews', 'selected_news', 'selected_news'); $root->append($serializer); } $this->renderLayout(); } } } 



1, ajax_grid_in_category , / , ( / / ). _prepareCollection , _addColumnFilterToCollection . $this->setDefaultFilter(array('ajax_grid_in_category' => 1)); ( ). getSelectedNews , . , , POST- ( ), — .

. . $root = $layout->createBlock(...) root , . , , , . .

initSerializerBlock , . 4 : , , ( ), , getSelectedNews .



Step 20. Use beautiful URLs
— , HTML/CSS . — . , . . , , , .

  1. sql/dsnews_setup/upgrade-0.0.2-0.0.3.php
     <?php echo '<h1>Upgrade DS News to version 0.0.3</h1>'; exit; $installer = $this; $tableNews = $installer->getTable('dsnews/table_news'); $installer->startSetup(); $installer->getConnection() ->addColumn($tableNews, 'link', array( 'comment' => 'News URL link', 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, 'length' => '255', 'nullable' => true, )); $installer->getConnection() ->addKey($tableNews, 'IDX_UNIQUE_NEWS_LINK', 'link', Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE); foreach (Mage::getModel('dsnews/news')->getCollection() as $news) { try { $news->load($news->getId())->setDataChanges(true)->save(); } catch (Exception $e) { $news->setId($news->getId())->setLink($news->getId())->save(); } } $installer->endSetup(); 

  2. etc/config.xml
     <?xml version="1.0" ?> <config> <modules> <DS_News> <version>0.0.3</version> </DS_News> </modules> ... </config> 

  3. Helper/Data.php
     <?php class DS_News_Helper_Data extends Mage_Core_Helper_Abstract { public function getImagePath($id = 0){ ... } public function getImageUrl($id = 0){ ... } public function getCategoriesList(){ ... } public function getCategoriesOptions(){ ... } public function prepareUrl($url) { return trim(preg_replace('/-+/', '-', preg_replace('/[^a-z0-9]/sUi', '-', strtolower(trim($url)))), '-'); } } 

  4. Model/News.php
     <?php class DS_News_Model_News extends Mage_Core_Model_Abstract { protected function _construct(){ ... } protected function _afterDelete(){ ... } protected function _beforeSave() { $helper = Mage::helper('dsnews'); if (!$this->getData('link')) { $this->setData('link', $helper->prepareUrl($this->getTitle())); } else { $this->setData('link', $helper->prepareUrl($this->getData('link'))); } return parent::_beforeSave(); } public function getImageUrl(){ ... } } 

  5. Block/Adminhtml/News/Edit/Tabs/General.php
     <?php class DS_News_Block_Adminhtml_News_Edit_Tabs_General extends Mage_Adminhtml_Block_Widget_Form { protected function _prepareForm() { ... $fieldset->addField('link', 'text', array( 'label' => $helper->__('Link'), 'name' => 'link', )); ... } } 

  6. etc/config.xml
     <?xml version="1.0" ?> <config> ... <global> ... <events> <controller_front_init_routers> <observers> <dsnews> <class>DS_News_Controller_Router</class> <method>initControllerRouters</method> </dsnews> </observers> </controller_front_init_routers> </events> ... </global> </config> 

  7. Controller/Router.php
     <?php class DS_News_Controller_Router extends Mage_Core_Controller_Varien_Router_Abstract { public function initControllerRouters($observer) { $front = $observer->getEvent()->getFront(); $front->addRouter('dsnews', $this); } public function match(Zend_Controller_Request_Http $request) { $identifier = trim($request->getPathInfo(), '/'); $cmd = explode('/', $identifier); if ($cmd[0] == 'news') { if (count($cmd) == 1) { return $this->_fillRequest($request); } else { $model = Mage::getModel('dsnews/news')->load($cmd[1], 'link'); if ($model->getId()) { $params = array( 'id' => $model->getId() ); return $this->_fillRequest($request, $params, 'index', 'view'); } } } return false; } protected function _fillRequest($request, $cmd = array(), $controller = 'index', $action = 'index') { $request->setModuleName('news') ->setControllerName($controller) ->setActionName($action) ->setParam('is_routed', 1); if (is_array($cmd) && count($cmd)) { foreach ($cmd as $key => $value) { $request->setParam($key, $value); } } $request->setAlias(Mage_Core_Model_Url_Rewrite::REWRITE_REQUEST_PATH_ALIAS, $request->getPathInfo()); return true; } } 



Upgrade DS News to version 0.0.3 , — , Magento ( ). 1 5 — : . 7, 6 . http://site.com/news/{news-link}

. controller_front_init_routers , $this : $front->addRouter('dsnews', $this) . Mage_Core_Controller_Varien_Router_Abstract , match . , , . match , URL. , . true , , false . _fillRequest — , .

, _fillRequest $request->setModuleName('news') frontName config.xml ( frontend/routers/[module Name]/args/frontName ), .

, , .



Source codes for each step are available here .


Due to updating the code of examples, there may be situations when the code in the text will not match the code from the archive, or the code will not work as intended. Please write a message to the author in PM if you notice any inaccuracies or differences in the code.

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


All Articles