Introduction

This webinar is dedicated to creating the simple News module for X-Cart 5. In this module, admin can define news in back-end and customers can see them in the store-front.

Table of contents

Video

The process of module creation

1. Create an empty module

Run next-sdk’s macros: 

../../next-sdk/devkit/macros/create-module.php --module=Tony\\News --version=5.0

2. Create a News entity

Create the classes/XLite/Module/Tony/News/Model/News.php file with the following content:

<?php

namespace XLite\Module\Tony\News\Model;

/**
 * @Entity
 * @Table  (name="news")
 */
class News extends \XLite\Model\AEntity
{
   /**
    * @Id
    * @GeneratedValue (strategy="AUTO")
    * @Column         (type="integer")
    */
   protected $id;

   /**
    * @Column (type="boolean")
    */
   protected $enabled = true;

   /**
    * @Column (type="string", length=255)
    */
   protected $title = '';

   /**
    * @Column (type="text")
    */
   protected $body = '';
}

Create Repo for News model

Create the classes/XLite/Module/Tony/News/Model/Repo/News.php file with the following content: 

<?php

namespace XLite\Module\Tony\News\Model\Repo;

class News extends \XLite\Model\Repo\ARepo
{
   // {{{ Search

   const SEARCH_LIMIT = 'limit';

   public function search(\XLite\Core\CommonCell $cnd, $countOnly = false)
   {
       $queryBuilder = $this->createQueryBuilder('n');
       $this->currentSearchCnd = $cnd;

       foreach ($this->currentSearchCnd as $key => $value) {
           $this->callSearchConditionHandler($value, $key, $queryBuilder, $countOnly);
       }

       return $countOnly
           ? $this->searchCount($queryBuilder)
           : $this->searchResult($queryBuilder);
   }

   public function searchCount(\Doctrine\ORM\QueryBuilder $qb)
   {
       $qb->select('COUNT(DISTINCT n.id)');

       return intval($qb->getSingleScalarResult());
   }

   public function searchResult(\Doctrine\ORM\QueryBuilder $qb)
   {
       return $qb->getResult();
   }

   protected function callSearchConditionHandler($value, $key, \Doctrine\ORM\QueryBuilder $queryBuilder, $countOnly)
   {
       if ($this->isSearchParamHasHandler($key)) {
           $this->{'prepareCnd' . ucfirst($key)}($queryBuilder, $value, $countOnly);
       }
   }

   protected function isSearchParamHasHandler($param)
   {
       return in_array($param, $this->getHandlingSearchParams());
   }

   protected function getHandlingSearchParams()
   {
       return array(
           static::SEARCH_LIMIT,
       );
   }

   protected function prepareCndLimit(\Doctrine\ORM\QueryBuilder $queryBuilder, array $value)
   {
       call_user_func_array(array($this, 'assignFrame'), array_merge(array($queryBuilder), $value));
   }
}

3. Admin area: create a page that displays list of news

Create a page via macros of next-sdk:

../../next-sdk/devkit/macros/create-page.php --module=Tony\\News --target=news --interface=admin --menu=content

It creates the following files:

  • classes/XLite/Module/Tony/News/Controller/Admin/News.php

  • classes/XLite/Module/Tony/News/View/Page/Admin/News.php

  • skins/admin/en/modules/Tony/News/page/news/body.tpl

  • classes/XLite/Module/Tony/News/News/View/Menu/Admin/TopMenu.php

Change the classes/XLite/Module/Tony/News/View/Menu/Admin/TopMenu.php script and define new version of the defineItems() method:

protected function defineItems()
{
	$list = parent::defineItems();

    if (!isset($list['content'])) {
      	$list['content'] = array(
          	self::ITEM_TITLE      => 'Content',
            self::ITEM_TARGET     => news,
            self::ITEM_WEIGHT     => 600,
            self::ITEM_CHILDREN   => array(),
        );
    }        

  	$list['content'][static::ITEM_CHILDREN]['news'] = array(
   		static::ITEM_TITLE  => 'News',
        static::ITEM_TARGET => 'news',
    );

  	return $list;
}

Create ItemsList for News

Create the classes/XLite/Module/Tony/News/View/ItemsList/Model/News.php file with the following content:

<?php

namespace XLite\Module\Tony\News\View\ItemsList\Model;

class News extends \XLite\View\ItemsList\Model\Table
{

	protected function defineColumns()
	{
  		return array(
	    	'title' => array(
            	static::COLUMN_CLASS => 'XLite\View\FormField\Inline\Input\Text',
    			static::COLUMN_NAME => \XLite\Core\Translation::lbl('News title'),
	    		static::COLUMN_ORDERBY => 100,
    		),
   		);
 	}

	protected function defineRepositoryName()
	{
		return 'XLite\Module\Tony\News\Model\News';
	}

   protected function isSwitchable()
   {
       return true;
   }

   /**
    * Mark list as removable
    *
    * @return boolean
    */
   protected function isRemoved()
   {
       return true;
   } 

   protected function isCreation()
   {
       return static::CREATE_INLINE_TOP;
   }

   protected function getCreateURL()
   {
       return \XLite\Core\Converter::buildUrl('news_edit');
   }
}

Add News ItemsList to this page

Put the following code into the skins/admin/en/modules/Tony/News/page/news/body.tpl template:

<widget class="XLite\Module\Tony\News\View\ItemsList\Model\News" />

Add form for this ItemsList

Create the classes/XLite/Module/Tony/News/View/Form/ItemsList/News/Table.php file:

<?php

namespace XLite\Module\Tony\News\View\Form\ItemsList\News;

class Table extends \XLite\View\Form\ItemsList\AItemsList
{
 	protected function getDefaultTarget()
 	{
  		return 'news';
 	}

 	protected function getDefaultAction()
 	{
  		return 'update';
 	}
}

Update skins/admin/en/modules/Tony/News/page/news/body.tpl template and add the following code there:

<widget class="XLite\Module\Tony\News\View\Form\ItemsList\News\Table" name="list" />
 <widget class="XLite\Module\Tony\News\View\ItemsList\Model\News" />
<widget name="list" end />

Add ItemsList handler to Controller

Update Controller/Admin/News.php file and put the following code there:

protected function doActionUpdate()
{
 	$list = new \XLite\Module\Tony\News\View\ItemsList\Model\News;
    $list->processQuick();
}

Add page for target=news_edit

Add page via macros: 

../../next-sdk/devkit/macros/create-page.php --module=Tony\\News --target=news_edit --interface=admin

It creates following files:

  • classes/XLite/Module/Tony/News/Controller/Admin/NewsEdit.php

  • classes/XLite/Module/Tony/News/View/Page/Admin/NewsEdit.php

  • skins/admin/en/modules/Tony/News/page/news_edit/body.tpl

4. Admin area: news details page

Create classes/XLite/Module/Tony/News/View/Model/News.php file with the following code:

<?php

namespace XLite\Module\Tony\News\View\Model;

class News extends \XLite\View\Model\AModel
{
	protected $schemaDefault = array(
		'title' => array(
   			self::SCHEMA_CLASS => 'XLite\View\FormField\Input\Text',
   			self::SCHEMA_LABEL => 'News title',
   			self::SCHEMA_REQUIRED => true,
   		),
  		'body' => array(
			self::SCHEMA_CLASS => 'XLite\View\FormField\Textarea\Advanced',
   			self::SCHEMA_LABEL => 'Main text',
   			self::SCHEMA_REQUIRED => true,
   		),
  	);

 	protected function getDefaultModelObject()
 	{
  		$id = \XLite\Core\Request::getInstance()->id;

  		$model = $id
   			? \XLite\Core\Database::getRepo('XLite\Module\Tony\News\Model\News')->find($id)
   			: null;

  		return $model ?: new \XLite\Module\Tony\News\Model\News;
 	}

 	protected function getFormClass()
 	{
  		return '\XLite\Module\Tony\News\View\Form\Model\News';
 	}

   	protected function getFormButtons()
   	{
       	$result = parent::getFormButtons();

       	$result['update'] = new \XLite\View\Button\Submit(
           	array(
               	\XLite\View\Button\AButton::PARAM_LABEL  => 'Update',
           	)
       	);

       return $result;
   	} 
}

Create form class for model editing

Create the classes/XLite/Module/Tony/News/View/Form/Model/News.php file with the following code:

<?php

namespace XLite\Module\Tony\News\View\Form\Model;

class News extends \XLite\View\Form\AForm
{
 	protected function getDefaultTarget()
 	{
  		return 'news_edit';
 	}

 	protected function getDefaultAction()
 	{
  		return 'update';
 	}
}

Update template

Edit the skins/admin/en/modules/Tony/News/page/news_edit/body.tpl template and add the following code there:

<widget class="XLite\Module\Tony\News\View\Model\News" useBodyTemplate="1" />

Update controller

Edit the classes/XLite/Module/Tony/News/Controller/Admin/NewsEdit.php file and add the following code there:

	protected $params = array('target', 'id');

 	protected function getModelFormClass()
 	{
  		return 'XLite\Module\Tony\News\View\Model\News';
 	}

 	protected function doActionUpdate()
 	{
  		$this->getModelForm()->performAction('modify');

  		if (!\XLite\Core\Request::getInstance()->id) {
   			$this->setReturnURL(
    			$this->buildURL(
     				'news_edit',
     				'',
     				array('id' => $this->getModelForm()->getModelObject()->getId())
     			)
    		);
  		}
 	}

5. Customer area: adding news menu

Create the classes/XLite/Module/Tony/News/View/NewsMenu.php file with the following content:

<?php

namespace XLite\Module\Tony\News\View;

/**
 * @ListChild (list="sidebar.first", zone="customer", weight="500")
 */

class NewsMenu extends \XLite\View\SideBarBox
{
 	protected function getHead()
 	{
  		return 'News';
 	}

 	protected function getDir()
 	{
  		return 'modules/Tony/News/menu';
 	}
}

Create the skins/default/en/modules/Tony/News/menu/body.tpl template with the following code:

{if:getNews()}
<ul class="menu menu-list news">
 {foreach:getNews(),row}
 <li><a href="{buildURL(#news#,##,_ARRAY_(#news_id#^row.id))}">{row.title}</a></li>
 {end:}
</ul>
{else:}
No news added
{end:}

Add getNews() method to classes/XLite/Module/Tony/News/View/NewsMenu.php file:

protected function getNews()
{
  	return \XLite\Core\Database::getRepo('\XLite\Module\Tony\News\Model\News')->findAll();
}

6. Customer area: news details page

Create page via macros:

../../next-sdk/devkit/macros/create-page.php --module=Tony\\News --target=news --interface=customer

The following files are created:

  • classes/XLite/Module/Tony/News/Controller/Customer/News.php

  • classes/XLite/Module/Tony/News/View/Page/Customer/News.php

  • skins/default/en/modules/Tony/News/page/news/body.tpl

Update template skins/default/en/modules/Tony/News/page/news/body.tpl and add the following code there: 

{getNewsBody():h}

Update the classes/XLite/Module/Tony/News/Controller/Customer/News.php controller:

protected $params = array('target', 'news_id');

public function getNewsBody()
{
  	$id = intval(\XLite\Core\Request::getInstance()->news_id);

  	$return = '';

  	if ($id != 0)
  	{
   		$news = \XLite\Core\Database::getRepo('XLite\Module\Tony\News\Model\News')->find($id);

   		if ($news) {
    		$return = $news->getBody();
   		}
  	}

  	return $return;
}

7. Mod is finished 

Run the final cache rebuild process and check the results.

Questions & Answers

How can I disable cache?

Cache regeneration process is an essential part of building working code of X-Cart 5. You cannot disable it.

If you only change templates, you can set the developer_mode option as On in the etc/config.php script and you will not need to rebuild the cache in order to see template changes.

We are looking for a solution that would allow cache rebuild process complete faster, so it would be less annoying.

How can we easily debug our code then?

You should debug code in the <X-Cart 5>/var/run/ folder. This is the actual PHP code that is run at the moment. If you change anything there, it will have an immediate effect. However, all custom code in the <X-Cart 5>/var/run folder will be lost after next cache rebuilding process.

How do you add a new field to product without modifying core code?

First of all, we are planning to write a thorough article describing the decoration approach that aims to help with such tasks.

In short words, you can add a new field to the product model by adding the classes/XLite/Module/Tony/News/Model/Product.php with the following content:

<?php
namespace XLite\Module\Tony\News\Model;
class Product extends \XLite\Model\Product implements \XLite\Base\IDecorator
{
	/**
	 * @Column (type="string", length=255)
	 */
	protected $secondary_name;
}

After you rebuild the cache, the xc_products table will have a new field called secondary_name. 

Is there a reason that events were not used in the code? 

We are introducing decoration approach which is more powerful than hooks. When you are using decoration, you can change absolutely everything in the core, but when you are using hooks you can only add your custom code to parts that support these hooks.

Is there an easy way to determine the proper template file to modify for a given page section?

We are preparing the special mod that will help determine proper template. It will come in April/May 2014. Stay tuned for updates!

Where we can get macros like macros/create-module.php?

You can get it here: https://github.com/xcart/next-sdk

Can we modify the REST API in X-Cart 5 on our own?

Yes, sure. As well as anything else in X-Cart 5.

How can I check other module already installed/exists and if it is not installed?

The simplest way to check whether module is enabled shown below:

<?php

// init X-Cart 5
require 'top.inc.php';

// what module we are looking for
$developerId = 'Tony';
$moduleName = 'News';

$module = \XLite\Core\Database::getRepo('XLite\Model\Module')->findOneBy(array('author' => $developerId, 'name' => $moduleName));

if (is_object($module)) {
	if ($module->getEnabled()) {
		echo 'Module is enabled';
	} else {
		echo 'Module is disabled';
	}
} else {
	echo 'Module does not exist';
}

This script is supposed to be placed into the X-Cart 5 root folder.