fixed stretching for narrow crossword quests

This commit is contained in:
Daniel 2014-04-26 15:42:29 +02:00
commit c1a314f6e7
3452 changed files with 593206 additions and 0 deletions

View file

@ -0,0 +1,567 @@
<?php
/**
* Piwik - Open source web analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\Goals;
use Exception;
use Piwik\Archive;
use Piwik\Common;
use Piwik\DataTable;
use Piwik\Db;
use Piwik\Metrics;
use Piwik\Piwik;
use Piwik\Site;
use Piwik\Tracker\Cache;
use Piwik\Tracker\GoalManager;
/**
* Goals API lets you Manage existing goals, via "updateGoal" and "deleteGoal", create new Goals via "addGoal",
* or list existing Goals for one or several websites via "getGoals"
*
* If you are <a href='http://piwik.org/docs/ecommerce-analytics/' target='_blank'>tracking Ecommerce orders and products</a> on your site, the functions "getItemsSku", "getItemsName" and "getItemsCategory"
* will return the list of products purchased on your site, either grouped by Product SKU, Product Name or Product Category. For each name, SKU or category, the following
* metrics are returned: Total revenue, Total quantity, average price, average quantity, number of orders (or abandoned carts) containing this product, number of visits on the Product page,
* Conversion rate.
*
* By default, these functions return the 'Products purchased'. These functions also accept an optional parameter &abandonedCarts=1.
* If the parameter is set, it will instead return the metrics for products that were left in an abandoned cart therefore not purchased.
*
* The API also lets you request overall Goal metrics via the method "get": Conversions, Visits with at least one conversion, Conversion rate and Revenue.
* If you wish to request specific metrics about Ecommerce goals, you can set the parameter &idGoal=ecommerceAbandonedCart to get metrics about abandoned carts (including Lost revenue, and number of items left in the cart)
* or &idGoal=ecommerceOrder to get metrics about Ecommerce orders (number of orders, visits with an order, subtotal, tax, shipping, discount, revenue, items ordered)
*
* See also the documentation about <a href='http://piwik.org/docs/tracking-goals-web-analytics/' target='_blank'>Tracking Goals</a> in Piwik.
*
* @method static \Piwik\Plugins\Goals\API getInstance()
*/
class API extends \Piwik\Plugin\API
{
const AVG_PRICE_VIEWED = 'avg_price_viewed';
/**
* Returns all Goals for a given website, or list of websites
*
* @param string|array $idSite Array or Comma separated list of website IDs to request the goals for
* @return array Array of Goal attributes
*/
public function getGoals($idSite)
{
//TODO calls to this function could be cached as static
// would help UI at least, since some UI requests would call this 2-3 times..
$idSite = Site::getIdSitesFromIdSitesString($idSite);
if (empty($idSite)) {
return array();
}
Piwik::checkUserHasViewAccess($idSite);
$goals = Db::fetchAll("SELECT *
FROM " . Common::prefixTable('goal') . "
WHERE idsite IN (" . implode(", ", $idSite) . ")
AND deleted = 0");
$cleanedGoals = array();
foreach ($goals as &$goal) {
if ($goal['match_attribute'] == 'manually') {
unset($goal['pattern']);
unset($goal['pattern_type']);
unset($goal['case_sensitive']);
}
$cleanedGoals[$goal['idgoal']] = $goal;
}
return $cleanedGoals;
}
/**
* Creates a Goal for a given website.
*
* @param int $idSite
* @param string $name
* @param string $matchAttribute 'url', 'title', 'file', 'external_website' or 'manually'
* @param string $pattern eg. purchase-confirmation.htm
* @param string $patternType 'regex', 'contains', 'exact'
* @param bool $caseSensitive
* @param bool|float $revenue If set, default revenue to assign to conversions
* @param bool $allowMultipleConversionsPerVisit By default, multiple conversions in the same visit will only record the first conversion.
* If set to true, multiple conversions will all be recorded within a visit (useful for Ecommerce goals)
* @return int ID of the new goal
*/
public function addGoal($idSite, $name, $matchAttribute, $pattern, $patternType, $caseSensitive = false, $revenue = false, $allowMultipleConversionsPerVisit = false)
{
Piwik::checkUserHasAdminAccess($idSite);
$this->checkPatternIsValid($patternType, $pattern);
$name = $this->checkName($name);
$pattern = $this->checkPattern($pattern);
// save in db
$db = Db::get();
$idGoal = $db->fetchOne("SELECT max(idgoal) + 1
FROM " . Common::prefixTable('goal') . "
WHERE idsite = ?", $idSite);
if ($idGoal == false) {
$idGoal = 1;
}
$db->insert(Common::prefixTable('goal'),
array(
'idsite' => $idSite,
'idgoal' => $idGoal,
'name' => $name,
'match_attribute' => $matchAttribute,
'pattern' => $pattern,
'pattern_type' => $patternType,
'case_sensitive' => (int)$caseSensitive,
'allow_multiple' => (int)$allowMultipleConversionsPerVisit,
'revenue' => (float)$revenue,
'deleted' => 0,
));
Cache::regenerateCacheWebsiteAttributes($idSite);
return $idGoal;
}
/**
* Updates a Goal description.
* Will not update or re-process the conversions already recorded
*
* @see addGoal() for parameters description
* @param int $idSite
* @param int $idGoal
* @param $name
* @param $matchAttribute
* @param string $pattern
* @param string $patternType
* @param bool $caseSensitive
* @param bool|float $revenue
* @param bool $allowMultipleConversionsPerVisit
* @return void
*/
public function updateGoal($idSite, $idGoal, $name, $matchAttribute, $pattern, $patternType, $caseSensitive = false, $revenue = false, $allowMultipleConversionsPerVisit = false)
{
Piwik::checkUserHasAdminAccess($idSite);
$name = $this->checkName($name);
$pattern = $this->checkPattern($pattern);
$this->checkPatternIsValid($patternType, $pattern);
Db::get()->update(Common::prefixTable('goal'),
array(
'name' => $name,
'match_attribute' => $matchAttribute,
'pattern' => $pattern,
'pattern_type' => $patternType,
'case_sensitive' => (int)$caseSensitive,
'allow_multiple' => (int)$allowMultipleConversionsPerVisit,
'revenue' => (float)$revenue,
),
"idsite = '$idSite' AND idgoal = '$idGoal'"
);
Cache::regenerateCacheWebsiteAttributes($idSite);
}
private function checkPatternIsValid($patternType, $pattern)
{
if ($patternType == 'exact'
&& substr($pattern, 0, 4) != 'http'
) {
throw new Exception(Piwik::translate('Goals_ExceptionInvalidMatchingString', array("http:// or https://", "http://www.yourwebsite.com/newsletter/subscribed.html")));
}
}
private function checkName($name)
{
return urldecode($name);
}
private function checkPattern($pattern)
{
return urldecode($pattern);
}
/**
* Soft deletes a given Goal.
* Stats data in the archives will still be recorded, but not displayed.
*
* @param int $idSite
* @param int $idGoal
* @return void
*/
public function deleteGoal($idSite, $idGoal)
{
Piwik::checkUserHasAdminAccess($idSite);
Db::query("UPDATE " . Common::prefixTable('goal') . "
SET deleted = 1
WHERE idsite = ?
AND idgoal = ?",
array($idSite, $idGoal));
Db::deleteAllRows(Common::prefixTable("log_conversion"), "WHERE idgoal = ? AND idsite = ?", "idvisit", 100000, array($idGoal, $idSite));
Cache::regenerateCacheWebsiteAttributes($idSite);
}
/**
* Returns a datatable of Items SKU/name or categories and their metrics
* If $abandonedCarts set to 1, will return items abandoned in carts. If set to 0, will return items ordered
*/
protected function getItems($recordName, $idSite, $period, $date, $abandonedCarts, $segment)
{
Piwik::checkUserHasViewAccess($idSite);
$recordNameFinal = $recordName;
if ($abandonedCarts) {
$recordNameFinal = Archiver::getItemRecordNameAbandonedCart($recordName);
}
$archive = Archive::build($idSite, $period, $date, $segment);
$dataTable = $archive->getDataTable($recordNameFinal);
$dataTable->filter('Sort', array(Metrics::INDEX_ECOMMERCE_ITEM_REVENUE));
$this->enrichItemsTableWithViewMetrics($dataTable, $recordName, $idSite, $period, $date, $segment);
// First rename the avg_price_viewed column
$renameColumn = array(self::AVG_PRICE_VIEWED => 'avg_price');
$dataTable->queueFilter('ReplaceColumnNames', array($renameColumn));
$dataTable->queueFilter('ReplaceColumnNames');
$dataTable->queueFilter('ReplaceSummaryRowLabel');
$ordersColumn = 'orders';
if ($abandonedCarts) {
$ordersColumn = 'abandoned_carts';
$dataTable->renameColumn(Metrics::INDEX_ECOMMERCE_ORDERS, $ordersColumn);
}
// Average price = sum product revenue / quantity
$dataTable->queueFilter('ColumnCallbackAddColumnQuotient', array('avg_price', 'price', $ordersColumn, GoalManager::REVENUE_PRECISION));
// Average quantity = sum product quantity / abandoned carts
$dataTable->queueFilter('ColumnCallbackAddColumnQuotient',
array('avg_quantity', 'quantity', $ordersColumn, $precision = 1));
$dataTable->queueFilter('ColumnDelete', array('price'));
// Product conversion rate = orders / visits
$dataTable->queueFilter('ColumnCallbackAddColumnPercentage', array('conversion_rate', $ordersColumn, 'nb_visits', GoalManager::REVENUE_PRECISION));
return $dataTable;
}
protected function renameNotDefinedRow($dataTable, $notDefinedStringPretty)
{
if ($dataTable instanceof DataTable\Map) {
foreach ($dataTable->getDataTables() as $table) {
$this->renameNotDefinedRow($table, $notDefinedStringPretty);
}
return;
}
$rowNotDefined = $dataTable->getRowFromLabel(\Piwik\Plugins\CustomVariables\Archiver::LABEL_CUSTOM_VALUE_NOT_DEFINED);
if ($rowNotDefined) {
$rowNotDefined->setColumn('label', $notDefinedStringPretty);
}
}
protected function enrichItemsDataTableWithItemsViewMetrics($dataTable, $idSite, $period, $date, $segment, $idSubtable)
{
$ecommerceViews = \Piwik\Plugins\CustomVariables\API::getInstance()->getCustomVariablesValuesFromNameId($idSite, $period, $date, $idSubtable, $segment, $_leavePriceViewedColumn = true);
// For Product names and SKU reports, and for Category report
// Use the Price (tracked on page views)
// ONLY when the price sold in conversions is not found (ie. product viewed but not sold)
foreach ($ecommerceViews->getRows() as $rowView) {
// If there is not already a 'sum price' for this product
$rowFound = $dataTable->getRowFromLabel($rowView->getColumn('label'));
$price = $rowFound
? $rowFound->getColumn(Metrics::INDEX_ECOMMERCE_ITEM_PRICE)
: false;
if (empty($price)) {
// If a price was tracked on the product page
if ($rowView->getColumn(Metrics::INDEX_ECOMMERCE_ITEM_PRICE_VIEWED)) {
$rowView->renameColumn(Metrics::INDEX_ECOMMERCE_ITEM_PRICE_VIEWED, self::AVG_PRICE_VIEWED);
}
}
$rowView->deleteColumn(Metrics::INDEX_ECOMMERCE_ITEM_PRICE_VIEWED);
}
$dataTable->addDataTable($ecommerceViews);
}
public function getItemsSku($idSite, $period, $date, $abandonedCarts = false, $segment = false)
{
return $this->getItems('Goals_ItemsSku', $idSite, $period, $date, $abandonedCarts, $segment);
}
public function getItemsName($idSite, $period, $date, $abandonedCarts = false, $segment = false)
{
return $this->getItems('Goals_ItemsName', $idSite, $period, $date, $abandonedCarts, $segment);
}
public function getItemsCategory($idSite, $period, $date, $abandonedCarts = false, $segment = false)
{
return $this->getItems('Goals_ItemsCategory', $idSite, $period, $date, $abandonedCarts, $segment);
}
/**
* Helper function that checks for special string goal IDs and converts them to
* their integer equivalents.
*
* Checks for the following values:
* Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER
* Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART
*
* @param string|int $idGoal The goal id as an integer or a special string.
* @return int The numeric goal id.
*/
protected static function convertSpecialGoalIds($idGoal)
{
if ($idGoal == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER) {
return GoalManager::IDGOAL_ORDER;
} else if ($idGoal == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART) {
return GoalManager::IDGOAL_CART;
} else {
return $idGoal;
}
}
/**
* Returns Goals data
*
* @param int $idSite
* @param string $period
* @param string $date
* @param bool $segment
* @param bool|int $idGoal
* @param array $columns Array of metrics to fetch: nb_conversions, conversion_rate, revenue
* @return DataTable
*/
public function get($idSite, $period, $date, $segment = false, $idGoal = false, $columns = array())
{
Piwik::checkUserHasViewAccess($idSite);
$archive = Archive::build($idSite, $period, $date, $segment);
$columns = Piwik::getArrayFromApiParameter($columns);
// Mapping string idGoal to internal ID
$idGoal = self::convertSpecialGoalIds($idGoal);
if (empty($columns)) {
$columns = Goals::getGoalColumns($idGoal);
if ($idGoal == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER) {
$columns[] = 'avg_order_revenue';
}
}
if (in_array('avg_order_revenue', $columns)
&& $idGoal == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER
) {
$columns[] = 'nb_conversions';
$columns[] = 'revenue';
$columns = array_values(array_unique($columns));
}
$columnsToSelect = array();
foreach ($columns as &$columnName) {
$columnsToSelect[] = Archiver::getRecordName($columnName, $idGoal);
}
$dataTable = $archive->getDataTableFromNumeric($columnsToSelect);
// Rewrite column names as we expect them
foreach ($columnsToSelect as $id => $oldName) {
$dataTable->renameColumn($oldName, $columns[$id]);
}
if ($idGoal == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER) {
if ($dataTable instanceof DataTable\Map) {
foreach ($dataTable->getDataTables() as $row) {
$this->enrichTable($row);
}
} else {
$this->enrichTable($dataTable);
}
}
return $dataTable;
}
protected function enrichTable($table)
{
$row = $table->getFirstRow();
if (!$row) {
return;
}
// AVG order per visit
if (false !== $table->getColumn('avg_order_revenue')) {
$conversions = $row->getColumn('nb_conversions');
if ($conversions) {
$row->setColumn('avg_order_revenue', round($row->getColumn('revenue') / $conversions, 2));
}
}
}
protected function getNumeric($idSite, $period, $date, $segment, $toFetch)
{
Piwik::checkUserHasViewAccess($idSite);
$archive = Archive::build($idSite, $period, $date, $segment);
$dataTable = $archive->getDataTableFromNumeric($toFetch);
return $dataTable;
}
/**
* @ignore
*/
public function getConversions($idSite, $period, $date, $segment = false, $idGoal = false)
{
return $this->getNumeric($idSite, $period, $date, $segment, Archiver::getRecordName('nb_conversions', $idGoal));
}
/**
* @ignore
*/
public function getNbVisitsConverted($idSite, $period, $date, $segment = false, $idGoal = false)
{
return $this->getNumeric($idSite, $period, $date, $segment, Archiver::getRecordName('nb_visits_converted', $idGoal));
}
/**
* @ignore
*/
public function getConversionRate($idSite, $period, $date, $segment = false, $idGoal = false)
{
return $this->getNumeric($idSite, $period, $date, $segment, Archiver::getRecordName('conversion_rate', $idGoal));
}
/**
* @ignore
*/
public function getRevenue($idSite, $period, $date, $segment = false, $idGoal = false)
{
return $this->getNumeric($idSite, $period, $date, $segment, Archiver::getRecordName('revenue', $idGoal));
}
/**
* Utility method that retrieve an archived DataTable for a specific site, date range,
* segment and goal. If not goal is specified, this method will retrieve and sum the
* data for every goal.
*
* @param string $recordName The archive entry name.
* @param int|string $idSite The site(s) to select data for.
* @param string $period The period type.
* @param string $date The date type.
* @param string $segment The segment.
* @param int|bool $idGoal The id of the goal to get data for. If this is set to false,
* data for every goal that belongs to $idSite is returned.
* @return false|DataTable
*/
protected function getGoalSpecificDataTable($recordName, $idSite, $period, $date, $segment, $idGoal)
{
Piwik::checkUserHasViewAccess($idSite);
$archive = Archive::build($idSite, $period, $date, $segment);
// check for the special goal ids
$realGoalId = $idGoal != true ? false : self::convertSpecialGoalIds($idGoal);
// get the data table
$dataTable = $archive->getDataTable(Archiver::getRecordName($recordName, $realGoalId), $idSubtable = null);
$dataTable->queueFilter('ReplaceColumnNames');
return $dataTable;
}
/**
* Gets a DataTable that maps ranges of days to the number of conversions that occurred
* within those ranges, for the specified site, date range, segment and goal.
*
* @param int $idSite The site to select data from.
* @param string $period The period type.
* @param string $date The date type.
* @param string|bool $segment The segment.
* @param int|bool $idGoal The id of the goal to get data for. If this is set to false,
* data for every goal that belongs to $idSite is returned.
* @return false|DataTable
*/
public function getDaysToConversion($idSite, $period, $date, $segment = false, $idGoal = false)
{
$dataTable = $this->getGoalSpecificDataTable(
Archiver::DAYS_UNTIL_CONV_RECORD_NAME, $idSite, $period, $date, $segment, $idGoal);
$dataTable->queueFilter('Sort', array('label', 'asc', true));
$dataTable->queueFilter(
'BeautifyRangeLabels', array(Piwik::translate('General_OneDay'), Piwik::translate('General_NDays')));
return $dataTable;
}
/**
* Gets a DataTable that maps ranges of visit counts to the number of conversions that
* occurred on those visits for the specified site, date range, segment and goal.
*
* @param int $idSite The site to select data from.
* @param string $period The period type.
* @param string $date The date type.
* @param string|bool $segment The segment.
* @param int|bool $idGoal The id of the goal to get data for. If this is set to false,
* data for every goal that belongs to $idSite is returned.
* @return bool|DataTable
*/
public function getVisitsUntilConversion($idSite, $period, $date, $segment = false, $idGoal = false)
{
$dataTable = $this->getGoalSpecificDataTable(
Archiver::VISITS_UNTIL_RECORD_NAME, $idSite, $period, $date, $segment, $idGoal);
$dataTable->queueFilter('Sort', array('label', 'asc', true));
$dataTable->queueFilter(
'BeautifyRangeLabels', array(Piwik::translate('General_OneVisit'), Piwik::translate('General_NVisits')));
return $dataTable;
}
/**
* Enhances the dataTable with Items attributes found in the Custom Variables report.
*
* @param $dataTable
* @param $recordName
* @param $idSite
* @param $period
* @param $date
* @param $segment
*/
protected function enrichItemsTableWithViewMetrics($dataTable, $recordName, $idSite, $period, $date, $segment)
{
// Enrich the datatable with Product/Categories views, and conversion rates
$customVariables = \Piwik\Plugins\CustomVariables\API::getInstance()->getCustomVariables($idSite, $period, $date, $segment, $expanded = false,
$_leavePiwikCoreVariables = true);
$mapping = array(
'Goals_ItemsSku' => '_pks',
'Goals_ItemsName' => '_pkn',
'Goals_ItemsCategory' => '_pkc',
);
$reportToNotDefinedString = array(
'Goals_ItemsSku' => Piwik::translate('General_NotDefined', Piwik::translate('Goals_ProductSKU')), // Note: this should never happen
'Goals_ItemsName' => Piwik::translate('General_NotDefined', Piwik::translate('Goals_ProductName')),
'Goals_ItemsCategory' => Piwik::translate('General_NotDefined', Piwik::translate('Goals_ProductCategory'))
);
$notDefinedStringPretty = $reportToNotDefinedString[$recordName];
$customVarNameToLookFor = $mapping[$recordName];
// Handle case where date=last30&period=day
if ($customVariables instanceof DataTable\Map) {
$customVariableDatatables = $customVariables->getDataTables();
$dataTables = $dataTable->getDataTables();
foreach ($customVariableDatatables as $key => $customVariableTableForDate) {
$dataTableForDate = isset($dataTables[$key]) ? $dataTables[$key] : new DataTable();
// we do not enter the IF
// if case idSite=1,3 AND period=day&date=datefrom,dateto,
if ($customVariableTableForDate instanceof DataTable
&& $customVariableTableForDate->getMetadata(Archive\DataTableFactory::TABLE_METADATA_PERIOD_INDEX)
) {
$dateRewrite = $customVariableTableForDate->getMetadata(Archive\DataTableFactory::TABLE_METADATA_PERIOD_INDEX)->getDateStart()->toString();
$row = $customVariableTableForDate->getRowFromLabel($customVarNameToLookFor);
if ($row) {
$idSubtable = $row->getIdSubDataTable();
$this->enrichItemsDataTableWithItemsViewMetrics($dataTableForDate, $idSite, $period, $dateRewrite, $segment, $idSubtable);
}
$dataTable->addTable($dataTableForDate, $key);
}
$this->renameNotDefinedRow($dataTableForDate, $notDefinedStringPretty);
}
} elseif ($customVariables instanceof DataTable) {
$row = $customVariables->getRowFromLabel($customVarNameToLookFor);
if ($row) {
$idSubtable = $row->getIdSubDataTable();
$this->enrichItemsDataTableWithItemsViewMetrics($dataTable, $idSite, $period, $date, $segment, $idSubtable);
}
$this->renameNotDefinedRow($dataTable, $notDefinedStringPretty);
}
}
}

View file

@ -0,0 +1,418 @@
<?php
/**
* Piwik - Open source web analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\Goals;
use Piwik\DataAccess\LogAggregator;
use Piwik\DataArray;
use Piwik\DataTable;
use Piwik\Metrics;
use Piwik\PluginsArchiver;
use Piwik\PluginsManager;
use Piwik\Tracker\GoalManager;
class Archiver extends \Piwik\Plugin\Archiver
{
const VISITS_UNTIL_RECORD_NAME = 'visits_until_conv';
const DAYS_UNTIL_CONV_RECORD_NAME = 'days_until_conv';
const ITEMS_SKU_RECORD_NAME = 'Goals_ItemsSku';
const ITEMS_NAME_RECORD_NAME = 'Goals_ItemsName';
const ITEMS_CATEGORY_RECORD_NAME = 'Goals_ItemsCategory';
const SKU_FIELD = 'idaction_sku';
const NAME_FIELD = 'idaction_name';
const CATEGORY_FIELD = 'idaction_category';
const CATEGORY2_FIELD = 'idaction_category2';
const CATEGORY3_FIELD = 'idaction_category3';
const CATEGORY4_FIELD = 'idaction_category4';
const CATEGORY5_FIELD = 'idaction_category5';
const NO_LABEL = ':';
const LOG_CONVERSION_TABLE = 'log_conversion';
const VISITS_COUNT_FIELD = 'visitor_count_visits';
const DAYS_SINCE_FIRST_VISIT_FIELD = 'visitor_days_since_first';
/**
* This array stores the ranges to use when displaying the 'visits to conversion' report
*/
public static $visitCountRanges = array(
array(1, 1),
array(2, 2),
array(3, 3),
array(4, 4),
array(5, 5),
array(6, 6),
array(7, 7),
array(8, 8),
array(9, 14),
array(15, 25),
array(26, 50),
array(51, 100),
array(100)
);
/**
* This array stores the ranges to use when displaying the 'days to conversion' report
*/
public static $daysToConvRanges = array(
array(0, 0),
array(1, 1),
array(2, 2),
array(3, 3),
array(4, 4),
array(5, 5),
array(6, 6),
array(7, 7),
array(8, 14),
array(15, 30),
array(31, 60),
array(61, 120),
array(121, 364),
array(364)
);
protected $dimensionRecord = array(
self::SKU_FIELD => self::ITEMS_SKU_RECORD_NAME,
self::NAME_FIELD => self::ITEMS_NAME_RECORD_NAME,
self::CATEGORY_FIELD => self::ITEMS_CATEGORY_RECORD_NAME
);
/**
* Array containing one DataArray for each Ecommerce items dimension (name/sku/category abandoned carts and orders)
* @var array
*/
protected $itemReports = array();
public function aggregateDayReport()
{
$this->aggregateGeneralGoalMetrics();
$this->aggregateEcommerceItems();
}
protected function aggregateGeneralGoalMetrics()
{
$prefixes = array(
self::VISITS_UNTIL_RECORD_NAME => 'vcv',
self::DAYS_UNTIL_CONV_RECORD_NAME => 'vdsf',
);
$selects = array();
$selects = array_merge($selects, LogAggregator::getSelectsFromRangedColumn(
self::VISITS_COUNT_FIELD, self::$visitCountRanges, self::LOG_CONVERSION_TABLE, $prefixes[self::VISITS_UNTIL_RECORD_NAME]
));
$selects = array_merge($selects, LogAggregator::getSelectsFromRangedColumn(
self::DAYS_SINCE_FIRST_VISIT_FIELD, self::$daysToConvRanges, self::LOG_CONVERSION_TABLE, $prefixes[self::DAYS_UNTIL_CONV_RECORD_NAME]
));
$query = $this->getLogAggregator()->queryConversionsByDimension(array(), false, $selects);
if ($query === false) {
return;
}
$totalConversions = $totalRevenue = 0;
$goals = new DataArray();
$visitsToConversions = $daysToConversions = array();
$conversionMetrics = $this->getLogAggregator()->getConversionsMetricFields();
while ($row = $query->fetch()) {
$idGoal = $row['idgoal'];
unset($row['idgoal']);
unset($row['label']);
$values = array();
foreach ($conversionMetrics as $field => $statement) {
$values[$field] = $row[$field];
}
$goals->sumMetrics($idGoal, $values);
if (empty($visitsToConversions[$idGoal])) {
$visitsToConversions[$idGoal] = new DataTable();
}
$array = LogAggregator::makeArrayOneColumn($row, Metrics::INDEX_NB_CONVERSIONS, $prefixes[self::VISITS_UNTIL_RECORD_NAME]);
$visitsToConversions[$idGoal]->addDataTable(DataTable::makeFromIndexedArray($array));
if (empty($daysToConversions[$idGoal])) {
$daysToConversions[$idGoal] = new DataTable();
}
$array = LogAggregator::makeArrayOneColumn($row, Metrics::INDEX_NB_CONVERSIONS, $prefixes[self::DAYS_UNTIL_CONV_RECORD_NAME]);
$daysToConversions[$idGoal]->addDataTable(DataTable::makeFromIndexedArray($array));
// We don't want to sum Abandoned cart metrics in the overall revenue/conversions/converted visits
// since it is a "negative conversion"
if ($idGoal != GoalManager::IDGOAL_CART) {
$totalConversions += $row[Metrics::INDEX_GOAL_NB_CONVERSIONS];
$totalRevenue += $row[Metrics::INDEX_GOAL_REVENUE];
}
}
// Stats by goal, for all visitors
$numericRecords = $this->getConversionsNumericMetrics($goals);
$this->getProcessor()->insertNumericRecords($numericRecords);
$this->insertReports(self::VISITS_UNTIL_RECORD_NAME, $visitsToConversions);
$this->insertReports(self::DAYS_UNTIL_CONV_RECORD_NAME, $daysToConversions);
// Stats for all goals
$nbConvertedVisits = $this->getProcessor()->getNumberOfVisitsConverted();
$metrics = array(
self::getRecordName('conversion_rate') => $this->getConversionRate($nbConvertedVisits),
self::getRecordName('nb_conversions') => $totalConversions,
self::getRecordName('nb_visits_converted') => $nbConvertedVisits,
self::getRecordName('revenue') => $totalRevenue,
);
$this->getProcessor()->insertNumericRecords($metrics);
}
protected function getConversionsNumericMetrics(DataArray $goals)
{
$numericRecords = array();
$goals = $goals->getDataArray();
foreach ($goals as $idGoal => $array) {
foreach ($array as $metricId => $value) {
$metricName = Metrics::$mappingFromIdToNameGoal[$metricId];
$recordName = self::getRecordName($metricName, $idGoal);
$numericRecords[$recordName] = $value;
}
if (!empty($array[Metrics::INDEX_GOAL_NB_VISITS_CONVERTED])) {
$conversion_rate = $this->getConversionRate($array[Metrics::INDEX_GOAL_NB_VISITS_CONVERTED]);
$recordName = self::getRecordName('conversion_rate', $idGoal);
$numericRecords[$recordName] = $conversion_rate;
}
}
return $numericRecords;
}
/**
* @param string $recordName 'nb_conversions'
* @param int|bool $idGoal idGoal to return the metrics for, or false to return overall
* @return string Archive record name
*/
static public function getRecordName($recordName, $idGoal = false)
{
$idGoalStr = '';
if ($idGoal !== false) {
$idGoalStr = $idGoal . "_";
}
return 'Goal_' . $idGoalStr . $recordName;
}
protected function getConversionRate($count)
{
$visits = $this->getProcessor()->getNumberOfVisits();
return round(100 * $count / $visits, GoalManager::REVENUE_PRECISION);
}
protected function insertReports($recordName, $visitsToConversions)
{
foreach ($visitsToConversions as $idGoal => $table) {
$record = self::getRecordName($recordName, $idGoal);
$this->getProcessor()->insertBlobRecord($record, $table->getSerialized());
}
$overviewTable = $this->getOverviewFromGoalTables($visitsToConversions);
$this->getProcessor()->insertBlobRecord(self::getRecordName($recordName), $overviewTable->getSerialized());
}
protected function getOverviewFromGoalTables($tableByGoal)
{
$overview = new DataTable();
foreach ($tableByGoal as $idGoal => $table) {
if ($this->isStandardGoal($idGoal)) {
$overview->addDataTable($table);
}
}
return $overview;
}
protected function isStandardGoal($idGoal)
{
return !in_array($idGoal, $this->getEcommerceIdGoals());
}
protected function aggregateEcommerceItems()
{
$this->initItemReports();
foreach ($this->getItemsDimensions() as $dimension) {
$query = $this->getLogAggregator()->queryEcommerceItems($dimension);
if ($query == false) {
continue;
}
$this->aggregateFromEcommerceItems($query, $dimension);
}
$this->insertItemReports();
return true;
}
protected function initItemReports()
{
foreach ($this->getEcommerceIdGoals() as $ecommerceType) {
foreach ($this->dimensionRecord as $dimension => $record) {
$this->itemReports[$dimension][$ecommerceType] = new DataArray();
}
}
}
protected function insertItemReports()
{
/** @var DataArray $array */
foreach ($this->itemReports as $dimension => $itemAggregatesByType) {
foreach ($itemAggregatesByType as $ecommerceType => $itemAggregate) {
$recordName = $this->dimensionRecord[$dimension];
if ($ecommerceType == GoalManager::IDGOAL_CART) {
$recordName = self::getItemRecordNameAbandonedCart($recordName);
}
$table = $itemAggregate->asDataTable();
$this->getProcessor()->insertBlobRecord($recordName, $table->getSerialized());
}
}
}
protected function getItemsDimensions()
{
$dimensions = array_keys($this->dimensionRecord);
foreach ($this->getItemExtraCategories() as $category) {
$dimensions[] = $category;
}
return $dimensions;
}
protected function getItemExtraCategories()
{
return array(self::CATEGORY2_FIELD, self::CATEGORY3_FIELD, self::CATEGORY4_FIELD, self::CATEGORY5_FIELD);
}
protected function isItemExtraCategory($field)
{
return in_array($field, $this->getItemExtraCategories());
}
protected function aggregateFromEcommerceItems($query, $dimension)
{
while ($row = $query->fetch()) {
$ecommerceType = $row['ecommerceType'];
$label = $this->cleanupRowGetLabel($row, $dimension);
if ($label === false) {
continue;
}
// Aggregate extra categories in the Item categories array
if ($this->isItemExtraCategory($dimension)) {
$array = $this->itemReports[self::CATEGORY_FIELD][$ecommerceType];
} else {
$array = $this->itemReports[$dimension][$ecommerceType];
}
$this->roundColumnValues($row);
$array->sumMetrics($label, $row);
}
}
protected function cleanupRowGetLabel(&$row, $currentField)
{
$label = $row['label'];
if (empty($label)) {
// An empty additional category -> skip this iteration
if ($this->isItemExtraCategory($currentField)) {
return false;
}
$label = "Value not defined";
// Product Name/Category not defined"
if (\Piwik\Plugin\Manager::getInstance()->isPluginActivated('CustomVariables')) {
$label = \Piwik\Plugins\CustomVariables\Archiver::LABEL_CUSTOM_VALUE_NOT_DEFINED;
}
}
if ($row['ecommerceType'] == GoalManager::IDGOAL_CART) {
// abandoned carts are the numner of visits with an abandoned cart
$row[Metrics::INDEX_ECOMMERCE_ORDERS] = $row[Metrics::INDEX_NB_VISITS];
}
unset($row[Metrics::INDEX_NB_VISITS]);
unset($row['label']);
unset($row['labelIdAction']);
unset($row['ecommerceType']);
return $label;
}
protected function roundColumnValues(&$row)
{
$columnsToRound = array(
Metrics::INDEX_ECOMMERCE_ITEM_REVENUE,
Metrics::INDEX_ECOMMERCE_ITEM_QUANTITY,
Metrics::INDEX_ECOMMERCE_ITEM_PRICE,
Metrics::INDEX_ECOMMERCE_ITEM_PRICE_VIEWED,
);
foreach ($columnsToRound as $column) {
if (isset($row[$column])
&& $row[$column] == round($row[$column])
) {
$row[$column] = round($row[$column]);
}
}
}
protected function getEcommerceIdGoals()
{
return array(GoalManager::IDGOAL_CART, GoalManager::IDGOAL_ORDER);
}
static public function getItemRecordNameAbandonedCart($recordName)
{
return $recordName . '_Cart';
}
/**
* @internal param $this->getProcessor()
*/
public function aggregateMultipleReports()
{
/*
* Archive Ecommerce Items
*/
$dataTableToSum = $this->dimensionRecord;
foreach ($this->dimensionRecord as $recordName) {
$dataTableToSum[] = self::getItemRecordNameAbandonedCart($recordName);
}
$this->getProcessor()->aggregateDataTableRecords($dataTableToSum);
/*
* Archive General Goal metrics
*/
$goalIdsToSum = GoalManager::getGoalIds($this->getProcessor()->getParams()->getSite()->getId());
//Ecommerce
$goalIdsToSum[] = GoalManager::IDGOAL_ORDER;
$goalIdsToSum[] = GoalManager::IDGOAL_CART; //bug here if idgoal=1
// Overall goal metrics
$goalIdsToSum[] = false;
$fieldsToSum = array();
foreach ($goalIdsToSum as $goalId) {
$metricsToSum = Goals::getGoalColumns($goalId);
unset($metricsToSum[array_search('conversion_rate', $metricsToSum)]);
foreach ($metricsToSum as $metricName) {
$fieldsToSum[] = self::getRecordName($metricName, $goalId);
}
}
$records = $this->getProcessor()->aggregateNumericMetrics($fieldsToSum);
// also recording conversion_rate for each goal
foreach ($goalIdsToSum as $goalId) {
$nb_conversions = $records[self::getRecordName('nb_visits_converted', $goalId)];
$conversion_rate = $this->getConversionRate($nb_conversions);
$this->getProcessor()->insertNumericRecord(self::getRecordName('conversion_rate', $goalId), $conversion_rate);
// sum up the visits to conversion data table & the days to conversion data table
$this->getProcessor()->aggregateDataTableRecords(array(
self::getRecordName(self::VISITS_UNTIL_RECORD_NAME, $goalId),
self::getRecordName(self::DAYS_UNTIL_CONV_RECORD_NAME, $goalId)));
}
// sum up goal overview reports
$this->getProcessor()->aggregateDataTableRecords(array(
self::getRecordName(self::VISITS_UNTIL_RECORD_NAME),
self::getRecordName(self::DAYS_UNTIL_CONV_RECORD_NAME)));
}
}

View file

@ -0,0 +1,494 @@
<?php
/**
* Piwik - Open source web analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\Goals;
use Exception;
use Piwik\API\Request;
use Piwik\Common;
use Piwik\DataTable\Filter\AddColumnsProcessedMetricsGoal;
use Piwik\DataTable;
use Piwik\FrontController;
use Piwik\Piwik;
use Piwik\Plugins\Referrers\API as APIReferrers;
use Piwik\View\ReportsByDimension;
use Piwik\View;
use Piwik\ViewDataTable\Factory;
/**
*
*/
class Controller extends \Piwik\Plugin\Controller
{
const CONVERSION_RATE_PRECISION = 1;
/**
* Number of "Your top converting keywords/etc are" to display in the per Goal overview page
* @var int
*/
const COUNT_TOP_ROWS_TO_DISPLAY = 3;
const ECOMMERCE_LOG_SHOW_ORDERS = 1;
const ECOMMERCE_LOG_SHOW_ABANDONED_CARTS = 2;
protected $goalColumnNameToLabel = array(
'avg_order_revenue' => 'General_AverageOrderValue',
'nb_conversions' => 'Goals_ColumnConversions',
'conversion_rate' => 'General_ColumnConversionRate',
'revenue' => 'General_TotalRevenue',
'items' => 'General_PurchasedProducts',
);
private function formatConversionRate($conversionRate)
{
if ($conversionRate instanceof DataTable) {
if ($conversionRate->getRowsCount() == 0) {
$conversionRate = 0;
} else {
$columns = $conversionRate->getFirstRow()->getColumns();
$conversionRate = (float)reset($columns);
}
}
return sprintf('%.' . self::CONVERSION_RATE_PRECISION . 'f%%', $conversionRate);
}
public function __construct()
{
parent::__construct();
$this->idSite = Common::getRequestVar('idSite', null, 'int');
$this->goals = API::getInstance()->getGoals($this->idSite);
foreach ($this->goals as &$goal) {
$goal['name'] = Common::sanitizeInputValue($goal['name']);
if (isset($goal['pattern'])) {
$goal['pattern'] = Common::sanitizeInputValue($goal['pattern']);
}
}
}
public function widgetGoalReport()
{
$view = $this->getGoalReportView($idGoal = Common::getRequestVar('idGoal', null, 'string'));
$view->displayFullReport = false;
return $view->render();
}
public function goalReport()
{
$view = $this->getGoalReportView($idGoal = Common::getRequestVar('idGoal', null, 'string'));
$view->displayFullReport = true;
return $view->render();
}
public function ecommerceReport()
{
if (!\Piwik\Plugin\Manager::getInstance()->isPluginActivated('CustomVariables')) {
throw new Exception("Ecommerce Tracking requires that the plugin Custom Variables is enabled. Please enable the plugin CustomVariables (or ask your admin).");
}
$view = $this->getGoalReportView($idGoal = Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER);
$view->displayFullReport = true;
return $view->render();
}
public function getEcommerceLog($fetch = false)
{
$saveGET = $_GET;
$filterEcommerce = Common::getRequestVar('filterEcommerce', self::ECOMMERCE_LOG_SHOW_ORDERS, 'int');
if($filterEcommerce == self::ECOMMERCE_LOG_SHOW_ORDERS) {
$segment = urlencode('visitEcommerceStatus==ordered,visitEcommerceStatus==orderedThenAbandonedCart');
} else {
$segment = urlencode('visitEcommerceStatus==abandonedCart,visitEcommerceStatus==orderedThenAbandonedCart');
}
$_GET['segment'] = $segment;
$_GET['filterEcommerce'] = $filterEcommerce;
$_GET['widget'] = 1;
$output = FrontController::getInstance()->dispatch('Live', 'getVisitorLog', array($fetch));
$_GET = $saveGET;
return $output;
}
protected function getGoalReportView($idGoal = false)
{
$view = new View('@Goals/getGoalReportView');
if ($idGoal == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER) {
$goalDefinition['name'] = Piwik::translate('Goals_Ecommerce');
$goalDefinition['allow_multiple'] = true;
$ecommerce = $view->ecommerce = true;
} else {
if (!isset($this->goals[$idGoal])) {
Piwik::redirectToModule('Goals', 'index', array('idGoal' => null));
}
$goalDefinition = $this->goals[$idGoal];
}
$this->setGeneralVariablesView($view);
$goal = $this->getMetricsForGoal($idGoal);
foreach ($goal as $name => $value) {
$view->$name = $value;
}
if ($idGoal == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER) {
$goal = $this->getMetricsForGoal(Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART);
foreach ($goal as $name => $value) {
$name = 'cart_' . $name;
$view->$name = $value;
}
}
$view->idGoal = $idGoal;
$view->goalName = $goalDefinition['name'];
$view->goalAllowMultipleConversionsPerVisit = $goalDefinition['allow_multiple'];
$view->graphEvolution = $this->getEvolutionGraph(array('nb_conversions'), $idGoal);
$view->nameGraphEvolution = 'Goals.getEvolutionGraph' . $idGoal;
$view->topDimensions = $this->getTopDimensions($idGoal);
// conversion rate for new and returning visitors
$segment = urldecode(\Piwik\Plugins\VisitFrequency\API::RETURNING_VISITOR_SEGMENT);
$conversionRateReturning = API::getInstance()->getConversionRate($this->idSite, Common::getRequestVar('period'), Common::getRequestVar('date'), $segment, $idGoal);
$view->conversion_rate_returning = $this->formatConversionRate($conversionRateReturning);
$segment = 'visitorType==new';
$conversionRateNew = API::getInstance()->getConversionRate($this->idSite, Common::getRequestVar('period'), Common::getRequestVar('date'), $segment, $idGoal);
$view->conversion_rate_new = $this->formatConversionRate($conversionRateNew);
$view->goalReportsByDimension = $this->getGoalReportsByDimensionTable(
$view->nb_conversions, isset($ecommerce), !empty($view->cart_nb_conversions));
return $view;
}
public function index()
{
$view = $this->getOverviewView();
// unsanitize goal names and other text data (not done in API so as not to break
// any other code/cause security issues)
$goals = $this->goals;
foreach ($goals as &$goal) {
$goal['name'] = Common::unsanitizeInputValue($goal['name']);
if (isset($goal['pattern'])) {
$goal['pattern'] = Common::unsanitizeInputValue($goal['pattern']);
}
}
$view->goalsJSON = Common::json_encode($goals);
$view->userCanEditGoals = Piwik::isUserHasAdminAccess($this->idSite);
$view->ecommerceEnabled = $this->site->isEcommerceEnabled();
$view->displayFullReport = true;
return $view->render();
}
public function widgetGoalsOverview()
{
$view = $this->getOverviewView();
$view->displayFullReport = false;
return $view->render();
}
protected function getOverviewView()
{
$view = new View('@Goals/getOverviewView');
$this->setGeneralVariablesView($view);
$view->graphEvolution = $this->getEvolutionGraph(array('nb_conversions'));
$view->nameGraphEvolution = 'GoalsgetEvolutionGraph';
// sparkline for the historical data of the above values
$view->urlSparklineConversions = $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('nb_conversions'), 'idGoal' => ''));
$view->urlSparklineConversionRate = $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('conversion_rate'), 'idGoal' => ''));
$view->urlSparklineRevenue = $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('revenue'), 'idGoal' => ''));
// Pass empty idGoal will return Goal overview
$request = new Request("method=Goals.get&format=original&idGoal=");
$datatable = $request->process();
$dataRow = $datatable->getFirstRow();
$view->nb_conversions = $dataRow->getColumn('nb_conversions');
$view->nb_visits_converted = $dataRow->getColumn('nb_visits_converted');
$view->conversion_rate = $this->formatConversionRate($dataRow->getColumn('conversion_rate'));
$view->revenue = $dataRow->getColumn('revenue');
$goalMetrics = array();
foreach ($this->goals as $idGoal => $goal) {
$goalMetrics[$idGoal] = $this->getMetricsForGoal($idGoal);
$goalMetrics[$idGoal]['name'] = $goal['name'];
$goalMetrics[$idGoal]['goalAllowMultipleConversionsPerVisit'] = $goal['allow_multiple'];
}
$view->goalMetrics = $goalMetrics;
$view->goals = $this->goals;
$view->goalReportsByDimension = $this->getGoalReportsByDimensionTable(
$view->nb_conversions, $ecommerce = false, !empty($view->cart_nb_conversions));
return $view;
}
public function getLastNbConversionsGraph()
{
$view = $this->getLastUnitGraph($this->pluginName, __FUNCTION__, 'Goals.getConversions');
return $this->renderView($view);
}
public function getLastConversionRateGraph()
{
$view = $this->getLastUnitGraph($this->pluginName, __FUNCTION__, 'Goals.getConversionRate');
return $this->renderView($view);
}
public function getLastRevenueGraph()
{
$view = $this->getLastUnitGraph($this->pluginName, __FUNCTION__, 'Goals.getRevenue');
return $this->renderView($view);
}
public function addNewGoal()
{
$view = new View('@Goals/addNewGoal');
$this->setGeneralVariablesView($view);
$view->userCanEditGoals = Piwik::isUserHasAdminAccess($this->idSite);
$view->onlyShowAddNewGoal = true;
return $view->render();
}
public function getEvolutionGraph(array $columns = array(), $idGoal = false)
{
if (empty($columns)) {
$columns = Common::getRequestVar('columns');
$columns = Piwik::getArrayFromApiParameter($columns);
}
$columns = !is_array($columns) ? array($columns) : $columns;
if (empty($idGoal)) {
$idGoal = Common::getRequestVar('idGoal', false, 'string');
}
$view = $this->getLastUnitGraph($this->pluginName, __FUNCTION__, 'Goals.get');
$view->requestConfig->request_parameters_to_modify['idGoal'] = $idGoal;
$nameToLabel = $this->goalColumnNameToLabel;
if ($idGoal == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER) {
$nameToLabel['nb_conversions'] = 'General_EcommerceOrders';
} elseif ($idGoal == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART) {
$nameToLabel['nb_conversions'] = Piwik::translate('General_VisitsWith', Piwik::translate('Goals_AbandonedCart'));
$nameToLabel['conversion_rate'] = $nameToLabel['nb_conversions'];
$nameToLabel['revenue'] = Piwik::translate('Goals_LeftInCart', Piwik::translate('General_ColumnRevenue'));
$nameToLabel['items'] = Piwik::translate('Goals_LeftInCart', Piwik::translate('Goals_Products'));
}
$selectableColumns = array('nb_conversions', 'conversion_rate', 'revenue');
if ($this->site->isEcommerceEnabled()) {
$selectableColumns[] = 'items';
$selectableColumns[] = 'avg_order_revenue';
}
foreach (array_merge($columns, $selectableColumns) as $columnName) {
$columnTranslation = '';
// find the right translation for this column, eg. find 'revenue' if column is Goal_1_revenue
foreach ($nameToLabel as $metric => $metricTranslation) {
if (strpos($columnName, $metric) !== false) {
$columnTranslation = Piwik::translate($metricTranslation);
break;
}
}
if (!empty($idGoal) && isset($this->goals[$idGoal])) {
$goalName = $this->goals[$idGoal]['name'];
$columnTranslation = "$columnTranslation (" . Piwik::translate('Goals_GoalX', "$goalName") . ")";
}
$view->config->translations[$columnName] = $columnTranslation;
}
$view->config->columns_to_display = $columns;
$view->config->selectable_columns = $selectableColumns;
$langString = $idGoal ? 'Goals_SingleGoalOverviewDocumentation' : 'Goals_GoalsOverviewDocumentation';
$view->config->documentation = Piwik::translate($langString, '<br />');
return $this->renderView($view);
}
protected function getTopDimensions($idGoal)
{
$columnNbConversions = 'goal_' . $idGoal . '_nb_conversions';
$columnConversionRate = 'goal_' . $idGoal . '_conversion_rate';
$topDimensionsToLoad = array();
if (\Piwik\Plugin\Manager::getInstance()->isPluginActivated('UserCountry')) {
$topDimensionsToLoad += array(
'country' => 'UserCountry.getCountry',
);
}
$keywordNotDefinedString = '';
if (\Piwik\Plugin\Manager::getInstance()->isPluginActivated('Referrers')) {
$keywordNotDefinedString = APIReferrers::getKeywordNotDefinedString();
$topDimensionsToLoad += array(
'keyword' => 'Referrers.getKeywords',
'website' => 'Referrers.getWebsites',
);
}
$topDimensions = array();
foreach ($topDimensionsToLoad as $dimensionName => $apiMethod) {
$request = new Request("method=$apiMethod
&format=original
&filter_update_columns_when_show_all_goals=1
&idGoal=" . AddColumnsProcessedMetricsGoal::GOALS_FULL_TABLE . "
&filter_sort_order=desc
&filter_sort_column=$columnNbConversions" .
// select a couple more in case some are not valid (ie. conversions==0 or they are "Keyword not defined")
"&filter_limit=" . (self::COUNT_TOP_ROWS_TO_DISPLAY + 2));
$datatable = $request->process();
$topDimension = array();
$count = 0;
foreach ($datatable->getRows() as $row) {
$conversions = $row->getColumn($columnNbConversions);
if ($conversions > 0
&& $count < self::COUNT_TOP_ROWS_TO_DISPLAY
// Don't put the "Keyword not defined" in the best segment since it's irritating
&& !($dimensionName == 'keyword'
&& $row->getColumn('label') == $keywordNotDefinedString)
) {
$topDimension[] = array(
'name' => $row->getColumn('label'),
'nb_conversions' => $conversions,
'conversion_rate' => $this->formatConversionRate($row->getColumn($columnConversionRate)),
'metadata' => $row->getMetadata(),
);
$count++;
}
}
$topDimensions[$dimensionName] = $topDimension;
}
return $topDimensions;
}
protected function getMetricsForGoal($idGoal)
{
$request = new Request("method=Goals.get&format=original&idGoal=$idGoal");
$datatable = $request->process();
$dataRow = $datatable->getFirstRow();
$nbConversions = $dataRow->getColumn('nb_conversions');
$nbVisitsConverted = $dataRow->getColumn('nb_visits_converted');
// Backward compatibilty before 1.3, this value was not processed
if (empty($nbVisitsConverted)) {
$nbVisitsConverted = $nbConversions;
}
$revenue = $dataRow->getColumn('revenue');
$return = array(
'id' => $idGoal,
'nb_conversions' => (int)$nbConversions,
'nb_visits_converted' => (int)$nbVisitsConverted,
'conversion_rate' => $this->formatConversionRate($dataRow->getColumn('conversion_rate')),
'revenue' => $revenue ? $revenue : 0,
'urlSparklineConversions' => $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('nb_conversions'), 'idGoal' => $idGoal)),
'urlSparklineConversionRate' => $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('conversion_rate'), 'idGoal' => $idGoal)),
'urlSparklineRevenue' => $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('revenue'), 'idGoal' => $idGoal)),
);
if ($idGoal == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER) {
$items = $dataRow->getColumn('items');
$aov = $dataRow->getColumn('avg_order_revenue');
$return = array_merge($return, array(
'revenue_subtotal' => $dataRow->getColumn('revenue_subtotal'),
'revenue_tax' => $dataRow->getColumn('revenue_tax'),
'revenue_shipping' => $dataRow->getColumn('revenue_shipping'),
'revenue_discount' => $dataRow->getColumn('revenue_discount'),
'items' => $items ? $items : 0,
'avg_order_revenue' => $aov ? $aov : 0,
'urlSparklinePurchasedProducts' => $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('items'), 'idGoal' => $idGoal)),
'urlSparklineAverageOrderValue' => $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('avg_order_revenue'), 'idGoal' => $idGoal)),
));
}
return $return;
}
/**
* Utility function that returns HTML that displays Goal information for reports. This
* is the HTML that is at the bottom of every goals page.
*
* @param int $conversions The number of conversions for this goal (or all goals
* in case of the overview).
* @param bool $ecommerce Whether to show ecommerce reports or not.
* @param bool $cartNbConversions Whether there are cart conversions or not for this
* goal.
* @return string
*/
private function getGoalReportsByDimensionTable($conversions, $ecommerce = false, $cartNbConversions = false)
{
$preloadAbandonedCart = $cartNbConversions !== false && $conversions == 0;
$goalReportsByDimension = new ReportsByDimension('Goals');
// add ecommerce reports
$ecommerceCustomParams = array();
if ($ecommerce) {
if ($preloadAbandonedCart) {
$ecommerceCustomParams['viewDataTable'] = 'ecommerceAbandonedCart';
$ecommerceCustomParams['filterEcommerce'] = self::ECOMMERCE_LOG_SHOW_ABANDONED_CARTS;
}
$goalReportsByDimension->addReport(
'Goals_EcommerceReports', 'Goals_ProductSKU', 'Goals.getItemsSku', $ecommerceCustomParams);
$goalReportsByDimension->addReport(
'Goals_EcommerceReports', 'Goals_ProductName', 'Goals.getItemsName', $ecommerceCustomParams);
$goalReportsByDimension->addReport(
'Goals_EcommerceReports', 'Goals_ProductCategory', 'Goals.getItemsCategory', $ecommerceCustomParams);
$goalReportsByDimension->addReport(
'Goals_EcommerceReports', 'Goals_EcommerceLog', 'Goals.getEcommerceLog', $ecommerceCustomParams);
}
if ($conversions > 0) {
// for non-Goals reports, we show the goals table
$customParams = $ecommerceCustomParams + array('documentationForGoalsPage' => '1');
if (Common::getRequestVar('idGoal', '') === '') // if no idGoal, use 0 for overview
{
$customParams['idGoal'] = '0'; // NOTE: Must be string! Otherwise Piwik_View_HtmlTable_Goals fails.
}
$allReports = Goals::getReportsWithGoalMetrics();
foreach ($allReports as $category => $reports) {
$categoryText = Piwik::translate('Goals_ViewGoalsBy', $category);
foreach ($reports as $report) {
if(empty($report['viewDataTable'])) {
$report['viewDataTable'] = 'tableGoals';
}
$customParams['viewDataTable'] = $report['viewDataTable'];
$goalReportsByDimension->addReport(
$categoryText, $report['name'], $report['module'] . '.' . $report['action'], $customParams);
}
}
}
return $goalReportsByDimension->render();
}
//
// Report rendering actions
//
public function getItemsSku()
{
return $this->renderReport(__FUNCTION__);
}
public function getItemsName()
{
return $this->renderReport(__FUNCTION__);
}
public function getItemsCategory()
{
return $this->renderReport(__FUNCTION__);
}
public function getVisitsUntilConversion()
{
return $this->renderReport(__FUNCTION__);
}
public function getDaysToConversion()
{
return $this->renderReport(__FUNCTION__);
}
}

View file

@ -0,0 +1,680 @@
<?php
/**
* Piwik - Open source web analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\Goals;
use Piwik\ArchiveProcessor;
use Piwik\Common;
use Piwik\Db;
use Piwik\Menu\MenuMain;
use Piwik\Piwik;
use Piwik\Plugin\ViewDataTable;
use Piwik\Site;
use Piwik\Tracker\GoalManager;
use Piwik\Translate;
use Piwik\WidgetsList;
/**
*
*/
class Goals extends \Piwik\Plugin
{
public function getInformation()
{
$suffix = Piwik::translate('SitesManager_PiwikOffersEcommerceAnalytics',
array('<a href="http://piwik.org/docs/ecommerce-analytics/" target="_blank">', '</a>'));
$info = parent::getInformation();
$info['description'] .= ' ' . $suffix;
return $info;
}
protected $ecommerceReports = array(
array('Goals_ProductSKU', 'Goals', 'getItemsSku'),
array('Goals_ProductName', 'Goals', 'getItemsName'),
array('Goals_ProductCategory', 'Goals', 'getItemsCategory')
);
static public function getReportsWithGoalMetrics()
{
$dimensions = self::getAllReportsWithGoalMetrics();
$dimensionsByGroup = array();
foreach ($dimensions as $dimension) {
$group = $dimension['category'];
unset($dimension['category']);
$dimensionsByGroup[$group][] = $dimension;
}
uksort($dimensionsByGroup, array('self', 'sortGoalDimensionsByModule'));
return $dimensionsByGroup;
}
public static function sortGoalDimensionsByModule($a, $b)
{
$order = array(
Piwik::translate('Referrers_Referrers'),
Piwik::translate('General_Visit'),
Piwik::translate('VisitTime_ColumnServerTime'),
);
$orderA = array_search($a, $order);
$orderB = array_search($b, $order);
return $orderA > $orderB;
}
static public function getGoalColumns($idGoal)
{
$columns = array(
'nb_conversions',
'nb_visits_converted',
'conversion_rate',
'revenue',
);
if ($idGoal === false) {
return $columns;
}
// Orders
if ($idGoal === GoalManager::IDGOAL_ORDER) {
$columns = array_merge($columns, array(
'revenue_subtotal',
'revenue_tax',
'revenue_shipping',
'revenue_discount',
));
}
// Abandoned carts & orders
if ($idGoal <= GoalManager::IDGOAL_ORDER) {
$columns[] = 'items';
}
return $columns;
}
/**
* @see Piwik\Plugin::getListHooksRegistered
*/
public function getListHooksRegistered()
{
$hooks = array(
'AssetManager.getJavaScriptFiles' => 'getJsFiles',
'AssetManager.getStylesheetFiles' => 'getStylesheetFiles',
'Tracker.Cache.getSiteAttributes' => 'fetchGoalsFromDb',
'API.getReportMetadata.end' => 'getReportMetadata',
'API.getSegmentDimensionMetadata' => 'getSegmentsMetadata',
'WidgetsList.addWidgets' => 'addWidgets',
'Menu.Reporting.addItems' => 'addMenus',
'SitesManager.deleteSite.end' => 'deleteSiteGoals',
'Goals.getReportsWithGoalMetrics' => 'getActualReportsWithGoalMetrics',
'ViewDataTable.configure' => 'configureViewDataTable',
'Translate.getClientSideTranslationKeys' => 'getClientSideTranslationKeys',
'ViewDataTable.addViewDataTable' => 'getAvailableDataTableVisualizations'
);
return $hooks;
}
public function getAvailableDataTableVisualizations(&$visualizations)
{
$visualizations[] = 'Piwik\\Plugins\\Goals\\Visualizations\\Goals';
}
/**
* Delete goals recorded for this site
*/
function deleteSiteGoals($idSite)
{
Db::query("DELETE FROM " . Common::prefixTable('goal') . " WHERE idsite = ? ", array($idSite));
}
/**
* Returns the Metadata for the Goals plugin API.
* The API returns general Goal metrics: conv, conv rate and revenue globally
* and for each goal.
*
* Also, this will update metadata of all other reports that have Goal segmentation
*/
public function getReportMetadata(&$reports, $info)
{
$idSites = $info['idSites'];
// Processed in AddColumnsProcessedMetricsGoal
// These metrics will also be available for some reports, for each goal
// Example: Conversion rate for Goal 2 for the keyword 'piwik'
$goalProcessedMetrics = array(
'revenue_per_visit' => Piwik::translate('General_ColumnValuePerVisit'),
);
$goalMetrics = array(
'nb_conversions' => Piwik::translate('Goals_ColumnConversions'),
'nb_visits_converted' => Piwik::translate('General_ColumnVisitsWithConversions'),
'conversion_rate' => Piwik::translate('General_ColumnConversionRate'),
'revenue' => Piwik::translate('General_ColumnRevenue')
);
$conversionReportMetrics = array(
'nb_conversions' => Piwik::translate('Goals_ColumnConversions')
);
// General Goal metrics: conversions, conv rate, revenue
$goalsCategory = Piwik::translate('Goals_Goals');
$reports[] = array(
'category' => $goalsCategory,
'name' => Piwik::translate('Goals_Goals'),
'module' => 'Goals',
'action' => 'get',
'metrics' => $goalMetrics,
'processedMetrics' => array(),
'order' => 1
);
// If only one website is selected, we add the Goal metrics
if (count($idSites) == 1) {
$idSite = reset($idSites);
$goals = API::getInstance()->getGoals($idSite);
// Add overall visits to conversion report
$reports[] = array(
'category' => $goalsCategory,
'name' => Piwik::translate('Goals_VisitsUntilConv'),
'module' => 'Goals',
'action' => 'getVisitsUntilConversion',
'dimension' => Piwik::translate('Goals_VisitsUntilConv'),
'constantRowsCount' => true,
'parameters' => array(),
'metrics' => $conversionReportMetrics,
'order' => 5
);
// Add overall days to conversion report
$reports[] = array(
'category' => $goalsCategory,
'name' => Piwik::translate('Goals_DaysToConv'),
'module' => 'Goals',
'action' => 'getDaysToConversion',
'dimension' => Piwik::translate('Goals_DaysToConv'),
'constantRowsCount' => true,
'parameters' => array(),
'metrics' => $conversionReportMetrics,
'order' => 10
);
foreach ($goals as $goal) {
// Add the general Goal metrics: ie. total Goal conversions,
// Goal conv rate or Goal total revenue.
// This API call requires a custom parameter
$goal['name'] = Common::sanitizeInputValue($goal['name']);
$reports[] = array(
'category' => $goalsCategory,
'name' => Piwik::translate('Goals_GoalX', $goal['name']),
'module' => 'Goals',
'action' => 'get',
'parameters' => array('idGoal' => $goal['idgoal']),
'metrics' => $goalMetrics,
'processedMetrics' => false,
'order' => 50 + $goal['idgoal'] * 3
);
// Add visits to conversion report
$reports[] = array(
'category' => $goalsCategory,
'name' => $goal['name'] . ' - ' . Piwik::translate('Goals_VisitsUntilConv'),
'module' => 'Goals',
'action' => 'getVisitsUntilConversion',
'dimension' => Piwik::translate('Goals_VisitsUntilConv'),
'constantRowsCount' => true,
'parameters' => array('idGoal' => $goal['idgoal']),
'metrics' => $conversionReportMetrics,
'order' => 51 + $goal['idgoal'] * 3
);
// Add days to conversion report
$reports[] = array(
'category' => $goalsCategory,
'name' => $goal['name'] . ' - ' . Piwik::translate('Goals_DaysToConv'),
'module' => 'Goals',
'action' => 'getDaysToConversion',
'dimension' => Piwik::translate('Goals_DaysToConv'),
'constantRowsCount' => true,
'parameters' => array('idGoal' => $goal['idgoal']),
'metrics' => $conversionReportMetrics,
'order' => 52 + $goal['idgoal'] * 3
);
}
$site = new Site($idSite);
if ($site->isEcommerceEnabled()) {
$category = Piwik::translate('Goals_Ecommerce');
$ecommerceMetrics = array_merge($goalMetrics, array(
'revenue_subtotal' => Piwik::translate('General_Subtotal'),
'revenue_tax' => Piwik::translate('General_Tax'),
'revenue_shipping' => Piwik::translate('General_Shipping'),
'revenue_discount' => Piwik::translate('General_Discount'),
'items' => Piwik::translate('General_PurchasedProducts'),
'avg_order_revenue' => Piwik::translate('General_AverageOrderValue')
));
$ecommerceMetrics['nb_conversions'] = Piwik::translate('General_EcommerceOrders');
// General Ecommerce metrics
$reports[] = array(
'category' => $category,
'name' => Piwik::translate('General_EcommerceOrders'),
'module' => 'Goals',
'action' => 'get',
'parameters' => array('idGoal' => Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER),
'metrics' => $ecommerceMetrics,
'processedMetrics' => false,
'order' => 10
);
$reports[] = array(
'category' => $category,
'name' => Piwik::translate('General_EcommerceOrders') . ' - ' . Piwik::translate('Goals_VisitsUntilConv'),
'module' => 'Goals',
'action' => 'getVisitsUntilConversion',
'dimension' => Piwik::translate('Goals_VisitsUntilConv'),
'constantRowsCount' => true,
'metrics' => $conversionReportMetrics,
'parameters' => array('idGoal' => Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER),
'order' => 11
);
$reports[] = array(
'category' => $category,
'name' => Piwik::translate('General_EcommerceOrders') . ' - ' . Piwik::translate('Goals_DaysToConv'),
'module' => 'Goals',
'action' => 'getDaysToConversion',
'dimension' => Piwik::translate('Goals_DaysToConv'),
'constantRowsCount' => true,
'metrics' => $conversionReportMetrics,
'parameters' => array('idGoal' => Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER),
'order' => 12
);
// Abandoned cart general metrics
$abandonedCartMetrics = $goalMetrics;
$abandonedCartMetrics['nb_conversions'] = Piwik::translate('General_AbandonedCarts');
$abandonedCartMetrics['revenue'] = Piwik::translate('Goals_LeftInCart', Piwik::translate('General_ColumnRevenue'));
$abandonedCartMetrics['items'] = Piwik::translate('Goals_LeftInCart', Piwik::translate('Goals_Products'));
unset($abandonedCartMetrics['nb_visits_converted']);
// Abandoned Cart metrics
$reports[] = array(
'category' => $category,
'name' => Piwik::translate('General_AbandonedCarts'),
'module' => 'Goals',
'action' => 'get',
'parameters' => array('idGoal' => Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART),
'metrics' => $abandonedCartMetrics,
'processedMetrics' => false,
'order' => 15
);
$reports[] = array(
'category' => $category,
'name' => Piwik::translate('General_AbandonedCarts') . ' - ' . Piwik::translate('Goals_VisitsUntilConv'),
'module' => 'Goals',
'action' => 'getVisitsUntilConversion',
'dimension' => Piwik::translate('Goals_VisitsUntilConv'),
'constantRowsCount' => true,
'metrics' => $conversionReportMetrics,
'parameters' => array('idGoal' => Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART),
'order' => 20
);
$reports[] = array(
'category' => $category,
'name' => Piwik::translate('General_AbandonedCarts') . ' - ' . Piwik::translate('Goals_DaysToConv'),
'module' => 'Goals',
'action' => 'getDaysToConversion',
'dimension' => Piwik::translate('Goals_DaysToConv'),
'constantRowsCount' => true,
'metrics' => $conversionReportMetrics,
'parameters' => array('idGoal' => Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART),
'order' => 25
);
// Product reports metadata
$productColumns = self::getProductReportColumns();
foreach ($this->ecommerceReports as $i => $ecommerceReport) {
$reports[] = array(
'category' => $category,
'name' => Piwik::translate($ecommerceReport[0]),
'module' => 'Goals',
'action' => $ecommerceReport[2],
'dimension' => Piwik::translate($ecommerceReport[0]),
'metrics' => $productColumns,
'processedMetrics' => false,
'order' => 30 + $i
);
}
}
}
unset($goalMetrics['nb_visits_converted']);
$reportsWithGoals = self::getAllReportsWithGoalMetrics();
foreach ($reportsWithGoals as $reportWithGoals) {
// Select this report from the API metadata array
// and add the Goal metrics to it
foreach ($reports as &$apiReportToUpdate) {
if ($apiReportToUpdate['module'] == $reportWithGoals['module']
&& $apiReportToUpdate['action'] == $reportWithGoals['action']
) {
$apiReportToUpdate['metricsGoal'] = $goalMetrics;
$apiReportToUpdate['processedMetricsGoal'] = $goalProcessedMetrics;
break;
}
}
}
}
static private function getAllReportsWithGoalMetrics()
{
$reportsWithGoals = array();
/**
* Triggered when gathering all reports that contain Goal metrics. The list of reports
* will be displayed on the left column of the bottom of every _Goals_ page.
*
* If plugins define reports that contain goal metrics (such as **conversions** or **revenue**),
* they can use this event to make sure their reports can be viewed on Goals pages.
*
* **Example**
*
* public function getReportsWithGoalMetrics(&$reports)
* {
* $reports[] = array(
* 'category' => Piwik::translate('MyPlugin_myReportCategory'),
* 'name' => Piwik::translate('MyPlugin_myReportDimension'),
* 'module' => 'MyPlugin',
* 'action' => 'getMyReport'
* );
* }
*
* @param array &$reportsWithGoals The list of arrays describing reports that have Goal metrics.
* Each element of this array must be an array with the following
* properties:
*
* - **category**: The report category. This should be a translated string.
* - **name**: The report's translated name.
* - **module**: The plugin the report is in, eg, `'UserCountry'`.
* - **action**: The API method of the report, eg, `'getCountry'`.
*/
Piwik::postEvent('Goals.getReportsWithGoalMetrics', array(&$reportsWithGoals));
return $reportsWithGoals;
}
static public function getProductReportColumns()
{
return array(
'revenue' => Piwik::translate('General_ProductRevenue'),
'quantity' => Piwik::translate('General_Quantity'),
'orders' => Piwik::translate('General_UniquePurchases'),
'avg_price' => Piwik::translate('General_AveragePrice'),
'avg_quantity' => Piwik::translate('General_AverageQuantity'),
'nb_visits' => Piwik::translate('General_ColumnNbVisits'),
'conversion_rate' => Piwik::translate('General_ProductConversionRate'),
);
}
/**
* This function executes when the 'Goals.getReportsWithGoalMetrics' event fires. It
* adds the 'visits to conversion' report metadata to the list of goal reports so
* this report will be displayed.
*/
public function getActualReportsWithGoalMetrics(&$dimensions)
{
$reportWithGoalMetrics = array(
array('category' => Piwik::translate('General_Visit'),
'name' => Piwik::translate('Goals_VisitsUntilConv'),
'module' => 'Goals',
'action' => 'getVisitsUntilConversion',
'viewDataTable' => 'table',
),
array('category' => Piwik::translate('General_Visit'),
'name' => Piwik::translate('Goals_DaysToConv'),
'module' => 'Goals',
'action' => 'getDaysToConversion',
'viewDataTable' => 'table',
)
);
$dimensions = array_merge($dimensions, $reportWithGoalMetrics);
}
public function getSegmentsMetadata(&$segments)
{
$segments[] = array(
'type' => 'dimension',
'category' => Piwik::translate('General_Visit'),
'name' => 'General_VisitConvertedGoalId',
'segment' => 'visitConvertedGoalId',
'sqlSegment' => 'log_conversion.idgoal',
'acceptedValues' => '1, 2, 3, etc.',
);
}
public function getJsFiles(&$jsFiles)
{
$jsFiles[] = "plugins/Goals/javascripts/goalsForm.js";
}
public function getStylesheetFiles(&$stylesheets)
{
$stylesheets[] = "plugins/Goals/stylesheets/goals.css";
}
public function fetchGoalsFromDb(&$array, $idSite)
{
// add the 'goal' entry in the website array
$array['goals'] = API::getInstance()->getGoals($idSite);
}
public function addWidgets()
{
$idSite = Common::getRequestVar('idSite', null, 'int');
// Ecommerce widgets
$site = new Site($idSite);
if ($site->isEcommerceEnabled()) {
WidgetsList::add('Goals_Ecommerce', 'Goals_EcommerceOverview', 'Goals', 'widgetGoalReport', array('idGoal' => Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER));
WidgetsList::add('Goals_Ecommerce', 'Goals_EcommerceLog', 'Goals', 'getEcommerceLog');
foreach ($this->ecommerceReports as $widget) {
WidgetsList::add('Goals_Ecommerce', $widget[0], $widget[1], $widget[2]);
}
}
// Goals widgets
WidgetsList::add('Goals_Goals', 'Goals_GoalsOverview', 'Goals', 'widgetGoalsOverview');
$goals = API::getInstance()->getGoals($idSite);
if (count($goals) > 0) {
foreach ($goals as $goal) {
WidgetsList::add('Goals_Goals', Common::sanitizeInputValue($goal['name']), 'Goals', 'widgetGoalReport', array('idGoal' => $goal['idgoal']));
}
}
}
function addMenus()
{
$idSite = Common::getRequestVar('idSite', null, 'int');
$goals = API::getInstance()->getGoals($idSite);
$mainGoalMenu = $this->getGoalCategoryName($idSite);
$site = new Site($idSite);
if (count($goals) == 0) {
MenuMain::getInstance()->add($mainGoalMenu, '', array(
'module' => 'Goals',
'action' => ($site->isEcommerceEnabled() ? 'ecommerceReport' : 'addNewGoal'),
'idGoal' => ($site->isEcommerceEnabled() ? Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER : null)),
true,
25);
if ($site->isEcommerceEnabled()) {
MenuMain::getInstance()->add($mainGoalMenu, 'Goals_Ecommerce', array('module' => 'Goals', 'action' => 'ecommerceReport', 'idGoal' => Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER), true, 1);
}
MenuMain::getInstance()->add($mainGoalMenu, 'Goals_AddNewGoal', array('module' => 'Goals', 'action' => 'addNewGoal'));
} else {
MenuMain::getInstance()->add($mainGoalMenu, '', array(
'module' => 'Goals',
'action' => ($site->isEcommerceEnabled() ? 'ecommerceReport' : 'index'),
'idGoal' => ($site->isEcommerceEnabled() ? Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER : null)),
true,
25);
if ($site->isEcommerceEnabled()) {
MenuMain::getInstance()->add($mainGoalMenu, 'Goals_Ecommerce', array('module' => 'Goals', 'action' => 'ecommerceReport', 'idGoal' => Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER), true, 1);
}
MenuMain::getInstance()->add($mainGoalMenu, 'Goals_GoalsOverview', array('module' => 'Goals', 'action' => 'index'), true, 2);
foreach ($goals as $goal) {
MenuMain::getInstance()->add($mainGoalMenu, str_replace('%', '%%', Translate::clean($goal['name'])), array('module' => 'Goals', 'action' => 'goalReport', 'idGoal' => $goal['idgoal']));
}
}
}
protected function getGoalCategoryName($idSite)
{
$site = new Site($idSite);
return $site->isEcommerceEnabled() ? 'Goals_EcommerceAndGoalsMenu' : 'Goals_Goals';
}
public function configureViewDataTable(ViewDataTable $view)
{
switch ($view->requestConfig->apiMethodToRequestDataTable) {
case 'Goals.getItemsSku':
$this->configureViewForGetItemsSku($view);
break;
case 'Goals.getItemsName':
$this->configureViewForGetItemsName($view);
break;
case 'Goals.getItemsCategory':
$this->configureViewForGetItemsCategory($view);
break;
case 'Goals.getVisitsUntilConversion':
$this->configureViewForGetVisitsUntilConversion($view);
break;
case 'Goals.getDaysToConversion':
$this->configureViewForGetDaysToConversion($view);
break;
}
}
private function configureViewForGetItemsSku(ViewDataTable $view)
{
return $this->configureViewForItemsReport($view, Piwik::translate('Goals_ProductSKU'));
}
private function configureViewForGetItemsName(ViewDataTable $view)
{
return $this->configureViewForItemsReport($view, Piwik::translate('Goals_ProductName'));
}
private function configureViewForGetItemsCategory(ViewDataTable $view)
{
return $this->configureViewForItemsReport($view, Piwik::translate('Goals_ProductCategory'));
}
private function configureViewForGetVisitsUntilConversion(ViewDataTable $view)
{
$view->config->show_search = false;
$view->config->show_exclude_low_population = false;
$view->config->show_table_all_columns = false;
$view->config->columns_to_display = array('label', 'nb_conversions');
$view->config->show_offset_information = false;
$view->config->show_pagination_control = false;
$view->config->show_all_views_icons = false;
$view->requestConfig->filter_sort_column = 'label';
$view->requestConfig->filter_sort_order = 'asc';
$view->requestConfig->filter_limit = count(Archiver::$visitCountRanges);
$view->config->addTranslations(array(
'label' => Piwik::translate('Goals_VisitsUntilConv'),
'nb_conversions' => Piwik::translate('Goals_ColumnConversions'),
));
}
private function configureViewForGetDaysToConversion(ViewDataTable $view)
{
$view->config->show_search = false;
$view->config->show_exclude_low_population = false;
$view->config->show_table_all_columns = false;
$view->config->show_all_views_icons = false;
$view->config->show_offset_information = false;
$view->config->show_pagination_control = false;
$view->config->columns_to_display = array('label', 'nb_conversions');
$view->requestConfig->filter_sort_column = 'label';
$view->requestConfig->filter_sort_order = 'asc';
$view->requestConfig->filter_limit = count(Archiver::$daysToConvRanges);
$view->config->addTranslations(array(
'label' => Piwik::translate('Goals_DaysToConv'),
'nb_conversions' => Piwik::translate('Goals_ColumnConversions'),
));
}
private function configureViewForItemsReport(ViewDataTable $view, $label)
{
$idSite = Common::getRequestVar('idSite');
$moneyColumns = array('revenue', 'avg_price');
$prettifyMoneyColumns = array(
'ColumnCallbackReplace', array($moneyColumns, '\Piwik\MetricsFormatter::getPrettyMoney', array($idSite)));
$view->config->show_ecommerce = true;
$view->config->show_table = false;
$view->config->show_all_views_icons = false;
$view->config->show_exclude_low_population = false;
$view->config->show_table_all_columns = false;
$view->config->addTranslation('label', $label);
$view->config->filters[] = $prettifyMoneyColumns;
$view->requestConfig->filter_limit = 10;
$view->requestConfig->filter_sort_column = 'revenue';
$view->requestConfig->filter_sort_order = 'desc';
// set columns/translations which differ based on viewDataTable TODO: shouldn't have to do this check... amount of reports should be dynamic, but metadata should be static
$columns = Goals::getProductReportColumns();
$abandonedCart = Common::getRequestVar('viewDataTable', 'ecommerceOrder', 'string') == 'ecommerceAbandonedCart';
if ($abandonedCart) {
$columns['abandoned_carts'] = Piwik::translate('General_AbandonedCarts');
$columns['revenue'] = Piwik::translate('Goals_LeftInCart', Piwik::translate('General_ProductRevenue'));
$columns['quantity'] = Piwik::translate('Goals_LeftInCart', Piwik::translate('General_Quantity'));
$columns['avg_quantity'] = Piwik::translate('Goals_LeftInCart', Piwik::translate('General_AverageQuantity'));
unset($columns['orders']);
unset($columns['conversion_rate']);
$view->requestConfig->request_parameters_to_modify['abandonedCarts'] = '1';
}
$translations = array_merge(array('label' => $label), $columns);
$view->config->addTranslations($translations);
$view->config->columns_to_display = array_keys($translations);
// set metrics documentation in normal ecommerce report
if (!$abandonedCart) {
$view->config->metrics_documentation = array(
'revenue' => Piwik::translate('Goals_ColumnRevenueDocumentation',
Piwik::translate('Goals_DocumentationRevenueGeneratedByProductSales')),
'quantity' => Piwik::translate('Goals_ColumnQuantityDocumentation', $label),
'orders' => Piwik::translate('Goals_ColumnOrdersDocumentation', $label),
'avg_price' => Piwik::translate('Goals_ColumnAveragePriceDocumentation', $label),
'avg_quantity' => Piwik::translate('Goals_ColumnAverageQuantityDocumentation', $label),
'nb_visits' => Piwik::translate('Goals_ColumnVisitsProductDocumentation', $label),
'conversion_rate' => Piwik::translate('Goals_ColumnConversionRateProductDocumentation', $label),
);
}
$view->config->custom_parameters['viewDataTable'] =
$abandonedCart ? Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART : Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER;
}
public function getClientSideTranslationKeys(&$translationKeys)
{
$translationKeys[] = 'Goals_AddGoal';
$translationKeys[] = 'Goals_UpdateGoal';
$translationKeys[] = 'Goals_DeleteGoalConfirm';
}
}

View file

@ -0,0 +1,258 @@
<?php
/**
* Piwik - Open source web analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\Goals\Visualizations;
use Piwik\Common;
use Piwik\DataTable\Filter\AddColumnsProcessedMetricsGoal;
use Piwik\MetricsFormatter;
use Piwik\Piwik;
use Piwik\Plugins\CoreVisualizations\Visualizations\HtmlTable;
use Piwik\Plugins\Goals\API as APIGoals;
use Piwik\Site;
use Piwik\View;
/**
* DataTable Visualization that derives from HtmlTable and sets show_goals_columns to true.
*/
class Goals extends HtmlTable
{
const ID = 'tableGoals';
const FOOTER_ICON = 'plugins/Zeitgeist/images/goal.png';
const FOOTER_ICON_TITLE = 'General_DisplayTableWithMoreMetrics';
public function beforeLoadDataTable()
{
parent::beforeLoadDataTable();
if($this->config->disable_subtable_when_show_goals) {
$this->config->subtable_controller_action = null;
}
$this->setShowGoalsColumnsProperties();
}
public function beforeRender()
{
$this->config->show_goals = true;
$this->config->show_goals_columns = true;
$this->config->datatable_css_class = 'dataTableVizGoals';
$this->config->show_exclude_low_population = true;
$this->config->translations += array(
'nb_conversions' => Piwik::translate('Goals_ColumnConversions'),
'conversion_rate' => Piwik::translate('General_ColumnConversionRate'),
'revenue' => Piwik::translate('General_ColumnRevenue'),
'revenue_per_visit' => Piwik::translate('General_ColumnValuePerVisit'),
);
$this->config->metrics_documentation['nb_visits'] = Piwik::translate('Goals_ColumnVisits');
if (1 == Common::getRequestVar('documentationForGoalsPage', 0, 'int')) {
// TODO: should not use query parameter
$this->config->documentation = Piwik::translate('Goals_ConversionByTypeReportDocumentation',
array('<br />', '<br />', '<a href="http://piwik.org/docs/tracking-goals-web-analytics/" target="_blank">', '</a>'));
}
parent::beforeRender();
}
private function setShowGoalsColumnsProperties()
{
// set view properties based on goal requested
$idSite = Common::getRequestVar('idSite', null, 'int');
$idGoal = Common::getRequestVar('idGoal', AddColumnsProcessedMetricsGoal::GOALS_OVERVIEW, 'string');
if (Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER == $idGoal) {
$this->setPropertiesForEcommerceView();
} else if (AddColumnsProcessedMetricsGoal::GOALS_FULL_TABLE == $idGoal) {
$this->setPropertiesForGoals($idSite, 'all');
} else if (AddColumnsProcessedMetricsGoal::GOALS_OVERVIEW == $idGoal) {
$this->setPropertiesForGoalsOverview($idSite);
} else {
$this->setPropertiesForGoals($idSite, array($idGoal));
}
// add goals columns
$this->config->filters[] = array('AddColumnsProcessedMetricsGoal', array($ignore = true, $idGoal), $priority = true);
// prettify columns
$setRatePercent = function ($rate, $thang = false) {
return $rate == 0 ? "0%" : $rate;
};
foreach ($this->config->columns_to_display as $columnName) {
if (false !== strpos($columnName, 'conversion_rate')) {
$this->config->filters[] = array('ColumnCallbackReplace', array($columnName, $setRatePercent));
}
}
$formatPercent = function ($value) use ($idSite) {
return MetricsFormatter::getPrettyMoney(sprintf("%.1f", $value), $idSite);
};
foreach ($this->config->columns_to_display as $columnName) {
if ($this->isRevenueColumn($columnName)) {
$this->config->filters[] = array('ColumnCallbackReplace', array($columnName, $formatPercent));
}
}
// this ensures that the value is set to zero for all rows where the value was not set (no conversion)
$identityFunction = function ($value) {
return $value;
};
foreach ($this->config->columns_to_display as $columnName) {
if (!$this->isRevenueColumn($columnName)) {
$this->config->filters[] = array('ColumnCallbackReplace', array($columnName, $identityFunction));
}
}
}
private function setPropertiesForEcommerceView()
{
$this->requestConfig->filter_sort_column = 'goal_ecommerceOrder_revenue';
$this->requestConfig->filter_sort_order = 'desc';
$this->config->columns_to_display = array(
'label', 'nb_visits', 'goal_ecommerceOrder_nb_conversions', 'goal_ecommerceOrder_revenue',
'goal_ecommerceOrder_conversion_rate', 'goal_ecommerceOrder_avg_order_revenue', 'goal_ecommerceOrder_items',
'goal_ecommerceOrder_revenue_per_visit'
);
$this->config->translations += array(
'goal_ecommerceOrder_conversion_rate' => Piwik::translate('Goals_ConversionRate', Piwik::translate('Goals_EcommerceOrder')),
'goal_ecommerceOrder_nb_conversions' => Piwik::translate('General_EcommerceOrders'),
'goal_ecommerceOrder_revenue' => Piwik::translate('General_TotalRevenue'),
'goal_ecommerceOrder_revenue_per_visit' => Piwik::translate('General_ColumnValuePerVisit'),
'goal_ecommerceOrder_avg_order_revenue' => Piwik::translate('General_AverageOrderValue'),
'goal_ecommerceOrder_items' => Piwik::translate('General_PurchasedProducts')
);
$goalName = Piwik::translate('General_EcommerceOrders');
$this->config->metrics_documentation += array(
'goal_ecommerceOrder_conversion_rate' => Piwik::translate('Goals_ColumnConversionRateDocumentation', $goalName),
'goal_ecommerceOrder_nb_conversions' => Piwik::translate('Goals_ColumnConversionsDocumentation', $goalName),
'goal_ecommerceOrder_revenue' => Piwik::translate('Goals_ColumnRevenueDocumentation', $goalName),
'goal_ecommerceOrder_revenue_per_visit' => Piwik::translate('Goals_ColumnAverageOrderRevenueDocumentation', $goalName),
'goal_ecommerceOrder_avg_order_revenue' => Piwik::translate('Goals_ColumnAverageOrderRevenueDocumentation', $goalName),
'goal_ecommerceOrder_items' => Piwik::translate('Goals_ColumnPurchasedProductsDocumentation', $goalName),
'revenue_per_visit' => Piwik::translate('Goals_ColumnRevenuePerVisitDocumentation', $goalName)
);
}
private function setPropertiesForGoalsOverview($idSite)
{
$allGoals = $this->getGoals($idSite);
// set view properties
$this->config->columns_to_display = array('label', 'nb_visits');
foreach ($allGoals as $goal) {
$column = "goal_{$goal['idgoal']}_conversion_rate";
$documentation = Piwik::translate('Goals_ColumnConversionRateDocumentation', $goal['quoted_name'] ? : $goal['name']);
$this->config->columns_to_display[] = $column;
$this->config->translations[$column] = Piwik::translate('Goals_ConversionRate', $goal['name']);
$this->config->metrics_documentation[$column] = $documentation;
}
$this->config->columns_to_display[] = 'revenue_per_visit';
$this->config->metrics_documentation['revenue_per_visit'] =
Piwik::translate('Goals_ColumnRevenuePerVisitDocumentation', Piwik::translate('Goals_EcommerceAndGoalsMenu'));
}
private function setPropertiesForGoals($idSite, $idGoals)
{
$allGoals = $this->getGoals($idSite);
if ('all' == $idGoals) {
$idGoals = array_keys($allGoals);
} else {
// only sort by a goal's conversions if not showing all goals (for FULL_REPORT)
$this->requestConfig->filter_sort_column = 'goal_' . reset($idGoals) . '_nb_conversions';
$this->requestConfig->filter_sort_order = 'desc';
}
$this->config->columns_to_display = array('label', 'nb_visits');
$goalColumnTemplates = array(
'goal_%s_nb_conversions',
'goal_%s_conversion_rate',
'goal_%s_revenue',
'goal_%s_revenue_per_visit',
);
// set columns to display (columns of same type but different goals will be next to each other,
// ie, goal_0_nb_conversions, goal_1_nb_conversions, etc.)
foreach ($goalColumnTemplates as $idx => $columnTemplate) {
foreach ($idGoals as $idGoal) {
$this->config->columns_to_display[] = sprintf($columnTemplate, $idGoal);
}
}
// set translations & metric docs for goal specific metrics
foreach ($idGoals as $idGoal) {
$goalName = $allGoals[$idGoal]['name'];
$quotedGoalName = $allGoals[$idGoal]['quoted_name'] ? : $goalName;
$this->config->translations += array(
'goal_' . $idGoal . '_nb_conversions' => Piwik::translate('Goals_Conversions', $goalName),
'goal_' . $idGoal . '_conversion_rate' => Piwik::translate('Goals_ConversionRate', $goalName),
'goal_' . $idGoal . '_revenue' =>
Piwik::translate('%s ' . Piwik::translate('General_ColumnRevenue'), $goalName),
'goal_' . $idGoal . '_revenue_per_visit' =>
Piwik::translate('%s ' . Piwik::translate('General_ColumnValuePerVisit'), $goalName),
);
$this->config->metrics_documentation += array(
'goal_' . $idGoal . '_nb_conversions' => Piwik::translate('Goals_ColumnConversionsDocumentation', $quotedGoalName),
'goal_' . $idGoal . '_conversion_rate' => Piwik::translate('Goals_ColumnConversionRateDocumentation', $quotedGoalName),
'goal_' . $idGoal . '_revenue' => Piwik::translate('Goals_ColumnRevenueDocumentation', $quotedGoalName),
'goal_' . $idGoal . '_revenue_per_visit' =>
Piwik::translate('Goals_ColumnRevenuePerVisitDocumentation', Piwik::translate('Goals_EcommerceAndGoalsMenu')),
);
}
$this->config->columns_to_display[] = 'revenue_per_visit';
}
private function getGoals($idSite)
{
// get all goals to display info for
$allGoals = array();
// add the ecommerce goal if ecommerce is enabled for the site
if (Site::isEcommerceEnabledFor($idSite)) {
$ecommerceGoal = array(
'idgoal' => Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER,
'name' => Piwik::translate('Goals_EcommerceOrder'),
'quoted_name' => false
);
$allGoals[$ecommerceGoal['idgoal']] = $ecommerceGoal;
}
// add the site's goals (and escape all goal names)
$siteGoals = APIGoals::getInstance()->getGoals($idSite);
foreach ($siteGoals as &$goal) {
$goal['name'] = Common::sanitizeInputValue($goal['name']);
$goal['quoted_name'] = '"' . $goal['name'] . '"';
$allGoals[$goal['idgoal']] = $goal;
}
return $allGoals;
}
private function isRevenueColumn($name)
{
return strpos($name, '_revenue') !== false || $name == 'revenue_per_visit';
}
}

View file

@ -0,0 +1,173 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
function showAddNewGoal() {
hideForms();
$(".entityAddContainer").show();
showCancel();
piwikHelper.lazyScrollTo(".entityContainer", 400);
return false;
}
function showEditGoals() {
hideForms();
$("#entityEditContainer").show();
showCancel();
piwikHelper.lazyScrollTo(".entityContainer", 400);
return false;
}
function hideForms() {
$(".entityAddContainer").hide();
$("#entityEditContainer").hide();
}
function showCancel() {
$(".entityCancel").show();
$('.entityCancelLink').click(function () {
hideForms();
$(".entityCancel").hide();
});
}
// init the goal form with existing goal value, if any
function initGoalForm(goalMethodAPI, submitText, goalName, matchAttribute, pattern, patternType, caseSensitive, revenue, allowMultiple, goalId) {
$('#goal_name').val(goalName);
if (matchAttribute == 'manually') {
$('select[name=trigger_type] option[value=manually]').prop('selected', true);
$('input[name=match_attribute]').prop('disabled', true);
$('#match_attribute_section').hide();
$('#manual_trigger_section').show();
matchAttribute = 'url';
} else {
$('select[name=trigger_type] option[value=visitors]').prop('selected', true);
}
$('input[name=match_attribute][value=' + matchAttribute + ']').prop('checked', true);
$('input[name=allow_multiple][value=' + allowMultiple + ']').prop('checked', true);
$('#match_attribute_name').html(mappingMatchTypeName[matchAttribute]);
$('#examples_pattern').html(mappingMatchTypeExamples[matchAttribute]);
$('select[name=pattern_type] option[value=' + patternType + ']').prop('selected', true);
$('input[name=pattern]').val(pattern);
$('#case_sensitive').prop('checked', caseSensitive);
$('input[name=revenue]').val(revenue);
$('input[name=methodGoalAPI]').val(goalMethodAPI);
$('#goal_submit').val(submitText);
if (goalId != undefined) {
$('input[name=goalIdUpdate]').val(goalId);
}
}
function bindGoalForm() {
$('select[name=trigger_type]').click(function () {
var triggerTypeId = $(this).val();
if (triggerTypeId == "manually") {
$('input[name=match_attribute]').prop('disabled', true);
$('#match_attribute_section').hide();
$('#manual_trigger_section').show();
} else {
$('input[name=match_attribute]').removeProp('disabled');
$('#match_attribute_section').show();
$('#manual_trigger_section').hide();
}
});
$('input[name=match_attribute]').click(function () {
var matchTypeId = $(this).val();
$('#match_attribute_name').html(mappingMatchTypeName[matchTypeId]);
$('#examples_pattern').html(mappingMatchTypeExamples[matchTypeId]);
});
$('#goal_submit').click(function () {
// prepare ajax query to API to add goal
ajaxAddGoal();
return false;
});
$('a[name=linkAddNewGoal]').click(function () {
initAndShowAddGoalForm();
piwikHelper.lazyScrollTo('#goal_name');
});
}
function ajaxDeleteGoal(idGoal) {
piwikHelper.lazyScrollTo(".entityContainer", 400);
var parameters = {};
parameters.format = 'json';
parameters.idGoal = idGoal;
parameters.module = 'API';
parameters.method = 'Goals.deleteGoal';
var ajaxRequest = new ajaxHelper();
ajaxRequest.addParams(parameters, 'get');
ajaxRequest.setLoadingElement('#goalAjaxLoading');
ajaxRequest.setCallback(function () { location.reload(); });
ajaxRequest.send(true);
}
function ajaxAddGoal() {
piwikHelper.lazyScrollTo(".entityContainer", 400);
var parameters = {};
parameters.name = encodeURIComponent($('#goal_name').val());
if ($('[name=trigger_type]').val() == 'manually') {
parameters.matchAttribute = 'manually';
parameters.patternType = 'regex';
parameters.pattern = '.*';
parameters.caseSensitive = 0;
} else {
parameters.matchAttribute = $('input[name=match_attribute]:checked').val();
parameters.patternType = $('[name=pattern_type]').val();
parameters.pattern = encodeURIComponent($('input[name=pattern]').val());
parameters.caseSensitive = $('#case_sensitive').prop('checked') == true ? 1 : 0;
}
parameters.revenue = $('input[name=revenue]').val();
parameters.allowMultipleConversionsPerVisit = $('input[name=allow_multiple]:checked').val() == true ? 1 : 0;
parameters.idGoal = $('input[name=goalIdUpdate]').val();
parameters.format = 'json';
parameters.module = 'API';
parameters.method = $('input[name=methodGoalAPI]').val();
var ajaxRequest = new ajaxHelper();
ajaxRequest.addParams(parameters, 'get');
ajaxRequest.setLoadingElement('#goalAjaxLoading');
ajaxRequest.setCallback(function () { location.reload(); });
ajaxRequest.send(true);
}
function bindListGoalEdit() {
$('a[name=linkEditGoal]').click(function () {
var goalId = $(this).attr('id');
var goal = piwik.goals[goalId];
initGoalForm("Goals.updateGoal", _pk_translate('Goals_UpdateGoal'), goal.name, goal.match_attribute, goal.pattern, goal.pattern_type, (goal.case_sensitive != '0'), goal.revenue, goal.allow_multiple, goalId);
showAddNewGoal();
return false;
});
$('a[name=linkDeleteGoal]').click(function () {
var goalId = $(this).attr('id');
var goal = piwik.goals[goalId];
$('#confirm').find('h2').text(sprintf(_pk_translate('Goals_DeleteGoalConfirm'), '"' + goal.name + '"'));
piwikHelper.modalConfirm('#confirm', {yes: function () {
ajaxDeleteGoal(goalId);
}});
return false;
});
$('a[name=linkEditGoals]').click(function () {
return showEditGoals();
});
}
function initAndShowAddGoalForm() {
initGoalForm('Goals.addGoal', _pk_translate('Goals_AddGoal'), '', 'url', '', 'contains', /*caseSensitive = */false, /*allowMultiple = */'0', '0');
return showAddNewGoal();
}

View file

@ -0,0 +1,27 @@
.goalTopElement {
border-bottom: 1px dotted;
}
.goalEntry {
margin: 0 0 20px 0;
padding: 0 0 10px 0;
border-bottom: 1px solid #7e7363;
width: 614px;
}
/* dimension selector */
#titleGoalsByDimension {
padding-top: 30px;
}
ul.ulGoalTopElements {
list-style-type: circle;
margin-left: 30px;
}
.ulGoalTopElements a {
text-decoration: none;
color: #0033CC;
border-bottom: 1px dotted #0033CC;
line-height: 2em;
}

View file

@ -0,0 +1,81 @@
{% if onlyShowAddNewGoal is defined %}
<h2 piwik-enriched-headline>{{ 'Goals_AddNewGoal'|translate }}</h2>
<p>{{ 'Goals_NewGoalIntro'|translate }}</p>
<p>{{ 'Goals_NewGoalDescription'|translate }}
{{ 'Goals_NewWhatDoYouWantUsersToDo'|translate }}
{{ 'Goals_NewGoalYouWillBeAbleTo'|translate }}</p>
<p>{{ 'Goals_LearnMoreAboutGoalTrackingDocumentation'|translate("<a href='?module=Proxy&action=redirect&url=http://piwik.org/docs/tracking-goals-web-analytics/' target='_blank'>","</a>")|raw }}
</p>
{% else %}
<div class="clear"></div>
<h2 piwik-enriched-headline>{{ 'Goals_GoalsManagement'|translate }}</h2>
<div class="entityList">
<ul class='listCircle'>
<li><a onclick='' name='linkAddNewGoal'>{{ 'Goals_CreateNewGOal'|translate }}</a></li>
<li><a onclick='' name='linkEditGoals'>{{ 'Goals_ViewAndEditGoals'|translate }}</a></li>
<li>{{ 'Goals_LearnMoreAboutGoalTrackingDocumentation'|translate("<a href='?module=Proxy&action=redirect&url=http://piwik.org/docs/tracking-goals-web-analytics/' target='_blank'>","</a>")|raw }}</li>
<li>
{% if not ecommerceEnabled %}
{% set websiteManageText %}
<a href='{{ linkTo({'module':'SitesManager','action':'index' }) }}'>{{ 'SitesManager_WebsitesManagement'|translate }}</a>
{% endset %}
{% set ecommerceReportText %}
<a href="http://piwik.org/docs/ecommerce-analytics/" target="_blank">{{ 'Goals_EcommerceReports'|translate }}</a>
{% endset %}
{{ 'Goals_Optional'|translate }} {{ 'Goals_Ecommerce'|translate }}: {{ 'Goals_YouCanEnableEcommerceReports'|translate(ecommerceReportText,websiteManageText)|raw }}
{% else %}
{{ 'SitesManager_PiwikOffersEcommerceAnalytics'|translate('<a href="http://piwik.org/docs/ecommerce-analytics/" target="_blank">',"</a>")|raw }}
{% endif %}
</li>
</ul>
</div>
<br/>
{% endif %}
{% import 'ajaxMacros.twig' as ajax %}
{{ ajax.errorDiv() }}
{{ ajax.loadingDiv('goalAjaxLoading') }}
<div class="entityContainer">
{% if onlyShowAddNewGoal is not defined %}
{% include "@Goals/_listGoalEdit.twig" %}
{% endif %}
{% include "@Goals/_formAddGoal.twig" %}
{% if onlyShowAddNewGoal is not defined %}
<div class='entityCancel' style='display:none;'>
{{ 'General_OrCancel'|translate("<a class='entityCancelLink'>","</a>")|raw }}
</div>
{% endif %}
<a id='bottom'></a>
</div>
<br/><br/>
<script type="text/javascript">
var mappingMatchTypeName = {
"url": "{{ 'Goals_URL'|translate }}",
"title": "{{ 'Goals_PageTitle'|translate }}",
"file": "{{ 'Goals_Filename'|translate }}",
"external_website": "{{ 'Goals_ExternalWebsiteUrl'|translate }}"
};
var mappingMatchTypeExamples = {
"url": "{{ 'General_ForExampleShort'|translate }} {{ 'Goals_Contains'|translate("'checkout/confirmation'") }} \
<br />{{ 'General_ForExampleShort'|translate }} {{ 'Goals_IsExactly'|translate("'http://example.com/thank-you.html'") }} \
<br />{{ 'General_ForExampleShort'|translate }} {{ 'Goals_MatchesExpression'|translate("'(.*)\\\/demo\\\/(.*)'") }}",
"title": "{{ 'General_ForExampleShort'|translate }} {{ 'Goals_Contains'|translate("'Order confirmation'") }}",
"file": "{{ 'General_ForExampleShort'|translate }} {{ 'Goals_Contains'|translate("'files/brochure.pdf'") }} \
<br />{{ 'General_ForExampleShort'|translate }} {{ 'Goals_IsExactly'|translate("'http://example.com/files/brochure.pdf'") }} \
<br />{{ 'General_ForExampleShort'|translate }} {{ 'Goals_MatchesExpression'|translate("'(.*)\\\.zip'") }}",
"external_website": "{{ 'General_ForExampleShort'|translate }} {{ 'Goals_Contains'|translate("'amazon.com'") }} \
<br />{{ 'General_ForExampleShort'|translate }} {{ 'Goals_IsExactly'|translate("'http://mypartner.com/landing.html'") }} \
<br />{{ 'General_ForExampleShort'|translate }} {{ 'Goals_MatchesExpression'|translate("'http://www.amazon.com\\\/(.*)\\\/yourAffiliateId'") }}"
};
bindGoalForm();
{% if onlyShowAddNewGoal is not defined %}
piwik.goals = {{ goalsJSON|raw }};
bindListGoalEdit();
{% else %}
initAndShowAddGoalForm();
{% endif %}
</script>

View file

@ -0,0 +1,97 @@
<div class='entityAddContainer' style="display:none;">
<form>
<table class="dataTable entityTable">
<thead>
<tr class="first">
<th colspan="2">{{ 'Goals_AddNewGoal'|translate }}</th>
<tr>
</thead>
<tbody>
<tr>
<td class="first">{{ 'Goals_GoalName'|translate }} </td>
<td><input type="text" name="name" value="" size="28" id="goal_name" class="inp"/></td>
</tr>
<tr>
<td style='width:260px;' class="first">{{ 'Goals_GoalIsTriggered'|translate }}
<select name="trigger_type" class="inp">
<option value="visitors">{{ 'Goals_WhenVisitors'|translate }}</option>
<option value="manually">{{ 'Goals_Manually'|translate }}</option>
</select>
</td>
<td>
<input type="radio" id="match_attribute_url" value="url" name="match_attribute"/>
<label for="match_attribute_url">{{ 'Goals_VisitUrl'|translate }}</label>
<br/>
<input type="radio" id="match_attribute_title" value="title" name="match_attribute"/>
<label for="match_attribute_title">{{ 'Goals_VisitPageTitle'|translate }}</label>
<br/>
<input type="radio" id="match_attribute_file" value="file" name="match_attribute"/>
<label for="match_attribute_file">{{ 'Goals_Download'|translate }}</label>
<br/>
<input type="radio" id="match_attribute_external_website" value="external_website" name="match_attribute"/>
<label for="match_attribute_external_website">{{ 'Goals_ClickOutlink'|translate }}</label>
</td>
</tr>
</tbody>
<tbody id="match_attribute_section">
<tr>
<td class="first">{{ 'Goals_WhereThe'|translate }} <span id="match_attribute_name"></span></td>
<td>
<select name="pattern_type" class="inp">
<option value="contains">{{ 'Goals_Contains'|translate("") }}</option>
<option value="exact">{{ 'Goals_IsExactly'|translate("") }}</option>
<option value="regex">{{ 'Goals_MatchesExpression'|translate("") }}</option>
</select>
<input type="text" name="pattern" value="" size="16" class="inp"/>
<br/>
<div id="examples_pattern" class="entityInlineHelp"></div>
<br/>
<span style="float:right;">
{{ 'Goals_Optional'|translate }} <input type="checkbox" id="case_sensitive"/>
<label for="case_sensitive">{{ 'Goals_CaseSensitive'|translate }}</label>
</span>
</td>
</tr>
</tbody>
<tbody id="manual_trigger_section" style="display:none;">
<tr>
<td colspan="2" class="first">
{{ 'Goals_WhereVisitedPageManuallyCallsJavascriptTrackerLearnMore'|translate("<a target='_blank' href='?module=Proxy&action=redirect&url=http://piwik.org/docs/javascript-tracking/%23toc-manually-trigger-a-conversion-for-a-goal'>","</a>")|raw }}
</td>
</tr>
</tbody>
<tbody>
<tr>
<td class="first"> {{ 'Goals_AllowMultipleConversionsPerVisit'|translate }} </td>
<td>
<input type="radio" id="allow_multiple_0" value="0" name="allow_multiple"/>
<label for="allow_multiple_0">{{ 'Goals_DefaultGoalConvertedOncePerVisit'|translate }}</label>
<div class="entityInlineHelp">
{{ 'Goals_HelpOneConversionPerVisit'|translate }}
</div>
<br/>
<input type="radio" id="allow_multiple_1" value="1" name="allow_multiple"/>
<label for="allow_multiple_1">{{ 'Goals_AllowGoalConvertedMoreThanOncePerVisit'|translate }}</label>
<br/><br/>
</tr>
<tr>
</tbody>
<tbody>
<tr>
<td class="first">{{ 'Goals_Optional'|translate }} {{ 'Goals_DefaultRevenue'|translate }}</td>
<td>{{ ' <input type="text" name="revenue" size="2" value="0" class="inp" /> '|money(idSite)|raw }}
<div class="entityInlineHelp"> {{ 'Goals_DefaultRevenueHelp'|translate }} </div>
</td>
</tr>
<tr>
</tbody>
</table>
<input type="hidden" name="methodGoalAPI" value=""/>
<input type="hidden" name="goalIdUpdate" value=""/>
<input type="submit" value="" name="submit" id="goal_submit" class="submit"/>
</form>
</div>

View file

@ -0,0 +1,64 @@
<div id='entityEditContainer' style="display:none;">
<table class="dataTable entityTable">
<thead>
<tr>
<th class="first">Id</th>
<th>{{ 'Goals_GoalName'|translate }}</th>
<th>{{ 'Goals_GoalIsTriggeredWhen'|translate }}</th>
<th>{{ 'General_ColumnRevenue'|translate }}</th>
<th>{{ 'General_Edit'|translate }}</th>
<th>{{ 'General_Delete'|translate }}</th>
</tr>
</thead>
{% for goal in goals %}
<tr>
<td class="first">{{ goal.idgoal }}</td>
<td>{{ goal.name|raw }}</td>{# NOTE: goal names are escaped in the DB #}
<td><span class='matchAttribute'>{{ goal.match_attribute }}</span>
{% if goal.pattern_type is defined %}
<br/>
{{ 'Goals_Pattern'|translate }} {{ goal.pattern_type }}: {{ goal.pattern|raw }}
{% endif %}
</td>
<td>{% if goal.revenue==0 %}-{% else %}{{ goal.revenue|money(idSite)|raw }}{% endif %}</td>
<td>
<a href='#' name="linkEditGoal" id="{{ goal.idgoal }}" class="link_but">
<img src='plugins/Zeitgeist/images/ico_edit.png' border="0"/>
{{ 'General_Edit'|translate }}
</a>
</td>
<td>
<a href='#' name="linkDeleteGoal" id="{{ goal.idgoal }}" class="link_but">
<img src='plugins/Zeitgeist/images/ico_delete.png' border="0"/>
{{ 'General_Delete'|translate }}
</a>
</td>
</tr>
{% endfor %}
</table>
</div>
<div class="ui-confirm" id="confirm">
<h2></h2>
<input role="yes" type="button" value="{{ 'General_Yes'|translate }}"/>
<input role="no" type="button" value="{{ 'General_No'|translate }}"/>
</div>
<script type="text/javascript">
var goalTypeToTranslation = {
"manually": "{{ 'Goals_ManuallyTriggeredUsingJavascriptFunction'|translate }}",
"file": "{{ 'Goals_Download'|translate }}",
"url": "{{ 'Goals_VisitUrl'|translate }}",
"title": "{{ 'Goals_VisitPageTitle'|translate }}",
"external_website": "{{ 'Goals_ClickOutlink'|translate }}"
};
$(document).ready(function () {
// translation of the goal "match attribute" to human readable description
$('.matchAttribute').each(function () {
var matchAttribute = $(this).text();
var translation = goalTypeToTranslation[matchAttribute];
$(this).text(translation);
});
});
</script>

View file

@ -0,0 +1,16 @@
{% for element in topDimension %}
{% set goal_nb_conversion=element.nb_conversions %}
{% set goal_conversion_rate=element.conversion_rate %}
<span class='goalTopElement' title='{{ 'Goals_Conversions'|translate("<b>"~goal_nb_conversion~"</b>")|raw }},
{{ 'Goals_ConversionRate'|translate("<b>"~goal_conversion_rate~"</b>")|raw }}'>
{{ element.name }}
</span>
{% import 'macros.twig' as piwik %}
{{ piwik.logoHtml(element.metadata, element.name) }}
{% if loop.index == loop.length-1 %}
and
{% elseif loop.index < loop.length-1 %}
,
{% endif %}
{% endfor %}

View file

@ -0,0 +1,81 @@
<span data-graph-id="{{ nameGraphEvolution }}"></span>
{% if displayFullReport %}
<h2 piwik-enriched-headline>{% if goalName is defined %}{{ 'Goals_GoalX'|translate(goalName)|raw }}{% else %}{{ 'Goals_GoalsOverview'|translate }}{% endif %}</h2>
{% endif %}
{{ graphEvolution|raw }}
<div id='leftcolumn' {% if not isWidget %}style='width:33%;'{% endif %}>
<div class="sparkline">{{ sparkline(urlSparklineConversions) }}
{% if ecommerce is defined %}
<strong>{{ nb_conversions }}</strong>
{{ 'General_EcommerceOrders'|translate }}
<img src='plugins/Zeitgeist/images/ecommerceOrder.gif'>
{% else %}
{{ 'Goals_Conversions'|translate("<strong>"~nb_conversions~"</strong>")|raw }}
{% endif %}
{% if goalAllowMultipleConversionsPerVisit is defined and goalAllowMultipleConversionsPerVisit %}
({{ 'General_NVisits'|translate("<strong>"~nb_visits_converted~"</strong>")|raw }})
{% endif %}
</div>
{% if revenue != 0 or ecommerce is defined %}
<div class="sparkline">
{{ sparkline(urlSparklineRevenue) }}
{% set revenue=revenue|money(idSite) %}
{% if ecommerce is defined %}
<strong>{{ revenue|raw }}</strong> {{ 'General_TotalRevenue'|translate }}
{% else %}
{{ 'Goals_OverallRevenue'|translate("<strong>"~revenue~"</strong>")|raw }}
{% endif %}
</div>
{% endif %}
{% if ecommerce is defined %}
<div class="sparkline">{{ sparkline(urlSparklineAverageOrderValue) }}
<strong>{{ avg_order_revenue|money(idSite)|raw }}</strong>
{{ 'General_AverageOrderValue'|translate }}
</div>
{% endif %}
</div>
<div id='leftcolumn' {% if not isWidget %}style='width:33%;'{% endif %}>
<div class="sparkline">{{ sparkline(urlSparklineConversionRate) }}
{% if ecommerce is defined %}
{% set ecommerceOrdersText %}{{ 'General_EcommerceOrders'|translate }}{% endset %}
{{ 'Goals_ConversionRate'|translate("<strong>"~conversion_rate~"</strong> "~ecommerceOrdersText)|raw }}
{% else %}
{{ 'Goals_OverallConversionRate'|translate("<strong>"~conversion_rate~"</strong>")|raw }}
{% endif %}
</div>
{% if ecommerce is defined %}
<div class="sparkline">{{ sparkline(urlSparklinePurchasedProducts) }}
<strong>{{ items }}</strong> {{ 'General_PurchasedProducts'|translate }}</div>
{% endif %}
</div>
{% if ecommerce is defined %}
<div id='rightcolumn' {% if not isWidget %}style='width:30%;'{% endif %}>
<div>
<img src='plugins/Zeitgeist/images/ecommerceAbandonedCart.gif'> <em>{{ 'General_AbandonedCarts'|translate }}</em>
</div>
<div class="sparkline">
{{ sparkline(cart_urlSparklineConversions) }}
{% set ecommerceAbandonedCartsText %}{{ 'Goals_AbandonedCart'|translate }}{% endset %}
<strong>{{ cart_nb_conversions }}</strong> {{ 'General_VisitsWith'|translate(ecommerceAbandonedCartsText) }}
</div>
<div class="sparkline">
{{ sparkline(cart_urlSparklineRevenue) }}
{% set revenue %}{{ cart_revenue|money(idSite)|raw }}{% endset %}
{% set revenueText %}{{ 'General_ColumnRevenue'|translate }}{% endset %}
<strong>{{ revenue }}</strong> {{ 'Goals_LeftInCart'|translate(revenueText) }}
</div>
<div class="sparkline">
{{ sparkline(cart_urlSparklineConversionRate) }}
<strong>{{ cart_conversion_rate }}</strong>
{{ 'General_VisitsWith'|translate(ecommerceAbandonedCartsText) }}
</div>
</div>
{% endif %}
{% include "_sparklineFooter.twig" %}

View file

@ -0,0 +1,11 @@
{% if userCanEditGoals %}
{% include "@Goals/_addEditGoal.twig" %}
{% else %}
<h2>{{ 'Goals_CreateNewGOal'|translate }}</h2>
<p>
{{ 'Goals_NoGoalsNeedAccess'|translate|raw }}
</p>
<p>
{{ 'Goals_LearnMoreAboutGoalTrackingDocumentation'|translate("<a href='?module=Proxy&action=redirect&url=http://piwik.org/docs/tracking-goals-web-analytics/' target='_blank'>","</a>")|raw }}
</p>
{% endif %}

View file

@ -0,0 +1,66 @@
<link rel="stylesheet" type="text/css" href="plugins/Goals/stylesheets/goals.css"/>
{% include "@Goals/_titleAndEvolutionGraph.twig" | raw %}
<div class="clear"></div>
{% if nb_conversions > 0 %}
<h2>{{ 'Goals_ConversionsOverview'|translate }}</h2>
<ul class="ulGoalTopElements">
{% if ecommerce is not defined %}
{% if topDimensions.country is defined %}
<li>{{ 'Goals_BestCountries'|translate }} {% include '@Goals/_listTopDimension.twig' with {'topDimension':topDimensions.country} %}</li>
{% endif %}
{% if topDimensions.keyword is defined and topDimensions.keyword|length > 0 %}
<li>{{ 'Goals_BestKeywords'|translate }} {% include '@Goals/_listTopDimension.twig' with {'topDimension':topDimensions.keyword} %}</li>
{% endif %}
{% if topDimensions.website is defined and topDimensions.website|length > 0 %}
<li>{{ 'Goals_BestReferrers'|translate }} {% include '@Goals/_listTopDimension.twig' with {'topDimension':topDimensions.website} %}</li>
{% endif %}
<li>
{{ 'Goals_ReturningVisitorsConversionRateIs'|translate("<strong>"~conversion_rate_returning~"</strong>")|raw }}
, {{ 'Goals_NewVisitorsConversionRateIs'|translate("<strong>"~conversion_rate_new~"</strong>")|raw }}
</li>
{% else %}
<li>
{{ 'General_ColumnRevenue'|translate }}: {{ revenue|money(idSite)|raw -}}
{% if revenue_subtotal is not empty %},
{{ 'General_Subtotal'|translate }}: {{ revenue_subtotal|money(idSite)|raw -}}
{% endif %}
{%- if revenue_tax is not empty -%},
{{ 'General_Tax'|translate }}: {{ revenue_tax|money(idSite)|raw -}}
{% endif %}
{%- if revenue_shipping is not empty -%},
{{ 'General_Shipping'|translate }}: {{ revenue_shipping|money(idSite)|raw -}}
{% endif %}
{%- if revenue_discount is not empty -%},
{{ 'General_Discount'|translate }}: {{ revenue_discount|money(idSite)|raw -}}
{% endif %}
</li>
{% endif %}
</ul>
{% endif %}
<script type="text/javascript">
$(document).ready(function () {
$('.goalTopElement').tooltip({
track: true,
content: function () {
return $(this).attr("title");
},
show: false,
hide: false
});
});
</script>
{% if displayFullReport %}
{% if nb_conversions > 0 or cart_nb_conversions is defined %}
<h2 id='titleGoalsByDimension'>
{% if idGoal is defined %}
{{ 'Goals_GoalConversionsBy'|translate(goalName)|raw }}
{% else %}
{{ 'Goals_ConversionsOverviewBy'|translate }}
{% endif %}
</h2>
{{ goalReportsByDimension|raw }}
{% endif %}
{% endif %}

View file

@ -0,0 +1,50 @@
<link rel="stylesheet" type="text/css" href="plugins/Goals/stylesheets/goals.css"/>
{% include "@Goals/_titleAndEvolutionGraph.twig" %}
{% set sum_nb_conversions=nb_conversions %}
{% for goal in goalMetrics %}
{% set nb_conversions=goal.nb_conversions %}
{% set nb_visits_converted=goal.nb_visits_converted %}
{% set conversion_rate=goal.conversion_rate %}
{% set name=goal.name %}
<div class="goalEntry">
<h2>
<a href="javascript:broadcast.propagateAjax('module=Goals&action=goalReport&idGoal={{ goal.id }}')">
{{ 'Goals_GoalX'|translate("'"~name~"'")|raw }}
</a>
</h2>
<div id='leftcolumn'>
<div class="sparkline">{{ sparkline(goal.urlSparklineConversions) }}
{{ 'Goals_Conversions'|translate("<strong>"~nb_conversions~"</strong>")|raw }}
{% if goal.goalAllowMultipleConversionsPerVisit %}
({{ 'General_NVisits'|translate("<strong>"~nb_visits_converted~"</strong>") | raw }})
{% endif %}
</div>
</div>
<div id='rightcolumn'>
<div class="sparkline">{{ sparkline(goal.urlSparklineConversionRate) }}
{{ 'Goals_ConversionRate'|translate("<strong>"~conversion_rate~"</strong>")|raw }}
</div>
</div>
<br class="clear"/>
</div>
{% endfor %}
{% if displayFullReport %}
{% if sum_nb_conversions != 0 %}
<h2 id='titleGoalsByDimension'>
{% if idGoal is defined %}
{{ 'Goals_GoalConversionsBy'|translate(goalName)|raw }}
{% else %}
{{ 'Goals_ConversionsOverviewBy'|translate }}
{% endif %}
</h2>
{{ goalReportsByDimension|raw }}
{% endif %}
{% if userCanEditGoals %}
{% include "@Goals/_addEditGoal.twig" %}
{% endif %}
{% endif %}