* @copyright 2007-2012 PrestaShop SA
* @version Release: $Revision: 7104 $
* @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0)
* International Registered Trademark & Property of PrestaShop SA
*/
if (!defined('_PS_VERSION_'))
exit;
class StatsForecast extends Module
{
private $_html = '';
private $t1 = 0;
private $t2 = 0;
private $t3 = 0;
private $t4 = 0;
private $t5 = 0;
private $t6 = 0;
private $t7 = 0;
private $t8 = 0;
public function __construct()
{
$this->name = 'statsforecast';
$this->tab = 'analytics_stats';
$this->version = 1.0;
$this->author = 'PrestaShop';
$this->need_instance = 0;
parent::__construct();
$this->displayName = $this->l('Stats Dashboard');
$this->description = '';
}
public function install()
{
return (parent::install() && $this->registerHook('AdminStatsModules'));
}
public function getContent()
{
Tools::redirectAdmin('index.php?controller=AdminStats&module=statsforecast&token='.Tools::getAdminTokenLite('AdminStats'));
}
public function hookAdminStatsModules()
{
$ru = AdminController::$currentIndex.'&module='.$this->name.'&token='.Tools::getValue('token');
$db = Db::getInstance();
if (!isset($this->context->cookie->stats_granularity))
$this->context->cookie->stats_granularity = 10;
if (Tools::isSubmit('submitIdZone'))
$this->context->cookie->stats_id_zone = (int)Tools::getValue('stats_id_zone');
if (Tools::isSubmit('submitGranularity'))
$this->context->cookie->stats_granularity = Tools::getValue('stats_granularity');
$currency = $this->context->currency;
$employee = $this->context->employee;
$from = max(strtotime(_PS_CREATION_DATE_.' 00:00:00'), strtotime($employee->stats_date_from.' 00:00:00'));
$to = strtotime($employee->stats_date_to.' 23:59:59');
$to2 = min(time(), $to);
$interval = ($to - $from) / 60 / 60 / 24;
$interval2 = ($to2 - $from) / 60 / 60 / 24;
$prop30 = $interval / $interval2;
if ($this->context->cookie->stats_granularity == 7)
$intervalAvg = $interval2 / 30;
if ($this->context->cookie->stats_granularity == 4)
$intervalAvg = $interval2 / 365;
if ($this->context->cookie->stats_granularity == 10)
$intervalAvg = $interval2;
if ($this->context->cookie->stats_granularity == 42)
$intervalAvg = $interval2 / 7;
$dataTable = array();
if ($this->context->cookie->stats_granularity == 10)
for ($i = $from; $i <= $to2; $i = strtotime('+1 day', $i))
$dataTable[date('Y-m-d', $i)] = array('fix_date' => date('Y-m-d', $i), 'countOrders' => 0, 'countProducts' => 0, 'totalSales' => 0);
$dateFromGAdd = ($this->context->cookie->stats_granularity != 42
? 'LEFT(date_add, '.(int)$this->context->cookie->stats_granularity.')'
: 'IFNULL(MAKEDATE(YEAR(date_add),DAYOFYEAR(date_add)-WEEKDAY(date_add)), CONCAT(YEAR(date_add),"-01-01*"))');
$dateFromGInvoice = ($this->context->cookie->stats_granularity != 42
? 'LEFT(invoice_date, '.(int)$this->context->cookie->stats_granularity.')'
: 'IFNULL(MAKEDATE(YEAR(invoice_date),DAYOFYEAR(invoice_date)-WEEKDAY(invoice_date)), CONCAT(YEAR(invoice_date),"-01-01*"))');
$result = $db->query('
SELECT
'.$dateFromGInvoice.' as fix_date,
COUNT(*) as countOrders,
(SELECT SUM(od.product_quantity) FROM '._DB_PREFIX_.'order_detail od WHERE o.id_order = od.id_order) as countProducts,
SUM(o.total_paid_tax_excl / o.conversion_rate) as totalSales
FROM '._DB_PREFIX_.'orders o
WHERE o.valid = 1
AND o.invoice_date BETWEEN '.ModuleGraph::getDateBetween().'
'.Shop::addSqlRestriction(Shop::SHARE_ORDER, 'o').'
GROUP BY '.$dateFromGInvoice);
while ($row = $db->nextRow($result))
$dataTable[$row['fix_date']] = $row;
$this->_html .= '
'.$this->displayName.'
'.$this->l('All amounts are without taxes.').'
|
'.$this->l('Visits').' |
'.$this->l('Reg.').' |
'.$this->l('Orders').' |
'.$this->l('Items').' |
'.$this->l('% Reg.').' |
'.$this->l('% Orders').' |
'.$this->l('Sales').' |
';
$visitArray = array();
$sql = 'SELECT '.$dateFromGAdd.' as fix_date, COUNT(*) as visits
FROM '._DB_PREFIX_.'connections c
WHERE c.date_add BETWEEN '.ModuleGraph::getDateBetween().'
'.Shop::addSqlRestriction(false, 'c').'
GROUP BY '.$dateFromGAdd;
$visits = Db::getInstance()->query($sql);
while ($row = $db->nextRow($visits))
$visitArray[$row['fix_date']] = $row['visits'];
$today = date('Y-m-d');
foreach ($dataTable as $row)
{
$visitsToday = (int)(isset($visitArray[$row['fix_date']]) ? $visitArray[$row['fix_date']] : 0);
$dateFromGReg = ($this->context->cookie->stats_granularity != 42
? 'LIKE \''.$row['fix_date'].'%\''
: 'BETWEEN \''.substr($row['fix_date'], 0, 10).' 00:00:00\' AND DATE_ADD(\''.substr($row['fix_date'], 0, 8).substr($row['fix_date'], 8, 2).' 23:59:59\', INTERVAL 7 DAY)');
$row['registrations'] = Db::getInstance()->getValue('
SELECT COUNT(*) FROM '._DB_PREFIX_.'customer
WHERE date_add BETWEEN '.ModuleGraph::getDateBetween().'
AND date_add '.$dateFromGReg
.Shop::addSqlRestriction(Shop::SHARE_CUSTOMER));
$this->_html .= '
| '.$row['fix_date'].' |
'.$visitsToday.' |
'.(int)($row['registrations']).' |
'.(int)($row['countOrders']).' |
'.(int)($row['countProducts']).' |
'.($visitsToday ? round(100 * (int)($row['registrations']) / $visitsToday, 2).' %' : '-').' |
'.($visitsToday ? round(100 * (int)($row['countOrders']) / $visitsToday, 2).' %' : '-').' |
'.Tools::displayPrice($row['totalSales'], $currency).' |
';
$this->t1 += $visitsToday;
$this->t2 += (int)($row['registrations']);
$this->t3 += (int)($row['countOrders']);
$this->t4 += (int)($row['countProducts']);
$this->t8 += $row['totalSales'];
}
$this->_html .= '
|
'.$this->l('Visits').' |
'.$this->l('Reg.').' |
'.$this->l('Orders').' |
'.$this->l('Items').' |
'.$this->l('% Reg.').' |
'.$this->l('% Orders').' |
'.$this->l('Sales').' |
| '.$this->l('Total').' |
'.(int)($this->t1).' |
'.(int)($this->t2).' |
'.(int)($this->t3).' |
'.(int)($this->t4).' |
-- |
-- |
'.Tools::displayPrice($this->t8, $currency).' |
| '.$this->l('Average').' |
'.(int)($this->t1 / $intervalAvg).' |
'.(int)($this->t2 / $intervalAvg).' |
'.(int)($this->t3 / $intervalAvg).' |
'.(int)($this->t4 / $intervalAvg).' |
'.($this->t1 ? round(100 * $this->t2 / $this->t1, 2) .' %' : '-').' |
'.($this->t1 ? round(100 * $this->t3 / $this->t1, 2) .' %' : '-').' |
'.Tools::displayPrice($this->t8 / $intervalAvg, $currency).' |
| '.$this->l('Forecast').' |
'.(int)($this->t1 * $prop30).' |
'.(int)($this->t2 * $prop30).' |
'.(int)($this->t3 * $prop30).' |
'.(int)($this->t4 * $prop30).' |
-- |
-- |
'.Tools::displayPrice($this->t8 * $prop30, $currency).' |
';
$ca = $this->getRealCA();
$sql = 'SELECT COUNT(DISTINCT c.id_guest)
FROM '._DB_PREFIX_.'connections c
WHERE c.date_add BETWEEN '.ModuleGraph::getDateBetween()
.Shop::addSqlRestriction(false, 'c');
$visitors = Db::getInstance()->getValue($sql);
$sql = 'SELECT COUNT(DISTINCT g.id_customer)
FROM '._DB_PREFIX_.'connections c
INNER JOIN '._DB_PREFIX_.'guest g ON c.id_guest = g.id_guest
WHERE g.id_customer != 0
AND c.date_add BETWEEN '.ModuleGraph::getDateBetween()
.Shop::addSqlRestriction(false, 'c');
$customers = Db::getInstance()->getValue($sql);
$sql = 'SELECT COUNT(*)
FROM '._DB_PREFIX_.'cart
WHERE id_cart IN (
SELECT id_cart FROM '._DB_PREFIX_.'cart_product
) AND (
date_add BETWEEN '.ModuleGraph::getDateBetween().' OR date_upd BETWEEN '.ModuleGraph::getDateBetween().'
)'.Shop::addSqlRestriction();
$carts = Db::getInstance()->getValue($sql);
$sql = 'SELECT COUNT(*)
FROM '._DB_PREFIX_.'cart
WHERE id_cart IN (
SELECT id_cart FROM '._DB_PREFIX_.'cart_product
) AND id_address_invoice != 0
AND (
date_add BETWEEN '.ModuleGraph::getDateBetween().' OR date_upd BETWEEN '.ModuleGraph::getDateBetween().'
)'.Shop::addSqlRestriction();
$fullcarts = Db::getInstance()->getValue($sql);
$sql = 'SELECT COUNT(*)
FROM '._DB_PREFIX_.'orders o
WHERE o.valid = 1
AND o.date_add BETWEEN '.ModuleGraph::getDateBetween()
.Shop::addSqlRestriction(Shop::SHARE_ORDER, 'o');
$orders = Db::getInstance()->getValue($sql);
$this->_html .= '
'.$this->l('Conversion').'
'.$this->l('Visitors').'
'.$visitors.'

'.round(100 * $customers / max(1, $visitors)).' %

'.round(100 * $carts / max(1, $visitors)).' %
'.$this->l('Accounts').'
'.$customers.'
'.$this->l('Carts').'
'.$carts.'

'.round(100 * $fullcarts / max(1, $customers)).' %

'.round(100 * $fullcarts / max(1, $carts)).' %
'.$this->l('Full carts').'
'.$fullcarts.'

'.round(100 * $orders / max(1, $fullcarts)).' %
'.$this->l('Orders').'
'.$orders.'
'.$this->l('Registered visitors').'
'.round(100 * $orders / max(1, $customers), 2).' %
'.$this->l('Orders').'
'.$this->l('Visitors').'
'.round(100 * $orders / max(1, $visitors), 2).' %
'.$this->l('Orders').'
'.$this->l('Turn your visitors into money:').'
'.$this->l('Each visitor yields').' '.Tools::displayPrice($ca['ventil']['total'] / max(1, $visitors), $currency).'.
'.$this->l('Each registered visitor yields').' '.Tools::displayPrice($ca['ventil']['total'] / max(1, $customers), $currency).'.
';
$from = strtotime($employee->stats_date_from.' 00:00:00');
$to = strtotime($employee->stats_date_to.' 23:59:59');
$interval = ($to - $from) / 60 / 60 / 24;
$prop5000 = 5000 / 30 * $interval;
$this->_html .= '
';
$this->_html .= '
'.$this->l('Payment distibution').'
| '.$this->l('Module').' | '.$this->l('Count').' | '.$this->l('Total').' | '.$this->l('Cart').' |
';
foreach ($ca['payment'] as $payment)
$this->_html .= '
| '.$payment['module'].' |
'.(int)$payment['nb'].' '.($ca['ventil']['nb'] ? number_format((100 * $payment['nb'] / $ca['ventil']['nb']), 1, '.', ' ') : '0').' % |
'.Tools::displayPrice($payment['total'], $currency).' '.((int)$ca['ventil']['total'] ? number_format((100 * $payment['total'] / $ca['ventil']['total']), 1, '.', ' ') : '0').' % |
'.Tools::displayPrice($payment['cart'], $currency).' |
';
$this->_html .= '
'.$this->l('Category distribution').'
| '.$this->l('Category').' | '.$this->l('Count').' | '.$this->l('Sales').' | '.$this->l('% Count').' | '.$this->l('% Sales').' | '.$this->l('Avg price').' |
';
foreach ($ca['cat'] as $catrow)
$this->_html .= '
| '.(empty($catrow['name']) ? $this->l('Unknown') : $catrow['name']).' |
'.$catrow['orderQty'].' |
'.Tools::displayPrice($catrow['orderSum'], $currency).' |
'.number_format((100 * $catrow['orderQty'] / $this->t4), 1, '.', ' ').'% |
'.((int)$ca['ventil']['total'] ? number_format((100 * $catrow['orderSum'] / $ca['ventil']['total']), 1, '.', ' ') : '0').'% |
'.Tools::displayPrice($catrow['priveAvg'], $currency).' |
';
$this->_html .= '
'.$this->l('Language distribution').'
| '.$this->l('Customers').' | '.$this->l('Sales').' | '.$this->l('%').' | '.$this->l('Growth').' |
';
foreach ($ca['lang'] as $ophone => $amount)
{
$percent = (int)($ca['langprev'][$ophone]) ? number_format((100 * $amount / $ca['langprev'][$ophone]) - 100, 1, '.', ' ') : '∞';
$this->_html .= '
| '.$ophone.' |
'.Tools::displayPrice($amount, $currency).' |
'.((int)$ca['ventil']['total'] ? number_format((100 * $amount / $ca['ventil']['total']), 1, '.', ' ').'%' : '-').' |
'.(($percent > 0 OR $percent == '∞') ? ' ' : ' ').' |
'.(($percent > 0 OR $percent == '∞') ? '+' : '').$percent.'% |
';
}
$this->_html .= '
'.$this->l('Zone distribution').'
| '.$this->l('Zone').' | '.$this->l('Count').' | '.$this->l('Total').' | '.$this->l('% Count').' | '.$this->l('% Sales').' |
';
foreach ($ca['zones'] as $zone)
$this->_html .= '
| '.(isset($zone['name']) ? $zone['name'] : $this->l('Undefined')).' |
'.(int)($zone['nb']).' |
'.Tools::displayPrice($zone['total'], $currency).' |
'.($ca['ventil']['nb'] ? number_format((100 * $zone['nb'] / $ca['ventil']['nb']), 1, '.', ' ') : '0').'% |
'.((int)$ca['ventil']['total'] ? number_format((100 * $zone['total'] / $ca['ventil']['total']), 1, '.', ' ') : '0').'% |
';
$this->_html .= '
'.$this->l('Currency distribution').'
| '.$this->l('Currency').' | '.$this->l('Count').' | '.$this->l('Sales (converted)').' | '.$this->l('% Count').' | '.$this->l('% Sales').' |
';
foreach ($ca['currencies'] as $currencyRow)
$this->_html .= '
| '.$currencyRow['name'].' |
'.(int)($currencyRow['nb']).' |
'.Tools::displayPrice($currencyRow['total'], $currency).' |
'.($ca['ventil']['nb'] ? number_format((100 * $currencyRow['nb'] / $ca['ventil']['nb']), 1, '.', ' ') : '0').'% |
'.((int)$ca['ventil']['total'] ? number_format((100 * $currencyRow['total'] / $ca['ventil']['total']), 1, '.', ' ') : '0').'% |
';
$this->_html .= '
'.$this->l('Attribute distribution').'
| '.$this->l('Group').' | '.$this->l('Attribute').' | '.$this->l('Count').' |
';
foreach ($ca['attributes'] as $attribut)
$this->_html .= '
| '.$attribut['gname'].' |
'.$attribut['aname'].' |
'.(int)($attribut['total']).' |
';
$this->_html .= '
';
return $this->_html;
}
private function getRealCA()
{
$employee = $this->context->employee;
$ca = array();
$where = $join = '';
if ((int)$this->context->cookie->stats_id_zone)
{
$join = ' LEFT JOIN `'._DB_PREFIX_.'address` a ON o.id_address_invoice = a.id_address LEFT JOIN `'._DB_PREFIX_.'country` co ON co.id_country = a.id_country';
$where = ' AND co.id_zone = '.(int)$this->context->cookie->stats_id_zone.' ';
}
$sql = 'SELECT SUM(od.`product_price` * od.`product_quantity` / o.conversion_rate) as orderSum, COUNT(*) AS orderQty, cl.name, AVG(od.`product_price` / o.conversion_rate) as priveAvg
FROM `'._DB_PREFIX_.'orders` o
LEFT JOIN `'._DB_PREFIX_.'order_detail` od ON o.id_order = od.id_order
LEFT JOIN `'._DB_PREFIX_.'product` p ON p.id_product = od.product_id
'.Shop::addSqlAssociation('product', 'p').'
LEFT JOIN `'._DB_PREFIX_.'category_lang` cl ON (product_shop.id_category_default = cl.id_category AND cl.id_lang = '.(int)$this->context->language->id.Shop::addSqlRestrictionOnLang('cl').')
'.$join.'
WHERE o.valid = 1
AND o.`invoice_date` BETWEEN '.ModuleGraph::getDateBetween().'
'.$where.'
'.Shop::addSqlRestriction(Shop::SHARE_ORDER, 'o').'
GROUP BY product_shop.id_category_default';
$ca['cat'] = Db::getInstance()->executeS($sql);
uasort($ca['cat'], 'statsforecast_sort');
$langValues = '';
$sql = 'SELECT l.id_lang, l.iso_code
FROM `'._DB_PREFIX_.'lang` l
'.Shop::addSqlAssociation('lang', 'l').'
WHERE l.active = 1';
$languages = Db::getInstance()->executeS($sql);
foreach ($languages as $language)
$langValues .= 'SUM(IF(o.id_lang = '.(int)$language['id_lang'].', total_products / o.conversion_rate, 0)) as '.pSQL($language['iso_code']).',';
$langValues = rtrim($langValues, ',');
if ($langValues)
{
$sql = 'SELECT '.$langValues.'
FROM `'._DB_PREFIX_.'orders` o
WHERE o.valid = 1
AND o.`invoice_date` BETWEEN '.ModuleGraph::getDateBetween().'
'.Shop::addSqlRestriction(Shop::SHARE_ORDER, 'o');
$ca['lang'] = Db::getInstance()->getRow($sql);
arsort($ca['lang']);
$sql = 'SELECT '.$langValues.'
FROM `'._DB_PREFIX_.'orders` o
WHERE o.valid = 1
AND ADDDATE(o.`invoice_date`, interval 30 day) BETWEEN \''.$employee->stats_date_from.' 00:00:00\' AND \''.min(date('Y-m-d H:i:s'), $employee->stats_date_to.' 23:59:59').'\'
'.Shop::addSqlRestriction(Shop::SHARE_ORDER, 'o');
$ca['langprev'] = Db::getInstance()->getRow($sql);
}
else
{
$ca['lang'] = array();
$ca['langprev'] = array();
}
$sql = 'SELECT module, SUM(total_products / o.conversion_rate) as total, COUNT(*) as nb, AVG(total_products / o.conversion_rate) as cart
FROM `'._DB_PREFIX_.'orders` o
'.$join.'
WHERE o.valid = 1
AND o.`invoice_date` BETWEEN '.ModuleGraph::getDateBetween().'
'.$where.'
'.Shop::addSqlRestriction(Shop::SHARE_ORDER, 'o').'
GROUP BY o.module
ORDER BY total DESC';
$ca['payment'] = Db::getInstance()->executeS($sql);
$sql = 'SELECT z.name, SUM(o.total_products / o.conversion_rate) as total, COUNT(*) as nb
FROM `'._DB_PREFIX_.'orders` o
LEFT JOIN `'._DB_PREFIX_.'address` a ON o.id_address_invoice = a.id_address
LEFT JOIN `'._DB_PREFIX_.'country` c ON c.id_country = a.id_country
LEFT JOIN `'._DB_PREFIX_.'zone` z ON z.id_zone = c.id_zone
WHERE o.valid = 1
AND o.`invoice_date` BETWEEN '.ModuleGraph::getDateBetween().'
'.Shop::addSqlRestriction(Shop::SHARE_ORDER, 'o').'
GROUP BY c.id_zone
ORDER BY total DESC';
$ca['zones'] = Db::getInstance()->executeS($sql);
$sql = 'SELECT cu.name, SUM(o.total_products / o.conversion_rate) as total, COUNT(*) as nb
FROM `'._DB_PREFIX_.'orders` o
LEFT JOIN `'._DB_PREFIX_.'currency` cu ON o.id_currency = cu.id_currency
'.$join.'
WHERE o.valid = 1
AND o.`invoice_date` BETWEEN '.ModuleGraph::getDateBetween().'
'.$where.'
'.Shop::addSqlRestriction(Shop::SHARE_ORDER, 'o').'
GROUP BY o.id_currency
ORDER BY total DESC';
$ca['currencies'] = Db::getInstance()->executeS($sql);
$sql = 'SELECT SUM(total_products / o.conversion_rate) as total, COUNT(*) AS nb
FROM `'._DB_PREFIX_.'orders` o
WHERE o.valid = 1
AND o.`invoice_date` BETWEEN '.ModuleGraph::getDateBetween().'
'.Shop::addSqlRestriction(Shop::SHARE_ORDER, 'o');
$ca['ventil'] = Db::getInstance()->getRow($sql);
$sql = 'SELECT /*pac.id_attribute,*/ agl.name as gname, al.name as aname, COUNT(*) as total
FROM '._DB_PREFIX_.'orders o
LEFT JOIN '._DB_PREFIX_.'order_detail od ON o.id_order = od.id_order
INNER JOIN '._DB_PREFIX_.'product_attribute_combination pac ON od.product_attribute_id = pac.id_product_attribute
INNER JOIN '._DB_PREFIX_.'attribute a ON pac.id_attribute = a.id_attribute
INNER JOIN '._DB_PREFIX_.'attribute_group_lang agl ON (a.id_attribute_group = agl.id_attribute_group AND agl.id_lang = '.(int)$this->context->language->id.')
INNER JOIN '._DB_PREFIX_.'attribute_lang al ON (a.id_attribute = al.id_attribute AND al.id_lang = '.(int)$this->context->language->id.')
WHERE o.valid = 1
AND o.`invoice_date` BETWEEN '.ModuleGraph::getDateBetween().'
'.Shop::addSqlRestriction(Shop::SHARE_ORDER, 'o').'
GROUP BY pac.id_attribute';
$ca['attributes'] = Db::getInstance()->executeS($sql);
return $ca;
}
}
function statsforecast_sort($a, $b)
{
if ($a['orderSum'] == $b['orderSum'])
return 0;
return ($a['orderSum'] > $b['orderSum']) ? -1 : 1;
}