Version Notes
- Full refactor of the module
- Add two grids to the backend to see the abandoned carts
- Add a log database table to easily see what's going on
- Implement an autologin link system
- Implement Google Campaign tags
- Improve the templates to list all items
- Change the way dryrun and test email behaves
- Add notification flags columns to the native abandoned carts report
Download this release
Release Info
Developer | Digital Pianism |
Extension | DigitalPianism_Abandonedcarts |
Version | 1.0.0 |
Comparing to | |
See all releases |
Code changes from version 0.3.6 to 1.0.0
- app/code/community/DigitalPianism/Abandonedcarts/Block/Adminhtml/Abandonedcarts.php +49 -0
- app/code/community/DigitalPianism/Abandonedcarts/Block/Adminhtml/Abandonedcarts/Grid.php +210 -0
- app/code/community/DigitalPianism/Abandonedcarts/Block/Adminhtml/Logs.php +20 -0
- app/code/community/DigitalPianism/Abandonedcarts/Block/Adminhtml/Logs/Grid.php +76 -0
- app/code/community/DigitalPianism/Abandonedcarts/Block/Adminhtml/Saleabandonedcarts.php +49 -0
- app/code/community/DigitalPianism/Abandonedcarts/Block/Adminhtml/Saleabandonedcarts/Grid.php +218 -0
- app/code/community/DigitalPianism/Abandonedcarts/Block/Adminhtml/System/Config/Form/Button.php +0 -54
- app/code/community/DigitalPianism/Abandonedcarts/Helper/Data.php +58 -26
- app/code/community/DigitalPianism/Abandonedcarts/Model/Adminhtml/Observer.php +87 -0
- app/code/community/DigitalPianism/Abandonedcarts/Model/Collection.php +280 -0
- app/code/community/DigitalPianism/Abandonedcarts/Model/Link.php +14 -0
- app/code/community/DigitalPianism/Abandonedcarts/Model/Link/Cleaner.php +31 -0
- app/code/community/DigitalPianism/Abandonedcarts/Model/Log.php +25 -0
- app/code/community/DigitalPianism/Abandonedcarts/Model/Notifier.php +637 -0
- app/code/community/DigitalPianism/Abandonedcarts/Model/Observer.php +0 -742
- app/code/community/DigitalPianism/Abandonedcarts/Model/Resource/Link.php +14 -0
- app/code/community/DigitalPianism/Abandonedcarts/Model/Resource/Link/Collection.php +14 -0
- app/code/community/DigitalPianism/Abandonedcarts/Model/Resource/Log.php +14 -0
- app/code/community/DigitalPianism/Abandonedcarts/Model/Resource/Log/Collection.php +14 -0
- app/code/community/DigitalPianism/Abandonedcarts/controllers/Adminhtml/AbandonedcartsController.php +113 -11
- app/code/community/DigitalPianism/Abandonedcarts/controllers/IndexController.php +50 -0
- app/code/community/DigitalPianism/Abandonedcarts/data/abandonedcarts_setup/data-upgrade-1.0.0-1.0.1.php +33 -0
- app/code/community/DigitalPianism/Abandonedcarts/etc/adminhtml.xml +60 -0
- app/code/community/DigitalPianism/Abandonedcarts/etc/config.xml +94 -5
- app/code/community/DigitalPianism/Abandonedcarts/etc/system.xml +106 -43
- app/code/community/DigitalPianism/Abandonedcarts/sql/abandonedcarts_setup/upgrade-0.3.6-1.0.0.php +27 -0
- app/design/adminhtml/default/default/layout/digitalpianism/abandonedcarts.xml +10 -0
- app/design/adminhtml/default/default/template/digitalpianism/abandonedcarts/list.phtml +12 -0
- app/design/adminhtml/default/default/template/digitalpianism/abandonedcarts/system/config/button.phtml +0 -17
- app/design/frontend/base/default/template/digitalpianism/abandonedcarts/email/items.phtml +26 -0
- app/design/frontend/base/default/template/digitalpianism/abandonedcarts/email/sale_items.phtml +40 -0
- app/locale/en_US/DigitalPianism_Abandonedcarts.csv +32 -4
- app/locale/en_US/template/email/digitalpianism/abandonedcarts/sales_abandonedcarts.html +3 -5
- app/locale/en_US/template/email/digitalpianism/abandonedcarts/sales_abandonedcarts_sale.html +4 -10
- app/locale/fr_FR/DigitalPianism_Abandonedcarts.csv +33 -5
- app/locale/fr_FR/template/email/digitalpianism/abandonedcarts/sales_abandonedcarts.html +4 -6
- app/locale/fr_FR/template/email/digitalpianism/abandonedcarts/sales_abandonedcarts_sale.html +5 -12
- package.xml +12 -5
app/code/community/DigitalPianism/Abandonedcarts/Block/Adminhtml/Abandonedcarts.php
ADDED
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Class DigitalPianism_Abandonedcarts_Block_Adminhtml_Abandonedcarts
|
5 |
+
*/
|
6 |
+
class DigitalPianism_Abandonedcarts_Block_Adminhtml_Abandonedcarts extends Mage_Adminhtml_Block_Widget_Grid_Container
|
7 |
+
{
|
8 |
+
/**
|
9 |
+
* Constructor
|
10 |
+
*/
|
11 |
+
public function __construct()
|
12 |
+
{
|
13 |
+
$this->_controller = 'adminhtml_abandonedcarts';
|
14 |
+
$this->_blockGroup = 'abandonedcarts';
|
15 |
+
$this->_headerText = Mage::helper('abandonedcarts')->__('Abandoned Carts (Applied delay: %s days)', Mage::getStoreConfig('abandonedcartsconfig/options/notify_delay'));
|
16 |
+
parent::__construct();
|
17 |
+
$this->_removeButton('add');
|
18 |
+
$this->_addButton('notify', array(
|
19 |
+
'label' => Mage::helper('abandonedcarts')->__('Send notifications'),
|
20 |
+
'onclick' => "setLocation('".$this->getUrl('*/*/notifyAll')."')",
|
21 |
+
));
|
22 |
+
$this->setTemplate('digitalpianism/abandonedcarts/list.phtml');
|
23 |
+
}
|
24 |
+
|
25 |
+
/**
|
26 |
+
* Prepare the layout
|
27 |
+
*/
|
28 |
+
protected function _prepareLayout()
|
29 |
+
{
|
30 |
+
// Display store switcher if system has more one store
|
31 |
+
if (!Mage::app()->isSingleStoreMode())
|
32 |
+
{
|
33 |
+
$this->setChild('store_switcher', $this->getLayout()->createBlock('adminhtml/store_switcher')
|
34 |
+
->setUseConfirm(false)
|
35 |
+
->setSwitchUrl($this->getUrl('*/*/*', array('store' => null)))
|
36 |
+
);
|
37 |
+
}
|
38 |
+
return parent::_prepareLayout();
|
39 |
+
}
|
40 |
+
|
41 |
+
/**
|
42 |
+
* Getter for the store switcher HTML
|
43 |
+
*/
|
44 |
+
public function getStoreSwitcherHtml()
|
45 |
+
{
|
46 |
+
return $this->getChildHtml('store_switcher');
|
47 |
+
}
|
48 |
+
|
49 |
+
}
|
app/code/community/DigitalPianism/Abandonedcarts/Block/Adminhtml/Abandonedcarts/Grid.php
ADDED
@@ -0,0 +1,210 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Class DigitalPianism_Abandonedcarts_Block_Adminhtml_Abandonedcarts_Grid
|
5 |
+
*/
|
6 |
+
class DigitalPianism_Abandonedcarts_Block_Adminhtml_Abandonedcarts_Grid extends Mage_Adminhtml_Block_Widget_Grid
|
7 |
+
{
|
8 |
+
|
9 |
+
/**
|
10 |
+
*
|
11 |
+
*/
|
12 |
+
public function __construct()
|
13 |
+
{
|
14 |
+
parent::__construct();
|
15 |
+
$this->setId('abandonedcartsGrid');
|
16 |
+
$this->setDefaultSort('cart_updated_at');
|
17 |
+
$this->setDefaultDir('DESC');
|
18 |
+
$this->setSaveParametersInSession(true);
|
19 |
+
$this->setUseAjax(true);
|
20 |
+
}
|
21 |
+
|
22 |
+
/**
|
23 |
+
* @return mixed
|
24 |
+
*/
|
25 |
+
protected function _getStore()
|
26 |
+
{
|
27 |
+
$storeId = (int) $this->getRequest()->getParam('store', 0);
|
28 |
+
return Mage::app()->getStore($storeId);
|
29 |
+
}
|
30 |
+
|
31 |
+
protected function _prepareCollection()
|
32 |
+
{
|
33 |
+
// Delay
|
34 |
+
$delay = Mage::getStoreConfig('abandonedcartsconfig/options/notify_delay');
|
35 |
+
$delay = date('Y-m-d H:i:s', time() - $delay * 24 * 3600);
|
36 |
+
|
37 |
+
// Default store and website
|
38 |
+
$defaults = $this->_getDefaultStoreAndWebsite();
|
39 |
+
|
40 |
+
// Store and website from the multistore switcher
|
41 |
+
$store = $this->_getStore();
|
42 |
+
if ($storeId = $store->getId())
|
43 |
+
{
|
44 |
+
$defaults = array(
|
45 |
+
$storeId,
|
46 |
+
Mage::getModel('core/store')->load($storeId)->getWebsiteId()
|
47 |
+
);
|
48 |
+
}
|
49 |
+
|
50 |
+
$collection = Mage::getModel('abandonedcarts/collection')->getCollection($delay, $defaults[0], $defaults[1]);
|
51 |
+
|
52 |
+
// Group by to have a nice grid
|
53 |
+
if (Mage::helper('catalog/product_flat')->isEnabled()) {
|
54 |
+
$collection->getSelect()->columns(
|
55 |
+
array(
|
56 |
+
'product_ids' => 'GROUP_CONCAT(e.entity_id)',
|
57 |
+
'product_names' => 'GROUP_CONCAT(catalog_flat.name)',
|
58 |
+
'product_prices' => 'SUM(catalog_flat.price)'
|
59 |
+
)
|
60 |
+
);
|
61 |
+
} else {
|
62 |
+
$collection->getSelect()->columns(
|
63 |
+
array(
|
64 |
+
'product_ids' => 'GROUP_CONCAT(e.entity_id)',
|
65 |
+
'product_names' => 'GROUP_CONCAT(catalog_name.value)',
|
66 |
+
'product_prices' => 'SUM(catalog_price.value)'
|
67 |
+
)
|
68 |
+
);
|
69 |
+
}
|
70 |
+
$collection->getSelect()->group('customer_email');
|
71 |
+
|
72 |
+
$this->setCollection($collection);
|
73 |
+
return parent::_prepareCollection();
|
74 |
+
}
|
75 |
+
|
76 |
+
protected function _prepareColumns()
|
77 |
+
{
|
78 |
+
$this->addColumn('customer_email', array(
|
79 |
+
'header' => Mage::helper('abandonedcarts')->__('Customer Email'),
|
80 |
+
'index' => 'customer_email',
|
81 |
+
'filter_condition_callback' => array($this, 'filterCallback')
|
82 |
+
));
|
83 |
+
|
84 |
+
$this->addColumn('customer_firstname', array(
|
85 |
+
'header' => Mage::helper('abandonedcarts')->__('Customer Firstname'),
|
86 |
+
'index' => 'customer_firstname',
|
87 |
+
'filter_condition_callback' => array($this, 'filterCallback')
|
88 |
+
));
|
89 |
+
|
90 |
+
$this->addColumn('customer_lastname', array(
|
91 |
+
'header' => Mage::helper('abandonedcarts')->__('Customer Lastname'),
|
92 |
+
'index' => 'customer_lastname',
|
93 |
+
'filter_condition_callback' => array($this, 'filterCallback')
|
94 |
+
));
|
95 |
+
|
96 |
+
$this->addColumn('product_ids', array(
|
97 |
+
'header' => Mage::helper('abandonedcarts')->__('Product Ids'),
|
98 |
+
'index' => 'product_ids',
|
99 |
+
'filter_index' => "e.entity_id",
|
100 |
+
'filter_condition_callback' => array($this, 'filterEqualCallback')
|
101 |
+
));
|
102 |
+
|
103 |
+
$this->addColumn('product_names', array(
|
104 |
+
'header' => Mage::helper('abandonedcarts')->__('Product Names'),
|
105 |
+
'index' => 'product_names',
|
106 |
+
'filter_index' => (Mage::helper('catalog/product_flat')->isEnabled() ? "catalog_flat.name" : "catalog_name.value"),
|
107 |
+
'filter_condition_callback' => array($this, 'filterEqualCallback')
|
108 |
+
));
|
109 |
+
|
110 |
+
$this->addColumn('product_prices', array(
|
111 |
+
'header' => Mage::helper('abandonedcarts')->__('Cart Total'),
|
112 |
+
'index' => 'product_prices',
|
113 |
+
'filter' => false
|
114 |
+
));
|
115 |
+
|
116 |
+
// Output format for the start and end dates
|
117 |
+
$outputFormat = Mage::app()->getLocale()->getDateTimeFormat(Mage_Core_Model_Locale::FORMAT_TYPE_MEDIUM);
|
118 |
+
|
119 |
+
$this->addColumn('cart_updated_at', array(
|
120 |
+
'header' => Mage::helper('abandonedcarts')->__('Cart Updated At'),
|
121 |
+
'index' => 'cart_updated_at',
|
122 |
+
'type' => 'datetime',
|
123 |
+
'format' => $outputFormat,
|
124 |
+
'default' => ' -- ',
|
125 |
+
'filter_index' => 'quote_table.updated_at',
|
126 |
+
'filter_condition_callback' => array($this, 'filterDateCallback')
|
127 |
+
));
|
128 |
+
|
129 |
+
return parent::_prepareColumns();
|
130 |
+
}
|
131 |
+
|
132 |
+
/**
|
133 |
+
* @return $this
|
134 |
+
*/
|
135 |
+
protected function _prepareMassaction()
|
136 |
+
{
|
137 |
+
$this->setMassactionIdField('customer_email');
|
138 |
+
$this->getMassactionBlock()->setFormFieldName('abandonedcarts');
|
139 |
+
|
140 |
+
$this->getMassactionBlock()->addItem('notify', array(
|
141 |
+
'label' => Mage::helper('abandonedcarts')->__('Send notification'),
|
142 |
+
'url' => $this->getUrl('*/*/notify')
|
143 |
+
));
|
144 |
+
|
145 |
+
return $this;
|
146 |
+
}
|
147 |
+
|
148 |
+
/**
|
149 |
+
* @return array
|
150 |
+
*/
|
151 |
+
protected function _getDefaultStoreAndWebsite()
|
152 |
+
{
|
153 |
+
foreach (Mage::app()->getWebsites() as $website) {
|
154 |
+
// Get the website id
|
155 |
+
$websiteId = $website->getWebsiteId();
|
156 |
+
foreach ($website->getGroups() as $group) {
|
157 |
+
$stores = $group->getStores();
|
158 |
+
foreach ($stores as $store) {
|
159 |
+
|
160 |
+
// Get the store id
|
161 |
+
$storeId = $store->getStoreId();
|
162 |
+
break 3;
|
163 |
+
}
|
164 |
+
}
|
165 |
+
}
|
166 |
+
return array($storeId, $websiteId);
|
167 |
+
}
|
168 |
+
|
169 |
+
/**
|
170 |
+
* @return string
|
171 |
+
*/
|
172 |
+
public function getGridUrl()
|
173 |
+
{
|
174 |
+
return $this->getUrl('*/*/grid', array('current' => true));
|
175 |
+
}
|
176 |
+
|
177 |
+
/**
|
178 |
+
* @param $collection
|
179 |
+
* @param $column
|
180 |
+
*/
|
181 |
+
public function filterCallback($collection, $column)
|
182 |
+
{
|
183 |
+
$field = $column->getFilterIndex() ? $column->getFilterIndex() : $column->getIndex();
|
184 |
+
$value = $column->getFilter()->getValue();
|
185 |
+
$collection->getSelect()->where("$field like ?", '%' . $value . '%');
|
186 |
+
}
|
187 |
+
|
188 |
+
/**
|
189 |
+
* @param $collection
|
190 |
+
* @param $column
|
191 |
+
*/
|
192 |
+
public function filterEqualCallback($collection, $column)
|
193 |
+
{
|
194 |
+
$field = $column->getFilterIndex() ? $column->getFilterIndex() : $column->getIndex();
|
195 |
+
$value = $column->getFilter()->getValue();
|
196 |
+
$collection->getSelect()->where("$field = ?", $value);
|
197 |
+
}
|
198 |
+
|
199 |
+
/**
|
200 |
+
* @param $collection
|
201 |
+
* @param $column
|
202 |
+
*/
|
203 |
+
public function filterDateCallback($collection, $column)
|
204 |
+
{
|
205 |
+
$field = $column->getFilterIndex() ? $column->getFilterIndex() : $column->getIndex();
|
206 |
+
$value = $column->getFilter()->getValue();
|
207 |
+
$collection->getSelect()->where("$field > '" . $value['from']->toString('Y-MM-dd HH:mm:ss') . "' AND $field < '" . $value['to']->toString('Y-MM-dd HH:mm:ss') . "'");
|
208 |
+
}
|
209 |
+
|
210 |
+
}
|
app/code/community/DigitalPianism/Abandonedcarts/Block/Adminhtml/Logs.php
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Class DigitalPianism_Abandonedcarts_Block_Adminhtml_Logs
|
5 |
+
*/
|
6 |
+
class DigitalPianism_Abandonedcarts_Block_Adminhtml_Logs extends Mage_Adminhtml_Block_Widget_Grid_Container
|
7 |
+
{
|
8 |
+
/**
|
9 |
+
* Constructor
|
10 |
+
*/
|
11 |
+
public function __construct()
|
12 |
+
{
|
13 |
+
$this->_controller = 'adminhtml_logs';
|
14 |
+
$this->_blockGroup = 'abandonedcarts';
|
15 |
+
$this->_headerText = Mage::helper('abandonedcarts')->__('Abandoned Carts Logs');
|
16 |
+
parent::__construct();
|
17 |
+
$this->_removeButton('add');
|
18 |
+
}
|
19 |
+
|
20 |
+
}
|
app/code/community/DigitalPianism/Abandonedcarts/Block/Adminhtml/Logs/Grid.php
ADDED
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Class DigitalPianism_Abandonedcarts_Block_Adminhtml_Logs_Grid
|
5 |
+
*/
|
6 |
+
class DigitalPianism_Abandonedcarts_Block_Adminhtml_Logs_Grid extends Mage_Adminhtml_Block_Widget_Grid
|
7 |
+
{
|
8 |
+
|
9 |
+
/**
|
10 |
+
*
|
11 |
+
*/
|
12 |
+
public function __construct()
|
13 |
+
{
|
14 |
+
parent::__construct();
|
15 |
+
$this->setId('abandonedcartLogsGrid');
|
16 |
+
$this->setDefaultSort('log_id');
|
17 |
+
$this->setDefaultDir('DESC');
|
18 |
+
$this->setSaveParametersInSession(true);
|
19 |
+
}
|
20 |
+
|
21 |
+
protected function _prepareCollection()
|
22 |
+
{
|
23 |
+
$collection = Mage::getResourceModel('abandonedcarts/log_collection');
|
24 |
+
$this->setCollection($collection);
|
25 |
+
return parent::_prepareCollection();
|
26 |
+
}
|
27 |
+
|
28 |
+
protected function _prepareColumns()
|
29 |
+
{
|
30 |
+
$this->addColumn('customer_email', array(
|
31 |
+
'header' => Mage::helper('abandonedcarts')->__('Customer Email'),
|
32 |
+
'index' => 'customer_email',
|
33 |
+
));
|
34 |
+
|
35 |
+
$this->addColumn('type', array(
|
36 |
+
'header' => Mage::helper('abandonedcarts')->__('Type'),
|
37 |
+
'index' => 'type',
|
38 |
+
'type' => 'options',
|
39 |
+
'options' => Mage::getModel('abandonedcarts/log')->toOptionArray()
|
40 |
+
));
|
41 |
+
|
42 |
+
$this->addColumn('comment', array(
|
43 |
+
'header' => Mage::helper('abandonedcarts')->__('Comment'),
|
44 |
+
'index' => 'comment',
|
45 |
+
));
|
46 |
+
|
47 |
+
$this->addColumn('store', array(
|
48 |
+
'header' => Mage::helper('abandonedcarts')->__('Store #'),
|
49 |
+
'index' => 'store',
|
50 |
+
));
|
51 |
+
|
52 |
+
$this->addColumn('dryrun', array(
|
53 |
+
'header' => Mage::helper('abandonedcarts')->__('Dry Run'),
|
54 |
+
'type' => 'options',
|
55 |
+
'index' => 'dryrun',
|
56 |
+
'options' => array(
|
57 |
+
0 => Mage::helper('abandonedcarts')->__("No"),
|
58 |
+
1 => Mage::helper('abandonedcarts')->__("Yes")
|
59 |
+
)
|
60 |
+
));
|
61 |
+
|
62 |
+
// Output format for the start and end dates
|
63 |
+
$outputFormat = Mage::app()->getLocale()->getDateTimeFormat(Mage_Core_Model_Locale::FORMAT_TYPE_MEDIUM);
|
64 |
+
|
65 |
+
$this->addColumn('added', array(
|
66 |
+
'header' => Mage::helper('abandonedcarts')->__('Date'),
|
67 |
+
'index' => 'added',
|
68 |
+
'type' => 'datetime',
|
69 |
+
'format' => $outputFormat,
|
70 |
+
'default' => ' -- '
|
71 |
+
));
|
72 |
+
|
73 |
+
return parent::_prepareColumns();
|
74 |
+
}
|
75 |
+
|
76 |
+
}
|
app/code/community/DigitalPianism/Abandonedcarts/Block/Adminhtml/Saleabandonedcarts.php
ADDED
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Class DigitalPianism_Abandonedcarts_Block_Adminhtml_Saleabandonedcarts
|
5 |
+
*/
|
6 |
+
class DigitalPianism_Abandonedcarts_Block_Adminhtml_Saleabandonedcarts extends Mage_Adminhtml_Block_Widget_Grid_Container
|
7 |
+
{
|
8 |
+
/**
|
9 |
+
* Constructor
|
10 |
+
*/
|
11 |
+
public function __construct()
|
12 |
+
{
|
13 |
+
$this->_controller = 'adminhtml_saleabandonedcarts';
|
14 |
+
$this->_blockGroup = 'abandonedcarts';
|
15 |
+
$this->_headerText = Mage::helper('abandonedcarts')->__('Sale Abandoned Carts');
|
16 |
+
parent::__construct();
|
17 |
+
$this->_removeButton('add');
|
18 |
+
$this->_addButton('notify', array(
|
19 |
+
'label' => Mage::helper('abandonedcarts')->__('Send notifications'),
|
20 |
+
'onclick' => "setLocation('".$this->getUrl('*/*/notifySaleAll')."')",
|
21 |
+
));
|
22 |
+
$this->setTemplate('digitalpianism/abandonedcarts/list.phtml');
|
23 |
+
}
|
24 |
+
|
25 |
+
/**
|
26 |
+
* Prepare the layout
|
27 |
+
*/
|
28 |
+
protected function _prepareLayout()
|
29 |
+
{
|
30 |
+
// Display store switcher if system has more one store
|
31 |
+
if (!Mage::app()->isSingleStoreMode())
|
32 |
+
{
|
33 |
+
$this->setChild('store_switcher', $this->getLayout()->createBlock('adminhtml/store_switcher')
|
34 |
+
->setUseConfirm(false)
|
35 |
+
->setSwitchUrl($this->getUrl('*/*/*', array('store' => null)))
|
36 |
+
);
|
37 |
+
}
|
38 |
+
return parent::_prepareLayout();
|
39 |
+
}
|
40 |
+
|
41 |
+
/**
|
42 |
+
* Getter for the store switcher HTML
|
43 |
+
*/
|
44 |
+
public function getStoreSwitcherHtml()
|
45 |
+
{
|
46 |
+
return $this->getChildHtml('store_switcher');
|
47 |
+
}
|
48 |
+
|
49 |
+
}
|
app/code/community/DigitalPianism/Abandonedcarts/Block/Adminhtml/Saleabandonedcarts/Grid.php
ADDED
@@ -0,0 +1,218 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Class DigitalPianism_Abandonedcarts_Block_Adminhtml_Saleabandonedcarts_Grid
|
5 |
+
*/
|
6 |
+
class DigitalPianism_Abandonedcarts_Block_Adminhtml_Saleabandonedcarts_Grid extends Mage_Adminhtml_Block_Widget_Grid
|
7 |
+
{
|
8 |
+
|
9 |
+
/**
|
10 |
+
*
|
11 |
+
*/
|
12 |
+
public function __construct()
|
13 |
+
{
|
14 |
+
parent::__construct();
|
15 |
+
$this->setId('saleabandonedcartsGrid');
|
16 |
+
$this->setDefaultSort('cart_updated_at');
|
17 |
+
$this->setDefaultDir('DESC');
|
18 |
+
$this->setSaveParametersInSession(true);
|
19 |
+
$this->setUseAjax(true);
|
20 |
+
}
|
21 |
+
|
22 |
+
/**
|
23 |
+
* @return mixed
|
24 |
+
*/
|
25 |
+
protected function _getStore()
|
26 |
+
{
|
27 |
+
$storeId = (int) $this->getRequest()->getParam('store', 0);
|
28 |
+
return Mage::app()->getStore($storeId);
|
29 |
+
}
|
30 |
+
|
31 |
+
protected function _prepareCollection()
|
32 |
+
{
|
33 |
+
// Default store and website
|
34 |
+
$defaults = $this->_getDefaultStoreAndWebsite();
|
35 |
+
|
36 |
+
// Store and website from the multistore switcher
|
37 |
+
$store = $this->_getStore();
|
38 |
+
if ($storeId = $store->getId())
|
39 |
+
{
|
40 |
+
$defaults = array(
|
41 |
+
$storeId,
|
42 |
+
Mage::getModel('core/store')->load($storeId)->getWebsiteId()
|
43 |
+
);
|
44 |
+
}
|
45 |
+
|
46 |
+
$collection = Mage::getModel('abandonedcarts/collection')->getSalesCollection($defaults[0], $defaults[1]);
|
47 |
+
|
48 |
+
// Group by to have a nice grid
|
49 |
+
$collection->getSelect()->group('customer_email');
|
50 |
+
|
51 |
+
if (Mage::helper('catalog/product_flat')->isEnabled()) {
|
52 |
+
$collection->getSelect()->columns(
|
53 |
+
array(
|
54 |
+
'product_ids' => 'GROUP_CONCAT(e.entity_id)',
|
55 |
+
'product_names' => 'GROUP_CONCAT(catalog_flat.name)',
|
56 |
+
'product_prices' => 'SUM(quote_items.price)',
|
57 |
+
'product_special_prices' => 'SUM(IFNULL(catalog_flat.special_price,quote_items.price))',
|
58 |
+
)
|
59 |
+
);
|
60 |
+
|
61 |
+
$collection->getSelect()->having("SUM(quote_items.price) < SUM(IFNULL(catalog_flat.special_price,quote_items.price))");
|
62 |
+
} else {
|
63 |
+
$collection->getSelect()->columns(
|
64 |
+
array(
|
65 |
+
'product_ids' => 'GROUP_CONCAT(e.entity_id)',
|
66 |
+
'product_names' => 'GROUP_CONCAT(catalog_name.value)',
|
67 |
+
'product_prices' => 'SUM(quote_items.price)',
|
68 |
+
'product_special_prices' => 'SUM(IFNULL(catalog_sprice.value,quote_items.price))',
|
69 |
+
)
|
70 |
+
);
|
71 |
+
|
72 |
+
$collection->getSelect()->having("SUM(quote_items.price) > SUM(IFNULL(catalog_sprice.value,quote_items.price))");
|
73 |
+
}
|
74 |
+
|
75 |
+
$this->setCollection($collection);
|
76 |
+
return parent::_prepareCollection();
|
77 |
+
}
|
78 |
+
|
79 |
+
protected function _prepareColumns()
|
80 |
+
{
|
81 |
+
$this->addColumn('customer_email', array(
|
82 |
+
'header' => Mage::helper('abandonedcarts')->__('Customer Email'),
|
83 |
+
'index' => 'customer_email',
|
84 |
+
'filter_condition_callback' => array($this, 'filterCallback')
|
85 |
+
));
|
86 |
+
|
87 |
+
$this->addColumn('customer_firstname', array(
|
88 |
+
'header' => Mage::helper('abandonedcarts')->__('Customer Firstname'),
|
89 |
+
'index' => 'customer_firstname',
|
90 |
+
'filter_condition_callback' => array($this, 'filterCallback')
|
91 |
+
));
|
92 |
+
|
93 |
+
$this->addColumn('customer_lastname', array(
|
94 |
+
'header' => Mage::helper('abandonedcarts')->__('Customer Lastname'),
|
95 |
+
'index' => 'customer_lastname',
|
96 |
+
'filter_condition_callback' => array($this, 'filterCallback')
|
97 |
+
));
|
98 |
+
|
99 |
+
$this->addColumn('product_ids', array(
|
100 |
+
'header' => Mage::helper('abandonedcarts')->__('Product Ids'),
|
101 |
+
'index' => 'product_ids',
|
102 |
+
'filter_index' => "e.entity_id",
|
103 |
+
'filter_condition_callback' => array($this, 'filterEqualCallback')
|
104 |
+
));
|
105 |
+
|
106 |
+
$this->addColumn('product_names', array(
|
107 |
+
'header' => Mage::helper('abandonedcarts')->__('Product Names'),
|
108 |
+
'index' => 'product_names',
|
109 |
+
'filter_index' => (Mage::helper('catalog/product_flat')->isEnabled() ? "catalog_flat.name" : "catalog_name.value"),
|
110 |
+
'filter_condition_callback' => array($this, 'filterEqualCallback')
|
111 |
+
));
|
112 |
+
|
113 |
+
$this->addColumn('product_prices', array(
|
114 |
+
'header' => Mage::helper('abandonedcarts')->__('Cart Regular Total'),
|
115 |
+
'index' => 'product_prices',
|
116 |
+
'filter' => false
|
117 |
+
));
|
118 |
+
|
119 |
+
$this->addColumn('product_special_prices', array(
|
120 |
+
'header' => Mage::helper('abandonedcarts')->__('Cart Sale Total'),
|
121 |
+
'index' => 'product_special_prices',
|
122 |
+
'filter' => false
|
123 |
+
));
|
124 |
+
|
125 |
+
// Output format for the start and end dates
|
126 |
+
$outputFormat = Mage::app()->getLocale()->getDateTimeFormat(Mage_Core_Model_Locale::FORMAT_TYPE_MEDIUM);
|
127 |
+
|
128 |
+
$this->addColumn('cart_updated_at', array(
|
129 |
+
'header' => Mage::helper('abandonedcarts')->__('Cart Updated At'),
|
130 |
+
'index' => 'cart_updated_at',
|
131 |
+
'type' => 'datetime',
|
132 |
+
'format' => $outputFormat,
|
133 |
+
'default' => ' -- ',
|
134 |
+
'filter_index' => 'quote_table.updated_at',
|
135 |
+
'filter_condition_callback' => array($this, 'filterDateCallback')
|
136 |
+
));
|
137 |
+
|
138 |
+
return parent::_prepareColumns();
|
139 |
+
}
|
140 |
+
|
141 |
+
/**
|
142 |
+
* @return $this
|
143 |
+
*/
|
144 |
+
protected function _prepareMassaction()
|
145 |
+
{
|
146 |
+
$this->setMassactionIdField('customer_email');
|
147 |
+
$this->getMassactionBlock()->setFormFieldName('abandonedcarts');
|
148 |
+
|
149 |
+
$this->getMassactionBlock()->addItem('notifySale', array(
|
150 |
+
'label' => Mage::helper('abandonedcarts')->__('Send notification'),
|
151 |
+
'url' => $this->getUrl('*/*/notifySale')
|
152 |
+
));
|
153 |
+
|
154 |
+
return $this;
|
155 |
+
}
|
156 |
+
|
157 |
+
/**
|
158 |
+
* @return array
|
159 |
+
*/
|
160 |
+
protected function _getDefaultStoreAndWebsite()
|
161 |
+
{
|
162 |
+
foreach (Mage::app()->getWebsites() as $website) {
|
163 |
+
// Get the website id
|
164 |
+
$websiteId = $website->getWebsiteId();
|
165 |
+
foreach ($website->getGroups() as $group) {
|
166 |
+
$stores = $group->getStores();
|
167 |
+
foreach ($stores as $store) {
|
168 |
+
|
169 |
+
// Get the store id
|
170 |
+
$storeId = $store->getStoreId();
|
171 |
+
break 3;
|
172 |
+
}
|
173 |
+
}
|
174 |
+
}
|
175 |
+
return array($storeId, $websiteId);
|
176 |
+
}
|
177 |
+
|
178 |
+
/**
|
179 |
+
* @return string
|
180 |
+
*/
|
181 |
+
public function getGridUrl()
|
182 |
+
{
|
183 |
+
return $this->getUrl('*/*/salegrid', array('current' => true));
|
184 |
+
}
|
185 |
+
|
186 |
+
/**
|
187 |
+
* @param $collection
|
188 |
+
* @param $column
|
189 |
+
*/
|
190 |
+
public function filterCallback($collection, $column)
|
191 |
+
{
|
192 |
+
$field = $column->getFilterIndex() ? $column->getFilterIndex() : $column->getIndex();
|
193 |
+
$value = $column->getFilter()->getValue();
|
194 |
+
$collection->getSelect()->where("$field like ?", '%' . $value . '%');
|
195 |
+
}
|
196 |
+
|
197 |
+
/**
|
198 |
+
* @param $collection
|
199 |
+
* @param $column
|
200 |
+
*/
|
201 |
+
public function filterEqualCallback($collection, $column)
|
202 |
+
{
|
203 |
+
$field = $column->getFilterIndex() ? $column->getFilterIndex() : $column->getIndex();
|
204 |
+
$value = $column->getFilter()->getValue();
|
205 |
+
$collection->getSelect()->where("$field = ?", $value);
|
206 |
+
}
|
207 |
+
|
208 |
+
/**
|
209 |
+
* @param $collection
|
210 |
+
* @param $column
|
211 |
+
*/
|
212 |
+
public function filterDateCallback($collection, $column)
|
213 |
+
{
|
214 |
+
$field = $column->getFilterIndex() ? $column->getFilterIndex() : $column->getIndex();
|
215 |
+
$value = $column->getFilter()->getValue();
|
216 |
+
$collection->getSelect()->where("$field > '" . $value['from']->toString('Y-MM-dd HH:mm:ss') . "' AND $field < '" . $value['to']->toString('Y-MM-dd HH:mm:ss') . "'");
|
217 |
+
}
|
218 |
+
}
|
app/code/community/DigitalPianism/Abandonedcarts/Block/Adminhtml/System/Config/Form/Button.php
DELETED
@@ -1,54 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
/**
|
4 |
-
* Class DigitalPianism_Abandonedcarts_Block_Adminhtml_System_Config_Form_Button
|
5 |
-
*/
|
6 |
-
class DigitalPianism_Abandonedcarts_Block_Adminhtml_System_Config_Form_Button extends Mage_Adminhtml_Block_System_Config_Form_Field
|
7 |
-
{
|
8 |
-
/*
|
9 |
-
* Set template
|
10 |
-
*/
|
11 |
-
protected function _construct()
|
12 |
-
{
|
13 |
-
parent::_construct();
|
14 |
-
$this->setTemplate('digitalpianism/abandonedcarts/system/config/button.phtml');
|
15 |
-
}
|
16 |
-
|
17 |
-
/**
|
18 |
-
* Return element html
|
19 |
-
*
|
20 |
-
* @param Varien_Data_Form_Element_Abstract $element
|
21 |
-
* @return string
|
22 |
-
*/
|
23 |
-
protected function _getElementHtml(Varien_Data_Form_Element_Abstract $element)
|
24 |
-
{
|
25 |
-
return $this->_toHtml();
|
26 |
-
}
|
27 |
-
|
28 |
-
/**
|
29 |
-
* Return ajax url for button
|
30 |
-
*
|
31 |
-
* @return string
|
32 |
-
*/
|
33 |
-
public function getAjaxCheckUrl()
|
34 |
-
{
|
35 |
-
return Mage::helper('adminhtml')->getUrl('adminhtml/abandonedcarts/send');
|
36 |
-
}
|
37 |
-
|
38 |
-
/**
|
39 |
-
* Generate button html
|
40 |
-
*
|
41 |
-
* @return string
|
42 |
-
*/
|
43 |
-
public function getButtonHtml()
|
44 |
-
{
|
45 |
-
$button = $this->getLayout()->createBlock('adminhtml/widget_button')
|
46 |
-
->setData(array(
|
47 |
-
'id' => 'abandonedcarts_button',
|
48 |
-
'label' => $this->helper('adminhtml')->__('Send'),
|
49 |
-
'onclick' => 'javascript:send(); return false;'
|
50 |
-
));
|
51 |
-
|
52 |
-
return $button->toHtml();
|
53 |
-
}
|
54 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/code/community/DigitalPianism/Abandonedcarts/Helper/Data.php
CHANGED
@@ -6,54 +6,86 @@
|
|
6 |
class DigitalPianism_Abandonedcarts_Helper_Data extends Mage_Core_Helper_Abstract
|
7 |
{
|
8 |
protected $logFileName = 'digitalpianism_abandonedcarts.log';
|
9 |
-
|
10 |
/**
|
11 |
* Log data
|
12 |
* @param string|object|array data to log
|
13 |
*/
|
14 |
-
public function log($data)
|
15 |
{
|
16 |
Mage::log($data, null, $this->logFileName);
|
17 |
}
|
18 |
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
{
|
24 |
-
return Mage::
|
25 |
}
|
26 |
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
{
|
32 |
-
return Mage::
|
33 |
}
|
34 |
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
{
|
40 |
-
return Mage::
|
41 |
}
|
42 |
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
{
|
48 |
-
return Mage::getStoreConfig('abandonedcartsconfig/
|
49 |
}
|
50 |
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
{
|
56 |
return explode(',',Mage::getStoreConfig('abandonedcartsconfig/options/customer_groups'));
|
57 |
}
|
58 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
59 |
}
|
6 |
class DigitalPianism_Abandonedcarts_Helper_Data extends Mage_Core_Helper_Abstract
|
7 |
{
|
8 |
protected $logFileName = 'digitalpianism_abandonedcarts.log';
|
9 |
+
|
10 |
/**
|
11 |
* Log data
|
12 |
* @param string|object|array data to log
|
13 |
*/
|
14 |
+
public function log($data)
|
15 |
{
|
16 |
Mage::log($data, null, $this->logFileName);
|
17 |
}
|
18 |
|
19 |
+
/**
|
20 |
+
* @return mixed
|
21 |
+
*/
|
22 |
+
public function isEnabled()
|
23 |
{
|
24 |
+
return Mage::getStoreConfigFlag('abandonedcartsconfig/options/enable');
|
25 |
}
|
26 |
|
27 |
+
/**
|
28 |
+
* @return mixed
|
29 |
+
*/
|
30 |
+
public function isSaleEnabled()
|
31 |
{
|
32 |
+
return Mage::getStoreConfigFlag('abandonedcartsconfig/options/enable_sale');
|
33 |
}
|
34 |
|
35 |
+
/**
|
36 |
+
* @return mixed
|
37 |
+
*/
|
38 |
+
public function getDryRun()
|
39 |
{
|
40 |
+
return Mage::getStoreConfigFlag('abandonedcartsconfig/test/dryrun');
|
41 |
}
|
42 |
|
43 |
+
/**
|
44 |
+
* @return mixed
|
45 |
+
*/
|
46 |
+
public function getTestEmail()
|
47 |
{
|
48 |
+
return Mage::getStoreConfig('abandonedcartsconfig/test/testemail');
|
49 |
}
|
50 |
|
51 |
+
/**
|
52 |
+
* @return mixed
|
53 |
+
*/
|
54 |
+
public function getCustomerGroupsLimitation()
|
55 |
{
|
56 |
return explode(',',Mage::getStoreConfig('abandonedcartsconfig/options/customer_groups'));
|
57 |
}
|
58 |
|
59 |
+
/**
|
60 |
+
* @return bool
|
61 |
+
*/
|
62 |
+
public function isCampaignEnabled()
|
63 |
+
{
|
64 |
+
return Mage::getStoreConfigFlag('abandonedcartsconfig/campaign/enable');
|
65 |
+
}
|
66 |
+
|
67 |
+
/**
|
68 |
+
* @return mixed
|
69 |
+
*/
|
70 |
+
public function getCampaignName()
|
71 |
+
{
|
72 |
+
return Mage::getStoreConfig('abandonedcartsconfig/campaign/name');
|
73 |
+
}
|
74 |
+
|
75 |
+
/**
|
76 |
+
* @return bool
|
77 |
+
*/
|
78 |
+
public function isAutologin()
|
79 |
+
{
|
80 |
+
return Mage::getStoreConfigFlag('abandonedcartsconfig/email/autologin');
|
81 |
+
}
|
82 |
+
|
83 |
+
/**
|
84 |
+
* @return bool
|
85 |
+
*/
|
86 |
+
public function isLogEnabled()
|
87 |
+
{
|
88 |
+
return Mage::getStoreConfigFlag('abandonedcartsconfig/test/log');
|
89 |
+
}
|
90 |
+
|
91 |
}
|
app/code/community/DigitalPianism/Abandonedcarts/Model/Adminhtml/Observer.php
ADDED
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Class DigitalPianism_Abandonedcarts_Model_Adminhtml_Observer
|
5 |
+
*/
|
6 |
+
class DigitalPianism_Abandonedcarts_Model_Adminhtml_Observer {
|
7 |
+
|
8 |
+
/**
|
9 |
+
* @param Varien_Event_Observer $observer
|
10 |
+
* @return $this
|
11 |
+
*/
|
12 |
+
public function registerController(Varien_Event_Observer $observer)
|
13 |
+
{
|
14 |
+
$action = $observer->getControllerAction()->getFullActionName();
|
15 |
+
|
16 |
+
switch ($action)
|
17 |
+
{
|
18 |
+
case 'adminhtml_report_shopcart_abandoned':
|
19 |
+
case 'adminhtml_report_shopcart_exportAbandonedCsv':
|
20 |
+
case 'adminhtml_report_shopcart_exportAbandonedExcel':
|
21 |
+
Mage::register('abandonedcart_report', true);
|
22 |
+
break;
|
23 |
+
}
|
24 |
+
|
25 |
+
return $this;
|
26 |
+
}
|
27 |
+
|
28 |
+
/**
|
29 |
+
* @param Varien_Event_Observer $observer
|
30 |
+
*/
|
31 |
+
public function addExtraColumnsToGrid(Varien_Event_Observer $observer)
|
32 |
+
{
|
33 |
+
// Get the block
|
34 |
+
$block = $observer->getBlock();
|
35 |
+
|
36 |
+
if ($block instanceof Mage_Adminhtml_Block_Report_Shopcart_Abandoned_Grid) {
|
37 |
+
$block->addColumnAfter(
|
38 |
+
'abandoned_notified',
|
39 |
+
array(
|
40 |
+
'header' => Mage::helper('abandonedcarts')->__('Abandoned cart email sent'),
|
41 |
+
'index' => 'abandoned_notified',
|
42 |
+
'type' => 'options',
|
43 |
+
'options' => array(
|
44 |
+
0 => Mage::helper('abandonedcarts')->__('No'),
|
45 |
+
1 => Mage::helper('abandonedcarts')->__('Yes')
|
46 |
+
)
|
47 |
+
),
|
48 |
+
'remote_ip'
|
49 |
+
);
|
50 |
+
|
51 |
+
$block->addColumnAfter(
|
52 |
+
'abandoned_sale_notified',
|
53 |
+
array(
|
54 |
+
'header' => Mage::helper('abandonedcarts')->__('Abandoned cart sale email sent'),
|
55 |
+
'index' => 'abandoned_sale_notified',
|
56 |
+
'type' => 'options',
|
57 |
+
'options' => array(
|
58 |
+
0 => Mage::helper('abandonedcarts')->__('No'),
|
59 |
+
1 => Mage::helper('abandonedcarts')->__('Yes')
|
60 |
+
)
|
61 |
+
),
|
62 |
+
'abandoned_notified'
|
63 |
+
);
|
64 |
+
}
|
65 |
+
}
|
66 |
+
|
67 |
+
/**
|
68 |
+
* @param Varien_Event_Observer $observer
|
69 |
+
*/
|
70 |
+
public function addExtraColumnsToCollection(Varien_Event_Observer $observer)
|
71 |
+
{
|
72 |
+
// Get the collection
|
73 |
+
$collection = $observer->getCollection();
|
74 |
+
|
75 |
+
if ($collection instanceof Mage_Reports_Model_Resource_Quote_Collection
|
76 |
+
&& Mage::registry('abandonedcart_report')) {
|
77 |
+
// Add the extra fields
|
78 |
+
// Using columns() instead of addFieldToSelect seems to fix the ambiguous column error
|
79 |
+
$collection->getSelect()->columns(
|
80 |
+
array(
|
81 |
+
'abandoned_notified' => 'main_table.abandoned_notified',
|
82 |
+
'abandoned_sale_notified' => 'main_table.abandoned_sale_notified'
|
83 |
+
)
|
84 |
+
);
|
85 |
+
}
|
86 |
+
}
|
87 |
+
}
|
app/code/community/DigitalPianism/Abandonedcarts/Model/Collection.php
ADDED
@@ -0,0 +1,280 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Class DigitalPianism_Abandonedcarts_Model_Collection
|
5 |
+
*/
|
6 |
+
class DigitalPianism_Abandonedcarts_Model_Collection {
|
7 |
+
|
8 |
+
/**
|
9 |
+
* @param $delay
|
10 |
+
* @param $storeId
|
11 |
+
* @param $websiteId
|
12 |
+
* @param $emails
|
13 |
+
* @return mixed
|
14 |
+
*/
|
15 |
+
public function getCollection($delay, $storeId, $websiteId, $emails = array())
|
16 |
+
{
|
17 |
+
// Get the product collection
|
18 |
+
$collection = Mage::getResourceModel('catalog/product_collection')->setStore($storeId);
|
19 |
+
|
20 |
+
// Get the attribute id for the status attribute
|
21 |
+
$eavAttribute = Mage::getModel('eav/entity_attribute');
|
22 |
+
$statusId = $eavAttribute->getIdByCode('catalog_product', 'status');
|
23 |
+
$nameId = $eavAttribute->getIdByCode('catalog_product', 'name');
|
24 |
+
$priceId = $eavAttribute->getIdByCode('catalog_product', 'price');
|
25 |
+
|
26 |
+
// Normal join condition
|
27 |
+
$emailJoin = sprintf('quote_items.quote_id = quote_table.entity_id AND quote_table.items_count > 0 AND quote_table.is_active = 1 AND quote_table.customer_email IS NOT NULL AND quote_table.abandoned_notified = 0 AND quote_table.updated_at < "%s" AND quote_table.store_id = %s', $delay, $storeId);
|
28 |
+
|
29 |
+
// In case an array of emails has been specified
|
30 |
+
if (!empty($emails)) {
|
31 |
+
$emailJoin = sprintf('%s AND quote_table.customer_email IN (%s)', $emailJoin, '"' . implode('", "', $emails) . '"');
|
32 |
+
}
|
33 |
+
|
34 |
+
// If flat catalog is enabled
|
35 |
+
if (Mage::helper('catalog/product_flat')->isEnabled())
|
36 |
+
{
|
37 |
+
// First collection: carts with products that became on sale
|
38 |
+
// Join the collection with the required tables
|
39 |
+
$collection->getSelect()
|
40 |
+
->reset(Zend_Db_Select::COLUMNS)
|
41 |
+
->columns(array('e.entity_id AS product_id',
|
42 |
+
'e.sku',
|
43 |
+
'catalog_flat.name as product_name',
|
44 |
+
'catalog_flat.price as product_price',
|
45 |
+
'quote_table.entity_id as cart_id',
|
46 |
+
'quote_table.updated_at as cart_updated_at',
|
47 |
+
'quote_table.abandoned_notified as has_been_notified',
|
48 |
+
'quote_table.customer_email as customer_email',
|
49 |
+
'quote_table.customer_firstname as customer_firstname',
|
50 |
+
'quote_table.customer_lastname as customer_lastname',
|
51 |
+
'customer.group_id as customer_group'
|
52 |
+
)
|
53 |
+
)
|
54 |
+
->joinInner(
|
55 |
+
array('quote_items' => Mage::getSingleton("core/resource")->getTableName('sales_flat_quote_item')),
|
56 |
+
'quote_items.product_id = e.entity_id AND quote_items.price > 0.00',
|
57 |
+
null)
|
58 |
+
->joinInner(
|
59 |
+
array('quote_table' => Mage::getSingleton("core/resource")->getTableName('sales_flat_quote')),
|
60 |
+
$emailJoin,
|
61 |
+
null)
|
62 |
+
->joinInner(
|
63 |
+
array('catalog_flat' => Mage::getSingleton("core/resource")->getTableName('catalog_product_flat_'.$storeId)),
|
64 |
+
'catalog_flat.entity_id = e.entity_id',
|
65 |
+
null)
|
66 |
+
->joinInner(
|
67 |
+
array('catalog_enabled' => Mage::getSingleton("core/resource")->getTableName('catalog_product_entity_int')),
|
68 |
+
'catalog_enabled.entity_id = e.entity_id AND catalog_enabled.attribute_id = '.$statusId.' AND catalog_enabled.value = 1',
|
69 |
+
null)
|
70 |
+
->joinInner(
|
71 |
+
array('inventory' => Mage::getSingleton("core/resource")->getTableName('cataloginventory_stock_status')),
|
72 |
+
'inventory.product_id = e.entity_id AND inventory.stock_status = 1 AND website_id = '.$websiteId,
|
73 |
+
null)
|
74 |
+
->joinInner(
|
75 |
+
array('customer' => Mage::getSingleton("core/resource")->getTableName('customer_entity')),
|
76 |
+
'quote_table.customer_email = customer.email',
|
77 |
+
null)
|
78 |
+
->order('quote_table.updated_at DESC');
|
79 |
+
}
|
80 |
+
else
|
81 |
+
{
|
82 |
+
// First collection: carts with products that became on sale
|
83 |
+
// Join the collection with the required tables
|
84 |
+
$collection->getSelect()
|
85 |
+
->reset(Zend_Db_Select::COLUMNS)
|
86 |
+
->columns(array('e.entity_id AS product_id',
|
87 |
+
'e.sku',
|
88 |
+
'catalog_name.value as product_name',
|
89 |
+
'catalog_price.value as product_price',
|
90 |
+
'quote_table.entity_id as cart_id',
|
91 |
+
'quote_table.updated_at as cart_updated_at',
|
92 |
+
'quote_table.abandoned_notified as has_been_notified',
|
93 |
+
'quote_table.customer_email as customer_email',
|
94 |
+
'quote_table.customer_firstname as customer_firstname',
|
95 |
+
'quote_table.customer_lastname as customer_lastname',
|
96 |
+
'customer.group_id as customer_group'
|
97 |
+
)
|
98 |
+
)
|
99 |
+
// Name
|
100 |
+
->joinInner(
|
101 |
+
array('catalog_name' => Mage::getSingleton("core/resource")->getTableName('catalog_product_entity_varchar')),
|
102 |
+
"catalog_name.entity_id = e.entity_id AND catalog_name.attribute_id = $nameId",
|
103 |
+
null)
|
104 |
+
// Price
|
105 |
+
->joinInner(
|
106 |
+
array('catalog_price' => Mage::getSingleton("core/resource")->getTableName('catalog_product_entity_decimal')),
|
107 |
+
"catalog_price.entity_id = e.entity_id AND catalog_price.attribute_id = $priceId",
|
108 |
+
null)
|
109 |
+
->joinInner(
|
110 |
+
array('quote_items' => Mage::getSingleton("core/resource")->getTableName('sales_flat_quote_item')),
|
111 |
+
'quote_items.product_id = e.entity_id AND quote_items.price > 0.00',
|
112 |
+
null)
|
113 |
+
->joinInner(
|
114 |
+
array('quote_table' => Mage::getSingleton("core/resource")->getTableName('sales_flat_quote')),
|
115 |
+
$emailJoin,
|
116 |
+
null)
|
117 |
+
->joinInner(
|
118 |
+
array('catalog_enabled' => Mage::getSingleton("core/resource")->getTableName('catalog_product_entity_int')),
|
119 |
+
'catalog_enabled.entity_id = e.entity_id AND catalog_enabled.attribute_id = '.$statusId.' AND catalog_enabled.value = 1',
|
120 |
+
null)
|
121 |
+
->joinInner(
|
122 |
+
array('inventory' => Mage::getSingleton("core/resource")->getTableName('cataloginventory_stock_status')),
|
123 |
+
'inventory.product_id = e.entity_id AND inventory.stock_status = 1 AND website_id = '.$websiteId,
|
124 |
+
null)
|
125 |
+
->joinInner(
|
126 |
+
array('customer' => Mage::getSingleton("core/resource")->getTableName('customer_entity')),
|
127 |
+
'quote_table.customer_email = customer.email',
|
128 |
+
null)
|
129 |
+
->order('quote_table.updated_at DESC');
|
130 |
+
}
|
131 |
+
|
132 |
+
return $collection;
|
133 |
+
}
|
134 |
+
|
135 |
+
public function getSalesCollection($storeId, $websiteId, $emails = array())
|
136 |
+
{
|
137 |
+
// Get the product collection
|
138 |
+
$collection = Mage::getResourceModel('catalog/product_collection')->setStore($storeId);
|
139 |
+
|
140 |
+
// Get the attribute id for the status attribute
|
141 |
+
$eavAttribute = Mage::getModel('eav/entity_attribute');
|
142 |
+
$statusId = $eavAttribute->getIdByCode('catalog_product', 'status');
|
143 |
+
$nameId = $eavAttribute->getIdByCode('catalog_product', 'name');
|
144 |
+
$priceId = $eavAttribute->getIdByCode('catalog_product', 'price');
|
145 |
+
$spriceId = $eavAttribute->getIdByCode('catalog_product', 'special_price');
|
146 |
+
$spfromId = $eavAttribute->getIdByCode('catalog_product', 'special_from_date');
|
147 |
+
$sptoId = $eavAttribute->getIdByCode('catalog_product', 'special_to_date');
|
148 |
+
|
149 |
+
// Normal join condition
|
150 |
+
$emailJoin = sprintf('quote_items.quote_id = quote_table.entity_id AND quote_table.items_count > 0 AND quote_table.is_active = 1 AND quote_table.customer_email IS NOT NULL AND quote_table.abandoned_sale_notified = 0 AND quote_table.store_id = %s', $storeId);
|
151 |
+
|
152 |
+
// In case an array of emails has been specified
|
153 |
+
if (!empty($emails)) {
|
154 |
+
$emailJoin = sprintf('%s AND quote_table.customer_email IN (%s)', $emailJoin, '"' . implode('", "', $emails) . '"');
|
155 |
+
}
|
156 |
+
|
157 |
+
// If flat catalog is enabled
|
158 |
+
if (Mage::helper('catalog/product_flat')->isEnabled())
|
159 |
+
{
|
160 |
+
// First collection: carts with products that became on sale
|
161 |
+
// Join the collection with the required tables
|
162 |
+
$collection->getSelect()
|
163 |
+
->reset(Zend_Db_Select::COLUMNS)
|
164 |
+
->columns(array('e.entity_id AS product_id',
|
165 |
+
'e.sku',
|
166 |
+
'catalog_flat.name as product_name',
|
167 |
+
'catalog_flat.price as product_price',
|
168 |
+
'catalog_flat.special_price as product_special_price',
|
169 |
+
'catalog_flat.special_from_date as product_special_from_date',
|
170 |
+
'catalog_flat.special_to_date as product_special_to_date',
|
171 |
+
'quote_table.entity_id as cart_id',
|
172 |
+
'quote_table.updated_at as cart_updated_at',
|
173 |
+
'quote_table.abandoned_sale_notified as has_been_notified',
|
174 |
+
'quote_items.price as product_price_in_cart',
|
175 |
+
'quote_table.customer_email as customer_email',
|
176 |
+
'quote_table.customer_firstname as customer_firstname',
|
177 |
+
'quote_table.customer_lastname as customer_lastname',
|
178 |
+
'customer.group_id as customer_group'
|
179 |
+
)
|
180 |
+
)
|
181 |
+
->joinInner(
|
182 |
+
array('quote_items' => Mage::getSingleton("core/resource")->getTableName('sales_flat_quote_item')),
|
183 |
+
'quote_items.product_id = e.entity_id AND quote_items.price > 0.00',
|
184 |
+
null)
|
185 |
+
->joinInner(
|
186 |
+
array('quote_table' => Mage::getSingleton("core/resource")->getTableName('sales_flat_quote')),
|
187 |
+
$emailJoin,
|
188 |
+
null)
|
189 |
+
->joinInner(
|
190 |
+
array('catalog_flat' => Mage::getSingleton("core/resource")->getTableName('catalog_product_flat_'.$storeId)),
|
191 |
+
'catalog_flat.entity_id = e.entity_id',
|
192 |
+
null)
|
193 |
+
->joinInner(
|
194 |
+
array('catalog_enabled' => Mage::getSingleton("core/resource")->getTableName('catalog_product_entity_int')),
|
195 |
+
'catalog_enabled.entity_id = e.entity_id AND catalog_enabled.attribute_id = '.$statusId.' AND catalog_enabled.value = 1',
|
196 |
+
null)
|
197 |
+
->joinInner(
|
198 |
+
array('inventory' => Mage::getSingleton("core/resource")->getTableName('cataloginventory_stock_status')),
|
199 |
+
'inventory.product_id = e.entity_id AND inventory.stock_status = 1 AND inventory.website_id = '.$websiteId,
|
200 |
+
null)
|
201 |
+
->joinInner(
|
202 |
+
array('customer' => Mage::getSingleton("core/resource")->getTableName('customer_entity')),
|
203 |
+
'quote_table.customer_email = customer.email',
|
204 |
+
null)
|
205 |
+
->order('quote_table.updated_at DESC');
|
206 |
+
}
|
207 |
+
else
|
208 |
+
{
|
209 |
+
// First collection: carts with products that became on sale
|
210 |
+
// Join the collection with the required tables
|
211 |
+
$collection->getSelect()
|
212 |
+
->reset(Zend_Db_Select::COLUMNS)
|
213 |
+
->columns(array('e.entity_id AS product_id',
|
214 |
+
'e.sku',
|
215 |
+
'catalog_name.value as product_name',
|
216 |
+
'catalog_price.value as product_price',
|
217 |
+
'catalog_sprice.value as product_special_price',
|
218 |
+
'catalog_spfrom.value as product_special_from_date',
|
219 |
+
'catalog_spto.value as product_special_to_date',
|
220 |
+
'quote_table.entity_id as cart_id',
|
221 |
+
'quote_table.updated_at as cart_updated_at',
|
222 |
+
'quote_table.abandoned_sale_notified as has_been_notified',
|
223 |
+
'quote_items.price as product_price_in_cart',
|
224 |
+
'quote_table.customer_email as customer_email',
|
225 |
+
'quote_table.customer_firstname as customer_firstname',
|
226 |
+
'quote_table.customer_lastname as customer_lastname',
|
227 |
+
'customer.group_id as customer_group'
|
228 |
+
)
|
229 |
+
)
|
230 |
+
// Name
|
231 |
+
->joinInner(
|
232 |
+
array('catalog_name' => Mage::getSingleton("core/resource")->getTableName('catalog_product_entity_varchar')),
|
233 |
+
"catalog_name.entity_id = e.entity_id AND catalog_name.attribute_id = $nameId",
|
234 |
+
null)
|
235 |
+
// Price
|
236 |
+
->joinInner(
|
237 |
+
array('catalog_price' => Mage::getSingleton("core/resource")->getTableName('catalog_product_entity_decimal')),
|
238 |
+
"catalog_price.entity_id = e.entity_id AND catalog_price.attribute_id = $priceId",
|
239 |
+
null)
|
240 |
+
// Special Price
|
241 |
+
->joinInner(
|
242 |
+
array('catalog_sprice' => Mage::getSingleton("core/resource")->getTableName('catalog_product_entity_decimal')),
|
243 |
+
"catalog_sprice.entity_id = e.entity_id AND catalog_sprice.attribute_id = $spriceId",
|
244 |
+
null)
|
245 |
+
// Special From Date
|
246 |
+
->joinInner(
|
247 |
+
array('catalog_spfrom' => Mage::getSingleton("core/resource")->getTableName('catalog_product_entity_datetime')),
|
248 |
+
"catalog_spfrom.entity_id = e.entity_id AND catalog_spfrom.attribute_id = $spfromId",
|
249 |
+
null)
|
250 |
+
// Special To Date
|
251 |
+
->joinInner(
|
252 |
+
array('catalog_spto' => Mage::getSingleton("core/resource")->getTableName('catalog_product_entity_datetime')),
|
253 |
+
"catalog_spto.entity_id = e.entity_id AND catalog_spto.attribute_id = $sptoId",
|
254 |
+
null)
|
255 |
+
->joinInner(
|
256 |
+
array('quote_items' => Mage::getSingleton("core/resource")->getTableName('sales_flat_quote_item')),
|
257 |
+
'quote_items.product_id = e.entity_id AND quote_items.price > 0.00',
|
258 |
+
null)
|
259 |
+
->joinInner(
|
260 |
+
array('quote_table' => Mage::getSingleton("core/resource")->getTableName('sales_flat_quote')),
|
261 |
+
$emailJoin,
|
262 |
+
null)
|
263 |
+
->joinInner(
|
264 |
+
array('catalog_enabled' => Mage::getSingleton("core/resource")->getTableName('catalog_product_entity_int')),
|
265 |
+
'catalog_enabled.entity_id = e.entity_id AND catalog_enabled.attribute_id = '.$statusId.' AND catalog_enabled.value = 1',
|
266 |
+
null)
|
267 |
+
->joinInner(
|
268 |
+
array('inventory' => Mage::getSingleton("core/resource")->getTableName('cataloginventory_stock_status')),
|
269 |
+
'inventory.product_id = e.entity_id AND inventory.stock_status = 1 AND inventory.website_id = '.$websiteId,
|
270 |
+
null)
|
271 |
+
->joinInner(
|
272 |
+
array('customer' => Mage::getSingleton("core/resource")->getTableName('customer_entity')),
|
273 |
+
'quote_table.customer_email = customer.email',
|
274 |
+
null)
|
275 |
+
->order('quote_table.updated_at DESC');
|
276 |
+
}
|
277 |
+
|
278 |
+
return $collection;
|
279 |
+
}
|
280 |
+
}
|
app/code/community/DigitalPianism/Abandonedcarts/Model/Link.php
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Class DigitalPianism_Abandonedcarts_Model_Link
|
5 |
+
*/
|
6 |
+
class DigitalPianism_Abandonedcarts_Model_Link extends Mage_Core_Model_Abstract
|
7 |
+
{
|
8 |
+
|
9 |
+
protected function _construct()
|
10 |
+
{
|
11 |
+
$this->_init('abandonedcarts/link', 'link_id');
|
12 |
+
}
|
13 |
+
|
14 |
+
}
|
app/code/community/DigitalPianism/Abandonedcarts/Model/Link/Cleaner.php
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Class DigitalPianism_Abandonedcarts_Model_Link_Cleaner
|
5 |
+
*/
|
6 |
+
class DigitalPianism_Abandonedcarts_Model_Link_Cleaner {
|
7 |
+
|
8 |
+
/**
|
9 |
+
*
|
10 |
+
*/
|
11 |
+
public function cleanExpiredLinks()
|
12 |
+
{
|
13 |
+
$now = new Zend_Date(Mage::getModel('core/date')->timestamp());
|
14 |
+
|
15 |
+
// Get the collection of links expired
|
16 |
+
$collection = Mage::getResourceModel('abandonedcarts/link_collection')
|
17 |
+
->addFieldToSelect('link_id')
|
18 |
+
->addFieldToFilter('expiration_date', array(
|
19 |
+
'lteq' => $now->toString('YYYY-MM-dd HH:mm:ss')
|
20 |
+
)
|
21 |
+
);
|
22 |
+
|
23 |
+
if (!$collection->getSize())
|
24 |
+
return;
|
25 |
+
|
26 |
+
// Delete the expired links
|
27 |
+
foreach ($collection as $expiredLink) {
|
28 |
+
$expiredLink->delete();
|
29 |
+
}
|
30 |
+
}
|
31 |
+
}
|
app/code/community/DigitalPianism/Abandonedcarts/Model/Log.php
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Class DigitalPianism_Abandonedcarts_Model_Log
|
5 |
+
*/
|
6 |
+
class DigitalPianism_Abandonedcarts_Model_Log extends Mage_Core_Model_Abstract
|
7 |
+
{
|
8 |
+
|
9 |
+
const TYPE_NORMAL = 0;
|
10 |
+
const TYPE_SALES = 1;
|
11 |
+
|
12 |
+
protected function _construct()
|
13 |
+
{
|
14 |
+
$this->_init('abandonedcarts/log', 'log_id');
|
15 |
+
}
|
16 |
+
|
17 |
+
public function toOptionArray()
|
18 |
+
{
|
19 |
+
return array(
|
20 |
+
self::TYPE_NORMAL => Mage::helper('abandonedcarts')->__('Abandoned cart email'),
|
21 |
+
self::TYPE_SALES => Mage::helper('abandonedcarts')->__('Sale abandoned cart email')
|
22 |
+
);
|
23 |
+
}
|
24 |
+
|
25 |
+
}
|
app/code/community/DigitalPianism/Abandonedcarts/Model/Notifier.php
ADDED
@@ -0,0 +1,637 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Class DigitalPianism_Abandonedcarts_Model_Notifier
|
5 |
+
*/
|
6 |
+
class DigitalPianism_Abandonedcarts_Model_Notifier extends Mage_Core_Model_Abstract
|
7 |
+
{
|
8 |
+
const IMAGE_SIZE = 250;
|
9 |
+
const CAMPAIGN_SOURCE = "abandonedcarts";
|
10 |
+
const CAMPAIGN_MEDIUM = "email";
|
11 |
+
/**
|
12 |
+
* Autologin links expiration in days
|
13 |
+
*/
|
14 |
+
const EXPIRATION = "2";
|
15 |
+
|
16 |
+
/**
|
17 |
+
* @var array
|
18 |
+
*/
|
19 |
+
protected $_recipients = array();
|
20 |
+
|
21 |
+
/**
|
22 |
+
* @var array
|
23 |
+
*/
|
24 |
+
protected $_saleRecipients = array();
|
25 |
+
|
26 |
+
/**
|
27 |
+
* @var string
|
28 |
+
*/
|
29 |
+
protected $_today = "";
|
30 |
+
|
31 |
+
/**
|
32 |
+
* @var array
|
33 |
+
*/
|
34 |
+
protected $_customerGroups = array();
|
35 |
+
|
36 |
+
/**
|
37 |
+
* @var
|
38 |
+
*/
|
39 |
+
protected $_currentStoreId;
|
40 |
+
|
41 |
+
/**
|
42 |
+
* @var
|
43 |
+
*/
|
44 |
+
protected $_originalStoreId;
|
45 |
+
|
46 |
+
/**
|
47 |
+
* @throws Zend_Date_Exception
|
48 |
+
*/
|
49 |
+
protected function _setToday()
|
50 |
+
{
|
51 |
+
// Date handling
|
52 |
+
$store = Mage_Core_Model_App::ADMIN_STORE_ID;
|
53 |
+
$timezone = Mage::app()->getStore($store)->getConfig(Mage_Core_Model_Locale::XML_PATH_DEFAULT_TIMEZONE);
|
54 |
+
date_default_timezone_set($timezone);
|
55 |
+
|
56 |
+
// Current date
|
57 |
+
$currentdate = date("Ymd");
|
58 |
+
|
59 |
+
$day = (int)substr($currentdate,-2);
|
60 |
+
$month = (int)substr($currentdate,4,2);
|
61 |
+
$year = (int)substr($currentdate,0,4);
|
62 |
+
|
63 |
+
$date = array(
|
64 |
+
'year' => $year,
|
65 |
+
'month' => $month,
|
66 |
+
'day' => $day,
|
67 |
+
'hour' => 23,
|
68 |
+
'minute' => 59,
|
69 |
+
'second' => 59
|
70 |
+
);
|
71 |
+
|
72 |
+
$today = new Zend_Date($date);
|
73 |
+
$today->setTimeZone("UTC");
|
74 |
+
|
75 |
+
date_default_timezone_set($timezone);
|
76 |
+
|
77 |
+
$this->_today = $today->toString("Y-MM-dd HH:mm:ss");
|
78 |
+
}
|
79 |
+
|
80 |
+
/**
|
81 |
+
* @return string
|
82 |
+
*/
|
83 |
+
protected function _getToday()
|
84 |
+
{
|
85 |
+
return $this->_today;
|
86 |
+
}
|
87 |
+
|
88 |
+
/**
|
89 |
+
* @return array
|
90 |
+
*/
|
91 |
+
protected function _getRecipients()
|
92 |
+
{
|
93 |
+
return $this->_recipients;
|
94 |
+
}
|
95 |
+
|
96 |
+
/**
|
97 |
+
* @return array
|
98 |
+
*/
|
99 |
+
protected function _getSaleRecipients()
|
100 |
+
{
|
101 |
+
return $this->_saleRecipients;
|
102 |
+
}
|
103 |
+
|
104 |
+
protected function _getProductImage($productId)
|
105 |
+
{
|
106 |
+
// Get product image via collection
|
107 |
+
$_productCollection = Mage::getResourceModel('catalog/product_collection');
|
108 |
+
// Add attributes to the collection
|
109 |
+
$_productCollection->addAttributeToFilter('entity_id',array('eq' => $productId));
|
110 |
+
// Add image to the collection
|
111 |
+
$_productCollection->joinAttribute('small_image', 'catalog_product/image', 'entity_id', null, 'left');
|
112 |
+
// Limit the collection to get the specific product
|
113 |
+
$_productCollection->setPageSize(1);
|
114 |
+
|
115 |
+
try {
|
116 |
+
$productImg = (string)Mage::helper('catalog/image')->init($_productCollection->getFirstItem(), 'small_image')->resize(self::IMAGE_SIZE);
|
117 |
+
} catch (Exception $e) {
|
118 |
+
$productImg = false;
|
119 |
+
}
|
120 |
+
|
121 |
+
return $productImg;
|
122 |
+
}
|
123 |
+
|
124 |
+
/**
|
125 |
+
* @param $args
|
126 |
+
*/
|
127 |
+
public function generateRecipients($args)
|
128 |
+
{
|
129 |
+
// Customer group check
|
130 |
+
if (array_key_exists('customer_group',$args['row'])
|
131 |
+
&& !in_array($args['row']['customer_group'],$this->_customerGroups)) {
|
132 |
+
return;
|
133 |
+
}
|
134 |
+
|
135 |
+
// Test if the customer is already in the array
|
136 |
+
if (!array_key_exists($args['row']['customer_email'], $this->_recipients)) {
|
137 |
+
// Create an array of variables to assign to template
|
138 |
+
$emailTemplateVariables = array();
|
139 |
+
|
140 |
+
// Array that contains the data which will be used inside the template
|
141 |
+
$emailTemplateVariables['fullname'] = $args['row']['customer_firstname'].' '.$args['row']['customer_lastname'];
|
142 |
+
$emailTemplateVariables['firstname'] = $args['row']['customer_firstname'];
|
143 |
+
$emailTemplateVariables['productname'][] = $args['row']['product_name'];
|
144 |
+
|
145 |
+
// Assign the values to the array of recipients
|
146 |
+
$this->_recipients[$args['row']['customer_email']]['cartId'] = $args['row']['cart_id'];
|
147 |
+
|
148 |
+
// Add product image
|
149 |
+
$emailTemplateVariables['productimage'][] = $this->_getProductImage($args['row']['product_id']);
|
150 |
+
|
151 |
+
// Add the link
|
152 |
+
$token = "";
|
153 |
+
// Autologin only applies to real customer (skip not logged in customer group)
|
154 |
+
if (Mage::helper('abandonedcarts')->isAutologin()) {
|
155 |
+
$token = $this->_generateToken($args['row']['customer_email']);
|
156 |
+
}
|
157 |
+
$emailTemplateVariables['link'] = $this->_generateUrl($token);
|
158 |
+
} else {
|
159 |
+
// We create some extra variables if there is several products in the cart
|
160 |
+
$emailTemplateVariables = $this->_recipients[$args['row']['customer_email']]['emailTemplateVariables'];
|
161 |
+
// We increase the product count
|
162 |
+
//$emailTemplateVariables['extraproductcount'] += 1;
|
163 |
+
$emailTemplateVariables['productname'][] = $args['row']['product_name'];
|
164 |
+
|
165 |
+
// Add product image
|
166 |
+
$emailTemplateVariables['productimage'][] = $this->_getProductImage($args['row']['product_id']);
|
167 |
+
}
|
168 |
+
|
169 |
+
// Assign the array of template variables
|
170 |
+
$this->_recipients[$args['row']['customer_email']]['emailTemplateVariables'] = $emailTemplateVariables;
|
171 |
+
$this->_recipients[$args['row']['customer_email']]['store_id'] = $this->_currentStoreId;
|
172 |
+
}
|
173 |
+
|
174 |
+
/**
|
175 |
+
* @param $args
|
176 |
+
*/
|
177 |
+
public function generateSaleRecipients($args)
|
178 |
+
{
|
179 |
+
// Customer group check
|
180 |
+
if (array_key_exists('customer_group',$args['row'])
|
181 |
+
&& !in_array($args['row']['customer_group'],$this->_customerGroups)) {
|
182 |
+
|
183 |
+
return;
|
184 |
+
}
|
185 |
+
|
186 |
+
// Double check if the special from date is set
|
187 |
+
if (!array_key_exists('product_special_from_date',$args['row'])
|
188 |
+
|| !$args['row']['product_special_from_date']) {
|
189 |
+
|
190 |
+
// If not we use today for the comparison
|
191 |
+
$fromDate = $this->_getToday();
|
192 |
+
} else {
|
193 |
+
$fromDate = $args['row']['product_special_from_date'];
|
194 |
+
}
|
195 |
+
|
196 |
+
// Do the same for the special to date
|
197 |
+
if (!array_key_exists('product_special_to_date',$args['row'])
|
198 |
+
|| !$args['row']['product_special_to_date']) {
|
199 |
+
|
200 |
+
$toDate = $this->_getToday();
|
201 |
+
} else {
|
202 |
+
$toDate = $args['row']['product_special_to_date'];
|
203 |
+
}
|
204 |
+
|
205 |
+
// We need to ensure that the price in cart is higher than the new special price
|
206 |
+
// As well as the date comparison in case the sale is over or hasn't started
|
207 |
+
if ($args['row']['product_price_in_cart'] > 0.00
|
208 |
+
&& $args['row']['product_special_price'] > 0.00
|
209 |
+
&& ($args['row']['product_price_in_cart'] > $args['row']['product_special_price'])
|
210 |
+
&& ($fromDate <= $this->_getToday())
|
211 |
+
&& ($toDate >= $this->_getToday())) {
|
212 |
+
|
213 |
+
// Test if the customer is already in the array
|
214 |
+
if (!array_key_exists($args['row']['customer_email'], $this->_saleRecipients)) {
|
215 |
+
|
216 |
+
// Create an array of variables to assign to template
|
217 |
+
$emailTemplateVariables = array();
|
218 |
+
|
219 |
+
// Array that contains the data which will be used inside the template
|
220 |
+
$emailTemplateVariables['fullname'] = $args['row']['customer_firstname'].' '.$args['row']['customer_lastname'];
|
221 |
+
$emailTemplateVariables['firstname'] = $args['row']['customer_firstname'];
|
222 |
+
$emailTemplateVariables['productname'][] = $args['row']['product_name'];
|
223 |
+
$emailTemplateVariables['cartprice'][] = Mage::helper('core')->currency(floatval(number_format(floatval($args['row']['product_price_in_cart']),2)), true, false);
|
224 |
+
$emailTemplateVariables['specialprice'][] = Mage::helper('core')->currency(floatval(number_format(floatval($args['row']['product_special_price']),2)), true, false);
|
225 |
+
|
226 |
+
// Assign the values to the array of recipients
|
227 |
+
$this->_saleRecipients[$args['row']['customer_email']]['cartId'] = $args['row']['cart_id'];
|
228 |
+
|
229 |
+
// Add product image
|
230 |
+
$emailTemplateVariables['productimage'][] = $this->_getProductImage($args['row']['product_id']);
|
231 |
+
|
232 |
+
// Add the link
|
233 |
+
$token = "";
|
234 |
+
// Autologin only applies to real customer (skip not logged in customer group)
|
235 |
+
if (Mage::helper('abandonedcarts')->isAutologin()) {
|
236 |
+
$token = $this->_generateToken($args['row']['customer_email']);
|
237 |
+
}
|
238 |
+
$emailTemplateVariables['link'] = $this->_generateUrl($token);
|
239 |
+
|
240 |
+
// If one product before
|
241 |
+
$emailTemplateVariables['discount'] = number_format(floatval($args['row']['product_price_in_cart']),2) - number_format(floatval($args['row']['product_special_price']),2);
|
242 |
+
} else {
|
243 |
+
// We create some extra variables if there is several products in the cart
|
244 |
+
$emailTemplateVariables = $this->_saleRecipients[$args['row']['customer_email']]['emailTemplateVariables'];
|
245 |
+
// Discount amount
|
246 |
+
// We add the discount on the second product
|
247 |
+
$moreDiscount = number_format(floatval($args['row']['product_price_in_cart']),2) - number_format(floatval($args['row']['product_special_price']),2);
|
248 |
+
$emailTemplateVariables['discount'] += $moreDiscount;
|
249 |
+
|
250 |
+
$emailTemplateVariables['productname'][] = $args['row']['product_name'];
|
251 |
+
$emailTemplateVariables['cartprice'][] = Mage::helper('core')->currency(floatval(number_format(floatval($args['row']['product_price_in_cart']),2)), true, false);
|
252 |
+
$emailTemplateVariables['specialprice'][] = Mage::helper('core')->currency(floatval(number_format(floatval($args['row']['product_special_price']),2)), true, false);
|
253 |
+
|
254 |
+
// Add product image
|
255 |
+
$emailTemplateVariables['productimage'][] = $this->_getProductImage($args['row']['product_id']);
|
256 |
+
}
|
257 |
+
|
258 |
+
// Assign the array of template variables
|
259 |
+
$this->_saleRecipients[$args['row']['customer_email']]['emailTemplateVariables'] = $emailTemplateVariables;
|
260 |
+
$this->_saleRecipients[$args['row']['customer_email']]['store_id'] = $this->_currentStoreId;
|
261 |
+
}
|
262 |
+
}
|
263 |
+
|
264 |
+
/**
|
265 |
+
* @param $dryrun
|
266 |
+
* @param $testemail
|
267 |
+
* @throws Exception
|
268 |
+
*/
|
269 |
+
protected function _sendSaleEmails($dryrun, $testemail)
|
270 |
+
{
|
271 |
+
// Send the emails via a loop
|
272 |
+
foreach ($this->_getSaleRecipients() as $email => $recipient) {
|
273 |
+
|
274 |
+
// Store Id
|
275 |
+
Mage::app()->setCurrentStore($recipient['store_id']);
|
276 |
+
// Get the transactional email template
|
277 |
+
$templateId = Mage::getStoreConfig('abandonedcartsconfig/email/email_template_sale');
|
278 |
+
// Get the sender
|
279 |
+
$sender = array();
|
280 |
+
$sender['email'] = Mage::getStoreConfig('abandonedcartsconfig/email/email');
|
281 |
+
$sender['name'] = Mage::getStoreConfig('abandonedcartsconfig/email/name');
|
282 |
+
$recipient['emailTemplateVariables']['email'] = $email;
|
283 |
+
|
284 |
+
// Format discount with currency
|
285 |
+
$recipient['emailTemplateVariables']['discount'] = Mage::helper('core')->currency($recipient['emailTemplateVariables']['discount'], true, false);
|
286 |
+
|
287 |
+
// Don't send the email if dryrun is set
|
288 |
+
if ($dryrun) {
|
289 |
+
// If the test email is set and found
|
290 |
+
if (isset($testemail)) {
|
291 |
+
// Send to the test email
|
292 |
+
Mage::getModel('core/email_template')
|
293 |
+
->sendTransactional(
|
294 |
+
$templateId,
|
295 |
+
$sender,
|
296 |
+
$testemail,
|
297 |
+
$recipient['emailTemplateVariables']['fullname'] ,
|
298 |
+
$recipient['emailTemplateVariables'],
|
299 |
+
null);
|
300 |
+
}
|
301 |
+
} else {
|
302 |
+
// Send the email
|
303 |
+
Mage::getModel('core/email_template')
|
304 |
+
->sendTransactional(
|
305 |
+
$templateId,
|
306 |
+
$sender,
|
307 |
+
$email,
|
308 |
+
$recipient['emailTemplateVariables']['fullname'] ,
|
309 |
+
$recipient['emailTemplateVariables'],
|
310 |
+
null);
|
311 |
+
}
|
312 |
+
|
313 |
+
if (Mage::helper('abandonedcarts')->isLogEnabled()) {
|
314 |
+
// Log the details
|
315 |
+
$comment = sprintf(
|
316 |
+
"Email sent to %s, product name: %s, cart price: %s, special price: %s, discount: %s, product image: %s, link: %s",
|
317 |
+
$recipient['emailTemplateVariables']['fullname'],
|
318 |
+
implode(',', $recipient['emailTemplateVariables']['productname']),
|
319 |
+
implode(',', $recipient['emailTemplateVariables']['cartprice']),
|
320 |
+
implode(',', $recipient['emailTemplateVariables']['specialprice']),
|
321 |
+
$recipient['emailTemplateVariables']['discount'],
|
322 |
+
implode(',', $recipient['emailTemplateVariables']['productimage']),
|
323 |
+
$recipient['emailTemplateVariables']['link']
|
324 |
+
);
|
325 |
+
|
326 |
+
Mage::getModel('abandonedcarts/log')->setData(
|
327 |
+
array(
|
328 |
+
'customer_email' => $email,
|
329 |
+
'type' => DigitalPianism_Abandonedcarts_Model_Log::TYPE_SALES,
|
330 |
+
'comment' => $comment,
|
331 |
+
'store' => $recipient['store_id'],
|
332 |
+
'dryrun' => $dryrun ? 1 : 0
|
333 |
+
)
|
334 |
+
)->save();
|
335 |
+
}
|
336 |
+
|
337 |
+
// Save only if dryrun is false
|
338 |
+
if (!$dryrun) {
|
339 |
+
|
340 |
+
// Load the quote
|
341 |
+
$quote = Mage::getModel('sales/quote')->load($recipient['cartId']);
|
342 |
+
|
343 |
+
// We change the notification attribute
|
344 |
+
$quote->setAbandonedSaleNotified(1);
|
345 |
+
|
346 |
+
$quote->getResource()->saveAttribute($quote,array('abandoned_sale_notified'));
|
347 |
+
}
|
348 |
+
}
|
349 |
+
}
|
350 |
+
|
351 |
+
/**
|
352 |
+
* @param $dryrun
|
353 |
+
* @param $testemail
|
354 |
+
* @throws Exception
|
355 |
+
*/
|
356 |
+
protected function _sendEmails($dryrun, $testemail)
|
357 |
+
{
|
358 |
+
// Send the emails via a loop
|
359 |
+
foreach ($this->_getRecipients() as $email => $recipient) {
|
360 |
+
|
361 |
+
// Store ID
|
362 |
+
Mage::app()->setCurrentStore($recipient['store_id']);
|
363 |
+
// Get the transactional email template
|
364 |
+
$templateId = Mage::getStoreConfig('abandonedcartsconfig/email/email_template');
|
365 |
+
// Get the sender
|
366 |
+
$sender = array();
|
367 |
+
$sender['email'] = Mage::getStoreConfig('abandonedcartsconfig/email/email');
|
368 |
+
$sender['name'] = Mage::getStoreConfig('abandonedcartsconfig/email/name');
|
369 |
+
$recipient['emailTemplateVariables']['email'] = $email;
|
370 |
+
|
371 |
+
// Don't send the email if dryrun is set
|
372 |
+
if ($dryrun) {
|
373 |
+
// If the test email is set and found
|
374 |
+
if (isset($testemail)) {
|
375 |
+
// Send the email to the test email
|
376 |
+
Mage::getModel('core/email_template')
|
377 |
+
->sendTransactional(
|
378 |
+
$templateId,
|
379 |
+
$sender,
|
380 |
+
$testemail,
|
381 |
+
$recipient['emailTemplateVariables']['fullname'] ,
|
382 |
+
$recipient['emailTemplateVariables'],
|
383 |
+
null);
|
384 |
+
}
|
385 |
+
} else {
|
386 |
+
// Send the email
|
387 |
+
Mage::getModel('core/email_template')
|
388 |
+
->sendTransactional(
|
389 |
+
$templateId,
|
390 |
+
$sender,
|
391 |
+
$email,
|
392 |
+
$recipient['emailTemplateVariables']['fullname'] ,
|
393 |
+
$recipient['emailTemplateVariables'],
|
394 |
+
null);
|
395 |
+
}
|
396 |
+
|
397 |
+
if (Mage::helper('abandonedcarts')->isLogEnabled()) {
|
398 |
+
// Log the details
|
399 |
+
$comment = sprintf(
|
400 |
+
//"Email sent to %s, product name: %s, product image: %s, extra product count: %s, link: %s",
|
401 |
+
"Email sent to %s, product name: %s, product image: %s, link: %s",
|
402 |
+
$recipient['emailTemplateVariables']['fullname'],
|
403 |
+
implode(',', $recipient['emailTemplateVariables']['productname']),
|
404 |
+
implode(',', $recipient['emailTemplateVariables']['productimage']) ? implode(',', $recipient['emailTemplateVariables']['productimage']) : "none",
|
405 |
+
$recipient['emailTemplateVariables']['link']
|
406 |
+
);
|
407 |
+
|
408 |
+
Mage::getModel('abandonedcarts/log')->setData(
|
409 |
+
array(
|
410 |
+
'customer_email' => $email,
|
411 |
+
'type' => DigitalPianism_Abandonedcarts_Model_Log::TYPE_NORMAL,
|
412 |
+
'comment' => $comment,
|
413 |
+
'store' => $recipient['store_id'],
|
414 |
+
'dryrun' => $dryrun ? 1 : 0
|
415 |
+
)
|
416 |
+
)->save();
|
417 |
+
}
|
418 |
+
|
419 |
+
// Save only if dryrun is false
|
420 |
+
if (!$dryrun) {
|
421 |
+
// Load the quote
|
422 |
+
$quote = Mage::getModel('sales/quote')->load($recipient['cartId']);
|
423 |
+
|
424 |
+
// We change the notification attribute
|
425 |
+
$quote->setAbandonedNotified(1);
|
426 |
+
|
427 |
+
$quote->getResource()->saveAttribute($quote,array('abandoned_notified'));
|
428 |
+
}
|
429 |
+
}
|
430 |
+
}
|
431 |
+
|
432 |
+
/**
|
433 |
+
* Send notification email to customer with abandoned cart containing sale products
|
434 |
+
* If dryrun is set to true, it won't send emails and won't alter quotes
|
435 |
+
* @param boolean
|
436 |
+
* @param string
|
437 |
+
*/
|
438 |
+
public function sendAbandonedCartsSaleEmail($dryrun = false, $testemail = null, $emails = array())
|
439 |
+
{
|
440 |
+
if (Mage::helper('abandonedcarts')->getDryRun()) {
|
441 |
+
$dryrun = true;
|
442 |
+
}
|
443 |
+
|
444 |
+
if (Mage::helper('abandonedcarts')->getTestEmail()) {
|
445 |
+
$testemail = Mage::helper('abandonedcarts')->getTestEmail();
|
446 |
+
}
|
447 |
+
|
448 |
+
// Set customer groups
|
449 |
+
$this->_customerGroups = $this->_customerGroups ? $this->_customerGroups : Mage::helper('abandonedcarts')->getCustomerGroupsLimitation();
|
450 |
+
// Original store id
|
451 |
+
$this->_originalStoreId = Mage::app()->getStore()->getId();
|
452 |
+
try
|
453 |
+
{
|
454 |
+
if (Mage::helper('abandonedcarts')->isSaleEnabled()) {
|
455 |
+
|
456 |
+
$this->_setToday();
|
457 |
+
|
458 |
+
// Loop through the stores
|
459 |
+
foreach (Mage::app()->getWebsites() as $website) {
|
460 |
+
// Get the website id
|
461 |
+
$websiteId = $website->getWebsiteId();
|
462 |
+
foreach ($website->getGroups() as $group) {
|
463 |
+
$stores = $group->getStores();
|
464 |
+
foreach ($stores as $store) {
|
465 |
+
|
466 |
+
// Get the store id
|
467 |
+
$storeId = $store->getStoreId();
|
468 |
+
$this->_currentStoreId = $storeId;
|
469 |
+
|
470 |
+
// Init the store to be able to load the quote and the collections properly
|
471 |
+
Mage::app()->init($storeId,'store');
|
472 |
+
|
473 |
+
// Get the collection
|
474 |
+
$collection = Mage::getModel('abandonedcarts/collection')->getSalesCollection($storeId, $websiteId, $emails);
|
475 |
+
|
476 |
+
//$collection->printlogquery(true,true);
|
477 |
+
$collection->load();
|
478 |
+
|
479 |
+
// Skip the rest of the code if the collection is empty
|
480 |
+
if ($collection->getSize() == 0) {
|
481 |
+
continue;
|
482 |
+
}
|
483 |
+
|
484 |
+
// Call iterator walk method with collection query string and callback method as parameters
|
485 |
+
// Has to be used to handle massive collection instead of foreach
|
486 |
+
Mage::getSingleton('core/resource_iterator')->walk($collection->getSelect(), array(array($this, 'generateSaleRecipients')));
|
487 |
+
}
|
488 |
+
}
|
489 |
+
}
|
490 |
+
$this->_sendSaleEmails($dryrun, $testemail);
|
491 |
+
}
|
492 |
+
Mage::app()->setCurrentStore($this->_originalStoreId);
|
493 |
+
|
494 |
+
return count($this->_getSaleRecipients());
|
495 |
+
}
|
496 |
+
catch (Exception $e)
|
497 |
+
{
|
498 |
+
Mage::app()->setCurrentStore($this->_originalStoreId);
|
499 |
+
Mage::helper('abandonedcarts')->log(sprintf("%s->Error: %s", __METHOD__, $e->getMessage()));
|
500 |
+
return 0;
|
501 |
+
}
|
502 |
+
}
|
503 |
+
|
504 |
+
/**
|
505 |
+
* Send notification email to customer with abandoned carts after the number of days specified in the config
|
506 |
+
* @param bool $nodate
|
507 |
+
* @param bool $dryrun
|
508 |
+
* @param string $testemail
|
509 |
+
* @internal param if $boolean dryrun is set to true, it won't send emails and won't alter quotes
|
510 |
+
*/
|
511 |
+
public function sendAbandonedCartsEmail($nodate = false, $dryrun = false, $testemail = null, $emails = array())
|
512 |
+
{
|
513 |
+
if (Mage::helper('abandonedcarts')->getDryRun()) {
|
514 |
+
$dryrun = true;
|
515 |
+
}
|
516 |
+
|
517 |
+
if (Mage::helper('abandonedcarts')->getTestEmail()) {
|
518 |
+
$testemail = Mage::helper('abandonedcarts')->getTestEmail();
|
519 |
+
}
|
520 |
+
|
521 |
+
// Set customer groups
|
522 |
+
$this->_customerGroups = $this->_customerGroups ? $this->_customerGroups : Mage::helper('abandonedcarts')->getCustomerGroupsLimitation();
|
523 |
+
// Original store id
|
524 |
+
$this->_originalStoreId = Mage::app()->getStore()->getId();
|
525 |
+
try
|
526 |
+
{
|
527 |
+
if (Mage::helper('abandonedcarts')->isEnabled()) {
|
528 |
+
|
529 |
+
// Date handling
|
530 |
+
$store = Mage_Core_Model_App::ADMIN_STORE_ID;
|
531 |
+
$timezone = Mage::app()->getStore($store)->getConfig(Mage_Core_Model_Locale::XML_PATH_DEFAULT_TIMEZONE);
|
532 |
+
date_default_timezone_set($timezone);
|
533 |
+
|
534 |
+
// If the nodate parameter is set to false
|
535 |
+
if (!$nodate) {
|
536 |
+
// Get the delay provided and convert it to a proper date
|
537 |
+
$delay = Mage::getStoreConfig('abandonedcartsconfig/options/notify_delay');
|
538 |
+
$delay = date('Y-m-d H:i:s', time() - $delay * 24 * 3600);
|
539 |
+
} else {
|
540 |
+
// We create a date in the future to handle all abandoned carts
|
541 |
+
$delay = date('Y-m-d H:i:s', strtotime("+7 day"));
|
542 |
+
}
|
543 |
+
|
544 |
+
// Loop through the stores
|
545 |
+
foreach (Mage::app()->getWebsites() as $website) {
|
546 |
+
// Get the website id
|
547 |
+
$websiteId = $website->getWebsiteId();
|
548 |
+
foreach ($website->getGroups() as $group) {
|
549 |
+
$stores = $group->getStores();
|
550 |
+
foreach ($stores as $store) {
|
551 |
+
|
552 |
+
// Get the store id
|
553 |
+
$storeId = $store->getStoreId();
|
554 |
+
$this->_currentStoreId = $storeId;
|
555 |
+
// Init the store to be able to load the quote and the collections properly
|
556 |
+
Mage::app()->init($storeId, 'store');
|
557 |
+
|
558 |
+
// Get the collection
|
559 |
+
$collection = Mage::getModel('abandonedcarts/collection')->getCollection($delay, $storeId, $websiteId, $emails);
|
560 |
+
|
561 |
+
//$collection->printlogquery(false,true);
|
562 |
+
$collection->load();
|
563 |
+
|
564 |
+
// Skip the rest of the code if the collection is empty
|
565 |
+
if ($collection->getSize() == 0) {
|
566 |
+
continue;
|
567 |
+
}
|
568 |
+
|
569 |
+
// Call iterator walk method with collection query string and callback method as parameters
|
570 |
+
// Has to be used to handle massive collection instead of foreach
|
571 |
+
Mage::getSingleton('core/resource_iterator')->walk($collection->getSelect(), array(array($this, 'generateRecipients')));
|
572 |
+
}
|
573 |
+
}
|
574 |
+
}
|
575 |
+
// Send the emails
|
576 |
+
$this->_sendEmails($dryrun, $testemail);
|
577 |
+
}
|
578 |
+
Mage::app()->setCurrentStore($this->_originalStoreId);
|
579 |
+
|
580 |
+
return count($this->_getRecipients());
|
581 |
+
}
|
582 |
+
catch (Exception $e)
|
583 |
+
{
|
584 |
+
Mage::app()->setCurrentStore($this->_originalStoreId);
|
585 |
+
Mage::helper('abandonedcarts')->log(sprintf("%s->Error: %s", __METHOD__, $e->getMessage()));
|
586 |
+
return 0;
|
587 |
+
}
|
588 |
+
}
|
589 |
+
|
590 |
+
/**
|
591 |
+
* @return mixed|string
|
592 |
+
*/
|
593 |
+
protected function _generateUrl($token = "")
|
594 |
+
{
|
595 |
+
if (!Mage::helper('abandonedcarts')->isCampaignEnabled()) {
|
596 |
+
return Mage::getUrl('abandonedcarts',
|
597 |
+
array(
|
598 |
+
'_query' => ($token ? "?token=" . $token : ''),
|
599 |
+
'_secure' => true
|
600 |
+
)
|
601 |
+
);
|
602 |
+
}
|
603 |
+
|
604 |
+
return Mage::getUrl('abandonedcarts', array(
|
605 |
+
'_query' => "?utm_source=" . self::CAMPAIGN_SOURCE . "&utm_medium=" . self::CAMPAIGN_MEDIUM . "&utm_campaign=" . Mage::helper('abandonedcarts')->getCampaignName() . ($token ? "&token=" . $token : ''),
|
606 |
+
'_secure' => true
|
607 |
+
)
|
608 |
+
);
|
609 |
+
}
|
610 |
+
|
611 |
+
/**
|
612 |
+
* @param $customerEmail
|
613 |
+
* @return string
|
614 |
+
*/
|
615 |
+
protected function _generateToken($customerEmail)
|
616 |
+
{
|
617 |
+
// Generate the token
|
618 |
+
$token = openssl_random_pseudo_bytes(9, $cstrong);
|
619 |
+
// Generate the token hash
|
620 |
+
$hash = hash("sha256", $token);
|
621 |
+
|
622 |
+
// Generate the expiration date
|
623 |
+
$expiration = new Zend_Date(Mage::getModel('core/date')->timestamp());
|
624 |
+
$expiration->addDay(self::EXPIRATION);
|
625 |
+
|
626 |
+
// Create the autologin link
|
627 |
+
Mage::getModel('abandonedcarts/link')->setData(
|
628 |
+
array(
|
629 |
+
'token_hash' => $hash,
|
630 |
+
'customer_email' => $customerEmail,
|
631 |
+
'expiration_date' => $expiration->toString('YYYY-MM-dd HH:mm:ss')
|
632 |
+
)
|
633 |
+
)->save();
|
634 |
+
|
635 |
+
return $token;
|
636 |
+
}
|
637 |
+
}
|
app/code/community/DigitalPianism/Abandonedcarts/Model/Observer.php
DELETED
@@ -1,742 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
/**
|
4 |
-
* Class DigitalPianism_Abandonedcarts_Model_Observer
|
5 |
-
*/
|
6 |
-
class DigitalPianism_Abandonedcarts_Model_Observer extends Mage_Core_Model_Abstract
|
7 |
-
{
|
8 |
-
|
9 |
-
protected $_recipients = array();
|
10 |
-
protected $_saleRecipients = array();
|
11 |
-
protected $_today = "";
|
12 |
-
protected $_customerGroups = "";
|
13 |
-
protected $_currentStoreId;
|
14 |
-
protected $_originalStoreId;
|
15 |
-
|
16 |
-
protected function _setToday()
|
17 |
-
{
|
18 |
-
// Date handling
|
19 |
-
$store = Mage_Core_Model_App::ADMIN_STORE_ID;
|
20 |
-
$timezone = Mage::app()->getStore($store)->getConfig(Mage_Core_Model_Locale::XML_PATH_DEFAULT_TIMEZONE);
|
21 |
-
date_default_timezone_set($timezone);
|
22 |
-
|
23 |
-
// Current date
|
24 |
-
$currentdate = date("Ymd");
|
25 |
-
|
26 |
-
$day = (int)substr($currentdate,-2);
|
27 |
-
$month = (int)substr($currentdate,4,2);
|
28 |
-
$year = (int)substr($currentdate,0,4);
|
29 |
-
|
30 |
-
$date = array('year' => $year,'month' => $month,'day' => $day,'hour' => 23,'minute' => 59,'second' => 59);
|
31 |
-
|
32 |
-
$today = new Zend_Date($date);
|
33 |
-
$today->setTimeZone("UTC");
|
34 |
-
|
35 |
-
date_default_timezone_set($timezone);
|
36 |
-
|
37 |
-
$this->_today = $today->toString("Y-MM-dd HH:mm:ss");
|
38 |
-
}
|
39 |
-
|
40 |
-
/**
|
41 |
-
* @return string
|
42 |
-
*/
|
43 |
-
protected function _getToday()
|
44 |
-
{
|
45 |
-
return $this->_today;
|
46 |
-
}
|
47 |
-
|
48 |
-
/**
|
49 |
-
* @return array
|
50 |
-
*/
|
51 |
-
protected function _getRecipients()
|
52 |
-
{
|
53 |
-
return $this->_recipients;
|
54 |
-
}
|
55 |
-
|
56 |
-
/**
|
57 |
-
* @return array
|
58 |
-
*/
|
59 |
-
protected function _getSaleRecipients()
|
60 |
-
{
|
61 |
-
return $this->_saleRecipients;
|
62 |
-
}
|
63 |
-
|
64 |
-
/**
|
65 |
-
* @param $args
|
66 |
-
*/
|
67 |
-
public function generateRecipients($args)
|
68 |
-
{
|
69 |
-
// Customer group check
|
70 |
-
if (array_key_exists('customer_group',$args['row']) && !in_array($args['row']['customer_group'],$this->_customerGroups))
|
71 |
-
{
|
72 |
-
return;
|
73 |
-
}
|
74 |
-
|
75 |
-
// Test if the customer is already in the array
|
76 |
-
if (!array_key_exists($args['row']['customer_email'], $this->_recipients))
|
77 |
-
{
|
78 |
-
// Create an array of variables to assign to template
|
79 |
-
$emailTemplateVariables = array();
|
80 |
-
|
81 |
-
// Array that contains the data which will be used inside the template
|
82 |
-
$emailTemplateVariables['fullname'] = $args['row']['customer_firstname'].' '.$args['row']['customer_lastname'];
|
83 |
-
$emailTemplateVariables['firstname'] = $args['row']['customer_firstname'];
|
84 |
-
$emailTemplateVariables['productname'] = $args['row']['product_name'];
|
85 |
-
|
86 |
-
// Assign the values to the array of recipients
|
87 |
-
$this->_recipients[$args['row']['customer_email']]['cartId'] = $args['row']['cart_id'];
|
88 |
-
|
89 |
-
// Get product image via collection
|
90 |
-
$_productCollection = Mage::getResourceModel('catalog/product_collection');
|
91 |
-
// Add attributes to the collection
|
92 |
-
$_productCollection->addAttributeToFilter('entity_id',array('eq' => $args['row']['product_id']));
|
93 |
-
// Add image to the collection
|
94 |
-
$_productCollection->joinAttribute('image', 'catalog_product/image', 'entity_id', null, 'left');
|
95 |
-
// Limit the collection to get the specific product
|
96 |
-
$_productCollection->setPageSize(1);
|
97 |
-
|
98 |
-
try {
|
99 |
-
$productImg = (string)Mage::helper('catalog/image')->init($_productCollection->getFirstItem(), 'image');
|
100 |
-
}
|
101 |
-
catch (Exception $e) {
|
102 |
-
$productImg = false;
|
103 |
-
}
|
104 |
-
|
105 |
-
// Add product image
|
106 |
-
$emailTemplateVariables['productimage'] = $productImg;
|
107 |
-
|
108 |
-
$emailTemplateVariables['extraproductcount'] = 0;
|
109 |
-
}
|
110 |
-
else
|
111 |
-
{
|
112 |
-
// We create some extra variables if there is several products in the cart
|
113 |
-
$emailTemplateVariables = $this->_recipients[$args['row']['customer_email']]['emailTemplateVariables'];
|
114 |
-
// We increase the product count
|
115 |
-
$emailTemplateVariables['extraproductcount'] += 1;
|
116 |
-
}
|
117 |
-
// Assign the array of template variables
|
118 |
-
$this->_recipients[$args['row']['customer_email']]['emailTemplateVariables'] = $emailTemplateVariables;
|
119 |
-
$this->_recipients[$args['row']['customer_email']]['store_id'] = $this->_currentStoreId;
|
120 |
-
}
|
121 |
-
|
122 |
-
/**
|
123 |
-
* @param $args
|
124 |
-
*/
|
125 |
-
public function generateSaleRecipients($args)
|
126 |
-
{
|
127 |
-
// Customer group check
|
128 |
-
if (array_key_exists('customer_group',$args['row']) && !in_array($args['row']['customer_group'],$this->_customerGroups))
|
129 |
-
{
|
130 |
-
return;
|
131 |
-
}
|
132 |
-
|
133 |
-
// Double check if the special from date is set
|
134 |
-
if (!array_key_exists('product_special_from_date',$args['row']) || !$args['row']['product_special_from_date'])
|
135 |
-
{
|
136 |
-
// If not we use today for the comparison
|
137 |
-
$fromDate = $this->_getToday();
|
138 |
-
}
|
139 |
-
else $fromDate = $args['row']['product_special_from_date'];
|
140 |
-
|
141 |
-
// Do the same for the special to date
|
142 |
-
if (!array_key_exists('product_special_to_date',$args['row']) || !$args['row']['product_special_to_date'])
|
143 |
-
{
|
144 |
-
$toDate = $this->_getToday();
|
145 |
-
}
|
146 |
-
else $toDate = $args['row']['product_special_to_date'];
|
147 |
-
|
148 |
-
// We need to ensure that the price in cart is higher than the new special price
|
149 |
-
// As well as the date comparison in case the sale is over or hasn't started
|
150 |
-
if ($args['row']['product_price_in_cart'] > 0.00
|
151 |
-
&& $args['row']['product_special_price'] > 0.00
|
152 |
-
&& ($args['row']['product_price_in_cart'] > $args['row']['product_special_price'])
|
153 |
-
&& ($fromDate <= $this->_getToday())
|
154 |
-
&& ($toDate >= $this->_getToday()))
|
155 |
-
{
|
156 |
-
|
157 |
-
// Test if the customer is already in the array
|
158 |
-
if (!array_key_exists($args['row']['customer_email'], $this->_saleRecipients))
|
159 |
-
{
|
160 |
-
// Create an array of variables to assign to template
|
161 |
-
$emailTemplateVariables = array();
|
162 |
-
|
163 |
-
// Array that contains the data which will be used inside the template
|
164 |
-
$emailTemplateVariables['fullname'] = $args['row']['customer_firstname'].' '.$args['row']['customer_lastname'];
|
165 |
-
$emailTemplateVariables['firstname'] = $args['row']['customer_firstname'];
|
166 |
-
$emailTemplateVariables['productname'] = $args['row']['product_name'];
|
167 |
-
$emailTemplateVariables['cartprice'] = number_format($args['row']['product_price_in_cart'],2);
|
168 |
-
$emailTemplateVariables['specialprice'] = number_format($args['row']['product_special_price'],2);
|
169 |
-
|
170 |
-
// Assign the values to the array of recipients
|
171 |
-
$this->_saleRecipients[$args['row']['customer_email']]['cartId'] = $args['row']['cart_id'];
|
172 |
-
|
173 |
-
// Get product image via collection
|
174 |
-
$_productCollection = Mage::getResourceModel('catalog/product_collection');
|
175 |
-
// Add attributes to the collection
|
176 |
-
$_productCollection->addAttributeToFilter('entity_id',array('eq' => $args['row']['product_id']));
|
177 |
-
// Add image to the collection
|
178 |
-
$_productCollection->joinAttribute('image', 'catalog_product/image', 'entity_id', null, 'left');
|
179 |
-
// Limit the collection to get the specific product
|
180 |
-
$_productCollection->setPageSize(1);
|
181 |
-
|
182 |
-
try {
|
183 |
-
$productImg = (string)Mage::helper('catalog/image')->init($_productCollection->getFirstItem(), 'image');
|
184 |
-
}
|
185 |
-
catch (Exception $e) {
|
186 |
-
$productImg = false;
|
187 |
-
}
|
188 |
-
|
189 |
-
// Add product image
|
190 |
-
$emailTemplateVariables['productimage'] = $productImg;
|
191 |
-
}
|
192 |
-
else
|
193 |
-
{
|
194 |
-
// We create some extra variables if there is several products in the cart
|
195 |
-
$emailTemplateVariables = $this->_saleRecipients[$args['row']['customer_email']]['emailTemplateVariables'];
|
196 |
-
// Discount amount
|
197 |
-
// If one product before
|
198 |
-
if (!array_key_exists('discount',$emailTemplateVariables))
|
199 |
-
{
|
200 |
-
$emailTemplateVariables['discount'] = $emailTemplateVariables['cartprice'] - $emailTemplateVariables['specialprice'];
|
201 |
-
}
|
202 |
-
// We add the discount on the second product
|
203 |
-
$moreDiscount = number_format($args['row']['product_price_in_cart'],2) - number_format($args['row']['product_special_price'],2);
|
204 |
-
$emailTemplateVariables['discount'] += $moreDiscount;
|
205 |
-
// We increase the product count
|
206 |
-
if (!array_key_exists('extraproductcount',$emailTemplateVariables))
|
207 |
-
{
|
208 |
-
$emailTemplateVariables['extraproductcount'] = 0;
|
209 |
-
}
|
210 |
-
$emailTemplateVariables['extraproductcount'] += 1;
|
211 |
-
}
|
212 |
-
|
213 |
-
// Add currency codes to prices
|
214 |
-
$emailTemplateVariables['cartprice'] = Mage::helper('core')->currency($emailTemplateVariables['cartprice'], true, false);
|
215 |
-
$emailTemplateVariables['specialprice'] = Mage::helper('core')->currency($emailTemplateVariables['specialprice'], true, false);
|
216 |
-
if (array_key_exists('discount',$emailTemplateVariables))
|
217 |
-
{
|
218 |
-
$emailTemplateVariables['discount'] = Mage::helper('core')->currency($emailTemplateVariables['discount'], true, false);
|
219 |
-
}
|
220 |
-
|
221 |
-
// Assign the array of template variables
|
222 |
-
$this->_saleRecipients[$args['row']['customer_email']]['emailTemplateVariables'] = $emailTemplateVariables;
|
223 |
-
$this->_saleRecipients[$args['row']['customer_email']]['store_id'] = $this->_currentStoreId;
|
224 |
-
}
|
225 |
-
}
|
226 |
-
|
227 |
-
/**
|
228 |
-
* @param $dryrun
|
229 |
-
* @param $testemail
|
230 |
-
*/
|
231 |
-
protected function _sendSaleEmails($dryrun,$testemail)
|
232 |
-
{
|
233 |
-
try
|
234 |
-
{
|
235 |
-
// Send the emails via a loop
|
236 |
-
foreach ($this->_getSaleRecipients() as $email => $recipient)
|
237 |
-
{
|
238 |
-
// Store Id
|
239 |
-
Mage::app()->setCurrentStore($recipient['store_id']);
|
240 |
-
// Get the transactional email template
|
241 |
-
$templateId = Mage::getStoreConfig('abandonedcartsconfig/options/email_template_sale');
|
242 |
-
// Get the sender
|
243 |
-
$sender = array();
|
244 |
-
$sender['email'] = Mage::getStoreConfig('abandonedcartsconfig/options/email');
|
245 |
-
$sender['name'] = Mage::getStoreConfig('abandonedcartsconfig/options/name');
|
246 |
-
|
247 |
-
// Don't send the email if dryrun is set
|
248 |
-
if ($dryrun)
|
249 |
-
{
|
250 |
-
// Log data when dried run
|
251 |
-
Mage::helper('abandonedcarts')->log(__METHOD__);
|
252 |
-
Mage::helper('abandonedcarts')->log($recipient['emailTemplateVariables']);
|
253 |
-
// If the test email is set and found
|
254 |
-
if (isset($testemail) && $email == $testemail)
|
255 |
-
{
|
256 |
-
Mage::helper('abandonedcarts')->log(__METHOD__ . "sendAbandonedCartsSaleEmail test: " . $email);
|
257 |
-
// Send the test email
|
258 |
-
Mage::getModel('core/email_template')
|
259 |
-
->sendTransactional(
|
260 |
-
$templateId,
|
261 |
-
$sender,
|
262 |
-
$email,
|
263 |
-
$recipient['emailTemplateVariables']['fullname'] ,
|
264 |
-
$recipient['emailTemplateVariables'],
|
265 |
-
null);
|
266 |
-
}
|
267 |
-
}
|
268 |
-
else
|
269 |
-
{
|
270 |
-
Mage::helper('abandonedcarts')->log(__METHOD__ . "sendAbandonedCartsSaleEmail: " . $email);
|
271 |
-
|
272 |
-
// Send the email
|
273 |
-
Mage::getModel('core/email_template')
|
274 |
-
->sendTransactional(
|
275 |
-
$templateId,
|
276 |
-
$sender,
|
277 |
-
$email,
|
278 |
-
$recipient['emailTemplateVariables']['fullname'] ,
|
279 |
-
$recipient['emailTemplateVariables'],
|
280 |
-
null);
|
281 |
-
}
|
282 |
-
|
283 |
-
// Load the quote
|
284 |
-
$quote = Mage::getModel('sales/quote')->load($recipient['cartId']);
|
285 |
-
|
286 |
-
// We change the notification attribute
|
287 |
-
$quote->setAbandonedSaleNotified(1);
|
288 |
-
|
289 |
-
// Save only if dryrun is false or if the test email is set and found
|
290 |
-
if (!$dryrun || (isset($testemail) && $email == $testemail))
|
291 |
-
{
|
292 |
-
$quote->getResource()->saveAttribute($quote,array('abandoned_sale_notified'));
|
293 |
-
}
|
294 |
-
}
|
295 |
-
}
|
296 |
-
catch (Exception $e)
|
297 |
-
{
|
298 |
-
Mage::helper('abandonedcarts')->log(__METHOD__ . " " . $e->getMessage());
|
299 |
-
}
|
300 |
-
}
|
301 |
-
|
302 |
-
/**
|
303 |
-
* @param $dryrun
|
304 |
-
* @param $testemail
|
305 |
-
*/
|
306 |
-
protected function _sendEmails($dryrun,$testemail)
|
307 |
-
{
|
308 |
-
try
|
309 |
-
{
|
310 |
-
// Send the emails via a loop
|
311 |
-
foreach ($this->_getRecipients() as $email => $recipient)
|
312 |
-
{
|
313 |
-
// Store Id
|
314 |
-
Mage::app()->setCurrentStore($recipient['store_id']);
|
315 |
-
// Get the transactional email template
|
316 |
-
$templateId = Mage::getStoreConfig('abandonedcartsconfig/options/email_template');
|
317 |
-
// Get the sender
|
318 |
-
$sender = array();
|
319 |
-
$sender['email'] = Mage::getStoreConfig('abandonedcartsconfig/options/email');
|
320 |
-
$sender['name'] = Mage::getStoreConfig('abandonedcartsconfig/options/name');
|
321 |
-
|
322 |
-
// Don't send the email if dryrun is set
|
323 |
-
if ($dryrun)
|
324 |
-
{
|
325 |
-
// Log data when dried run
|
326 |
-
Mage::helper('abandonedcarts')->log(__METHOD__);
|
327 |
-
Mage::helper('abandonedcarts')->log($recipient['emailTemplateVariables']);
|
328 |
-
// If the test email is set and found
|
329 |
-
if (isset($testemail) && $email == $testemail)
|
330 |
-
{
|
331 |
-
Mage::helper('abandonedcarts')->log(__METHOD__ . "sendAbandonedCartsEmail test: " . $email);
|
332 |
-
// Send the test email
|
333 |
-
Mage::getModel('core/email_template')
|
334 |
-
->sendTransactional(
|
335 |
-
$templateId,
|
336 |
-
$sender,
|
337 |
-
$email,
|
338 |
-
$recipient['emailTemplateVariables']['fullname'] ,
|
339 |
-
$recipient['emailTemplateVariables'],
|
340 |
-
null);
|
341 |
-
}
|
342 |
-
}
|
343 |
-
else
|
344 |
-
{
|
345 |
-
Mage::helper('abandonedcarts')->log(__METHOD__ . "sendAbandonedCartsEmail: " . $email);
|
346 |
-
|
347 |
-
// Send the email
|
348 |
-
Mage::getModel('core/email_template')
|
349 |
-
->sendTransactional(
|
350 |
-
$templateId,
|
351 |
-
$sender,
|
352 |
-
$email,
|
353 |
-
$recipient['emailTemplateVariables']['fullname'] ,
|
354 |
-
$recipient['emailTemplateVariables'],
|
355 |
-
null);
|
356 |
-
}
|
357 |
-
|
358 |
-
// Load the quote
|
359 |
-
$quote = Mage::getModel('sales/quote')->load($recipient['cartId']);
|
360 |
-
|
361 |
-
// We change the notification attribute
|
362 |
-
$quote->setAbandonedNotified(1);
|
363 |
-
|
364 |
-
// Save only if dryrun is false or if the test email is set and found
|
365 |
-
if (!$dryrun || (isset($testemail) && $email == $testemail))
|
366 |
-
{
|
367 |
-
$quote->getResource()->saveAttribute($quote,array('abandoned_notified'));
|
368 |
-
}
|
369 |
-
}
|
370 |
-
}
|
371 |
-
catch (Exception $e)
|
372 |
-
{
|
373 |
-
Mage::helper('abandonedcarts')->log(__METHOD__ . " " . $e->getMessage());
|
374 |
-
}
|
375 |
-
}
|
376 |
-
|
377 |
-
/**
|
378 |
-
* Send notification email to customer with abandoned cart containing sale products
|
379 |
-
* @param boolean $dryrun if dryrun is set to true, it won't send emails and won't alter quotes
|
380 |
-
* @param string $testemail email to test
|
381 |
-
*/
|
382 |
-
public function sendAbandonedCartsSaleEmail($dryrun = false, $testemail = null)
|
383 |
-
{
|
384 |
-
try
|
385 |
-
{
|
386 |
-
if (Mage::helper('abandonedcarts')->getDryRun()) $dryrun = true;
|
387 |
-
if (Mage::helper('abandonedcarts')->getTestEmail()) $testemail = Mage::helper('abandonedcarts')->getTestEmail();
|
388 |
-
// Set customer groups
|
389 |
-
$this->_customerGroups = $this->_customerGroups ? $this->_customerGroups : Mage::helper('abandonedcarts')->getCustomerGroupsLimitation();
|
390 |
-
// Original store id
|
391 |
-
$this->_originalStoreId = Mage::app()->getStore()->getId();
|
392 |
-
|
393 |
-
if (Mage::helper('abandonedcarts')->isSaleEnabled())
|
394 |
-
{
|
395 |
-
$this->_setToday();
|
396 |
-
|
397 |
-
// Get the attribute id for the status attribute
|
398 |
-
$eavAttribute = Mage::getModel('eav/entity_attribute');
|
399 |
-
$statusId = $eavAttribute->getIdByCode('catalog_product', 'status');
|
400 |
-
$nameId = $eavAttribute->getIdByCode('catalog_product', 'name');
|
401 |
-
$priceId = $eavAttribute->getIdByCode('catalog_product', 'price');
|
402 |
-
$spriceId = $eavAttribute->getIdByCode('catalog_product', 'special_price');
|
403 |
-
$spfromId = $eavAttribute->getIdByCode('catalog_product', 'special_from_date');
|
404 |
-
$sptoId = $eavAttribute->getIdByCode('catalog_product', 'special_to_date');
|
405 |
-
|
406 |
-
// Loop through the stores
|
407 |
-
foreach (Mage::app()->getWebsites() as $website) {
|
408 |
-
// Get the website id
|
409 |
-
$websiteId = $website->getWebsiteId();
|
410 |
-
foreach ($website->getGroups() as $group) {
|
411 |
-
$stores = $group->getStores();
|
412 |
-
foreach ($stores as $store) {
|
413 |
-
|
414 |
-
// Get the store id
|
415 |
-
$storeId = $store->getStoreId();
|
416 |
-
$this->_currentStoreId = $storeId;
|
417 |
-
|
418 |
-
// Init the store to be able to load the quote and the collections properly
|
419 |
-
Mage::app()->init($storeId,'store');
|
420 |
-
|
421 |
-
// Get the product collection
|
422 |
-
$collection = Mage::getResourceModel('catalog/product_collection')->setStore($storeId);
|
423 |
-
|
424 |
-
// Database TableNams
|
425 |
-
$eavEntityType = Mage::getSingleton("core/resource")->getTableName('eav_entity_type');
|
426 |
-
$eavAttribute = Mage::getSingleton("core/resource")->getTableName('eav_attribute');
|
427 |
-
|
428 |
-
// If flat catalog is enabled
|
429 |
-
if (Mage::helper('catalog/product_flat')->isEnabled())
|
430 |
-
{
|
431 |
-
// First collection: carts with products that became on sale
|
432 |
-
// Join the collection with the required tables
|
433 |
-
$collection->getSelect()
|
434 |
-
->reset(Zend_Db_Select::COLUMNS)
|
435 |
-
->columns(array('e.entity_id AS product_id',
|
436 |
-
'e.sku',
|
437 |
-
'catalog_flat.name as product_name',
|
438 |
-
'catalog_flat.price as product_price',
|
439 |
-
'catalog_flat.special_price as product_special_price',
|
440 |
-
'catalog_flat.special_from_date as product_special_from_date',
|
441 |
-
'catalog_flat.special_to_date as product_special_to_date',
|
442 |
-
'quote_table.entity_id as cart_id',
|
443 |
-
'quote_table.updated_at as cart_updated_at',
|
444 |
-
'quote_table.abandoned_sale_notified as has_been_notified',
|
445 |
-
'quote_items.price as product_price_in_cart',
|
446 |
-
'quote_table.customer_email as customer_email',
|
447 |
-
'quote_table.customer_firstname as customer_firstname',
|
448 |
-
'quote_table.customer_lastname as customer_lastname',
|
449 |
-
'quote_table.customer_group_id as customer_group'
|
450 |
-
)
|
451 |
-
)
|
452 |
-
->joinInner(
|
453 |
-
array('quote_items' => Mage::getSingleton("core/resource")->getTableName('sales_flat_quote_item')),
|
454 |
-
'quote_items.product_id = e.entity_id AND quote_items.price > 0.00',
|
455 |
-
null)
|
456 |
-
->joinInner(
|
457 |
-
array('quote_table' => Mage::getSingleton("core/resource")->getTableName('sales_flat_quote')),
|
458 |
-
'quote_items.quote_id = quote_table.entity_id AND quote_table.items_count > 0 AND quote_table.is_active = 1 AND quote_table.customer_email IS NOT NULL AND quote_table.abandoned_sale_notified = 0 AND quote_table.store_id = '.$storeId,
|
459 |
-
null)
|
460 |
-
->joinInner(
|
461 |
-
array('catalog_flat' => Mage::getSingleton("core/resource")->getTableName('catalog_product_flat_'.$storeId)),
|
462 |
-
'catalog_flat.entity_id = e.entity_id',
|
463 |
-
null)
|
464 |
-
->joinInner(
|
465 |
-
array('catalog_enabled' => Mage::getSingleton("core/resource")->getTableName('catalog_product_entity_int')),
|
466 |
-
'catalog_enabled.entity_id = e.entity_id AND catalog_enabled.attribute_id = '.$statusId.' AND catalog_enabled.value = 1',
|
467 |
-
null)
|
468 |
-
->joinInner(
|
469 |
-
array('inventory' => Mage::getSingleton("core/resource")->getTableName('cataloginventory_stock_status')),
|
470 |
-
'inventory.product_id = e.entity_id AND inventory.stock_status = 1 AND inventory.website_id = '.$websiteId,
|
471 |
-
null)
|
472 |
-
->order('quote_table.updated_at DESC');
|
473 |
-
}
|
474 |
-
else
|
475 |
-
{
|
476 |
-
// First collection: carts with products that became on sale
|
477 |
-
// Join the collection with the required tables
|
478 |
-
$collection->getSelect()
|
479 |
-
->reset(Zend_Db_Select::COLUMNS)
|
480 |
-
->columns(array('e.entity_id AS product_id',
|
481 |
-
'e.sku',
|
482 |
-
'catalog_name.value as product_name',
|
483 |
-
'catalog_price.value as product_price',
|
484 |
-
'catalog_sprice.value as product_special_price',
|
485 |
-
'catalog_spfrom.value as product_special_from_date',
|
486 |
-
'catalog_spto.value as product_special_to_date',
|
487 |
-
'quote_table.entity_id as cart_id',
|
488 |
-
'quote_table.updated_at as cart_updated_at',
|
489 |
-
'quote_table.abandoned_sale_notified as has_been_notified',
|
490 |
-
'quote_items.price as product_price_in_cart',
|
491 |
-
'quote_table.customer_email as customer_email',
|
492 |
-
'quote_table.customer_firstname as customer_firstname',
|
493 |
-
'quote_table.customer_lastname as customer_lastname',
|
494 |
-
'quote_table.customer_group_id as customer_group'
|
495 |
-
)
|
496 |
-
)
|
497 |
-
// Name
|
498 |
-
->joinInner(
|
499 |
-
array('catalog_name' => Mage::getSingleton("core/resource")->getTableName('catalog_product_entity_varchar')),
|
500 |
-
"catalog_name.entity_id = e.entity_id AND catalog_name.attribute_id = $nameId",
|
501 |
-
null)
|
502 |
-
// Price
|
503 |
-
->joinInner(
|
504 |
-
array('catalog_price' => Mage::getSingleton("core/resource")->getTableName('catalog_product_entity_decimal')),
|
505 |
-
"catalog_price.entity_id = e.entity_id AND catalog_price.attribute_id = $priceId",
|
506 |
-
null)
|
507 |
-
// Special Price
|
508 |
-
->joinInner(
|
509 |
-
array('catalog_sprice' => Mage::getSingleton("core/resource")->getTableName('catalog_product_entity_decimal')),
|
510 |
-
"catalog_sprice.entity_id = e.entity_id AND catalog_sprice.attribute_id = $spriceId",
|
511 |
-
null)
|
512 |
-
// Special From Date
|
513 |
-
->joinInner(
|
514 |
-
array('catalog_spfrom' => Mage::getSingleton("core/resource")->getTableName('catalog_product_entity_datetime')),
|
515 |
-
"catalog_spfrom.entity_id = e.entity_id AND catalog_spfrom.attribute_id = $spfromId",
|
516 |
-
null)
|
517 |
-
// Special To Date
|
518 |
-
->joinInner(
|
519 |
-
array('catalog_spto' => Mage::getSingleton("core/resource")->getTableName('catalog_product_entity_datetime')),
|
520 |
-
"catalog_spto.entity_id = e.entity_id AND catalog_spto.attribute_id = $sptoId",
|
521 |
-
null)
|
522 |
-
->joinInner(
|
523 |
-
array('quote_items' => Mage::getSingleton("core/resource")->getTableName('sales_flat_quote_item')),
|
524 |
-
'quote_items.product_id = e.entity_id AND quote_items.price > 0.00',
|
525 |
-
null)
|
526 |
-
->joinInner(
|
527 |
-
array('quote_table' => Mage::getSingleton("core/resource")->getTableName('sales_flat_quote')),
|
528 |
-
'quote_items.quote_id = quote_table.entity_id AND quote_table.items_count > 0 AND quote_table.is_active = 1 AND quote_table.customer_email IS NOT NULL AND quote_table.abandoned_sale_notified = 0 AND quote_table.store_id = '.$storeId,
|
529 |
-
null)
|
530 |
-
->joinInner(
|
531 |
-
array('catalog_enabled' => Mage::getSingleton("core/resource")->getTableName('catalog_product_entity_int')),
|
532 |
-
'catalog_enabled.entity_id = e.entity_id AND catalog_enabled.attribute_id = '.$statusId.' AND catalog_enabled.value = 1',
|
533 |
-
null)
|
534 |
-
->joinInner(
|
535 |
-
array('inventory' => Mage::getSingleton("core/resource")->getTableName('cataloginventory_stock_status')),
|
536 |
-
'inventory.product_id = e.entity_id AND inventory.stock_status = 1 AND inventory.website_id = '.$websiteId,
|
537 |
-
null)
|
538 |
-
->order('quote_table.updated_at DESC');
|
539 |
-
}
|
540 |
-
|
541 |
-
//$collection->printlogquery(true,true);
|
542 |
-
$collection->load();
|
543 |
-
|
544 |
-
// Skip the rest of the code if the collection is empty
|
545 |
-
if ($collection->getSize() == 0) continue;
|
546 |
-
|
547 |
-
// Call iterator walk method with collection query string and callback method as parameters
|
548 |
-
// Has to be used to handle massive collection instead of foreach
|
549 |
-
Mage::getSingleton('core/resource_iterator')->walk($collection->getSelect(), array(array($this, 'generateSaleRecipients')));
|
550 |
-
}
|
551 |
-
}
|
552 |
-
}
|
553 |
-
|
554 |
-
// Send the emails
|
555 |
-
$this->_sendSaleEmails($dryrun,$testemail);
|
556 |
-
}
|
557 |
-
|
558 |
-
Mage::app()->setCurrentStore($this->_originalStoreId);
|
559 |
-
}
|
560 |
-
catch (Exception $e)
|
561 |
-
{
|
562 |
-
Mage::app()->setCurrentStore($this->_originalStoreId);
|
563 |
-
Mage::helper('abandonedcarts')->log(__METHOD__ . " " . $e->getMessage());
|
564 |
-
}
|
565 |
-
}
|
566 |
-
|
567 |
-
/**
|
568 |
-
* Send notification email to customer with abandoned carts after the number of days specified in the config
|
569 |
-
* @param bool $nodate
|
570 |
-
* @param boolean $dryrun if dryrun is set to true, it won't send emails and won't alter quotes
|
571 |
-
* @param string $testemail email to test
|
572 |
-
*/
|
573 |
-
public function sendAbandonedCartsEmail($nodate = false, $dryrun = false, $testemail = null)
|
574 |
-
{
|
575 |
-
if (Mage::helper('abandonedcarts')->getDryRun()) $dryrun = true;
|
576 |
-
if (Mage::helper('abandonedcarts')->getTestEmail()) $testemail = Mage::helper('abandonedcarts')->getTestEmail();
|
577 |
-
// Set customer groups
|
578 |
-
$this->_customerGroups = $this->_customerGroups ? $this->_customerGroups : Mage::helper('abandonedcarts')->getCustomerGroupsLimitation();
|
579 |
-
|
580 |
-
$this->_originalStoreId = Mage::app()->getStore()->getId();
|
581 |
-
|
582 |
-
try
|
583 |
-
{
|
584 |
-
if (Mage::helper('abandonedcarts')->isEnabled())
|
585 |
-
{
|
586 |
-
// Date handling
|
587 |
-
$store = Mage_Core_Model_App::ADMIN_STORE_ID;
|
588 |
-
$timezone = Mage::app()->getStore($store)->getConfig(Mage_Core_Model_Locale::XML_PATH_DEFAULT_TIMEZONE);
|
589 |
-
date_default_timezone_set($timezone);
|
590 |
-
|
591 |
-
// If the nodate parameter is set to false
|
592 |
-
if (!$nodate)
|
593 |
-
{
|
594 |
-
// Get the delay provided and convert it to a proper date
|
595 |
-
$delay = Mage::getStoreConfig('abandonedcartsconfig/options/notify_delay');
|
596 |
-
$delay = date('Y-m-d H:i:s', time() - $delay * 24 * 3600);
|
597 |
-
}
|
598 |
-
else
|
599 |
-
{
|
600 |
-
// We create a date in the future to handle all abandoned carts
|
601 |
-
$delay = date('Y-m-d H:i:s', strtotime("+7 day"));
|
602 |
-
}
|
603 |
-
|
604 |
-
// Get the attribute id for several attributes
|
605 |
-
$eavAttribute = Mage::getModel('eav/entity_attribute');
|
606 |
-
$statusId = $eavAttribute->getIdByCode('catalog_product', 'status');
|
607 |
-
$nameId = $eavAttribute->getIdByCode('catalog_product', 'name');
|
608 |
-
$priceId = $eavAttribute->getIdByCode('catalog_product', 'price');
|
609 |
-
|
610 |
-
// Loop through the stores
|
611 |
-
foreach (Mage::app()->getWebsites() as $website) {
|
612 |
-
// Get the website id
|
613 |
-
$websiteId = $website->getWebsiteId();
|
614 |
-
foreach ($website->getGroups() as $group) {
|
615 |
-
$stores = $group->getStores();
|
616 |
-
foreach ($stores as $store) {
|
617 |
-
|
618 |
-
// Get the store id
|
619 |
-
$storeId = $store->getStoreId();
|
620 |
-
$this->_currentStoreId = $storeId;
|
621 |
-
// Init the store to be able to load the quote and the collections properly
|
622 |
-
Mage::app()->init($storeId,'store');
|
623 |
-
|
624 |
-
// Get the product collection
|
625 |
-
$collection = Mage::getResourceModel('catalog/product_collection')->setStore($storeId);
|
626 |
-
|
627 |
-
// If flat catalog is enabled
|
628 |
-
if (Mage::helper('catalog/product_flat')->isEnabled())
|
629 |
-
{
|
630 |
-
// First collection: carts with products that became on sale
|
631 |
-
// Join the collection with the required tables
|
632 |
-
$collection->getSelect()
|
633 |
-
->reset(Zend_Db_Select::COLUMNS)
|
634 |
-
->columns(array('e.entity_id AS product_id',
|
635 |
-
'e.sku',
|
636 |
-
'catalog_flat.name as product_name',
|
637 |
-
'catalog_flat.price as product_price',
|
638 |
-
'quote_table.entity_id as cart_id',
|
639 |
-
'quote_table.updated_at as cart_updated_at',
|
640 |
-
'quote_table.abandoned_notified as has_been_notified',
|
641 |
-
'quote_table.customer_email as customer_email',
|
642 |
-
'quote_table.customer_firstname as customer_firstname',
|
643 |
-
'quote_table.customer_lastname as customer_lastname',
|
644 |
-
'quote_table.customer_group_id as customer_group'
|
645 |
-
)
|
646 |
-
)
|
647 |
-
->joinInner(
|
648 |
-
array('quote_items' => Mage::getSingleton("core/resource")->getTableName('sales_flat_quote_item')),
|
649 |
-
'quote_items.product_id = e.entity_id AND quote_items.price > 0.00',
|
650 |
-
null)
|
651 |
-
->joinInner(
|
652 |
-
array('quote_table' => Mage::getSingleton("core/resource")->getTableName('sales_flat_quote')),
|
653 |
-
'quote_items.quote_id = quote_table.entity_id AND quote_table.items_count > 0 AND quote_table.is_active = 1 AND quote_table.customer_email IS NOT NULL AND quote_table.abandoned_notified = 0 AND quote_table.updated_at < "'.$delay.'" AND quote_table.store_id = '.$storeId,
|
654 |
-
null)
|
655 |
-
->joinInner(
|
656 |
-
array('catalog_flat' => Mage::getSingleton("core/resource")->getTableName('catalog_product_flat_'.$storeId)),
|
657 |
-
'catalog_flat.entity_id = e.entity_id',
|
658 |
-
null)
|
659 |
-
->joinInner(
|
660 |
-
array('catalog_enabled' => Mage::getSingleton("core/resource")->getTableName('catalog_product_entity_int')),
|
661 |
-
'catalog_enabled.entity_id = e.entity_id AND catalog_enabled.attribute_id = '.$statusId.' AND catalog_enabled.value = 1',
|
662 |
-
null)
|
663 |
-
->joinInner(
|
664 |
-
array('inventory' => Mage::getSingleton("core/resource")->getTableName('cataloginventory_stock_status')),
|
665 |
-
'inventory.product_id = e.entity_id AND inventory.stock_status = 1 AND website_id = '.$websiteId,
|
666 |
-
null)
|
667 |
-
->order('quote_table.updated_at DESC');
|
668 |
-
}
|
669 |
-
else
|
670 |
-
{
|
671 |
-
// First collection: carts with products that became on sale
|
672 |
-
// Join the collection with the required tables
|
673 |
-
$collection->getSelect()
|
674 |
-
->reset(Zend_Db_Select::COLUMNS)
|
675 |
-
->columns(array('e.entity_id AS product_id',
|
676 |
-
'e.sku',
|
677 |
-
'catalog_name.value as product_name',
|
678 |
-
'catalog_price.value as product_price',
|
679 |
-
'quote_table.entity_id as cart_id',
|
680 |
-
'quote_table.updated_at as cart_updated_at',
|
681 |
-
'quote_table.abandoned_notified as has_been_notified',
|
682 |
-
'quote_table.customer_email as customer_email',
|
683 |
-
'quote_table.customer_firstname as customer_firstname',
|
684 |
-
'quote_table.customer_lastname as customer_lastname',
|
685 |
-
'quote_table.customer_group_id as customer_group'
|
686 |
-
)
|
687 |
-
)
|
688 |
-
// Name
|
689 |
-
->joinInner(
|
690 |
-
array('catalog_name' => Mage::getSingleton("core/resource")->getTableName('catalog_product_entity_varchar')),
|
691 |
-
"catalog_name.entity_id = e.entity_id AND catalog_name.attribute_id = $nameId",
|
692 |
-
null)
|
693 |
-
// Price
|
694 |
-
->joinInner(
|
695 |
-
array('catalog_price' => Mage::getSingleton("core/resource")->getTableName('catalog_product_entity_decimal')),
|
696 |
-
"catalog_price.entity_id = e.entity_id AND catalog_price.attribute_id = $priceId",
|
697 |
-
null)
|
698 |
-
->joinInner(
|
699 |
-
array('quote_items' => Mage::getSingleton("core/resource")->getTableName('sales_flat_quote_item')),
|
700 |
-
'quote_items.product_id = e.entity_id AND quote_items.price > 0.00',
|
701 |
-
null)
|
702 |
-
->joinInner(
|
703 |
-
array('quote_table' => Mage::getSingleton("core/resource")->getTableName('sales_flat_quote')),
|
704 |
-
'quote_items.quote_id = quote_table.entity_id AND quote_table.items_count > 0 AND quote_table.is_active = 1 AND quote_table.customer_email IS NOT NULL AND quote_table.abandoned_notified = 0 AND quote_table.updated_at < "'.$delay.'" AND quote_table.store_id = '.$storeId,
|
705 |
-
null)
|
706 |
-
->joinInner(
|
707 |
-
array('catalog_enabled' => Mage::getSingleton("core/resource")->getTableName('catalog_product_entity_int')),
|
708 |
-
'catalog_enabled.entity_id = e.entity_id AND catalog_enabled.attribute_id = '.$statusId.' AND catalog_enabled.value = 1',
|
709 |
-
null)
|
710 |
-
->joinInner(
|
711 |
-
array('inventory' => Mage::getSingleton("core/resource")->getTableName('cataloginventory_stock_status')),
|
712 |
-
'inventory.product_id = e.entity_id AND inventory.stock_status = 1 AND website_id = '.$websiteId,
|
713 |
-
null)
|
714 |
-
->order('quote_table.updated_at DESC');
|
715 |
-
}
|
716 |
-
|
717 |
-
//$collection->printlogquery(true,true);
|
718 |
-
$collection->load();
|
719 |
-
|
720 |
-
// Skip the rest of the code if the collection is empty
|
721 |
-
if ($collection->getSize() == 0) continue;
|
722 |
-
|
723 |
-
// Call iterator walk method with collection query string and callback method as parameters
|
724 |
-
// Has to be used to handle massive collection instead of foreach
|
725 |
-
Mage::getSingleton('core/resource_iterator')->walk($collection->getSelect(), array(array($this, 'generateRecipients')));
|
726 |
-
}
|
727 |
-
}
|
728 |
-
}
|
729 |
-
|
730 |
-
// Send the emails
|
731 |
-
$this->_sendEmails($dryrun,$testemail);
|
732 |
-
}
|
733 |
-
|
734 |
-
Mage::app()->setCurrentStore($this->_originalStoreId);
|
735 |
-
}
|
736 |
-
catch (Exception $e)
|
737 |
-
{
|
738 |
-
Mage::app()->setCurrentStore($this->_originalStoreId);
|
739 |
-
Mage::helper('abandonedcarts')->log(__METHOD__ . " " . $e->getMessage());
|
740 |
-
}
|
741 |
-
}
|
742 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/code/community/DigitalPianism/Abandonedcarts/Model/Resource/Link.php
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Class DigitalPianism_Abandonedcarts_Model_Resource_Link
|
5 |
+
*/
|
6 |
+
class DigitalPianism_Abandonedcarts_Model_Resource_Link extends Mage_Core_Model_Mysql4_Abstract
|
7 |
+
{
|
8 |
+
|
9 |
+
protected function _construct()
|
10 |
+
{
|
11 |
+
$this->_init('abandonedcarts/link', 'link_id');
|
12 |
+
}
|
13 |
+
|
14 |
+
}
|
app/code/community/DigitalPianism/Abandonedcarts/Model/Resource/Link/Collection.php
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Class DigitalPianism_Abandonedcarts_Model_Resource_Link_Collection
|
5 |
+
*/
|
6 |
+
class DigitalPianism_Abandonedcarts_Model_Resource_Link_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract {
|
7 |
+
|
8 |
+
public function _construct()
|
9 |
+
{
|
10 |
+
parent::_construct();
|
11 |
+
$this->_init('abandonedcarts/link');
|
12 |
+
}
|
13 |
+
|
14 |
+
}
|
app/code/community/DigitalPianism/Abandonedcarts/Model/Resource/Log.php
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Class DigitalPianism_Abandonedcarts_Model_Resource_Log
|
5 |
+
*/
|
6 |
+
class DigitalPianism_Abandonedcarts_Model_Resource_Log extends Mage_Core_Model_Mysql4_Abstract
|
7 |
+
{
|
8 |
+
|
9 |
+
protected function _construct()
|
10 |
+
{
|
11 |
+
$this->_init('abandonedcarts/log', 'log_id');
|
12 |
+
}
|
13 |
+
|
14 |
+
}
|
app/code/community/DigitalPianism/Abandonedcarts/Model/Resource/Log/Collection.php
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Class DigitalPianism_Abandonedcarts_Model_Resource_Log_Collection
|
5 |
+
*/
|
6 |
+
class DigitalPianism_Abandonedcarts_Model_Resource_Log_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract {
|
7 |
+
|
8 |
+
public function _construct()
|
9 |
+
{
|
10 |
+
parent::_construct();
|
11 |
+
$this->_init('abandonedcarts/log');
|
12 |
+
}
|
13 |
+
|
14 |
+
}
|
app/code/community/DigitalPianism/Abandonedcarts/controllers/Adminhtml/AbandonedcartsController.php
CHANGED
@@ -12,21 +12,123 @@ class DigitalPianism_Abandonedcarts_Adminhtml_AbandonedcartsController extends M
|
|
12 |
*/
|
13 |
protected function _isAllowed()
|
14 |
{
|
15 |
-
return Mage::getSingleton('admin/session')->isAllowed('
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
16 |
}
|
17 |
|
18 |
/**
|
19 |
-
* Manually send the notifications
|
20 |
*
|
21 |
-
* @return void
|
22 |
*/
|
23 |
-
public function
|
24 |
-
{
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
31 |
}
|
32 |
}
|
12 |
*/
|
13 |
protected function _isAllowed()
|
14 |
{
|
15 |
+
return Mage::getSingleton('admin/session')->isAllowed('digitalpianism_menu/abandonedcarts');
|
16 |
+
}
|
17 |
+
|
18 |
+
protected function _initAction()
|
19 |
+
{
|
20 |
+
$this->loadLayout()
|
21 |
+
->_setActiveMenu('digitalpianism_menu/abandonedcarts');
|
22 |
+
|
23 |
+
return $this;
|
24 |
+
}
|
25 |
+
|
26 |
+
public function logsAction()
|
27 |
+
{
|
28 |
+
$this->_initAction();
|
29 |
+
$this->_addContent($this->getLayout()->createBlock('abandonedcarts/adminhtml_logs'));
|
30 |
+
$this->renderLayout();
|
31 |
+
}
|
32 |
+
|
33 |
+
public function gridAction()
|
34 |
+
{
|
35 |
+
$this->loadLayout();
|
36 |
+
$this->renderLayout();
|
37 |
+
}
|
38 |
+
|
39 |
+
public function indexAction()
|
40 |
+
{
|
41 |
+
$this->_initAction();
|
42 |
+
$this->_addContent($this->getLayout()->createBlock('abandonedcarts/adminhtml_abandonedcarts'));
|
43 |
+
$this->renderLayout();
|
44 |
+
}
|
45 |
+
|
46 |
+
public function salegridAction()
|
47 |
+
{
|
48 |
+
$this->loadLayout();
|
49 |
+
$this->renderLayout();
|
50 |
+
}
|
51 |
+
|
52 |
+
public function saleAction()
|
53 |
+
{
|
54 |
+
$this->_initAction();
|
55 |
+
$this->_addContent($this->getLayout()->createBlock('abandonedcarts/adminhtml_saleabandonedcarts'));
|
56 |
+
$this->renderLayout();
|
57 |
+
}
|
58 |
+
|
59 |
+
public function notifyAllAction()
|
60 |
+
{
|
61 |
+
try {
|
62 |
+
$count = Mage::getModel('abandonedcarts/notifier')->sendAbandonedCartsEmail();
|
63 |
+
Mage::getSingleton('adminhtml/session')->addSuccess(
|
64 |
+
Mage::helper('abandonedcarts')->__(
|
65 |
+
'%sTotal of %d customer(s) were successfully notified', (Mage::helper('abandonedcarts')->getDryRun() ? "!DRY RUN! " : ""), $count
|
66 |
+
)
|
67 |
+
);
|
68 |
+
} catch (Exception $e) {
|
69 |
+
Mage::getSingleton('adminhtml/session')->addError($e->getMessage());
|
70 |
+
}
|
71 |
+
$this->_redirect('*/*/index');
|
72 |
+
}
|
73 |
+
|
74 |
+
public function notifySaleAllAction()
|
75 |
+
{
|
76 |
+
try {
|
77 |
+
$count = Mage::getModel('abandonedcarts/notifier')->sendAbandonedCartsSaleEmail();
|
78 |
+
Mage::getSingleton('adminhtml/session')->addSuccess(
|
79 |
+
Mage::helper('abandonedcarts')->__(
|
80 |
+
'%sTotal of %d customer(s) were successfully notified', (Mage::helper('abandonedcarts')->getDryRun() ? "!DRY RUN! " : ""), $count
|
81 |
+
)
|
82 |
+
);
|
83 |
+
} catch (Exception $e) {
|
84 |
+
Mage::getSingleton('adminhtml/session')->addError($e->getMessage());
|
85 |
+
}
|
86 |
+
$this->_redirect('*/*/sale');
|
87 |
+
}
|
88 |
+
|
89 |
+
/**
|
90 |
+
*
|
91 |
+
*/
|
92 |
+
public function notifySaleAction()
|
93 |
+
{
|
94 |
+
$emails = $this->getRequest()->getParam('abandonedcarts');
|
95 |
+
if (!is_array($emails)) {
|
96 |
+
Mage::getSingleton('adminhtml/session')->addError(Mage::helper('abandonedcarts')->__('Please select email(s)'));
|
97 |
+
} else {
|
98 |
+
try {
|
99 |
+
Mage::getModel('abandonedcarts/notifier')->sendAbandonedCartsSaleEmail(false, false, $emails);
|
100 |
+
Mage::getSingleton('adminhtml/session')->addSuccess(
|
101 |
+
Mage::helper('abandonedcarts')->__(
|
102 |
+
'%sTotal of %d customer(s) were successfully notified', (Mage::helper('abandonedcarts')->getDryRun() ? "!DRY RUN! " : ""), count($emails)
|
103 |
+
)
|
104 |
+
);
|
105 |
+
} catch (Exception $e) {
|
106 |
+
Mage::getSingleton('adminhtml/session')->addError($e->getMessage());
|
107 |
+
}
|
108 |
+
}
|
109 |
+
$this->_redirect('*/*/sale');
|
110 |
}
|
111 |
|
112 |
/**
|
|
|
113 |
*
|
|
|
114 |
*/
|
115 |
+
public function notifyAction()
|
116 |
+
{
|
117 |
+
$emails = $this->getRequest()->getParam('abandonedcarts');
|
118 |
+
if (!is_array($emails)) {
|
119 |
+
Mage::getSingleton('adminhtml/session')->addError(Mage::helper('abandonedcarts')->__('Please select email(s)'));
|
120 |
+
} else {
|
121 |
+
try {
|
122 |
+
Mage::getModel('abandonedcarts/notifier')->sendAbandonedCartsEmail(false, false, null, $emails);
|
123 |
+
Mage::getSingleton('adminhtml/session')->addSuccess(
|
124 |
+
Mage::helper('abandonedcarts')->__(
|
125 |
+
'%sTotal of %d customer(s) were successfully notified', (Mage::helper('abandonedcarts')->getDryRun() ? "!DRY RUN! " : ""), count($emails)
|
126 |
+
)
|
127 |
+
);
|
128 |
+
} catch (Exception $e) {
|
129 |
+
Mage::getSingleton('adminhtml/session')->addError($e->getMessage());
|
130 |
+
}
|
131 |
+
}
|
132 |
+
$this->_redirect('*/*/index');
|
133 |
}
|
134 |
}
|
app/code/community/DigitalPianism/Abandonedcarts/controllers/IndexController.php
ADDED
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Class DigitalPianism_Abandonedcarts_IndexController
|
5 |
+
*/
|
6 |
+
class DigitalPianism_Abandonedcarts_IndexController extends Mage_Core_Controller_Front_Action {
|
7 |
+
|
8 |
+
/**
|
9 |
+
*
|
10 |
+
*/
|
11 |
+
public function indexAction()
|
12 |
+
{
|
13 |
+
// Get the token
|
14 |
+
if ($token = $this->getRequest()->getParam('token')) {
|
15 |
+
|
16 |
+
// Find corresponding hash entry in the database
|
17 |
+
$link = Mage::getResourceModel('abandonedcarts/link_collection')
|
18 |
+
->addFieldToSelect(array('link_id','customer_email'))
|
19 |
+
->addFieldToFilter('token_hash', hash('sha256', $token))
|
20 |
+
->setPageSize(1);
|
21 |
+
|
22 |
+
if ($link->getSize()) {
|
23 |
+
|
24 |
+
$customerEmail = $link->getFirstItem()->getCustomerEmail();
|
25 |
+
/**
|
26 |
+
* @TODO add an entry in the log
|
27 |
+
*/
|
28 |
+
// Delete so it's one use only
|
29 |
+
$link->getFirstItem()->delete();
|
30 |
+
|
31 |
+
/** @var Mage_Customer_Model_Customer $customer */
|
32 |
+
$customer = Mage::getModel('customer/customer')
|
33 |
+
->setWebsiteId(Mage::app()->getStore()->getWebsiteId());
|
34 |
+
|
35 |
+
// Load the customer
|
36 |
+
$customer->loadByEmail($customerEmail);
|
37 |
+
|
38 |
+
// Log the customer in
|
39 |
+
if ($customer->getId()) {
|
40 |
+
Mage::getSingleton('customer/session')->setCustomerAsLoggedIn($customer);
|
41 |
+
Mage::getSingleton('customer/session')->renewSession();
|
42 |
+
// Redirects to cart
|
43 |
+
$this->_redirect('checkout/cart');
|
44 |
+
}
|
45 |
+
}
|
46 |
+
}
|
47 |
+
|
48 |
+
$this->_redirect('checkout/cart');
|
49 |
+
}
|
50 |
+
}
|
app/code/community/DigitalPianism/Abandonedcarts/data/abandonedcarts_setup/data-upgrade-1.0.0-1.0.1.php
ADDED
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
$installer = $this;
|
4 |
+
$installer->startSetup();
|
5 |
+
|
6 |
+
/** @var Mage_Core_Model_Config $coreConfig */
|
7 |
+
$coreConfig = Mage::getModel('core/config');
|
8 |
+
|
9 |
+
if ($data = Mage::getStoreConfig('abandonedcartsconfig/options/name')) {
|
10 |
+
$coreConfig->saveConfig('abandonedcartsconfig/email/name', $data);
|
11 |
+
}
|
12 |
+
|
13 |
+
if ($data = Mage::getStoreConfig('abandonedcartsconfig/options/email')) {
|
14 |
+
$coreConfig->saveConfig('abandonedcartsconfig/email/email', $data);
|
15 |
+
}
|
16 |
+
|
17 |
+
if ($data = Mage::getStoreConfig('abandonedcartsconfig/options/email_template')) {
|
18 |
+
$coreConfig->saveConfig('abandonedcartsconfig/email/email_template', $data);
|
19 |
+
}
|
20 |
+
|
21 |
+
if ($data = Mage::getStoreConfig('abandonedcartsconfig/options/email_template_sale')) {
|
22 |
+
$coreConfig->saveConfig('abandonedcartsconfig/email/email_template_sale', $data);
|
23 |
+
}
|
24 |
+
|
25 |
+
if ($data = Mage::getStoreConfig('abandonedcartsconfig/options/dryrun')) {
|
26 |
+
$coreConfig->saveConfig('abandonedcartsconfig/test/dryrun', $data);
|
27 |
+
}
|
28 |
+
|
29 |
+
if ($data = Mage::getStoreConfig('abandonedcartsconfig/options/testemail')) {
|
30 |
+
$coreConfig->saveConfig('abandonedcartsconfig/test/testemail', $data);
|
31 |
+
}
|
32 |
+
|
33 |
+
$installer->endSetup();
|
app/code/community/DigitalPianism/Abandonedcarts/etc/adminhtml.xml
CHANGED
@@ -1,5 +1,39 @@
|
|
1 |
<?xml version="1.0"?>
|
2 |
<config>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3 |
<acl>
|
4 |
<resources>
|
5 |
<all>
|
@@ -7,6 +41,32 @@
|
|
7 |
</all>
|
8 |
<admin>
|
9 |
<children>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
<system>
|
11 |
<children>
|
12 |
<config>
|
1 |
<?xml version="1.0"?>
|
2 |
<config>
|
3 |
+
<menu>
|
4 |
+
<digitalpianism_menu translate="title">
|
5 |
+
<title>Digital Pianism</title>
|
6 |
+
<sort_order>110</sort_order>
|
7 |
+
<children>
|
8 |
+
<abandonedcarts translate="title" module="abandonedcarts">
|
9 |
+
<title>Abandoned Carts</title>
|
10 |
+
<sort_order>1</sort_order>
|
11 |
+
<children>
|
12 |
+
<logs translate="title" module="abandonedcarts">
|
13 |
+
<title>Logs</title>
|
14 |
+
<sort_order>1</sort_order>
|
15 |
+
<action>adminhtml/abandonedcarts/logs</action>
|
16 |
+
</logs>
|
17 |
+
<list translate="title" module="abandonedcarts">
|
18 |
+
<title>Abandoned Carts List</title>
|
19 |
+
<sort_order>2</sort_order>
|
20 |
+
<action>adminhtml/abandonedcarts/index</action>
|
21 |
+
</list>
|
22 |
+
<sale_list translate="title" module="abandonedcarts">
|
23 |
+
<title>Abandoned Carts Sale List</title>
|
24 |
+
<sort_order>3</sort_order>
|
25 |
+
<action>adminhtml/abandonedcarts/sale</action>
|
26 |
+
</sale_list>
|
27 |
+
<config translate="title" module="abandonedcarts">
|
28 |
+
<title>Configuration</title>
|
29 |
+
<sort_order>4</sort_order>
|
30 |
+
<action>adminhtml/system_config/edit/section/abandonedcartsconfig</action>
|
31 |
+
</config>
|
32 |
+
</children>
|
33 |
+
</abandonedcarts>
|
34 |
+
</children>
|
35 |
+
</digitalpianism_menu>
|
36 |
+
</menu>
|
37 |
<acl>
|
38 |
<resources>
|
39 |
<all>
|
41 |
</all>
|
42 |
<admin>
|
43 |
<children>
|
44 |
+
<digitalpianism_menu>
|
45 |
+
<children>
|
46 |
+
<abandonedcarts translate="title">
|
47 |
+
<title>Abandoned Carts</title>
|
48 |
+
<sort_order>1</sort_order>
|
49 |
+
<children>
|
50 |
+
<logs translate="title">
|
51 |
+
<title>Logs</title>
|
52 |
+
<sort_order>1</sort_order>
|
53 |
+
</logs>
|
54 |
+
<list translate="title" module="abandonedcarts">
|
55 |
+
<title>Abandoned Carts List</title>
|
56 |
+
<sort_order>2</sort_order>
|
57 |
+
</list>
|
58 |
+
<sale_list translate="title" module="abandonedcarts">
|
59 |
+
<title>Abandoned Carts Sale List</title>
|
60 |
+
<sort_order>3</sort_order>
|
61 |
+
</sale_list>
|
62 |
+
<config translate="title">
|
63 |
+
<title>Configuration</title>
|
64 |
+
<sort_order>4</sort_order>
|
65 |
+
</config>
|
66 |
+
</children>
|
67 |
+
</abandonedcarts>
|
68 |
+
</children>
|
69 |
+
</digitalpianism_menu>
|
70 |
<system>
|
71 |
<children>
|
72 |
<config>
|
app/code/community/DigitalPianism/Abandonedcarts/etc/config.xml
CHANGED
@@ -4,7 +4,7 @@
|
|
4 |
|
5 |
<modules>
|
6 |
<DigitalPianism_Abandonedcarts>
|
7 |
-
<version>0.
|
8 |
</DigitalPianism_Abandonedcarts>
|
9 |
</modules>
|
10 |
|
@@ -33,6 +33,20 @@
|
|
33 |
</DigitalPianism_Abandonedcarts>
|
34 |
</modules>
|
35 |
</translate>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
36 |
</frontend>
|
37 |
|
38 |
<adminhtml>
|
@@ -46,6 +60,46 @@
|
|
46 |
</DigitalPianism_Abandonedcarts>
|
47 |
</modules>
|
48 |
</translate>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
49 |
</adminhtml>
|
50 |
|
51 |
<global>
|
@@ -64,7 +118,22 @@
|
|
64 |
<models>
|
65 |
<abandonedcarts>
|
66 |
<class>DigitalPianism_Abandonedcarts_Model</class>
|
|
|
67 |
</abandonedcarts>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
68 |
<sales_resource>
|
69 |
<rewrite>
|
70 |
<quote>DigitalPianism_Abandonedcarts_Model_Sales_Resource_Quote</quote>
|
@@ -117,7 +186,7 @@
|
|
117 |
<config_path>abandonedcartsconfig/options/cron_expr</config_path>
|
118 |
</schedule>
|
119 |
<run>
|
120 |
-
<model>abandonedcarts/
|
121 |
</run>
|
122 |
</digitalpianism_abandonedcarts_send>
|
123 |
<digitalpianism_abandonedcarts_sendsale>
|
@@ -125,21 +194,41 @@
|
|
125 |
<config_path>abandonedcartsconfig/options/cron_expr</config_path>
|
126 |
</schedule>
|
127 |
<run>
|
128 |
-
<model>abandonedcarts/
|
129 |
</run>
|
130 |
</digitalpianism_abandonedcarts_sendsale>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
131 |
</jobs>
|
132 |
</crontab>
|
133 |
|
134 |
<default>
|
135 |
<abandonedcartsconfig>
|
136 |
<options>
|
|
|
137 |
<cron_expr>0 1 * * *</cron_expr>
|
138 |
<notify_delay>20</notify_delay>
|
139 |
-
<
|
140 |
-
<email_template_sale>abandonedcartsconfig_options_email_template_sale</email_template_sale>
|
141 |
<customer_groups>1,2,3</customer_groups>
|
142 |
</options>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
143 |
</abandonedcartsconfig>
|
144 |
</default>
|
145 |
|
4 |
|
5 |
<modules>
|
6 |
<DigitalPianism_Abandonedcarts>
|
7 |
+
<version>1.0.0</version>
|
8 |
</DigitalPianism_Abandonedcarts>
|
9 |
</modules>
|
10 |
|
33 |
</DigitalPianism_Abandonedcarts>
|
34 |
</modules>
|
35 |
</translate>
|
36 |
+
|
37 |
+
<secure_url>
|
38 |
+
<abandonedcarts>/abandonedcarts/</abandonedcarts>
|
39 |
+
</secure_url>
|
40 |
+
|
41 |
+
<routers>
|
42 |
+
<abandonedcarts>
|
43 |
+
<use>standard</use>
|
44 |
+
<args>
|
45 |
+
<module>DigitalPianism_Abandonedcarts</module>
|
46 |
+
<frontName>abandonedcarts</frontName>
|
47 |
+
</args>
|
48 |
+
</abandonedcarts>
|
49 |
+
</routers>
|
50 |
</frontend>
|
51 |
|
52 |
<adminhtml>
|
60 |
</DigitalPianism_Abandonedcarts>
|
61 |
</modules>
|
62 |
</translate>
|
63 |
+
|
64 |
+
<layout>
|
65 |
+
<updates>
|
66 |
+
<abandonedcarts module="DigitalPianism_Abandonedcarts">
|
67 |
+
<file>digitalpianism/abandonedcarts.xml</file>
|
68 |
+
</abandonedcarts>
|
69 |
+
</updates>
|
70 |
+
</layout>
|
71 |
+
|
72 |
+
<events>
|
73 |
+
<!-- To register the controller action -->
|
74 |
+
<controller_action_predispatch_adminhtml>
|
75 |
+
<observers>
|
76 |
+
<digitalpianism_abandonedcarts_predispatch_register>
|
77 |
+
<type>singleton</type>
|
78 |
+
<class>abandonedcarts/adminhtml_observer</class>
|
79 |
+
<method>registerController</method>
|
80 |
+
</digitalpianism_abandonedcarts_predispatch_register>
|
81 |
+
</observers>
|
82 |
+
</controller_action_predispatch_adminhtml>
|
83 |
+
<!-- Called after creating a block -->
|
84 |
+
<core_layout_block_create_after>
|
85 |
+
<observers>
|
86 |
+
<digitalpianism_abandonedcarts_block_create_after>
|
87 |
+
<type>singleton</type>
|
88 |
+
<class>abandonedcarts/adminhtml_observer</class>
|
89 |
+
<method>addExtraColumnsToGrid</method>
|
90 |
+
</digitalpianism_abandonedcarts_block_create_after>
|
91 |
+
</observers>
|
92 |
+
</core_layout_block_create_after>
|
93 |
+
<!-- Called before loading a non EAV collection -->
|
94 |
+
<core_collection_abstract_load_before>
|
95 |
+
<observers>
|
96 |
+
<digitalpianism_abandonedcarts_before_core_load_collection>
|
97 |
+
<class>abandonedcarts/adminhtml_observer</class>
|
98 |
+
<method>addExtraColumnsToCollection</method>
|
99 |
+
</digitalpianism_abandonedcarts_before_core_load_collection>
|
100 |
+
</observers>
|
101 |
+
</core_collection_abstract_load_before>
|
102 |
+
</events>
|
103 |
</adminhtml>
|
104 |
|
105 |
<global>
|
118 |
<models>
|
119 |
<abandonedcarts>
|
120 |
<class>DigitalPianism_Abandonedcarts_Model</class>
|
121 |
+
<resourceModel>abandonedcarts_mysql4</resourceModel>
|
122 |
</abandonedcarts>
|
123 |
+
|
124 |
+
<abandonedcarts_mysql4>
|
125 |
+
<class>DigitalPianism_Abandonedcarts_Model_Resource</class>
|
126 |
+
<!-- declare table test -->
|
127 |
+
<entities>
|
128 |
+
<log>
|
129 |
+
<table>dp_abandonedcarts_log</table>
|
130 |
+
</log>
|
131 |
+
<link>
|
132 |
+
<table>dp_abandonedcarts_link</table>
|
133 |
+
</link>
|
134 |
+
</entities>
|
135 |
+
</abandonedcarts_mysql4>
|
136 |
+
|
137 |
<sales_resource>
|
138 |
<rewrite>
|
139 |
<quote>DigitalPianism_Abandonedcarts_Model_Sales_Resource_Quote</quote>
|
186 |
<config_path>abandonedcartsconfig/options/cron_expr</config_path>
|
187 |
</schedule>
|
188 |
<run>
|
189 |
+
<model>abandonedcarts/notifier::sendAbandonedCartsEmail</model>
|
190 |
</run>
|
191 |
</digitalpianism_abandonedcarts_send>
|
192 |
<digitalpianism_abandonedcarts_sendsale>
|
194 |
<config_path>abandonedcartsconfig/options/cron_expr</config_path>
|
195 |
</schedule>
|
196 |
<run>
|
197 |
+
<model>abandonedcarts/notifier::sendAbandonedCartsSaleEmail</model>
|
198 |
</run>
|
199 |
</digitalpianism_abandonedcarts_sendsale>
|
200 |
+
<digitalpianism_abandonedcarts_deleteexpiredlinks>
|
201 |
+
<schedule>
|
202 |
+
<cron_expr>*/5 * * * *</cron_expr>
|
203 |
+
</schedule>
|
204 |
+
<run>
|
205 |
+
<model>abandonedcarts/link_cleaner::cleanExpiredLinks</model>
|
206 |
+
</run>
|
207 |
+
</digitalpianism_abandonedcarts_deleteexpiredlinks>
|
208 |
</jobs>
|
209 |
</crontab>
|
210 |
|
211 |
<default>
|
212 |
<abandonedcartsconfig>
|
213 |
<options>
|
214 |
+
<enable>0</enable>
|
215 |
<cron_expr>0 1 * * *</cron_expr>
|
216 |
<notify_delay>20</notify_delay>
|
217 |
+
<enable_sale>1</enable_sale>
|
|
|
218 |
<customer_groups>1,2,3</customer_groups>
|
219 |
</options>
|
220 |
+
<email>
|
221 |
+
<email_template>abandonedcartsconfig_options_email_template</email_template>
|
222 |
+
<email_template_sale>abandonedcartsconfig_options_email_template_sale</email_template_sale>
|
223 |
+
<autologin>0</autologin>
|
224 |
+
</email>
|
225 |
+
<campaign>
|
226 |
+
<enable>0</enable>
|
227 |
+
</campaign>
|
228 |
+
<test>
|
229 |
+
<dryrun>0</dryrun>
|
230 |
+
<log>0</log>
|
231 |
+
</test>
|
232 |
</abandonedcartsconfig>
|
233 |
</default>
|
234 |
|
app/code/community/DigitalPianism/Abandonedcarts/etc/system.xml
CHANGED
@@ -16,9 +16,9 @@
|
|
16 |
<show_in_store>1</show_in_store>
|
17 |
<groups>
|
18 |
<options translate="label">
|
19 |
-
<label>
|
20 |
<frontend_type>text</frontend_type>
|
21 |
-
<sort_order>
|
22 |
<show_in_default>1</show_in_default>
|
23 |
<show_in_website>1</show_in_website>
|
24 |
<show_in_store>1</show_in_store>
|
@@ -26,26 +26,66 @@
|
|
26 |
<enable translate="label">
|
27 |
<label>Enable Abandoned Carts Notification</label>
|
28 |
<frontend_type>select</frontend_type>
|
29 |
-
|
30 |
<sort_order>10</sort_order>
|
31 |
<show_in_default>1</show_in_default>
|
32 |
<show_in_website>1</show_in_website>
|
33 |
<show_in_store>1</show_in_store>
|
34 |
</enable>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
35 |
<cron_expr translate="label">
|
36 |
<label>Cron Schedule</label>
|
37 |
<frontend_type>text</frontend_type>
|
38 |
-
<sort_order>
|
39 |
<show_in_default>1</show_in_default>
|
40 |
<show_in_website>1</show_in_website>
|
41 |
<show_in_store>1</show_in_store>
|
42 |
</cron_expr>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
43 |
<name translate="label">
|
44 |
<label>Sender Name</label>
|
45 |
<frontend_type>text</frontend_type>
|
46 |
<backend_model>adminhtml/system_config_backend_email_sender</backend_model>
|
47 |
-
<validate>validate-emailSender</validate>
|
48 |
-
<sort_order>
|
49 |
<show_in_default>1</show_in_default>
|
50 |
<show_in_website>1</show_in_website>
|
51 |
<show_in_store>1</show_in_store>
|
@@ -55,7 +95,7 @@
|
|
55 |
<frontend_type>text</frontend_type>
|
56 |
<validate>validate-email</validate>
|
57 |
<backend_model>adminhtml/system_config_backend_email_address</backend_model>
|
58 |
-
<sort_order>
|
59 |
<show_in_default>1</show_in_default>
|
60 |
<show_in_website>1</show_in_website>
|
61 |
<show_in_store>1</show_in_store>
|
@@ -64,78 +104,101 @@
|
|
64 |
<label>Email Template for Unaltered Abandoned Carts</label>
|
65 |
<frontend_type>select</frontend_type>
|
66 |
<source_model>adminhtml/system_config_source_email_template</source_model>
|
67 |
-
<sort_order>
|
68 |
<show_in_default>1</show_in_default>
|
69 |
<show_in_website>1</show_in_website>
|
70 |
<show_in_store>1</show_in_store>
|
71 |
</email_template>
|
72 |
-
<
|
73 |
-
<label>
|
74 |
-
<frontend_type>
|
75 |
-
<
|
76 |
-
<sort_order>
|
77 |
<show_in_default>1</show_in_default>
|
78 |
<show_in_website>1</show_in_website>
|
79 |
<show_in_store>1</show_in_store>
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
<label>Enable Sale Abandoned Carts Notification</label>
|
84 |
<frontend_type>select</frontend_type>
|
85 |
-
|
86 |
-
<sort_order>
|
87 |
<show_in_default>1</show_in_default>
|
88 |
<show_in_website>1</show_in_website>
|
89 |
<show_in_store>1</show_in_store>
|
90 |
-
|
91 |
-
|
92 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
93 |
<frontend_type>select</frontend_type>
|
94 |
-
<source_model>adminhtml/
|
95 |
-
<sort_order>
|
96 |
<show_in_default>1</show_in_default>
|
97 |
<show_in_website>1</show_in_website>
|
98 |
<show_in_store>1</show_in_store>
|
99 |
-
</
|
100 |
-
<
|
101 |
-
<label>
|
102 |
-
<frontend_type>
|
103 |
-
<
|
104 |
-
<sort_order>75</sort_order>
|
105 |
<show_in_default>1</show_in_default>
|
106 |
<show_in_website>1</show_in_website>
|
107 |
<show_in_store>1</show_in_store>
|
108 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
109 |
<dryrun translate="label comment">
|
110 |
<label>Dry Run</label>
|
111 |
<frontend_type>select</frontend_type>
|
112 |
-
|
113 |
-
<sort_order>
|
114 |
<show_in_default>1</show_in_default>
|
115 |
<show_in_website>1</show_in_website>
|
116 |
<show_in_store>1</show_in_store>
|
117 |
-
<comment>Setting this parameter to Yes will
|
118 |
</dryrun>
|
119 |
<testemail translate="label comment">
|
120 |
<label>Test Email</label>
|
121 |
<frontend_type>text</frontend_type>
|
122 |
-
<sort_order>
|
123 |
<show_in_default>1</show_in_default>
|
124 |
<show_in_website>1</show_in_website>
|
125 |
<show_in_store>1</show_in_store>
|
126 |
-
<
|
|
|
127 |
</testemail>
|
128 |
-
<
|
129 |
-
<label>
|
130 |
-
<frontend_type>
|
131 |
-
<
|
132 |
-
<sort_order>
|
133 |
<show_in_default>1</show_in_default>
|
134 |
<show_in_website>1</show_in_website>
|
135 |
<show_in_store>1</show_in_store>
|
136 |
-
|
|
|
137 |
</fields>
|
138 |
-
</
|
139 |
</groups>
|
140 |
</abandonedcartsconfig>
|
141 |
</sections>
|
16 |
<show_in_store>1</show_in_store>
|
17 |
<groups>
|
18 |
<options translate="label">
|
19 |
+
<label>Global Configuration</label>
|
20 |
<frontend_type>text</frontend_type>
|
21 |
+
<sort_order>1</sort_order>
|
22 |
<show_in_default>1</show_in_default>
|
23 |
<show_in_website>1</show_in_website>
|
24 |
<show_in_store>1</show_in_store>
|
26 |
<enable translate="label">
|
27 |
<label>Enable Abandoned Carts Notification</label>
|
28 |
<frontend_type>select</frontend_type>
|
29 |
+
<source_model>adminhtml/system_config_source_yesno</source_model>
|
30 |
<sort_order>10</sort_order>
|
31 |
<show_in_default>1</show_in_default>
|
32 |
<show_in_website>1</show_in_website>
|
33 |
<show_in_store>1</show_in_store>
|
34 |
</enable>
|
35 |
+
<enable_sale translate="label">
|
36 |
+
<label>Enable Sale Abandoned Carts Notification</label>
|
37 |
+
<frontend_type>select</frontend_type>
|
38 |
+
<source_model>adminhtml/system_config_source_yesno</source_model>
|
39 |
+
<sort_order>20</sort_order>
|
40 |
+
<show_in_default>1</show_in_default>
|
41 |
+
<show_in_website>1</show_in_website>
|
42 |
+
<show_in_store>1</show_in_store>
|
43 |
+
</enable_sale>
|
44 |
<cron_expr translate="label">
|
45 |
<label>Cron Schedule</label>
|
46 |
<frontend_type>text</frontend_type>
|
47 |
+
<sort_order>30</sort_order>
|
48 |
<show_in_default>1</show_in_default>
|
49 |
<show_in_website>1</show_in_website>
|
50 |
<show_in_store>1</show_in_store>
|
51 |
</cron_expr>
|
52 |
+
<notify_delay translate="label comment">
|
53 |
+
<label>Delay / Send Abandoned Cart Email After</label>
|
54 |
+
<frontend_type>text</frontend_type>
|
55 |
+
<validate>validate-not-negative-number</validate>
|
56 |
+
<sort_order>40</sort_order>
|
57 |
+
<show_in_default>1</show_in_default>
|
58 |
+
<show_in_website>1</show_in_website>
|
59 |
+
<show_in_store>1</show_in_store>
|
60 |
+
<depends><enable>1</enable></depends>
|
61 |
+
<comment>(days). NB: this only affects unaltered abandoned carts. If products go on sale, the email below is sent on the same day.</comment>
|
62 |
+
</notify_delay>
|
63 |
+
<customer_groups translate="label">
|
64 |
+
<label>Customer Groups Restriction</label>
|
65 |
+
<frontend_type>multiselect</frontend_type>
|
66 |
+
<source_model>adminhtml/system_config_source_customer_group_multiselect</source_model>
|
67 |
+
<sort_order>50</sort_order>
|
68 |
+
<show_in_default>1</show_in_default>
|
69 |
+
<show_in_website>1</show_in_website>
|
70 |
+
<show_in_store>1</show_in_store>
|
71 |
+
<depends><enable>1</enable></depends>
|
72 |
+
</customer_groups>
|
73 |
+
</fields>
|
74 |
+
</options>
|
75 |
+
<email translate="label">
|
76 |
+
<label>Email Configuration</label>
|
77 |
+
<frontend_type>text</frontend_type>
|
78 |
+
<sort_order>2</sort_order>
|
79 |
+
<show_in_default>1</show_in_default>
|
80 |
+
<show_in_website>1</show_in_website>
|
81 |
+
<show_in_store>1</show_in_store>
|
82 |
+
<fields>
|
83 |
<name translate="label">
|
84 |
<label>Sender Name</label>
|
85 |
<frontend_type>text</frontend_type>
|
86 |
<backend_model>adminhtml/system_config_backend_email_sender</backend_model>
|
87 |
+
<validate>validate-emailSender</validate>
|
88 |
+
<sort_order>10</sort_order>
|
89 |
<show_in_default>1</show_in_default>
|
90 |
<show_in_website>1</show_in_website>
|
91 |
<show_in_store>1</show_in_store>
|
95 |
<frontend_type>text</frontend_type>
|
96 |
<validate>validate-email</validate>
|
97 |
<backend_model>adminhtml/system_config_backend_email_address</backend_model>
|
98 |
+
<sort_order>20</sort_order>
|
99 |
<show_in_default>1</show_in_default>
|
100 |
<show_in_website>1</show_in_website>
|
101 |
<show_in_store>1</show_in_store>
|
104 |
<label>Email Template for Unaltered Abandoned Carts</label>
|
105 |
<frontend_type>select</frontend_type>
|
106 |
<source_model>adminhtml/system_config_source_email_template</source_model>
|
107 |
+
<sort_order>30</sort_order>
|
108 |
<show_in_default>1</show_in_default>
|
109 |
<show_in_website>1</show_in_website>
|
110 |
<show_in_store>1</show_in_store>
|
111 |
</email_template>
|
112 |
+
<email_template_sale translate="label">
|
113 |
+
<label>Email Template for Abandoned Carts Sale</label>
|
114 |
+
<frontend_type>select</frontend_type>
|
115 |
+
<source_model>adminhtml/system_config_source_email_template</source_model>
|
116 |
+
<sort_order>40</sort_order>
|
117 |
<show_in_default>1</show_in_default>
|
118 |
<show_in_website>1</show_in_website>
|
119 |
<show_in_store>1</show_in_store>
|
120 |
+
</email_template_sale>
|
121 |
+
<autologin translate="label comment">
|
122 |
+
<label>Enable Automatic login link in the email</label>
|
|
|
123 |
<frontend_type>select</frontend_type>
|
124 |
+
<source_model>adminhtml/system_config_source_yesno</source_model>
|
125 |
+
<sort_order>50</sort_order>
|
126 |
<show_in_default>1</show_in_default>
|
127 |
<show_in_website>1</show_in_website>
|
128 |
<show_in_store>1</show_in_store>
|
129 |
+
<comment>Autologin links are one use only and expire after 48 hours for security reasons.</comment>
|
130 |
+
</autologin>
|
131 |
+
</fields>
|
132 |
+
</email>
|
133 |
+
<campaign translate="label">
|
134 |
+
<label>Google Campaign Tracker</label>
|
135 |
+
<frontend_type>text</frontend_type>
|
136 |
+
<sort_order>3</sort_order>
|
137 |
+
<show_in_default>1</show_in_default>
|
138 |
+
<show_in_website>1</show_in_website>
|
139 |
+
<show_in_store>1</show_in_store>
|
140 |
+
<fields>
|
141 |
+
<enable translate="label comment">
|
142 |
+
<label>Enable</label>
|
143 |
<frontend_type>select</frontend_type>
|
144 |
+
<source_model>adminhtml/system_config_source_yesno</source_model>
|
145 |
+
<sort_order>10</sort_order>
|
146 |
<show_in_default>1</show_in_default>
|
147 |
<show_in_website>1</show_in_website>
|
148 |
<show_in_store>1</show_in_store>
|
149 |
+
</enable>
|
150 |
+
<name translate="label comment">
|
151 |
+
<label>Campaign Name</label>
|
152 |
+
<frontend_type>text</frontend_type>
|
153 |
+
<sort_order>20</sort_order>
|
|
|
154 |
<show_in_default>1</show_in_default>
|
155 |
<show_in_website>1</show_in_website>
|
156 |
<show_in_store>1</show_in_store>
|
157 |
+
<depends><enable>1</enable></depends>
|
158 |
+
<comment>utm_campaign</comment>
|
159 |
+
</name>
|
160 |
+
</fields>
|
161 |
+
</campaign>
|
162 |
+
<test translate="label">
|
163 |
+
<label>Test and debug</label>
|
164 |
+
<frontend_type>text</frontend_type>
|
165 |
+
<sort_order>3</sort_order>
|
166 |
+
<show_in_default>1</show_in_default>
|
167 |
+
<show_in_website>1</show_in_website>
|
168 |
+
<show_in_store>1</show_in_store>
|
169 |
+
<fields>
|
170 |
<dryrun translate="label comment">
|
171 |
<label>Dry Run</label>
|
172 |
<frontend_type>select</frontend_type>
|
173 |
+
<source_model>adminhtml/system_config_source_yesno</source_model>
|
174 |
+
<sort_order>10</sort_order>
|
175 |
<show_in_default>1</show_in_default>
|
176 |
<show_in_website>1</show_in_website>
|
177 |
<show_in_store>1</show_in_store>
|
178 |
+
<comment>Setting this parameter to Yes will not send the real email notification.</comment>
|
179 |
</dryrun>
|
180 |
<testemail translate="label comment">
|
181 |
<label>Test Email</label>
|
182 |
<frontend_type>text</frontend_type>
|
183 |
+
<sort_order>20</sort_order>
|
184 |
<show_in_default>1</show_in_default>
|
185 |
<show_in_website>1</show_in_website>
|
186 |
<show_in_store>1</show_in_store>
|
187 |
+
<depends><dryrun>1</dryrun></depends>
|
188 |
+
<comment>With dry run set to yes, this email is used as a recipient email replacement so the real customer don't receive the email.</comment>
|
189 |
</testemail>
|
190 |
+
<log translate="label comment">
|
191 |
+
<label>Enable Log</label>
|
192 |
+
<frontend_type>select</frontend_type>
|
193 |
+
<source_model>adminhtml/system_config_source_yesno</source_model>
|
194 |
+
<sort_order>30</sort_order>
|
195 |
<show_in_default>1</show_in_default>
|
196 |
<show_in_website>1</show_in_website>
|
197 |
<show_in_store>1</show_in_store>
|
198 |
+
<comment>Setting this parameter to Yes will log abandoned cart actions so it can be viewed in Digital Pianism > Abandoned Carts > Logs</comment>
|
199 |
+
</log>
|
200 |
</fields>
|
201 |
+
</test>
|
202 |
</groups>
|
203 |
</abandonedcartsconfig>
|
204 |
</sections>
|
app/code/community/DigitalPianism/Abandonedcarts/sql/abandonedcarts_setup/upgrade-0.3.6-1.0.0.php
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
$installer = $this;
|
4 |
+
|
5 |
+
$installer->startSetup();
|
6 |
+
|
7 |
+
$installer->run("
|
8 |
+
CREATE TABLE IF NOT EXISTS {$this->getTable('abandonedcarts/log')} (
|
9 |
+
`log_id` int(11) NOT NULL auto_increment,
|
10 |
+
`customer_email` varchar(255) default NULL,
|
11 |
+
`type` int(11) default NULL,
|
12 |
+
`comment` text default NULL,
|
13 |
+
`store` int(11) default NULL,
|
14 |
+
`dryrun` varchar(255) default NULL,
|
15 |
+
`added` timestamp NOT NULL default CURRENT_TIMESTAMP,
|
16 |
+
PRIMARY KEY (`log_id`)
|
17 |
+
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
|
18 |
+
|
19 |
+
CREATE TABLE IF NOT EXISTS {$this->getTable('abandonedcarts/link')} (
|
20 |
+
`link_id` int(11) NOT NULL auto_increment,
|
21 |
+
`token_hash` text default NULL,
|
22 |
+
`customer_email` varchar(255) default NULL,
|
23 |
+
`expiration_date` datetime default NULL,
|
24 |
+
PRIMARY KEY (`link_id`)
|
25 |
+
) ENGINE = InnoDB DEFAULT CHARSET = utf8;");
|
26 |
+
|
27 |
+
$installer->endSetup();
|
app/design/adminhtml/default/default/layout/digitalpianism/abandonedcarts.xml
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?xml version="1.0"?>
|
2 |
+
<layout version="0.1.0">
|
3 |
+
<adminhtml_abandonedcarts_grid>
|
4 |
+
<block type="abandonedcarts/adminhtml_abandonedcarts_grid" name="root" output="toHtml"/>
|
5 |
+
</adminhtml_abandonedcarts_grid>
|
6 |
+
|
7 |
+
<adminhtml_abandonedcarts_salegrid>
|
8 |
+
<block type="abandonedcarts/adminhtml_saleabandonedcarts_grid" name="root" output="toHtml"/>
|
9 |
+
</adminhtml_abandonedcarts_salegrid>
|
10 |
+
</layout>
|
app/design/adminhtml/default/default/template/digitalpianism/abandonedcarts/list.phtml
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<div class="content-header">
|
2 |
+
<table cellspacing="0">
|
3 |
+
<tr>
|
4 |
+
<td style="<?php echo $this->getHeaderWidth() ?>"><?php echo $this->getHeaderHtml() ?></td>
|
5 |
+
<td class="form-buttons"><?php echo $this->getButtonsHtml() ?></td>
|
6 |
+
</tr>
|
7 |
+
</table>
|
8 |
+
</div>
|
9 |
+
<?php echo $this->getStoreSwitcherHtml() ?>
|
10 |
+
<div>
|
11 |
+
<?php echo $this->getGridHtml() ?>
|
12 |
+
</div>
|
app/design/adminhtml/default/default/template/digitalpianism/abandonedcarts/system/config/button.phtml
DELETED
@@ -1,17 +0,0 @@
|
|
1 |
-
<script type="text/javascript">
|
2 |
-
//<![CDATA[
|
3 |
-
function send() {
|
4 |
-
new Ajax.Request('<?php echo $this->getAjaxCheckUrl() ?>', {
|
5 |
-
method: 'get',
|
6 |
-
onSuccess: function(transport){
|
7 |
-
|
8 |
-
if (transport.responseText){
|
9 |
-
alert('Abandoned carts notifications have been sent')
|
10 |
-
}
|
11 |
-
}
|
12 |
-
});
|
13 |
-
}
|
14 |
-
//]]>
|
15 |
-
</script>
|
16 |
-
|
17 |
-
<?php echo $this->getButtonHtml() ?>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/design/frontend/base/default/template/digitalpianism/abandonedcarts/email/items.phtml
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
$productNames = $this->getProductname();
|
3 |
+
$productImages = $this->getProductimage();
|
4 |
+
?>
|
5 |
+
<table>
|
6 |
+
<tr>
|
7 |
+
<th>
|
8 |
+
<?php echo Mage::helper('abandonedcarts')->__('Product Image'); ?>
|
9 |
+
</th>
|
10 |
+
<th>
|
11 |
+
<?php echo Mage::helper('abandonedcarts')->__('Product Name'); ?>
|
12 |
+
</th>
|
13 |
+
</tr>
|
14 |
+
<?php foreach($productNames as $i => $name): ?>
|
15 |
+
<tr>
|
16 |
+
<td>
|
17 |
+
<?php if ($productImages[$i]): ?>
|
18 |
+
<img src="<?php echo $productImages[$i]; ?>" alt="<?php echo $name; ?>" />
|
19 |
+
<?php endif; ?>
|
20 |
+
</td>
|
21 |
+
<td>
|
22 |
+
<?php echo $name; ?>
|
23 |
+
</td>
|
24 |
+
</tr>
|
25 |
+
<?php endforeach; ?>
|
26 |
+
</table>
|
app/design/frontend/base/default/template/digitalpianism/abandonedcarts/email/sale_items.phtml
ADDED
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
$productNames = $this->getProductname();
|
3 |
+
$productImages = $this->getProductimage();
|
4 |
+
$cartPrices = $this->getCartprice();
|
5 |
+
$salePrices = $this->getSpecialprice();
|
6 |
+
?>
|
7 |
+
<table>
|
8 |
+
<tr>
|
9 |
+
<th>
|
10 |
+
<?php echo Mage::helper('abandonedcarts')->__('Product Image'); ?>
|
11 |
+
</th>
|
12 |
+
<th>
|
13 |
+
<?php echo Mage::helper('abandonedcarts')->__('Product Name'); ?>
|
14 |
+
</th>
|
15 |
+
<th>
|
16 |
+
<?php echo Mage::helper('abandonedcarts')->__('Price when you left'); ?>
|
17 |
+
</th>
|
18 |
+
<th>
|
19 |
+
<?php echo Mage::helper('abandonedcarts')->__('Price now'); ?>
|
20 |
+
</th>
|
21 |
+
</tr>
|
22 |
+
<?php foreach($productNames as $i => $name): ?>
|
23 |
+
<tr>
|
24 |
+
<td>
|
25 |
+
<?php if ($productImages[$i]): ?>
|
26 |
+
<img src="<?php echo $productImages[$i]; ?>" alt="<?php echo $name; ?>" />
|
27 |
+
<?php endif; ?>
|
28 |
+
</td>
|
29 |
+
<td>
|
30 |
+
<?php echo $name; ?>
|
31 |
+
</td>
|
32 |
+
<td>
|
33 |
+
<?php echo $cartPrices[$i]; ?>
|
34 |
+
</td>
|
35 |
+
<td>
|
36 |
+
<?php echo $salePrices[$i]; ?>
|
37 |
+
</td>
|
38 |
+
</tr>
|
39 |
+
<?php endforeach; ?>
|
40 |
+
</table>
|
app/locale/en_US/DigitalPianism_Abandonedcarts.csv
CHANGED
@@ -3,15 +3,43 @@ Abandoned Carts Emails,Abandoned Carts Emails
|
|
3 |
Enable Abandoned Carts Notification,Enable Abandoned Carts Notification
|
4 |
Enable Sale Abandoned Carts Notification,Enable Sale Abandoned Carts Notification
|
5 |
Dry Run,Dry Run
|
6 |
-
Setting this parameter to Yes will log all the email addresses supposed to receive a notification into the var/log/digitalpianism_abandonedcarts.log file and will not send the real email notification,Setting this parameter to Yes will log all the email addresses supposed to receive a notification into the var/log/digitalpianism_abandonedcarts.log file and will not send the real email notification
|
7 |
Test Email,Test Email
|
8 |
-
With dry run set to yes, this email is used to filter the emails supposed to be sent and only send a notification email to the customer with this email address,With dry run set to yes, this email is used to filter the emails supposed to be sent and only send a notification email to the customer with this email address
|
9 |
Send Notifications Now,Send Notifications Now
|
10 |
Cron Schedule,Cron Schedule
|
11 |
Sender Name,Sender Name
|
12 |
Sender Email,Sender Email
|
13 |
Email Template for Unaltered Abandoned Carts,Email Template for Unaltered Abandoned Carts
|
14 |
-
Send Abandoned Cart Email After,Send Abandoned Cart Email After
|
15 |
(days). NB: this only affects unaltered abandoned carts. If products go on sale, the email below is sent on the same day.,(days). NB: this only affects unaltered abandoned carts. If products go on sale, the email below is sent on the same day.
|
16 |
Email Template for Sale Products,Email Template for Sale Products
|
17 |
-
Customer Groups Restriction,Customer Groups Restriction
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3 |
Enable Abandoned Carts Notification,Enable Abandoned Carts Notification
|
4 |
Enable Sale Abandoned Carts Notification,Enable Sale Abandoned Carts Notification
|
5 |
Dry Run,Dry Run
|
|
|
6 |
Test Email,Test Email
|
|
|
7 |
Send Notifications Now,Send Notifications Now
|
8 |
Cron Schedule,Cron Schedule
|
9 |
Sender Name,Sender Name
|
10 |
Sender Email,Sender Email
|
11 |
Email Template for Unaltered Abandoned Carts,Email Template for Unaltered Abandoned Carts
|
|
|
12 |
(days). NB: this only affects unaltered abandoned carts. If products go on sale, the email below is sent on the same day.,(days). NB: this only affects unaltered abandoned carts. If products go on sale, the email below is sent on the same day.
|
13 |
Email Template for Sale Products,Email Template for Sale Products
|
14 |
+
Customer Groups Restriction,Customer Groups Restriction
|
15 |
+
With dry run set to yes, this email is used as a recipient email replacement so the real customer don't receive the email.,With dry run set to yes, this email is used as a recipient email replacement so the real customer don't receive the email.
|
16 |
+
Setting this parameter to Yes will not send the real email notification.,Setting this parameter to Yes will not send the real email notification.
|
17 |
+
Delay / Send Abandoned Cart Email After,Delay / Send Abandoned Cart Email After
|
18 |
+
Abandoned Carts (Applied delay: %s days),Abandoned Carts (Applied delay: %s days)
|
19 |
+
Send notifications,Send notifications
|
20 |
+
Abandoned Carts Logs,Abandoned Carts Logs
|
21 |
+
Sale Abandoned Carts,Sale Abandoned Carts
|
22 |
+
Customer Email,Customer Email
|
23 |
+
Customer Firstname,Customer Firstname
|
24 |
+
Customer Lastname,Customer Lastname
|
25 |
+
Product Ids,Product Ids
|
26 |
+
Product Names,Product Names
|
27 |
+
Cart Total,Cart Total
|
28 |
+
Cart Updated At,Cart Updated At
|
29 |
+
Send notification,Send notification
|
30 |
+
Type,Type
|
31 |
+
Comment,Comment
|
32 |
+
Store #,Store #
|
33 |
+
Cart Regular Total,Cart Regular Total
|
34 |
+
Cart Sale Total,Cart Sale Total
|
35 |
+
%sTotal of %d customer(s) were successfully notified,%sTotal of %d customer(s) were successfully notified
|
36 |
+
Logs,Logs
|
37 |
+
Abandoned Carts List,Abandoned Carts List
|
38 |
+
Abandoned Carts Sale List,Abandoned Carts Sale List
|
39 |
+
Enable Automatic login link in the email,Enable Automatic login link in the email
|
40 |
+
Autologin links are one use only and expire after 48 hours for security reasons.,Autologin links are one use only and expire after 48 hours for security reasons.
|
41 |
+
Google Campaign Tracker,Google Campaign Tracker
|
42 |
+
Campaign Name,Campaign Name
|
43 |
+
Test and debug,Test and debug
|
44 |
+
Enable Log,Enable Log
|
45 |
+
Setting this parameter to Yes will log abandoned cart actions so it can be viewed in Digital Pianism > Abandoned Carts > Logs,Setting this parameter to Yes will log abandoned cart actions so it can be viewed in Digital Pianism > Abandoned Carts > Logs
|
app/locale/en_US/template/email/digitalpianism/abandonedcarts/sales_abandonedcarts.html
CHANGED
@@ -14,11 +14,9 @@
|
|
14 |
<td valign="top">
|
15 |
<div>
|
16 |
<p>Dear {{var firstname}},</p>
|
17 |
-
<p>You have abandoned the following product in your shopping bag:</p>
|
18 |
-
|
19 |
-
<p>{{var
|
20 |
-
<p>{{depend extraproductcount}} As well as {{var extraproductcount}} more products{{/depend}}.</p>
|
21 |
-
<p>Follow this link and log in to finalize your purchase: {{config path="web/unsecure/base_url"}}</p>
|
22 |
</div>
|
23 |
</td>
|
24 |
</tr>
|
14 |
<td valign="top">
|
15 |
<div>
|
16 |
<p>Dear {{var firstname}},</p>
|
17 |
+
<p>You have abandoned the following product(s) in your shopping bag:</p>
|
18 |
+
{{block type='core/template' area='frontend' template='digitalpianism/abandonedcarts/email/items.phtml' productname=$productname productimage=$productimage}}
|
19 |
+
<p>Follow this link and log in to finalize your purchase: <a href="{{var link}}">{{var link}}</a></p>
|
|
|
|
|
20 |
</div>
|
21 |
</td>
|
22 |
</tr>
|
app/locale/en_US/template/email/digitalpianism/abandonedcarts/sales_abandonedcarts_sale.html
CHANGED
@@ -14,16 +14,10 @@
|
|
14 |
<td valign="top">
|
15 |
<div>
|
16 |
<p>Dear {{var firstname}},</p>
|
17 |
-
<p>You have abandoned the following product in your shopping bag:</p>
|
18 |
-
|
19 |
-
<p>{{var
|
20 |
-
|
21 |
-
<p>
|
22 |
-
{{depend extraproductcount}}
|
23 |
-
Purchase the {{var extraproductcount}} other sale products in your cart and save {{var discount}} on your order.
|
24 |
-
{{/depend}}
|
25 |
-
</p>
|
26 |
-
<p>Follow this link and log in to finalize your purchase with the new special price: {{config path="web/unsecure/base_url"}}</p>
|
27 |
</div>
|
28 |
</td>
|
29 |
</tr>
|
14 |
<td valign="top">
|
15 |
<div>
|
16 |
<p>Dear {{var firstname}},</p>
|
17 |
+
<p>You have abandoned the following product(s) in your shopping bag and they are now on sale:</p>
|
18 |
+
{{block type='core/template' area='frontend' template='digitalpianism/abandonedcarts/email/sale_items.phtml' productname=$productname productimage=$productimage cartprice=$cartprice specialprice=$specialprice}}
|
19 |
+
<p>Purchase every product in your cart and save {{var discount}} on your order.</p>
|
20 |
+
<p>Follow this link to finalize your purchase with the new special price: <a href="{{var link}}">{{var link}}</a></p>
|
|
|
|
|
|
|
|
|
|
|
|
|
21 |
</div>
|
22 |
</td>
|
23 |
</tr>
|
app/locale/fr_FR/DigitalPianism_Abandonedcarts.csv
CHANGED
@@ -3,15 +3,43 @@ Abandoned Carts Emails,Emails des paniers abandonnés
|
|
3 |
Enable Abandoned Carts Notification,Activer les notifications de paniers abandonnés
|
4 |
Enable Sale Abandoned Carts Notification,Activer les notifications de paniers abandonnés en promo
|
5 |
Dry Run,Test
|
6 |
-
Setting this parameter to Yes will log all the email addresses supposed to receive a notification into the var/log/digitalpianism_abandonedcarts.log file and will not send the real email notification,Mettre la valeur à Oui enregistrera toutes les adresses emails supposées recevoir une notification dans le fichier var/log/digitalpianism_abandonedcarts.log et n'enverra pas les emails
|
7 |
Test Email,Email de test
|
8 |
-
With dry run set to yes, this email is used to filter the emails supposed to be sent and only send a notification email to the customer with this email address,Avec Test à Oui, cette adresse email sera utilisée pour filter les emails supposés être envoyé et seulement envoyé l'email de notification au client avec cette adresse
|
9 |
Send Notifications Now,Envoyer les notification maintenant
|
10 |
Cron Schedule,Réglage du cron
|
11 |
Sender Name,Nom de l'expéditeur
|
12 |
Sender Email,Email de l'expéditeur
|
13 |
Email Template for Unaltered Abandoned Carts,Gabarit d'email pour les paniers abandonnés non modifiés
|
14 |
-
|
15 |
-
(days). NB: this only affects unaltered abandoned carts. If products go on sale, the email below is sent on the same day.,(jours). NB: cela affecte seulement les paniers abandonnés non modifiés. Si un des produits passent en promotion, l'email ci dessous sera envoyé le m�me jour.
|
16 |
Email Template for Sale Products,Gabarit d'email pour les produits en promo
|
17 |
-
Customer Groups Restriction,Restriction de groupes clients
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3 |
Enable Abandoned Carts Notification,Activer les notifications de paniers abandonnés
|
4 |
Enable Sale Abandoned Carts Notification,Activer les notifications de paniers abandonnés en promo
|
5 |
Dry Run,Test
|
|
|
6 |
Test Email,Email de test
|
|
|
7 |
Send Notifications Now,Envoyer les notification maintenant
|
8 |
Cron Schedule,Réglage du cron
|
9 |
Sender Name,Nom de l'expéditeur
|
10 |
Sender Email,Email de l'expéditeur
|
11 |
Email Template for Unaltered Abandoned Carts,Gabarit d'email pour les paniers abandonnés non modifiés
|
12 |
+
(days). NB: this only affects unaltered abandoned carts. If products go on sale, the email below is sent on the same day.,(jours). NB: cela affecte seulement les paniers abandonnés non modifiés. Si un des produits passent en promotion, l'email ci dessous sera envoyé le m�me jour.
|
|
|
13 |
Email Template for Sale Products,Gabarit d'email pour les produits en promo
|
14 |
+
Customer Groups Restriction,Restriction de groupes clients
|
15 |
+
With dry run set to yes, this email is used as a recipient email replacement so the real customer don't receive the email.,Avec Test à Oui, cet email sera utilisé en remplacement de l'email du client afin qu'il ne recoive pas l'email
|
16 |
+
Setting this parameter to Yes will not send the real email notification.,Si oui, les emails ne seront pas envoyés
|
17 |
+
Delay / Send Abandoned Cart Email After,Delai / Envoyer les emails de notifications aprés
|
18 |
+
Abandoned Carts (Applied delay: %s days),Paniers Abandonnés (Delai appliqué: %s jours)
|
19 |
+
Send notifications,Envoyer les notifications
|
20 |
+
Abandoned Carts Logs,Logs de paniers abandonnés
|
21 |
+
Sale Abandoned Carts,Paniers abandonnés en promotion
|
22 |
+
Customer Email,Email client
|
23 |
+
Customer Firstname,Prénom client
|
24 |
+
Customer Lastname,Nom de famille client
|
25 |
+
Product Ids,Ids Produits
|
26 |
+
Product Names,Noms Produits
|
27 |
+
Cart Total,Total Panier
|
28 |
+
Cart Updated At,Mise à jour panier
|
29 |
+
Send notification,Envoyer notification
|
30 |
+
Type,Type
|
31 |
+
Comment,Commentaire
|
32 |
+
Store #,Boutique #
|
33 |
+
Cart Regular Total,Total panier
|
34 |
+
Cart Sale Total,Total panier en promotion
|
35 |
+
%sTotal of %d customer(s) were successfully notified,%s%d client(s) ont bien été notifié
|
36 |
+
Logs,Logs
|
37 |
+
Abandoned Carts List,Liste des paniers abandonnés
|
38 |
+
Abandoned Carts Sale List,Liste des paniers abandonnés en promotion
|
39 |
+
Enable Automatic login link in the email,Activer le lien de connexion automatique dans l'email
|
40 |
+
Autologin links are one use only and expire after 48 hours for security reasons.,Les liens de connexion automatiques sont à usage unique et expirent au bout de 48 heures pour des raisons de sérité
|
41 |
+
Google Campaign Tracker,Tracker Google Campaign
|
42 |
+
Campaign Name,Nom de la campagne
|
43 |
+
Test and debug,Test et déboguage
|
44 |
+
Enable Log,Activer les logs
|
45 |
+
Setting this parameter to Yes will log abandoned cart actions so it can be viewed in Digital Pianism > Abandoned Carts > Logs,Si oui, les logs seront visualisables dans Digital Pianism > Paniers Abandonnés > Logs
|
app/locale/fr_FR/template/email/digitalpianism/abandonedcarts/sales_abandonedcarts.html
CHANGED
@@ -12,12 +12,10 @@
|
|
12 |
<!-- [ middle starts here] -->
|
13 |
<tr>
|
14 |
<td valign="top">
|
15 |
-
<p
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
Suivez ce lien et connectez-vous pour finaliser votre achat: {{config path="web/unsecure/base_url"}}
|
20 |
-
</p>
|
21 |
</td>
|
22 |
</tr>
|
23 |
</table>
|
12 |
<!-- [ middle starts here] -->
|
13 |
<tr>
|
14 |
<td valign="top">
|
15 |
+
<p>Chèr(e) {{var firstname}},</p>
|
16 |
+
<p>Vous avez abandonné le(s) produit(s) suivant(s) dans votre panier.</p>
|
17 |
+
{{block type='core/template' area='frontend' template='digitalpianism/abandonedcarts/email/items.phtml' productname=$productname productimage=$productimage}}
|
18 |
+
<p>Suivez ce lien et connectez-vous pour finaliser votre achat: <a href="{{var link}}">{{var link}}</a></p>
|
|
|
|
|
19 |
</td>
|
20 |
</tr>
|
21 |
</table>
|
app/locale/fr_FR/template/email/digitalpianism/abandonedcarts/sales_abandonedcarts_sale.html
CHANGED
@@ -12,18 +12,11 @@
|
|
12 |
<!-- [ middle starts here] -->
|
13 |
<tr>
|
14 |
<td valign="top">
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
{{depend extraproductcount}}
|
22 |
-
Achetez les {{var extraproductcount}} autre produits en promotion dans votre panier et économisez {{var discount}} sur votre commande.<br/>
|
23 |
-
{{/depend}}
|
24 |
-
|
25 |
-
Suivez ce lien et connectez vous pour finaliser votre achat en profitant des promotions: {{config path="web/unsecure/base_url"}}
|
26 |
-
</p>
|
27 |
</td>
|
28 |
</tr>
|
29 |
</table>
|
12 |
<!-- [ middle starts here] -->
|
13 |
<tr>
|
14 |
<td valign="top">
|
15 |
+
<p>Chèr(e) {{var firstname}},</p>
|
16 |
+
<p>Vous avez abandonné le(s) produit(s) suivant(s) dans votre panier et ils sont désormais en promotion.</p>
|
17 |
+
{{block type='core/template' area='frontend' template='digitalpianism/abandonedcarts/email/sale_items.phtml' productname=$productname productimage=$productimage cartprice=$cartprice specialprice=$specialprice}}
|
18 |
+
<p>Acheter les produits dans votre panier et économisez {{var discount}} sur votre commande.</p>
|
19 |
+
<p>Suivez ce lien et connectez vous pour finaliser votre achat en profitant des promotions: <a href="{{var link}}">{{var link}}</a></p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
20 |
</td>
|
21 |
</tr>
|
22 |
</table>
|
package.xml
CHANGED
@@ -1,7 +1,7 @@
|
|
1 |
<?xml version="1.0"?>
|
2 |
<package>
|
3 |
<name>DigitalPianism_Abandonedcarts</name>
|
4 |
-
<version>0.
|
5 |
<stability>stable</stability>
|
6 |
<license uri="http://opensource.org/licenses/osl-3.0.php">OSL v3.0</license>
|
7 |
<channel>community</channel>
|
@@ -95,11 +95,18 @@ Save the configuration.
|
|
95 |

|
96 |
<p>To manually trigger the notification system, please access System &gt; Configuration &gt; Digital Pianism &gt; Abandoned carts email and click on the "Send" button</p>
|
97 |
<p>Please note that this functionality will send abandoned carts notification regardless the delay you provided, all possible abandoned carts emails will be sent.</p></description>
|
98 |
-
<notes>-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
99 |
<authors><author><name>Digital Pianism</name><user>digitalpianism</user><email>contact@digital-pianism.com</email></author></authors>
|
100 |
-
<date>2016-
|
101 |
-
<time>
|
102 |
-
<contents><target name="magecommunity"><dir name="DigitalPianism"><dir name="Abandonedcarts"><dir name="Block"><dir name="Adminhtml"><dir name="
|
103 |
<compatible/>
|
104 |
<dependencies><required><php><min>4.1.0</min><max>6.0.0</max></php></required></dependencies>
|
105 |
</package>
|
1 |
<?xml version="1.0"?>
|
2 |
<package>
|
3 |
<name>DigitalPianism_Abandonedcarts</name>
|
4 |
+
<version>1.0.0</version>
|
5 |
<stability>stable</stability>
|
6 |
<license uri="http://opensource.org/licenses/osl-3.0.php">OSL v3.0</license>
|
7 |
<channel>community</channel>
|
95 |

|
96 |
<p>To manually trigger the notification system, please access System &gt; Configuration &gt; Digital Pianism &gt; Abandoned carts email and click on the "Send" button</p>
|
97 |
<p>Please note that this functionality will send abandoned carts notification regardless the delay you provided, all possible abandoned carts emails will be sent.</p></description>
|
98 |
+
<notes>- Full refactor of the module
|
99 |
+
- Add two grids to the backend to see the abandoned carts
|
100 |
+
- Add a log database table to easily see what's going on
|
101 |
+
- Implement an autologin link system
|
102 |
+
- Implement Google Campaign tags
|
103 |
+
- Improve the templates to list all items
|
104 |
+
- Change the way dryrun and test email behaves
|
105 |
+
- Add notification flags columns to the native abandoned carts report</notes>
|
106 |
<authors><author><name>Digital Pianism</name><user>digitalpianism</user><email>contact@digital-pianism.com</email></author></authors>
|
107 |
+
<date>2016-07-14</date>
|
108 |
+
<time>15:18:45</time>
|
109 |
+
<contents><target name="magecommunity"><dir name="DigitalPianism"><dir name="Abandonedcarts"><dir name="Block"><dir name="Adminhtml"><dir name="Abandonedcarts"><file name="Grid.php" hash="f0d080d29b7d784004ef412ed5a337d6"/></dir><file name="Abandonedcarts.php" hash="3c8d3c5676cb3bda46a580566f35fd9d"/><dir name="Logs"><file name="Grid.php" hash="05c4ca332a6ad168e28e9a9128252231"/></dir><file name="Logs.php" hash="1173ec175c365fa5c01cbc72a98c0284"/><dir name="Saleabandonedcarts"><file name="Grid.php" hash="cc43378214837f8f9c7c7a66dc47adf4"/></dir><file name="Saleabandonedcarts.php" hash="da29b0f8262da3ec0b993816c5fc5be0"/></dir></dir><dir name="Helper"><file name="Data.php" hash="f7d07930e3276bb06e6209293f287dc3"/></dir><dir name="Model"><dir name="Adminhtml"><file name="Observer.php" hash="d54b2bc70a87fca4f0c3bbb519b84c81"/></dir><file name="Collection.php" hash="3e19497dd2cd2170293136f8cff891dc"/><dir name="Link"><file name="Cleaner.php" hash="aced9e659252056b0f4747a78c6154c8"/></dir><file name="Link.php" hash="6f19c7976980e558d98589021d4d294f"/><file name="Log.php" hash="c9ce940c6a14cfa85c401183559661e4"/><file name="Notifier.php" hash="38c1fcb51994dc155ce62bbb38564526"/><dir name="Resource"><dir name="Link"><file name="Collection.php" hash="39ea2cfb265412d82b9fda822af6d324"/></dir><file name="Link.php" hash="49d00b249de30aefc978f4515f6dbdd7"/><dir name="Log"><file name="Collection.php" hash="54abd79af31a1a853bc08eeed75dc7d0"/></dir><file name="Log.php" hash="00edba4d934093236ac78b42f066ba73"/></dir><dir name="Sales"><dir name="Resource"><file name="Quote.php" hash="3b2f9f24a74a6ea3b6851d64bd6ae5ba"/></dir></dir></dir><dir name="controllers"><dir name="Adminhtml"><file name="AbandonedcartsController.php" hash="32a56a5033b64320879da37f8b2a88e8"/></dir><file name="IndexController.php" hash="5c06db338a20d3de9b19c3f606edbc9a"/></dir><dir name="data"><dir name="abandonedcarts_setup"><file name="data-upgrade-1.0.0-1.0.1.php" hash="a60f9bccf9e42a458f808bc697320bb0"/></dir></dir><dir name="etc"><file name="adminhtml.xml" hash="ce393eb00049f28ff92401be828cd613"/><file name="config.xml" hash="5d3d15cec7d41a670cba49116b01a109"/><file name="system.xml" hash="e6a53269f6223eb246c2495600eb307d"/></dir><dir name="sql"><dir name="abandonedcarts_setup"><file name="install-0.0.1.php" hash="851338e4a710b5d94fead688b065f4b5"/><file name="upgrade-0.0.1-0.0.2.php" hash="0227c009e49b97bcf3f34f84c49f0927"/><file name="upgrade-0.3.6-1.0.0.php" hash="1ac772ef331c8a2278e2c8df77aeb799"/></dir></dir></dir></dir></target><target name="mageetc"><dir name="modules"><file name="DigitalPianism_Abandonedcarts.xml" hash="8a7657855486c68d548db4ba48e083d2"/></dir></target><target name="magedesign"><dir name="adminhtml"><dir name="default"><dir name="default"><dir name="template"><dir name="digitalpianism"><dir name="abandonedcarts"><file name="list.phtml" hash="6af16de73f1b0a3c580e65a95642722f"/></dir></dir></dir><dir name="layout"><dir name="digitalpianism"><file name="abandonedcarts.xml" hash="2f4ec5178aed1c84213605b5212d676e"/></dir></dir></dir></dir></dir><dir name="frontend"><dir name="base"><dir name="default"><dir name="template"><dir name="digitalpianism"><dir name="abandonedcarts"><dir name="email"><file name="items.phtml" hash="1938d5ee30752918b1be76be845b9214"/><file name="sale_items.phtml" hash="7dfce25f17ba19532e68592772bf63ad"/></dir></dir></dir></dir></dir></dir></dir></target><target name="magelocale"><dir name="en_US"><dir name="template"><dir name="email"><dir name="digitalpianism"><dir name="abandonedcarts"><file name="sales_abandonedcarts.html" hash="a35bd61e1f172b37ac4ed317e1ad44e9"/><file name="sales_abandonedcarts_sale.html" hash="4f437deca852efeacfec0fb3ba929971"/></dir></dir></dir></dir><file name="DigitalPianism_Abandonedcarts.csv" hash="bd3ed00291684eac5149305ed829a824"/></dir><dir name="fr_FR"><dir name="template"><dir name="email"><dir name="digitalpianism"><dir name="abandonedcarts"><file name="sales_abandonedcarts.html" hash="3ec93757d563ed926090a394577f1dbd"/><file name="sales_abandonedcarts_sale.html" hash="3586968516c8e8374cfa913a3eea7995"/></dir></dir></dir></dir><file name="DigitalPianism_Abandonedcarts.csv" hash="2a9c63b4d83cb922b3060a4735dabe38"/></dir></target></contents>
|
110 |
<compatible/>
|
111 |
<dependencies><required><php><min>4.1.0</min><max>6.0.0</max></php></required></dependencies>
|
112 |
</package>
|