forked from rebillar/site-accueil-insa
306 lines
No EOL
12 KiB
PHP
306 lines
No EOL
12 KiB
PHP
<?php
|
|
/**
|
|
* Matomo - free/libre analytics platform
|
|
*
|
|
* @link https://matomo.org
|
|
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
|
*
|
|
*/
|
|
namespace Piwik\Plugins\Ecommerce;
|
|
|
|
use Piwik\Common;
|
|
use Piwik\DataAccess\LogAggregator;
|
|
use Piwik\Piwik;
|
|
use Piwik\Plugins\Ecommerce\Columns\ProductCategory;
|
|
use Piwik\Plugins\Live\VisitorDetailsAbstract;
|
|
use Piwik\Site;
|
|
use Piwik\Tracker\GoalManager;
|
|
use Piwik\View;
|
|
|
|
class VisitorDetails extends VisitorDetailsAbstract
|
|
{
|
|
const CATEGORY_COUNT = 5;
|
|
const DEFAULT_LIFETIME_STAT = array(
|
|
'lifeTimeRevenue' => 0,
|
|
'lifeTimeConversions' => 0,
|
|
'lifeTimeEcommerceItems' => 0);
|
|
|
|
public function extendVisitorDetails(&$visitor)
|
|
{
|
|
if(Site::isEcommerceEnabledFor($visitor['idSite']))
|
|
{
|
|
$ecommerceMetrics = $this->queryEcommerceConversionsVisitorLifeTimeMetricsForVisitor($visitor['idSite'],
|
|
$visitor['visitorId']);
|
|
$visitor['totalEcommerceRevenue'] = $ecommerceMetrics['totalEcommerceRevenue'];
|
|
$visitor['totalEcommerceConversions'] = $ecommerceMetrics['totalEcommerceConversions'];
|
|
$visitor['totalEcommerceItems'] = $ecommerceMetrics['totalEcommerceItems'];
|
|
|
|
$visitor['totalAbandonedCartsRevenue'] = $ecommerceMetrics['totalAbandonedCartsRevenue'];
|
|
$visitor['totalAbandonedCarts'] = $ecommerceMetrics['totalAbandonedCarts'];
|
|
$visitor['totalAbandonedCartsItems'] = $ecommerceMetrics['totalAbandonedCartsItems'];
|
|
}
|
|
}
|
|
|
|
public function extendActionDetails(&$action, $nextAction, $visitorDetails)
|
|
{
|
|
if (empty($action['productViewName'])) {
|
|
unset($action['productViewName']);
|
|
}
|
|
if (empty($action['productViewSku'])) {
|
|
unset($action['productViewSku']);
|
|
}
|
|
if (empty($action['productViewPrice'])) {
|
|
unset($action['productViewPrice']);
|
|
}
|
|
|
|
$categories = [];
|
|
for($i = 1; $i <= ProductCategory::PRODUCT_CATEGORY_COUNT; $i++) {
|
|
if (!empty($action['productViewCategory'.$i])) {
|
|
$categories[] = $action['productViewCategory'.$i];
|
|
}
|
|
|
|
unset($action['productViewCategory'.$i]);
|
|
}
|
|
if (!empty($categories)) {
|
|
$action['productViewCategories'] = $categories;
|
|
}
|
|
}
|
|
|
|
public function renderActionTooltip($action, $visitInfo)
|
|
{
|
|
if (!isset($action['productViewName']) && !isset($action['productViewSku']) &&
|
|
!isset($action['productViewPrice']) && !isset($action['productViewCategories'])) {
|
|
return [];
|
|
}
|
|
|
|
$view = new View('@Ecommerce/_actionTooltip');
|
|
$view->sendHeadersWhenRendering = false;
|
|
$view->action = $action;
|
|
$view->visitInfo = $visitInfo;
|
|
return [[ 15, $view->render() ]];
|
|
}
|
|
|
|
public function provideActionsForVisitIds(&$actions, $idVisits)
|
|
{
|
|
$ecommerceDetails = $this->queryEcommerceConversionsForVisits($idVisits);
|
|
|
|
// use while / array_shift combination instead of foreach to save memory
|
|
while (is_array($ecommerceDetails) && count($ecommerceDetails)) {
|
|
$ecommerceDetail = array_shift($ecommerceDetails);
|
|
|
|
$idVisit = $ecommerceDetail['idvisit'];
|
|
|
|
unset($ecommerceDetail['idvisit']);
|
|
|
|
if ($ecommerceDetail['type'] == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART) {
|
|
unset($ecommerceDetail['orderId']);
|
|
unset($ecommerceDetail['revenueSubTotal']);
|
|
unset($ecommerceDetail['revenueTax']);
|
|
unset($ecommerceDetail['revenueShipping']);
|
|
unset($ecommerceDetail['revenueDiscount']);
|
|
}
|
|
|
|
// 25.00 => 25
|
|
foreach ($ecommerceDetail as $column => $value) {
|
|
if (strpos($column, 'revenue') !== false) {
|
|
if (!is_numeric($value)) {
|
|
$ecommerceDetail[$column] = 0;
|
|
} else if ($value == round($value)) {
|
|
$ecommerceDetail[$column] = round($value);
|
|
}
|
|
}
|
|
}
|
|
|
|
$idOrder = isset($ecommerceDetail['orderId']) ? $ecommerceDetail['orderId'] : GoalManager::ITEM_IDORDER_ABANDONED_CART;
|
|
|
|
$itemsDetails = $this->queryEcommerceItemsForOrder($idVisit, $idOrder);
|
|
foreach ($itemsDetails as &$detail) {
|
|
if ($detail['price'] == round($detail['price'])) {
|
|
$detail['price'] = round($detail['price']);
|
|
}
|
|
}
|
|
$ecommerceDetail['itemDetails'] = $itemsDetails;
|
|
|
|
$actions[$idVisit][] = $ecommerceDetail;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param $idSite
|
|
* @param $idVisitor
|
|
* @return array
|
|
* @throws \Exception
|
|
*/
|
|
protected function queryEcommerceConversionsVisitorLifeTimeMetricsForVisitor($idSite, $idVisitor)
|
|
{
|
|
$sql = $this->getSqlEcommerceConversionsLifeTimeMetricsForIdGoal();
|
|
$lifeTimeStats = $this->getDb()->fetchAll($sql, array($idSite, @Common::hex2bin($idVisitor)));
|
|
|
|
$defaultStats = array_fill_keys([GoalManager::IDGOAL_CART, GoalManager::IDGOAL_ORDER], self::DEFAULT_LIFETIME_STAT);
|
|
|
|
$lifeTimeStatsByGoal = array_reduce($lifeTimeStats, function ($carry, $statRow) {
|
|
$idgoal = $statRow['idgoal'];
|
|
$carry[$idgoal] = array_merge($carry[$idgoal], $statRow);
|
|
return $carry;
|
|
},$defaultStats);
|
|
|
|
$ecommerceOrders = $lifeTimeStatsByGoal[GoalManager::IDGOAL_ORDER];
|
|
$abandonedCarts = $lifeTimeStatsByGoal[GoalManager::IDGOAL_CART];
|
|
|
|
return array(
|
|
'totalEcommerceRevenue' => $ecommerceOrders['lifeTimeRevenue'],
|
|
'totalEcommerceConversions' => $ecommerceOrders['lifeTimeConversions'],
|
|
'totalEcommerceItems' => $ecommerceOrders['lifeTimeEcommerceItems'],
|
|
'totalAbandonedCartsRevenue' => $abandonedCarts['lifeTimeRevenue'],
|
|
'totalAbandonedCarts' => $abandonedCarts['lifeTimeConversions'],
|
|
'totalAbandonedCartsItems' => $abandonedCarts['lifeTimeEcommerceItems']
|
|
);
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns and SQL string that queries for `lifeTimeRevenue`, `lifeTimeConversions`, and `lifeTimeEcommerceItems` grouped by
|
|
* `idgoal` for abandoned carts and orders.
|
|
* @return string
|
|
*/
|
|
protected function getSqlEcommerceConversionsLifeTimeMetricsForIdGoal()
|
|
{
|
|
$sql = "SELECT
|
|
idgoal,
|
|
COALESCE(SUM(" . LogAggregator::getSqlRevenue('revenue') . "), 0) as lifeTimeRevenue,
|
|
COUNT(*) as lifeTimeConversions,
|
|
COALESCE(SUM(" . LogAggregator::getSqlRevenue('items') . "), 0) as lifeTimeEcommerceItems
|
|
FROM " . Common::prefixTable('log_visit') . " AS log_visit
|
|
STRAIGHT_JOIN " . Common::prefixTable('log_conversion') . " AS log_conversion
|
|
ON log_visit.idvisit = log_conversion.idvisit
|
|
WHERE
|
|
log_visit.idsite = ?
|
|
AND log_visit.idvisitor = ?
|
|
AND log_conversion.idgoal IN ( " . GoalManager::IDGOAL_CART . ", " . GoalManager::IDGOAL_ORDER . " )
|
|
GROUP BY idgoal
|
|
";
|
|
return $sql;
|
|
}
|
|
|
|
/**
|
|
* @param $idVisit
|
|
* @param $limit
|
|
* @return array
|
|
* @throws \Exception
|
|
*/
|
|
protected function queryEcommerceConversionsForVisits($idVisits)
|
|
{
|
|
$sql = "SELECT
|
|
log_conversion.idvisit,
|
|
case idgoal when " . GoalManager::IDGOAL_CART
|
|
. " then '" . Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART
|
|
. "' else '" . Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER . "' end as type,
|
|
idorder as orderId,
|
|
" . LogAggregator::getSqlRevenue('revenue') . " as revenue,
|
|
" . LogAggregator::getSqlRevenue('revenue_subtotal') . " as revenueSubTotal,
|
|
" . LogAggregator::getSqlRevenue('revenue_tax') . " as revenueTax,
|
|
" . LogAggregator::getSqlRevenue('revenue_shipping') . " as revenueShipping,
|
|
" . LogAggregator::getSqlRevenue('revenue_discount') . " as revenueDiscount,
|
|
items as items,
|
|
log_conversion.server_time as serverTimePretty,
|
|
log_conversion.idlink_va,
|
|
log_link_visit_action.idpageview
|
|
FROM " . Common::prefixTable('log_conversion') . " AS log_conversion
|
|
LEFT JOIN " . Common::prefixTable('log_link_visit_action') . " AS log_link_visit_action
|
|
ON log_link_visit_action.idlink_va = log_conversion.idlink_va
|
|
WHERE log_conversion.idvisit IN ('" . implode("','", $idVisits) . "')
|
|
AND idgoal <= " . GoalManager::IDGOAL_ORDER . "
|
|
ORDER BY log_conversion.idvisit, log_conversion.server_time ASC";
|
|
$ecommerceDetails = $this->getDb()->fetchAll($sql);
|
|
return $ecommerceDetails;
|
|
}
|
|
|
|
/**
|
|
* @param $idVisit
|
|
* @param $idOrder
|
|
* @param $actionsLimit
|
|
* @return array
|
|
* @throws \Exception
|
|
*/
|
|
protected function queryEcommerceItemsForOrder($idVisit, $idOrder)
|
|
{
|
|
$categorySelects = [];
|
|
$categoryJoins = [];
|
|
for ($i = 0; $i < self::CATEGORY_COUNT; ++$i) {
|
|
$suffix = $i === 0 ? '' : $i;
|
|
$column = $i === 0 ? 'idaction_category' : ('idaction_category' . ($i + 1));
|
|
$categorySelects[] = 'log_action_category' . $suffix . '.name as itemCategory' . $suffix;
|
|
$categoryJoins[] = 'LEFT JOIN ' . Common::prefixTable('log_action') . " AS log_action_category$suffix
|
|
ON $column = log_action_category$suffix.idaction";
|
|
}
|
|
$categorySelects = implode(',', $categorySelects);
|
|
$categoryJoins = implode("\n", $categoryJoins);
|
|
|
|
$sql = "SELECT
|
|
log_action_sku.name as itemSKU,
|
|
log_action_name.name as itemName,
|
|
$categorySelects,
|
|
" . LogAggregator::getSqlRevenue('price') . " as price,
|
|
quantity as quantity
|
|
FROM " . Common::prefixTable('log_conversion_item') . "
|
|
INNER JOIN " . Common::prefixTable('log_action') . " AS log_action_sku
|
|
ON idaction_sku = log_action_sku.idaction
|
|
LEFT JOIN " . Common::prefixTable('log_action') . " AS log_action_name
|
|
ON idaction_name = log_action_name.idaction
|
|
$categoryJoins
|
|
WHERE idvisit = ?
|
|
AND idorder = ?
|
|
AND deleted = 0
|
|
";
|
|
|
|
$bind = array($idVisit, $idOrder);
|
|
|
|
$itemsDetails = $this->getDb()->fetchAll($sql, $bind);
|
|
|
|
// create categories array for each item
|
|
foreach ($itemsDetails as &$item) {
|
|
$categories = [];
|
|
for ($i = 0; $i < self::CATEGORY_COUNT; ++$i) {
|
|
$suffix = $i === 0 ? '' : $i;
|
|
if (empty($item['itemCategory' . $suffix])) {
|
|
continue;
|
|
}
|
|
|
|
$categories[] = trim($item['itemCategory' . $suffix]);
|
|
}
|
|
$item['categories'] = array_filter($categories);
|
|
|
|
// remove itemCategotyN properties, except 'itemCategory' property for BC
|
|
for ($i = 1; $i < self::CATEGORY_COUNT; ++$i) {
|
|
unset($item['itemCategory' . $i]);
|
|
}
|
|
}
|
|
|
|
return $itemsDetails;
|
|
}
|
|
|
|
public function initProfile($visits, &$profile)
|
|
{
|
|
if (Site::isEcommerceEnabledFor($visits->getFirstRow()->getColumn('idSite'))) {
|
|
$profile['totalEcommerceRevenue'] = 0;
|
|
$profile['totalEcommerceConversions'] = 0;
|
|
$profile['totalEcommerceItems'] = 0;
|
|
$profile['totalAbandonedCarts'] = 0;
|
|
$profile['totalAbandonedCartsRevenue'] = 0;
|
|
$profile['totalAbandonedCartsItems'] = 0;
|
|
}
|
|
}
|
|
|
|
public function finalizeProfile($visits, &$profile)
|
|
{
|
|
$lastVisit = $visits->getLastRow();
|
|
if ($lastVisit && Site::isEcommerceEnabledFor($lastVisit->getColumn('idSite'))) {
|
|
$profile['totalEcommerceRevenue'] = $lastVisit->getColumn('totalEcommerceRevenue');
|
|
$profile['totalEcommerceConversions'] = $lastVisit->getColumn('totalEcommerceConversions');
|
|
$profile['totalEcommerceItems'] = $lastVisit->getColumn('totalEcommerceItems');
|
|
$profile['totalAbandonedCartsRevenue'] = $lastVisit->getColumn('totalAbandonedCartsRevenue');
|
|
$profile['totalAbandonedCarts'] = $lastVisit->getColumn('totalAbandonedCarts');
|
|
$profile['totalAbandonedCartsItems'] = $lastVisit->getColumn('totalAbandonedCartsItems');
|
|
}
|
|
}
|
|
} |