How to schedule the future product activation
Introduction
This article will help you to understand basic customizations for catalog products and performing custom cron jobs in Magento.
Functionality
Before start the development you should imagine what it does and choose the functionality that fits all the requirements.
The requirements for the module are the following:
- It should allow an admin user to specify the activation and expiration dates for a product.
- It should check products for the specified dates and perform the following actions depend on the date types:
- Active the product if the activation date came true
- Active the product if the expiry date came true
So the module development should follow these steps:
- Create a custom attribute back-end model to allow storing date for datetime attribute (Magento native eav implementation removes the time part from a datetime string)
- Create two attributes for a product, that might be fulfilled with activation and expiry dates and assign custom attribute backend model to them
- Create an event observer that should handle product edit block rendering to enable time specifying for the date fields
- Create a cron job that checks the activation and expiry dates for products and enables/disables them
Basic Structure
Now you know how it should work, so you can create a basic module structure.
The structure of the module should contain the following:
- the module bootstrap and main configuration files
- an SQL upgrade script for the attributes adding and a setup model
- the event observer class that also will contain the cron job
- the custom attribute backend model
You should name the module before its structure creation. The module name consists of the module namespace and an internal module name separated by the underscore sign. So the name of this module will be “EcomDev_ScheduledProduct”, where “EcomDev” it’s my extension namespace.
First of all you should create the module bootstrap configuration file to enable your module. Bootstrap files located in “app/etc/modules” folder. The name of file should be the same as the module name.
So you should create file “EcomDev_ScheduledProduct.xml”, the file structure should contain the module name, its code pool, activity state and dependence node for specifying of required modules. So it should be like the following:
<?xml version="1.0" encoding="UTF-8"?>
<config>
<modules>
<EcomDev_ScheduledProduct>
<active>true</active> <!-- the module activity state -->
<codePool>community</codePool> <!-- the module code pool -->
<depends>
<Mage_Catalog /> <!-- the module depends on Mage_Catalog module, so it mentioned here -->
</depends>
</EcomDev_ScheduledProduct>
</modules>
</config>
The module was specified to be placed in community code pool, so it should be placed in “app/code/community” folder and its path should be “app/code/community/EcomDev/ScheduledProduct”.
Now you should create the module configuration file, where you’ll specify the model namespace and setup script initialization statements.
Let’s create it:
“app/code/community/EcomDev/ScheduledProduct/etc/config.xml”
<?xml version="1.0" encoding="UTF-8"?>
<config>
<modules>
<EcomDev_ScheduledProduct>
<version>1.0.0</version>
</EcomDev_ScheduledProduct>
</modules>
<global>
<models>
<ecomdev_scheduledproduct>
<!-- specification of model name space,
so we can call models like Mage::getModel('ecomdev_scheduledproduct/somemodelname') -->
<class>EcomDev_ScheduledProduct_Model</class>
</ecomdev_scheduledproduct>
</models>
<resources>
<!-- specifying of setup model and setup script path in sql folder -->
<ecomdev_scheduledproduct_setup>
<setup>
<module>EcomDev_ScheduledProduct</module>
<class>EcomDev_ScheduledProduct_Model_Mysql4_Setup</class>
</setup>
<connection>
<!-- use catalog connection to modify products -->
<use>catalog_setup</use>
</connection>
</ecomdev_scheduledproduct_setup>
</resources>
</global>
</config>
Attribute Backend Model
Before creation of setup script you need to take care of datetime attribute backend model to specify it in setup. Create it in module “Model” folder. In this module you should call it “EcomDev_ScheduledProduct_Model_Attribute_Backend_Datetime”, the path “Attribute_Backend_Datetime” explains what the model does. In it you should override “beforeSave($object)”, “afterLoad($object)” and “formatDate($date)” methods to change the logic of your attributes storage. You might like also to add “compareDateToCurrent($date)” method to check activation or expiration date came true before product save. The model should be extended from “Mage_Eav_Model_Entity_Attribute_Backend_Datetime”.
“app/code/community/EcomDev/ScheduledProduct/Model/Attribute/Backend/Datetime.php”
<?php
/**
* Expiry and Activation dates custom backend model
*
*/
class EcomDev_ScheduledProduct_Model_Attribute_Backend_Datetime extends Mage_Eav_Model_Entity_Attribute_Backend_Datetime
{
/**
* Activation date attribute code
*
* @var string
*/
const ATTRIBUTE_ACTIVATION_DATE = 'ecomdev_activation_date';
/**
* Expiry date attribute code
*
* @var string
*/
const ATTRIBUTE_EXPIRY_DATE = 'ecomdev_expiry_date';
/**
* Status attribute code
*
* @var string
*/
const ATTRIBUTE_STATUS = 'status';
/**
* Checks date to update product status
* on the save in the admin panel
*
* @param Mage_Catalog_Model_Product $object
* @return EcomDev_ScheduledProduct_Model_Attribute_Backend_Datetime
*/
public function beforeSave($object)
{
parent::beforeSave($object);
$code = $this->getAttribute()->getAttributeCode();
$compareResult = $this->compareDateToCurrent($object->getData($code));
if ($compareResult !== false) {
// If the date is set
if (($compareResult < 0 && $code == self::ATTRIBUTE_ACTIVATION_DATE) ||
($compareResult >= 0 && $code == self::ATTRIBUTE_EXPIRY_DATE)) {
// If the date is in the past and it's activation date
// or the date is in the future and it's expiry date,
// so the product should be deactivated
$object->setData(
self::ATTRIBUTE_STATUS,
Mage_Catalog_Model_Product_Status::STATUS_DISABLED
);
}
}
return $this;
}
/**
* Magento native function doesn't save
* the time part of date so the logic of retrieving is changed
*
* @param string|int $date
* @return string|null
*/
public function formatDate($date)
{
if (empty($date)) {
return null;
} elseif (!($date instanceof Zend_Date)) {
// Parse locale representation of the date, eg. parse user input from date field
$dateString = $date;
$usedDateFormat = Mage::app()->getLocale()->getDateTimeFormat(
Mage_Core_Model_Locale::FORMAT_TYPE_SHORT
);
// Instantiate date object in current locale
$date = Mage::app()->getLocale()->date();
$date->set($dateString, $usedDateFormat);
}
// Set system timezone for date object
$date->setTimezone(Mage_Core_Model_Locale::DEFAULT_TIMEZONE);
return $date->toString(Varien_Date::DATETIME_INTERNAL_FORMAT);
}
/**
* Compare date to current date
*
* Returns -1 if the date is in the past, and 1 if it's in the future,
* returns 0 if the dates are equal.
*
* @param string $date
* @return int
*/
public function compareDateToCurrent($date)
{
if (empty($date)) {
return false;
}
$compareDate = Mage::app()->getLocale()->date($date, Varien_Date::DATETIME_INTERNAL_FORMAT);
$currentDate = Mage::app()->getLocale()->date();
return $currentDate->compare($compareDate);
}
/**
* Converts timezone after object load, fixes issue in the core form element
*
* @param Mage_Core_Model_Abstract $object
* @return EcomDev_ScheduledProduct_Model_Attribute_Backend_Datetime
*/
public function afterLoad($object)
{
$code = $this->getAttribute()->getAttributeCode();
if ($object->getData($code) && !($object->getData($code) instanceof Zend_Date)) {
$date = Mage::app()->getLocale()->date();
$dateString = $object->getData($code);
$currentTimezone = $date->getTimezone();
$date->setTimezone(Mage_Core_Model_Locale::DEFAULT_TIMEZONE);
$date->set($dateString, Varien_Date::DATETIME_INTERNAL_FORMAT);
$date->setTimezone($currentTimezone);
$object->setData($code, $date);
}
return parent::afterLoad($object);
}
}
Setup
Now you need to create setup model and setup script. In the configuration file, the setup model class name is “EcomDev_ScheduledProduct_Model_Mysql4_Setup”. The module extends the catalog functionality, so the module setup model extends from the catalog setup model.
“app/code/community/EcomDev/ScheduledProduct/Model/Mysql4/Setup.php”
<?php
/**
* Setup model for scheduled product module, extended from catalog module setup
*/
class EcomDev_ScheduledProduct_Model_Mysql4_Setup extends Mage_Catalog_Model_Resource_Eav_Mysql4_Setup
{
}
And you need to create the setup script in “sql/ecomdev_scheduledproduct_setup” folder. The module version is 1.0.0 and it’s actually the first version, so you need to name it “mysql4-install-1.0.0.php”. This script should contain adding of the attributes to EAV. Also for this module we should add columns to “catalog_product_entity” table, because it will speed up the product mass status update process.
“app/code/community/EcomDev/ScheduledProduct/sql/ecomdev_scheduledproduct_setup/mysql4-install-1.0.0.php”
<?php
/* @var $this EcomDev_ScheduledProduct_Model_Mysql4_Setup */
$this->startSetup();
// For performance reasons we should add this fields to main entity table
// Activation date column adding to product entity table
$this->getConnection()->addColumn(
$this->getTable('catalog/product'),
EcomDev_ScheduledProduct_Model_Attribute_Backend_Datetime::ATTRIBUTE_ACTIVATION_DATE,
'DATETIME DEFAULT NULL'
);
// Expiry date column adding to product entity table
$this->getConnection()->addColumn(
$this->getTable('catalog/product'),
EcomDev_ScheduledProduct_Model_Attribute_Backend_Datetime::ATTRIBUTE_EXPIRY_DATE,
'DATETIME DEFAULT NULL'
);
// Activation date attribute information adding to the product entity
$this->addAttribute(
'catalog_product',
EcomDev_ScheduledProduct_Model_Attribute_Backend_Datetime::ATTRIBUTE_ACTIVATION_DATE,
array(
'type' => 'static',
'input' => 'date',
'label' => 'Activation Date',
'global' => Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_GLOBAL,
'backend' => 'ecomdev_scheduledproduct/attribute_backend_datetime',
'visible' => 1,
'required' => 0,
'position' => 10,
'group' => 'Schedule Settings'
)
);
// Expiry date attribute information adding to the product entity
$this->addAttribute(
'catalog_product',
EcomDev_ScheduledProduct_Model_Attribute_Backend_Datetime::ATTRIBUTE_EXPIRY_DATE,
array(
'type' => 'static',
'input' => 'date',
'label' => 'Expiry Date',
'global' => Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_GLOBAL,
'backend' => 'ecomdev_scheduledproduct/attribute_backend_datetime',
'visible' => 1,
'required' => 0,
'position' => 20,
'group' => 'Schedule Settings'
)
);
$this->endSetup();
Event Observer
After creation of the setup script and the back-end model, you need to take care about the input field rendering for the attributes, that’s why you need to observe “adminhtml_catalog_product_edit_prepare_form” event. So you should create “EcomDev_ScheduledProduct_Model_Observer” class with “observeProductEditFortInitialization(Varien_Event_Observer $observer)” method. It should check the form object for elements with the date attributes code and add datetime format to them if found any.
<?php
/**
* Observer for core events handling
*
*/
class EcomDev_ScheduledProduct_Model_Observer
{
/**
* Observes event 'adminhtml_catalog_product_edit_prepare_form'
* and adds custom format for date input
*
* @param Varien_Event_Observer $observer
* @return void
*/
public function observeProductEditFortInitialization(Varien_Event_Observer $observer)
{
$form = $observer->getEvent()->getForm();
$elementsToCheck = array(
EcomDev_ScheduledProduct_Model_Attribute_Backend_Datetime::ATTRIBUTE_ACTIVATION_DATE,
EcomDev_ScheduledProduct_Model_Attribute_Backend_Datetime::ATTRIBUTE_EXPIRY_DATE
);
foreach ($elementsToCheck as $elementCode) {
$element = $form->getElement($elementCode);
if (!$element) {
continue;
}
$element->setFormat(
Mage::app()->getLocale()->getDateTimeFormat(
Mage_Core_Model_Locale::FORMAT_TYPE_SHORT
)
);
$element->setTime(true);
}
}
}
Also you need to define your event observer in the module configuration file (config.xml):
<config>
<!-- here goes the code you've created before
...
...
-->
<adminhtml>
<events>
<adminhtml_catalog_product_edit_prepare_form>
<observers>
<ecomdev_scheduledproduct>
<type>singleton</type>
<model>ecomdev_scheduledproduct/observer</model>
<method>observeProductEditFortInitialization</method>
</ecomdev_scheduledproduct>
</observers>
</adminhtml_catalog_product_edit_prepare_form>
</events>
</adminhtml>
</config>
Cron Job
When you’ve done with setting up the admin interface for your attributes you need to create a cron job that automatically activates/deactivates products. You can put the logic in “EcomDev_ScheduledProduct_Model_Observer” class, because realization of cron job processing calls is similar to event processing, except you wont get $observer argument.
<?php
/**
* Observer for core events handling and cron jobs processing
*
*/
class EcomDev_ScheduledProduct_Model_Observer
{
/*
* here goes the code you've created before
* ............
* ............
*/
/**
* Cron job for processing of scheduled products
*
* @return void
*/
public function cronProcessScheduledProducts()
{
$currentDate = Mage::app()->getLocale()->date()->toString(
Varien_Date::DATETIME_INTERNAL_FORMAT
);
$productModel = Mage::getModel('catalog/product');
/* @var $expiredProductsCollection Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Collection */
// Prepare collection of scheduled for expiry but haven't yet deactivated products
$expiredProductsCollection = $productModel->getCollection()
// Add filter for expired but products haven't yet deactivated
->addFieldToFilter(
EcomDev_ScheduledProduct_Model_Attribute_Backend_Datetime::ATTRIBUTE_EXPIRY_DATE,
array(
'nnull' => 1, // Specifies that date shouldn't be empty
'lteq' => $currentDate // And lower than current date
)
)
->addFieldToFilter(
EcomDev_ScheduledProduct_Model_Attribute_Backend_Datetime::ATTRIBUTE_STATUS,
Mage_Catalog_Model_Product_Status::STATUS_ENABLED
);
// Retrieve product ids for deactivation
$expiredProductIds = $expiredProductsCollection->getAllIds();
unset($expiredProductsCollection);
if ($expiredProductIds) {
Mage::getSingleton('catalog/product_action')
->updateAttributes(
$expiredProductIds,
array('status' => Mage_Catalog_Model_Product_Status::STATUS_DISABLED),
Mage_Core_Model_App::ADMIN_STORE_ID
);
}
/* @var $expiredProductsCollection Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Collection */
// Prepare collection of scheduled for activation but haven't yet activated products
$activatedProductsCollection = $productModel->getCollection()
->addFieldToFilter(
EcomDev_ScheduledProduct_Model_Attribute_Backend_Datetime::ATTRIBUTE_ACTIVATION_DATE,
array(
'nnull' => 1, // Specifies that date shouldn't be empty
'lteq' => $currentDate // And lower than current date
)
)
// Exclude expired products
->addFieldToFilter(
EcomDev_ScheduledProduct_Model_Attribute_Backend_Datetime::ATTRIBUTE_EXPIRY_DATE,
array(
array('null' => 1), // Specifies that date shouldn't be empty
array('gt' => $currentDate) // And greater than current date
)
)
->addFieldToFilter(
EcomDev_ScheduledProduct_Model_Attribute_Backend_Datetime::ATTRIBUTE_STATUS,
Mage_Catalog_Model_Product_Status::STATUS_DISABLED
);
// Retrieve product ids for activation
$activatedProductIds = $activatedProductsCollection->getAllIds();
unset($activatedProductsCollection);
if ($activatedProductIds) {
Mage::getSingleton('catalog/product_action')
->updateAttributes(
$activatedProductIds,
array('status' => Mage_Catalog_Model_Product_Status::STATUS_ENABLED),
Mage_Core_Model_App::ADMIN_STORE_ID
);
}
}
}
And of course you should define your cron job in the configuration file (config.xml):
<config>
<!-- here goes the code you've created before
...
...
-->
<crontab>
<jobs>
<ecomdev_scheduledproduct_process_schedule>
<schedule>
<!-- Schedule for every 5 minutes -->
<cron_expr>*/5 * * * *</cron_expr>
</schedule>
<run>
<model>ecomdev_scheduledproduct/observer::cronProcessScheduledProducts</model>
</run>
</ecomdev_scheduledproduct_process_schedule>
</jobs>
</crontab>
</config>
Result
Now you should login to the admin panel, navigate “System -> Cache Management -> Flush Magento Cache” to enable your extension.
You can find the fields you’ve created at “Catalog -> Manage Products -> Edit/Add Product” page in “Schedule Settings” tab.
And don’t forget to setup Magento cron in your system crontab.
Conclusion
Now you know how to create simple customization for catalog with using of cron jobs in Magento.
The module presented here available at Magento Connect as free community extension: Scheduled Products
Categories
Recent posts
- CheckItOut! 1.3.0 will make your customer much more happier!
- Test Driven Magento Development Seminar Video and Code
- Register for Free Magento Development Seminar!
- Checkout the new office of superior Magento development
- EcomDev breaks Magento speed record!
Recent comments
- Praful Rajput on Adding order attribute to the orders grid in Magento 1.4.1
- Omar on Adding order attribute to the orders grid in Magento 1.4.1
- Paulo on Estimate shipping rates on the product page
- jenish on Custom configuration fields in Magento
- Homero on Adding order attribute to the orders grid in Magento 1.4.1

26 comments
plz tell me in the last step where to add the event observer? which file to add? and aslo where to store the cron file?
Written by shiva
23 September 2010 on 2:25 pmAll the files are in the community extension. You will get familiar with Magento files structure by reading this article on MagentoCommerce.Com knowledge base: http://www.magentocommerce.com/knowledge-base/entry/magento-for-dev-part-1-introduction-to-magento
Actually the model class name indicates its path, just replace all underscores with slashes.
Written by Ivan Chepurnyi
30 September 2010 on 1:28 amHi,
Can these two attributes, that control the product’s behaviour, be exported and imported as a CSV file from the System -> Import/Export Profiles ?
I tried with a product and the fields come out empty or not at all, any ideas?
Thank you for your time reading this.
Written by Dimitrios Mistriotis
11 October 2010 on 2:38 pmHi,
Really creat solution. We would like to use the extension, but we are unable to download it from MagentoConnect. Could you help us?
Best regards,
Otto Smittenaar
Written by Otto Smittenaar
18 October 2010 on 2:08 pmYou should use Magento Connect.
Written by Ivan Chepurnyi
19 October 2010 on 6:42 pmHave you added fields mapping via creating of separate profile?
Written by Ivan Chepurnyi
19 October 2010 on 6:50 pmi want to make a module realted to social networking like facebook, twitter in the backend i want to manage user info like user email, no of hits and comments how can we do that can u help me i am just beginner of magento.. please help me still waiting for your answer…
thanks a lot in advance
Written by thegreat
16 November 2010 on 4:41 pmIt works perfectly on Magento 1.4.1.1 but I need this solution for 1.3.2.4 version! What I must change or what I must do so that works on this version to? Thanks!
Written by polish01
16 December 2010 on 3:43 pmHi, you need to port implementation of Mage_Catalog_Model_Product_Action::updateAttributes() method from 1.4.1 implementation to 1.3.2.4.
It used to disable / enable group of the products in my extension with running of index rebuild afterwards.
Written by Ivan Chepurnyi
16 December 2010 on 4:12 pmI don’t understand what do You mean with “you need to port implementation of Mage_Catalog_Model_Product_Action::updateAttributes() method from 1.4.1 implementation to 1.3.2.4.” What I must change in PHP file and in which file(s)?
Written by polish01
17 December 2010 on 9:25 amI mean that there is no proper implementation in 1.3.2 for Mass Attribute Updates, that can work in less than a second. That’s why the module is not compatible with 1.3.2. In my previous message I gave you a suggestion about placement of the code that you should copy to 1.3.2, but I am not guarantee that it will work properly without some possible modifications.
Written by Ivan Chepurnyi
17 December 2010 on 11:00 amhi,
How can i show the expiry date in frontend product page.
Written by ataoglu78
13 April 2011 on 10:09 amDoes this code do the trick?
Written by Ivan Chepurnyi
14 April 2011 on 12:14 pmHi,
Is it possible this extension to be easily changed to schedule categories instead products?
Written by venz
11 May 2011 on 1:34 pm…and also I’m wondering if it works on the latest 1.5.1.0 version
Written by venz
11 May 2011 on 2:20 pmit works on magento 1.5.1 so far.
but i cant import dates through csv. when exporting it shows “2001-11-30 00:00:00″, but when importing the same file both fields remain empty. whats wrong here?
Written by mh
16 August 2011 on 9:48 amNot working in 1.5.0.1 for me.
- the cronjob is not running…
- if I don’t choose a date, its set to “30-11–1 01:00″ (id MySQL as: 0000-00-00 00:00:00) instead of NULL
- when saved again, the date “30-11–1 01:00″ disables the product and is set to 30-11-01 01:00
Written by john
20 September 2011 on 9:47 amOn 1.6 it gives me an error while saving a product.
SQLSTATE[42S22]: Column not found: 1054 Unknown column ‘ecomdev_activation_date’ in ‘field list’
Written by Peter
24 October 2011 on 12:13 pmOK. Fixed after reindex!
Written by Peter
24 October 2011 on 12:21 pmHi Ivan Chepurnyi,
This module is working for “Expiry Date” but if we changed/extend Expiry Date but still product display is disabled…..can you please suggest how to fix it?
Written by Sunil Kansodiya
1 February 2012 on 9:47 amHi Ivan,
1.Is it possible to remove product from cart as soon it is disabled?
Can this be achieved?
It will be a great advantage for Admin.
Also One more thing I would Like to know.
2.Is there a simple way to Mass Update Activation and Expiry dates?
Thanks in Advance Ivan
Written by Chintan Parikh
2 February 2012 on 7:06 amEverything is possible. It works the same as core functionality.
Written by Ivan Chepurnyi
2 February 2012 on 7:19 amI’m in 1.6.1. I can’t get attributes to show up in backend–I see it is enabled when I go to Configuration > Advanced > Advanced, but there are no options in the Product page, and I’m not seeing anything in the database, no extra fields. I’ve cleared cache, I’ve reindexed everything, nothing works. Any ideas on what I can do?
Written by Jeremiah Lewis
13 February 2012 on 5:16 amAn update: I tried manually installing it as well as installing it through Magento Connect, with seemingly successful installs on both counts. Any help on this would be amazing.
Written by Jeremiah Lewis
13 February 2012 on 3:07 pmNever mind, finally figured it out. I had to delete the ecom reference in core_resource table to “reset” things.
Written by Jeremiah Lewis
13 February 2012 on 5:22 pmGot everything apparently working on backend. However, attribute isn’t appearing on front-end. I wanted it to display in the Additional Info section of the product page, so I set the expiry attribute to be visible on front-end. I reindexed and cleared cache, but still nothing appears. Oddly, when I remove the time value and resave, the attribute DOES appear as “No.” Any thoughts?
Written by Jeremiah Lewis
13 February 2012 on 6:03 pmComment?
Please use [php][/php] and [xml][/xml] for your code snippets