Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/matomo-org/matomo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormatt <matt@59fd770c-687e-43c8-a1e3-f5a4ff64c105>2008-12-26 17:37:46 +0300
committermatt <matt@59fd770c-687e-43c8-a1e3-f5a4ff64c105>2008-12-26 17:37:46 +0300
commitd389561c0ccb76536d18239c9b7f81cce3d02fc1 (patch)
tree2bac760977afe8e7f3ae7890d7cd286e37109d88 /plugins/Goals
parent47f4495a2adba4cbce5d1c3d3510ce26f8650f69 (diff)
parentb86d75c79c557a7c89899939055ee7306f40d28a (diff)
Diffstat (limited to 'plugins/Goals')
-rw-r--r--plugins/Goals/API.php227
-rw-r--r--plugins/Goals/Controller.php150
-rw-r--r--plugins/Goals/Goals.php206
-rw-r--r--plugins/Goals/templates/GoalForm.js124
-rw-r--r--plugins/Goals/templates/add_edit_goal.tpl49
-rw-r--r--plugins/Goals/templates/add_new_goal.tpl8
-rw-r--r--plugins/Goals/templates/form_add_goal.tpl67
-rw-r--r--plugins/Goals/templates/list_goal_edit.tpl22
-rw-r--r--plugins/Goals/templates/list_top_segment.tpl5
-rw-r--r--plugins/Goals/templates/overview.tpl27
-rw-r--r--plugins/Goals/templates/release_notes.tpl20
-rw-r--r--plugins/Goals/templates/single_goal.tpl37
-rw-r--r--plugins/Goals/templates/title_and_evolution_graph.tpl20
13 files changed, 962 insertions, 0 deletions
diff --git a/plugins/Goals/API.php b/plugins/Goals/API.php
new file mode 100644
index 0000000000..5b1b849f01
--- /dev/null
+++ b/plugins/Goals/API.php
@@ -0,0 +1,227 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id$
+ *
+ * @package Piwik_VisitsSummary
+ */
+
+/**
+ * @package Piwik_Goals
+ */
+class Piwik_Goals_API
+{
+ static private $instance = null;
+ static public function getInstance()
+ {
+ if (self::$instance == null)
+ {
+ $c = __CLASS__;
+ self::$instance = new $c();
+ }
+ return self::$instance;
+ }
+
+ static public function getGoals( $idSite )
+ {
+ $goals = Zend_Registry::get('db')->fetchAll("SELECT *
+ FROM ".Piwik_Common::prefixTable('goal')."
+ WHERE idsite = ?
+ AND deleted = 0", $idSite);
+ $cleanedGoals = array();
+ foreach($goals as &$goal)
+ {
+ unset($goal['idsite']);
+ $cleanedGoals[$goal['idgoal']] = $goal;
+ }
+ return $cleanedGoals;
+ }
+
+ public function addGoal( $idSite, $name, $matchAttribute, $pattern, $patternType, $caseSensitive, $revenue )
+ {
+ Piwik::checkUserHasAdminAccess($idSite);
+ // save in db
+ $db = Zend_Registry::get('db');
+ $idGoal = $db->fetchOne("SELECT max(idgoal) + 1
+ FROM ".Piwik::prefixTable('goal')."
+ WHERE idsite = ?", $idSite);
+ if($idGoal == false)
+ {
+ $idGoal = 1;
+ }
+ self::checkPatternIsValid($patternType, $pattern);
+ $name = self::checkName($name);
+ $pattern = self::checkPattern($pattern);
+ $db->insert(Piwik::prefixTable('goal'),
+ array(
+ 'idsite' => $idSite,
+ 'idgoal' => $idGoal,
+ 'name' => $name,
+ 'match_attribute' => $matchAttribute,
+ 'pattern' => $pattern,
+ 'pattern_type' => $patternType,
+ 'case_sensitive' => $caseSensitive,
+ 'revenue' => $revenue,
+ 'deleted' => 0,
+ ));
+ Piwik_Common::regenerateCacheWebsiteAttributes($idSite);
+ return $idGoal;
+ }
+
+ public function updateGoal( $idSite, $idGoal, $name, $matchAttribute, $pattern, $patternType, $caseSensitive, $revenue )
+ {
+ Piwik::checkUserHasAdminAccess($idSite);
+ $name = self::checkName($name);
+ $pattern = self::checkPattern($pattern);
+ self::checkPatternIsValid($patternType, $pattern);
+ Zend_Registry::get('db')->update( Piwik::prefixTable('goal'),
+ array(
+ 'name' => $name,
+ 'match_attribute' => $matchAttribute,
+ 'pattern' => $pattern,
+ 'pattern_type' => $patternType,
+ 'case_sensitive' => $caseSensitive,
+ 'revenue' => $revenue,
+ ),
+ "idsite = '$idSite' AND idgoal = '$idGoal'"
+ );
+ Piwik_Common::regenerateCacheWebsiteAttributes($idSite);
+ }
+
+ private function checkPatternIsValid($patternType, $pattern)
+ {
+ if($patternType == 'exact'
+ && substr($pattern, 0, 4) != 'http')
+ {
+ throw new Exception("If you choose 'exact match', the matching string must be a
+ URL starting with http:// or https://. For example, 'http://www.yourwebsite.com/newsletter/subscribed.html'.");
+ }
+ }
+
+ private function checkName($name)
+ {
+ return urldecode($name);
+ }
+
+ private function checkPattern($pattern)
+ {
+ return urldecode($pattern);
+ }
+
+ public function deleteGoal( $idSite, $idGoal )
+ {
+ Piwik::checkUserHasAdminAccess($idSite);
+ Zend_Registry::get('db')->query("UPDATE ".Piwik::prefixTable('goal')."
+ SET deleted = 1
+ WHERE idsite = ?
+ AND idgoal = ?",
+ array($idSite, $idGoal));
+ $db->query("DELETE FROM ".Piwik::prefixTable("log_conversion")." WHERE idgoal = ?", $idGoal);
+ Piwik_Common::regenerateCacheWebsiteAttributes($idSite);
+ }
+
+// public function getConversionsReturningVisitors( $idSite, $period, $date, $idGoal = false )
+// {
+//
+// }
+//
+// public function getConversionsNewVisitors( $idSite, $period, $date, $idGoal = false )
+// {
+//
+// }
+
+ // TODO
+ public function getConversionRateReturningVisitors( $idSite, $period, $date, $idGoal = false )
+ {
+ // visits converted for returning for all goals = call Frequency API
+ if($idGoal === false)
+ {
+ $request = new Piwik_API_Request("method=VisitFrequency.getConvertedVisitsReturning&idSite=$idSite&period=$period&date=$date&format=original");
+ $nbVisitsConvertedReturningVisitors = $request->process();
+ }
+ // visits converted for returning = nb conversion for this goal
+ else
+ {
+ $nbVisitsConvertedReturningVisitors = $this->getNumeric($idSite, $period, $date, Piwik_Goals::getRecordName('nb_conversions', $idGoal, 1));
+ }
+ // all returning visits
+ $request = new Piwik_API_Request("method=VisitFrequency.getVisitsReturning&idSite=$idSite&period=$period&date=$date&format=original");
+ $nbVisitsReturning = $request->process();
+// echo $nbVisitsConvertedReturningVisitors;
+// echo "<br>". $nbVisitsReturning;exit;
+ return $this->getPercentage($nbVisitsConvertedReturningVisitors, $nbVisitsReturning);
+ }
+
+ public function getConversionRateNewVisitors( $idSite, $period, $date, $idGoal = false )
+ {
+ // new visits converted for all goals = nb visits converted - nb visits converted for returning
+ if($idGoal == false)
+ {
+ $request = new Piwik_API_Request("method=VisitsSummary.getVisitsConverted&idSite=$idSite&period=$period&date=$date&format=original");
+ $convertedVisits = $request->process();
+ $request = new Piwik_API_Request("method=VisitFrequency.getConvertedVisitsReturning&idSite=$idSite&period=$period&date=$date&format=original");
+ $convertedReturningVisits = $request->process();
+ $convertedNewVisits = $convertedVisits - $convertedReturningVisits;
+ }
+ // new visits converted for a given goal = nb conversion for this goal for new visits
+ else
+ {
+ $convertedNewVisits = $this->getNumeric($idSite, $period, $date, Piwik_Goals::getRecordName('nb_conversions', $idGoal, 0));
+ }
+ // all new visits = all visits - all returning visits
+ $request = new Piwik_API_Request("method=VisitFrequency.getVisitsReturning&idSite=$idSite&period=$period&date=$date&format=original");
+ $nbVisitsReturning = $request->process();
+ $request = new Piwik_API_Request("method=VisitsSummary.getVisits&idSite=$idSite&period=$period&date=$date&format=original");
+ $nbVisits = $request->process();
+ $newVisits = $nbVisits - $nbVisitsReturning;
+ return $this->getPercentage($convertedNewVisits, $newVisits);
+ }
+
+ protected function getPercentage($a, $b)
+ {
+ if($b == 0)
+ {
+ return 0;
+ }
+ return round(100 * $a / $b, Piwik_Goals::ROUNDING_PRECISION);
+ }
+
+ public function get( $idSite, $period, $date, $idGoal = false )
+ {
+ Piwik::checkUserHasViewAccess( $idSite );
+ $archive = Piwik_Archive::build($idSite, $period, $date );
+ $toFetch = array( Piwik_Goals::getRecordName('nb_conversions', $idGoal),
+ Piwik_Goals::getRecordName('conversion_rate', $idGoal),
+ Piwik_Goals::getRecordName('revenue', $idGoal),
+ );
+ $dataTable = $archive->getDataTableFromNumeric($toFetch);
+ return $dataTable;
+ }
+
+ protected static function getNumeric( $idSite, $period, $date, $toFetch )
+ {
+ Piwik::checkUserHasViewAccess( $idSite );
+ $archive = Piwik_Archive::build($idSite, $period, $date );
+ $dataTable = $archive->getNumeric($toFetch);
+ return $dataTable;
+ }
+
+ public function getConversions( $idSite, $period, $date, $idGoal = false )
+ {
+ return self::getNumeric( $idSite, $period, $date, Piwik_Goals::getRecordName('nb_conversions', $idGoal));
+ }
+
+ public function getConversionRate( $idSite, $period, $date, $idGoal = false )
+ {
+ return self::getNumeric( $idSite, $period, $date, Piwik_Goals::getRecordName('conversion_rate', $idGoal));
+ }
+
+ public function getRevenue( $idSite, $period, $date, $idGoal = false )
+ {
+ return self::getNumeric( $idSite, $period, $date, Piwik_Goals::getRecordName('revenue', $idGoal));
+ }
+
+}
diff --git a/plugins/Goals/Controller.php b/plugins/Goals/Controller.php
new file mode 100644
index 0000000000..935d616258
--- /dev/null
+++ b/plugins/Goals/Controller.php
@@ -0,0 +1,150 @@
+<?php
+require_once "Goals/API.php";
+
+class Piwik_Goals_Controller extends Piwik_Controller
+{
+ const CONVERSION_RATE_PRECISION = 1;
+ function goalReport()
+ {
+ $idGoal = Piwik_Common::getRequestVar('idGoal', null, 'int');
+ $idSite = Piwik_Common::getRequestVar('idSite');
+ $goals = Piwik_Goals_API::getGoals($idSite);
+ if(!isset($goals[$idGoal]))
+ {
+ throw new Exception("idgoal $idGoal not valid.");
+ }
+ $goalDefinition = $goals[$idGoal];
+
+ $view = new Piwik_View('Goals/templates/single_goal.tpl');
+ $view->currency = Piwik::getCurrency();
+ $goal = $this->getMetricsForGoal($idGoal);
+ foreach($goal as $name => $value)
+ {
+ $view->$name = $value;
+ }
+ $view->name = $goalDefinition['name'];
+ $view->title = $goalDefinition['name'] . ' - Conversions';
+ $view->graphEvolution = $this->getLastNbConversionsGraph(true);
+ $view->nameGraphEvolution = 'GoalsgetLastNbConversionsGraph'; // must be the function name used above
+
+ $columnNbConversions = 'goal_'.$idGoal.'_nb_conversions';
+ $columnConversionRate = 'goal_'.$idGoal.'_conversion_rate';
+
+ $topSegmentsToLoad = array(
+ 'country' => 'UserCountry.getCountry',
+ 'keyword' => 'Referers.getKeywords',
+ 'website' => 'Referers.getWebsites',
+ );
+
+ $topSegments = array();
+ foreach($topSegmentsToLoad as $segmentName => $apiMethod)
+ {
+ $request = new Piwik_API_Request("method=$apiMethod
+ &format=original
+ &filter_update_columns_when_show_all_goals=1
+ &filter_sort_order=desc
+ &filter_sort_column=$columnNbConversions
+ &filter_limit=3");
+ $datatable = $request->process();
+ $topSegment = array();
+ foreach($datatable->getRows() as $row)
+ {
+ $topSegment[] = array (
+ 'name' => $row->getColumn('label'),
+ 'nb_conversions' => $row->getColumn($columnNbConversions),
+ 'conversion_rate' => $row->getColumn($columnConversionRate),
+ 'metadata' => $row->getMetadata(),
+ );
+ }
+ $topSegments[$segmentName] = $topSegment;
+// echo $datatable;
+ }
+
+ $request = new Piwik_API_Request("method=Goals.getConversionRateReturningVisitors&format=original");
+ $view->conversion_rate_returning = round( $request->process(), self::CONVERSION_RATE_PRECISION );
+ $request = new Piwik_API_Request("method=Goals.getConversionRateNewVisitors&format=original");
+ $view->conversion_rate_new = round( $request->process(), self::CONVERSION_RATE_PRECISION );
+
+ $view->topSegments = $topSegments;
+ echo $view->render();
+ //todo next: nice legends for graphs
+ }
+
+ protected function getMetricsForGoal($goalId)
+ {
+ $request = new Piwik_API_Request("method=Goals.get&format=original&idGoal=$goalId");
+ $datatable = $request->process();
+ return array (
+ 'id' => $goalId,
+ 'nb_conversions' => $datatable->getRowFromLabel(Piwik_Goals::getRecordName('nb_conversions', $goalId))->getColumn('value'),
+ 'conversion_rate' => round($datatable->getRowFromLabel(Piwik_Goals::getRecordName('conversion_rate', $goalId))->getColumn('value'), 1),
+ 'revenue' => $datatable->getRowFromLabel(Piwik_Goals::getRecordName('revenue', $goalId))->getColumn('value'),
+ 'urlSparklineConversions' => $this->getUrlSparkline('getLastNbConversionsGraph', $goalId) . "&idGoal=".$goalId,
+ 'urlSparklineConversionRate' => $this->getUrlSparkline('getLastConversionRateGraph', $goalId) . "&idGoal=".$goalId,
+ 'urlSparklineRevenue' => $this->getUrlSparkline('getLastRevenueGraph', $goalId) . "&idGoal=".$goalId,
+ );
+ }
+
+ function index()
+ {
+ $view = new Piwik_View('Goals/templates/overview.tpl');
+ $view->currency = Piwik::getCurrency();
+
+ $view->title = 'All goals - evolution';
+ $view->graphEvolution = $this->getLastNbConversionsGraph(true);
+ $view->nameGraphEvolution = 'GoalsgetLastNbConversionsGraph'; // must be the function name used above
+
+ // sparkline for the historical data of the above values
+ $view->urlSparklineConversions = $this->getUrlSparkline('getLastNbConversionsGraph');
+ $view->urlSparklineConversionRate = $this->getUrlSparkline('getLastConversionRateGraph');
+ $view->urlSparklineRevenue = $this->getUrlSparkline('getLastRevenueGraph');
+
+ $request = new Piwik_API_Request("method=Goals.get&format=original");
+ $datatable = $request->process();
+ $view->nb_conversions = $datatable->getRowFromLabel('Goal_nb_conversions')->getColumn('value');
+ $view->conversion_rate = $datatable->getRowFromLabel('Goal_conversion_rate')->getColumn('value');
+ $view->revenue = $datatable->getRowFromLabel('Goal_revenue')->getColumn('value');
+
+ $goalMetrics = array();
+
+ $idSite = Piwik_Common::getRequestVar('idSite');
+ $goals = Piwik_Goals_API::getGoals($idSite);
+ foreach($goals as $idGoal => $goal)
+ {
+ $goalMetrics[$idGoal] = $this->getMetricsForGoal($idGoal);
+ $goalMetrics[$idGoal]['name'] = $goal['name'];
+ }
+
+ $view->goalMetrics = $goalMetrics;
+ $view->goals = $goals;
+ $view->goalsJSON = json_encode($goals);
+ $view->userCanEditGoals = Piwik::isUserHasAdminAccess($idSite);
+ echo $view->render();
+ }
+
+ function addNewGoal()
+ {
+ $view = new Piwik_View('Goals/templates/add_new_goal.tpl');
+ $idSite = Piwik_Common::getRequestVar('idSite');
+ $view->userCanEditGoals = Piwik::isUserHasAdminAccess($idSite);
+ $view->currency = Piwik::getCurrency();
+ $view->onlyShowAddNewGoal = true;
+ echo $view->render();
+ }
+
+ function getLastNbConversionsGraph( $fetch = false )
+ {
+ $view = $this->getLastUnitGraph($this->pluginName, __FUNCTION__, 'Goals.getConversions');
+ return $this->renderView($view, $fetch);
+ }
+ function getLastConversionRateGraph( $fetch = false )
+ {
+ $view = $this->getLastUnitGraph($this->pluginName, __FUNCTION__, 'Goals.getConversionRate');
+ return $this->renderView($view, $fetch);
+ }
+ function getLastRevenueGraph( $fetch = false )
+ {
+ $view = $this->getLastUnitGraph($this->pluginName, __FUNCTION__, 'Goals.getRevenue');
+ return $this->renderView($view, $fetch);
+ }
+}
diff --git a/plugins/Goals/Goals.php b/plugins/Goals/Goals.php
new file mode 100644
index 0000000000..550f0e7288
--- /dev/null
+++ b/plugins/Goals/Goals.php
@@ -0,0 +1,206 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id$
+ *
+ * @package Piwik_Referers
+ */
+
+require_once "core/Tracker/GoalManager.php";
+
+/**
+ * @package Piwik_Referers
+ */
+class Piwik_Goals extends Piwik_Plugin
+{
+ const ROUNDING_PRECISION = 2;
+
+ public function getInformation()
+ {
+ $info = array(
+ 'name' => '(ALPHA) Goal Tracking',
+ 'description' => 'Create Goals and see reports about your goal conversions: evolution over time, revenue per visit, conversions per referer, per keyword, etc.',
+ 'author' => 'Piwik',
+ 'homepage' => 'http://piwik.org/',
+ 'version' => '0.1',
+ 'TrackerPlugin' => true
+ );
+
+ return $info;
+ }
+
+ function getListHooksRegistered()
+ {
+ $hooks = array(
+ 'Common.fetchWebsiteAttributes' => 'fetchGoalsFromDb',
+ 'ArchiveProcessing_Day.compute' => 'archiveDay',
+ 'ArchiveProcessing_Period.compute' => 'archivePeriod',
+ 'WidgetsList.add' => 'addWidgets',
+ 'Menu.add' => 'addMenus',
+ );
+ return $hooks;
+ }
+
+ function fetchGoalsFromDb($notification)
+ {
+ require_once "Goals/API.php";
+ $info = $notification->getNotificationInfo();
+ $idsite = $info['idsite'];
+
+ // add the 'goal' entry in the website array
+ $array =& $notification->getNotificationObject();
+ $array['goals'] = Piwik_Goals_API::getGoals($idsite);
+ }
+
+ function addWidgets()
+ {
+// Piwik_AddWidget( 'Referers', 'getKeywords', Piwik_Translate('Referers_WidgetKeywords'));
+ }
+
+ function addMenus()
+ {
+ $goals = Piwik_Tracker_GoalManager::getGoalDefinitions(Piwik_Common::getRequestVar('idSite'));
+ if(count($goals)==0)
+ {
+ Piwik_AddMenu('Goals', 'Add a new Goal', array('module' => 'Goals', 'action' => 'addNewGoal'));
+ }
+ else
+ {
+ Piwik_AddMenu('Goals', 'Overview', array('module' => 'Goals'));
+ foreach($goals as $goal)
+ {
+ Piwik_AddMenu('Goals', str_replace('%', '%%', $goal['name']), array('module' => 'Goals', 'action' => 'goalReport', 'idGoal' => $goal['idgoal']));
+ }
+ }
+ }
+
+ /**
+ * @param string $recordName 'nb_conversions'
+ * @param int $idGoal idGoal to return the metrics for, or false to return overall
+ * @param int $visitorReturning 0 for new visitors, 1 for returning visitors, false for all
+ * @return unknown
+ */
+ static public function getRecordName($recordName, $idGoal = false, $visitorReturning = false)
+ {
+ $idGoalStr = $returningStr = '';
+ if($idGoal !== false)
+ {
+ $idGoalStr = $idGoal . "_";
+ }
+ if($visitorReturning !== false)
+ {
+ $returningStr = 'visitor_returning_' . $visitorReturning . '_';
+ }
+ return 'Goal_' . $returningStr . $idGoalStr . $recordName;
+ }
+
+ function archivePeriod($notification )
+ {
+ /**
+ * @var Piwik_ArchiveProcessing_Period
+ */
+ $archiveProcessing = $notification->getNotificationObject();
+
+ $metricsToSum = array( 'nb_conversions', 'revenue');
+ $goalIdsToSum = Piwik_Tracker_GoalManager::getGoalIds($archiveProcessing->idsite);
+
+ $fieldsToSum = array();
+ foreach($metricsToSum as $metricName)
+ {
+ foreach($goalIdsToSum as $goalId)
+ {
+ $fieldsToSum[] = self::getRecordName($metricName, $goalId);
+ $fieldsToSum[] = self::getRecordName($metricName, $goalId, 0);
+ $fieldsToSum[] = self::getRecordName($metricName, $goalId, 1);
+ }
+ $fieldsToSum[] = self::getRecordName($metricName);
+ }
+ $records = $archiveProcessing->archiveNumericValuesSum($fieldsToSum);
+
+ // also recording conversion_rate for each goal
+ foreach($goalIdsToSum as $goalId)
+ {
+ $nb_conversions = $records[self::getRecordName('nb_conversions', $goalId)]->value;
+ $conversion_rate = $this->getConversionRate($nb_conversions, $archiveProcessing);
+ $record = new Piwik_ArchiveProcessing_Record_Numeric(self::getRecordName('conversion_rate', $goalId), $conversion_rate);
+ }
+
+ // global conversion rate
+ $nb_conversions = $records[self::getRecordName('nb_conversions')]->value;
+ $conversion_rate = $this->getConversionRate($nb_conversions, $archiveProcessing);
+ $record = new Piwik_ArchiveProcessing_Record_Numeric(self::getRecordName('conversion_rate'), $conversion_rate);
+
+ }
+
+ function archiveDay( $notification )
+ {
+ /**
+ * @var Piwik_ArchiveProcessing_Day
+ */
+ $archiveProcessing = $notification->getNotificationObject();
+
+ // by processing visitor_returning segment, we can also simply sum and get stats for all goals.
+ $query = $archiveProcessing->queryConversionsBySegment('visitor_returning');
+
+ $nb_conversions = $revenue = 0;
+ $goals = $goalsByVisitorReturning = array();
+ while($row = $query->fetch() )
+ {
+ $goalsByVisitorReturning[$row['idgoal']][$row['visitor_returning']] = $archiveProcessing->getGoalRowFromQueryRow($row);
+
+ if(!isset($goals[$row['idgoal']])) $goals[$row['idgoal']] = $archiveProcessing->getNewGoalRow();
+ $archiveProcessing->updateGoalStats($row, $goals[$row['idgoal']]);
+
+ $revenue += $row['revenue'];
+ $nb_conversions += $row['nb_conversions'];
+ }
+
+ // Stats by goal, for all visitors
+ foreach($goals as $idgoal => $values)
+ {
+ foreach($values as $metricId => $value)
+ {
+ $metricName = Piwik_Archive::$mappingFromIdToNameGoal[$metricId];
+ $recordName = self::getRecordName($metricName, $idgoal);
+ $record = new Piwik_ArchiveProcessing_Record_Numeric($recordName, $value);
+ }
+ $conversion_rate = $this->getConversionRate($values[Piwik_Archive::INDEX_GOAL_NB_CONVERSIONS], $archiveProcessing);
+ $recordName = self::getRecordName('conversion_rate', $idgoal);
+ $record = new Piwik_ArchiveProcessing_Record_Numeric($recordName, $conversion_rate);
+ }
+
+ // Stats by goal, for visitor returning / non returning
+ foreach($goalsByVisitorReturning as $idgoal => $values)
+ {
+ foreach($values as $visitor_returning => $goalValues)
+ {
+ foreach($goalValues as $metricId => $value)
+ {
+ $metricName = Piwik_Archive::$mappingFromIdToNameGoal[$metricId];
+ $recordName = self::getRecordName($metricName, $idgoal, $visitor_returning);
+ $record = new Piwik_ArchiveProcessing_Record_Numeric($recordName, $value);
+// echo $record . "<br>";
+ }
+ }
+ }
+
+ // Stats for all goals
+ $totalAllGoals = array(
+ self::getRecordName('conversion_rate') => round(100 * $archiveProcessing->getNumberOfVisitsConverted() / $archiveProcessing->getNumberOfVisits(), self::ROUNDING_PRECISION),
+ self::getRecordName('nb_conversions') => $nb_conversions,
+ self::getRecordName('revenue') => $revenue,
+ );
+ foreach($totalAllGoals as $recordName => $value)
+ {
+ $record = new Piwik_ArchiveProcessing_Record_Numeric($recordName, $value);
+ }
+ }
+
+ function getConversionRate($count, $archiveProcessing)
+ {
+ return round(100 * $count / $archiveProcessing->getNumberOfVisits(), self::ROUNDING_PRECISION);
+ }
+}
diff --git a/plugins/Goals/templates/GoalForm.js b/plugins/Goals/templates/GoalForm.js
new file mode 100644
index 0000000000..4345bc8958
--- /dev/null
+++ b/plugins/Goals/templates/GoalForm.js
@@ -0,0 +1,124 @@
+
+function showAddNewGoal()
+{
+ $("#GoalForm").show();
+ $("#EditGoals").hide();
+ $.scrollTo("#AddEditGoals", 400);
+ return false;
+}
+
+function showEditGoals()
+{
+ $("#EditGoals").show();
+ $("#GoalForm").hide();
+ $.scrollTo("#AddEditGoals", 400);
+ return false;
+}
+
+// init the goal form with existing goal value, if any
+function initGoalForm(goalMethodAPI, submitText, goalName, matchAttribute, pattern, patternType, caseSensitive, revenue, goalId)
+{
+ $('#goal_name').val(goalName);
+ $('input[@name=match_attribute][value='+matchAttribute+']').attr('checked', true);
+ $('#match_attribute_name').html(mappingMatchTypeName[matchAttribute]);
+ $('#examples_pattern').html(mappingMatchTypeExamples[matchAttribute]);
+ $('option[value='+patternType+']').attr('selected', true);
+ $('input[name=pattern]').val(pattern);
+ $('#case_sensitive').attr('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 initAndShowAddGoalForm()
+{
+ initGoalForm('Goals.addGoal', 'Add Goal', '', 'url', '', 'contains', false, '0');
+ return showAddNewGoal();
+}
+function bindGoalForm()
+{
+ $('input[@name=match_attribute]').click( function() {
+ var matchTypeId = $(this).attr('value');
+ $('#match_attribute_name').html(mappingMatchTypeName[matchTypeId]);
+ $('#examples_pattern').html(mappingMatchTypeExamples[matchTypeId]);
+ });
+
+ $('#goal_submit').click( function() {
+ // prepare ajax query to API to add goal
+ ajaxRequestAddGoal = getAjaxAddGoal();
+ $.ajax( ajaxRequestAddGoal );
+ return false;
+ });
+
+ $('a[name=linkAddNewGoal]').click( function(){
+ initAndShowAddGoalForm();
+ } );
+}
+
+function bindListGoalEdit()
+{
+ $('a[name=linkEditGoal]').click( function() {
+ var goalId = $(this).attr('id');
+ var goal = piwik.goals[goalId];
+ initGoalForm("Goals.updateGoal", "Update Goal", goal.name, goal.match_attribute, goal.pattern, goal.pattern_type, (goal.case_sensitive=='0' ? false : true), goal.revenue, goalId);
+ showAddNewGoal();
+ return false;
+ });
+
+ $('a[name=linkDeleteGoal]').click( function() {
+ var goalId = $(this).attr('id');
+ var goalName = 'test goal';//piwik.goals[goalId][name]
+ if(confirm(sprintf('Are you sure you want to delete the Goal %s?','"'+goalName+'"')))
+ {
+ $.ajax( getAjaxDeleteGoal( goalId ) );
+ return false;
+ }
+ });
+
+ $('a[name=linkEditGoals]').click( function(){
+ return showEditGoals();
+ } );
+}
+function getAjaxDeleteGoal(idGoal)
+{
+ var ajaxRequest = getStandardAjaxConf();
+ toggleAjaxLoading();
+
+ var parameters = new Object;
+ parameters.idSite = piwik.idSite;
+ parameters.idGoal = idGoal;
+ parameters.method = 'Goals.deleteGoal';
+ parameters.module = 'API';
+ parameters.format = 'json';
+ parameters.token_auth = piwik.token_auth;
+ ajaxRequest.data = parameters;
+ return ajaxRequest;
+}
+
+function getAjaxAddGoal()
+{
+ var ajaxRequest = getStandardAjaxConf();
+ toggleAjaxLoading();
+
+ var parameters = new Object;
+
+ parameters.idSite = piwik.idSite;
+ parameters.name = encodeURIComponent( $('#goal_name').val() );
+ 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').attr('checked') == true ? 1: 0;
+ parameters.revenue = $('input[name=revenue]').val();
+
+ parameters.idGoal = $('input[name=goalIdUpdate]').val();
+ parameters.method = $('input[name=methodGoalAPI]').val();
+ parameters.module = 'API';
+ parameters.format = 'json';
+ parameters.token_auth = piwik.token_auth;
+
+ ajaxRequest.data = parameters;
+ return ajaxRequest;
+} \ No newline at end of file
diff --git a/plugins/Goals/templates/add_edit_goal.tpl b/plugins/Goals/templates/add_edit_goal.tpl
new file mode 100644
index 0000000000..2ca55ca102
--- /dev/null
+++ b/plugins/Goals/templates/add_edit_goal.tpl
@@ -0,0 +1,49 @@
+
+<div id="AddEditGoals">
+{if isset($onlyShowAddNewGoal)}
+ <h2>Add a new Goal</h2>
+{else}
+ <h2><a onclick='' name="linkAddNewGoal">+ Add a new Goal</a>
+ or <a onclick='' name="linkEditGoals">Edit</a> existing Goals</h2>
+{/if}
+
+ <div>
+ <div id="ajaxError" style="display:none"></div>
+ <div id="ajaxLoading" style="display:none"><div id="loadingPiwik"><img src="themes/default/images/loading-blue.gif" alt="" /> {'General_LoadingData'|translate}</div></div>
+ </div>
+
+{if !isset($onlyShowAddNewGoal)}
+ {include file="Goals/templates/list_goal_edit.tpl"}
+{/if}
+ {include file="Goals/templates/form_add_goal.tpl"}
+
+ <a id='bottom'></a>
+</div>
+
+{literal}
+<script type="text/javascript" src="plugins/Goals/templates/GoalForm.js"></script>
+<script language="javascript">
+
+var mappingMatchTypeName = {
+ "url": "URL",
+ "file": "filename",
+ "external_website": "external website URL"
+};
+var mappingMatchTypeExamples = {
+ "url": "eg. contains 'checkout/confirmation'<br>eg. is exactly 'http://example.com/thank-you.html'<br>eg. matches the expression '[.*]\\\/demo\\\/[.*]'",
+ "file": "eg. contains 'files/brochure.pdf'<br>eg. is exactly 'http://example.com/files/brochure.pdf'<br>eg. matches the expression '[.*]\\\.zip'",
+ "external_website": "eg. contains 'amazon.com'<br>eg. is exactly 'http://mypartner.com/landing.html'<br>eg. matches the expression 'http://www.amazon.com\\\/[.*]\\\/yourAffiliateId'"
+};
+
+bindGoalForm();
+
+{/literal}
+
+{if !isset($onlyShowAddNewGoal)}
+piwik.goals = {$goalsJSON};
+bindListGoalEdit();
+{else}
+initAndShowAddGoalForm();
+{/if}
+
+</script>
diff --git a/plugins/Goals/templates/add_new_goal.tpl b/plugins/Goals/templates/add_new_goal.tpl
new file mode 100644
index 0000000000..05a7f66e8d
--- /dev/null
+++ b/plugins/Goals/templates/add_new_goal.tpl
@@ -0,0 +1,8 @@
+
+{if $userCanEditGoals}
+ {include file=Goals/templates/add_edit_goal.tpl}
+{else}
+Only an Administrator or the Super User can add Goals for a given website.
+Please ask your Piwik administrator to set up a Goal for your website.
+<br>Tracking Goals are a great tool to help you maximize your website performance!
+{/if}
diff --git a/plugins/Goals/templates/form_add_goal.tpl b/plugins/Goals/templates/form_add_goal.tpl
new file mode 100644
index 0000000000..95c4f6d3d9
--- /dev/null
+++ b/plugins/Goals/templates/form_add_goal.tpl
@@ -0,0 +1,67 @@
+{literal}
+<style>
+.goalInlineHelp{
+color:#9B9B9B;
+}
+</style>
+{/literal}
+<span id='GoalForm' style="display:none;">
+<form>
+ <table class="tableForm">
+ <tr>
+ <td>Goal Name </td>
+ <td><input type="text" name="name" value="" id="goal_name" /></td>
+ </tr>
+ <tr>
+ <td>Goal is triggered when visitors:</td>
+ <td>
+ <input type="radio" id="match_attribute_url" value="url" name="match_attribute"/>
+ <label for="match_attribute_url">Visit a given URL (page or group of pages)</label>
+ <br>
+ <input type="radio" id="match_attribute_file" value="file" name="match_attribute"/>
+ <label for="match_attribute_file">Download a file</label>
+ <br>
+ <input type="radio" id="match_attribute_external_website" value="external_website" name="match_attribute"/>
+ <label for="match_attribute_external_website">Click on a Link to an external website </label>
+ </td>
+ </tr>
+ <tr>
+ <td>where the <span id="match_attribute_name"></span></td>
+ <td>
+ <select name="pattern_type">
+ <option value="contains">contains</option>
+ <option value="exact">is exactly</option>
+ <option value="regex">matches the expression</option>
+ </select>
+
+ <input type="text" name="pattern" value=""/>
+ <br>
+ <div id="examples_pattern" class="goalInlineHelp"></div>
+ <br>
+ <span style="float:right">
+ (optional) <input type="checkbox" id="case_sensitive"/>
+ <label for="case_sensitive">Case sensitive match</label>
+ </span>
+ </td>
+ </tr>
+ <tr>
+ <td>(optional) Goal default value is </td>
+ <td>{$currency} <input type="text" name="revenue" size="1" value="0"/>
+ <div class="goalInlineHelp">
+ For example, a Contact Form submitted by a visitor <br>
+ may be worth $10 on average. Piwik will help you understand <br>
+ how well your visitors segments are performing.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2" style="border:0">
+ <div class="submit">
+ <input type="hidden" name="methodGoalAPI" value="">
+ <input type="hidden" name="goalIdUpdate" value="">
+ <input type="submit" value="Add Goal" name="submit" id="goal_submit" class="submit" />
+ </div>
+ </td>
+ </tr>
+ </table>
+</form>
+</span> \ No newline at end of file
diff --git a/plugins/Goals/templates/list_goal_edit.tpl b/plugins/Goals/templates/list_goal_edit.tpl
new file mode 100644
index 0000000000..081497cade
--- /dev/null
+++ b/plugins/Goals/templates/list_goal_edit.tpl
@@ -0,0 +1,22 @@
+<span id='EditGoals' style="display:none;">
+ <table class="tableForm">
+ <thead style="font-weight:bold">
+ <td>Id</td>
+ <td>Goal Name</td>
+ <td>Goal is Triggered when</td>
+ <td>Revenue</td>
+ <td>Edit</td>
+ <td>Delete</td>
+ </thead>
+ {foreach from=$goals item=goal}
+ <tr>
+ <td>{$goal.idgoal}</td>
+ <td>{$goal.name}</td>
+ <td>{$goal.match_attribute} <br>Pattern {$goal.pattern_type}: {$goal.pattern}</b></td>
+ <td>{if $goal.revenue==0}-{else}{$currency}{$goal.revenue}{/if}</td>
+ <td><a href='#' name="linkEditGoal" id="{$goal.idgoal}"><img src='plugins/UsersManager/images/edit.png' border=0> Edit</a></td>
+ <td><a href='#' name="linkDeleteGoal" id="{$goal.idgoal}"><img src='plugins/UsersManager/images/remove.png' border=0> Delete</a></td>
+ </tr>
+ {/foreach}
+ </table>
+</span> \ No newline at end of file
diff --git a/plugins/Goals/templates/list_top_segment.tpl b/plugins/Goals/templates/list_top_segment.tpl
new file mode 100644
index 0000000000..6dbe69813f
--- /dev/null
+++ b/plugins/Goals/templates/list_top_segment.tpl
@@ -0,0 +1,5 @@
+
+{foreach from=$topSegment item=element name=topGoalElements}
+<span class='goalTopElement' title='<b>{$element.nb_conversions}</b> conversions, <b>{$element.conversion_rate}%</b> conversion rate'>
+{$element.name}</span>{logoHtml metadata=$element.metadata alt=$element.name}{if $smarty.foreach.topGoalElements.iteration == $smarty.foreach.topGoalElements.total-1} and {elseif $smarty.foreach.topGoalElements.iteration < $smarty.foreach.topGoalElements.total-1}, {else}{/if}
+{/foreach} {* (<a href=''>more</a>) *}
diff --git a/plugins/Goals/templates/overview.tpl b/plugins/Goals/templates/overview.tpl
new file mode 100644
index 0000000000..04bf1af90d
--- /dev/null
+++ b/plugins/Goals/templates/overview.tpl
@@ -0,0 +1,27 @@
+
+{include file="Goals/templates/title_and_evolution_graph.tpl"}
+
+{foreach from=$goalMetrics item=goal}
+{assign var=nb_conversions value=$goal.nb_conversions}
+{assign var=conversion_rate value=$goal.conversion_rate}
+<h2 style="padding-top: 30px;">{$goal.name} (goal)</h3>
+<table width=700px>
+ <tr><td>
+ <p>{sparkline src=$goal.urlSparklineConversions}<span>
+ {'%s conversions'|translate:"<strong>$nb_conversions</strong>"}</span></p>
+ </td><td>
+ <p>{sparkline src=$goal.urlSparklineConversionRate}<span>
+ {'%s conversion rate'|translate:"<strong>$conversion_rate%</strong>"}</span></p>
+ </td><td>
+ {* (<a href=''>more</a>) *}
+ </td></tr>
+</table>
+
+{/foreach}
+
+{if $userCanEditGoals}
+ <hr style="margin:30px 0px">
+ {include file=Goals/templates/add_edit_goal.tpl}
+{/if}
+
+{include file="Goals/templates/release_notes.tpl} \ No newline at end of file
diff --git a/plugins/Goals/templates/release_notes.tpl b/plugins/Goals/templates/release_notes.tpl
new file mode 100644
index 0000000000..dce9ec63b2
--- /dev/null
+++ b/plugins/Goals/templates/release_notes.tpl
@@ -0,0 +1,20 @@
+<hr>
+<b>About the Goal Tracking Plugin</b><br>
+<pre>
+The Goal Tracking Plugin is in alpha release. There is more coming soon!
+- The Goal Report page will display conversion table by search engines, country, keyword, campaign, etc.
+- The Goal Overview page will link to a Goal Report page with a "(more)" link that will ajax reload the page
+- Goals could be triggered using javascript event, with custom revenue
+- internationalization of all strings
+- provide documentation, screenshots, blog post + add screenshot and inline help in "Add a New Goal"
+- provide widgets for the dashboard, general goal overview, and one widget for each goal. With: graph evolution, sparklines. Widget with top segments for each goal.
+
+Known bugs
+- The Goal total nb conversions should be sum of all goal conversions (wrong number when deleting a Goal)
+- After adding goal, the window should refresh to the goal report page, and not to the dashboard
+- Outlink trailing slash is automatically deleted from the URL, there would be a problem when trying to exact match a URL with trailing slash
+- All graph labelling are not correct (always printing nb_uniq_visitors even when showing conversion or conversion_rate) see <a href='http://dev.piwik.org/trac/ticket/322'>#322</a>
+
+Give us Feedback!
+If you find any other bug, or if you have suggestions, please send us a message using the "Give us feedback" link at the top of the Piwik pages.
+</pre> \ No newline at end of file
diff --git a/plugins/Goals/templates/single_goal.tpl b/plugins/Goals/templates/single_goal.tpl
new file mode 100644
index 0000000000..b605c17490
--- /dev/null
+++ b/plugins/Goals/templates/single_goal.tpl
@@ -0,0 +1,37 @@
+{include file="Goals/templates/title_and_evolution_graph.tpl"}
+
+{if $nb_conversions > 0}
+ <h2>Conversions Overview</h2>
+ <ul class="ulGoalTopElements">
+ <li>Your best converting countries are: {include file='Goals/templates/list_top_segment.tpl' topSegment=$topSegments.country}</li>
+ {if count($topSegments.keyword)>0}<li>Your top converting keywords are: {include file='Goals/templates/list_top_segment.tpl' topSegment=$topSegments.keyword}</li>{/if}
+ {if count($topSegments.website)>0}<li>Your best converting websites referers are: {include file='Goals/templates/list_top_segment.tpl' topSegment=$topSegments.website}</li>{/if}
+ <li>Returning visitors conversion rate is <b>{$conversion_rate_returning}%</b>, New Visitors conversion rate is <b>{$conversion_rate_new}%</b></li>
+ </ul>
+{/if}
+
+
+{literal}
+<style>
+ul.ulGoalTopElements {
+ list-style-type:circle;
+ margin-left:30px;
+}
+.ulGoalTopElements a {
+ text-decoration:none;
+ color:#0033CC;
+ border-bottom:1px dotted #0033CC;
+ line-height:2em;
+}
+.goalTopElement {
+ border-bottom:1px dotted;
+}
+</style>
+<script>
+$(document).ready( function() {
+ $('.goalTopElement')
+ .Tooltip()
+ ;
+ });
+</script>
+{/literal} \ No newline at end of file
diff --git a/plugins/Goals/templates/title_and_evolution_graph.tpl b/plugins/Goals/templates/title_and_evolution_graph.tpl
new file mode 100644
index 0000000000..0b1b8d2c7b
--- /dev/null
+++ b/plugins/Goals/templates/title_and_evolution_graph.tpl
@@ -0,0 +1,20 @@
+<script type="text/javascript" src="plugins/CoreHome/templates/sparkline.js"></script>
+
+<a name="evolutionGraph" graphId="{$nameGraphEvolution}"></a>
+<h2>{$title}</h2>
+{$graphEvolution}
+
+<table>
+ <tr><td>
+ <p>{sparkline src=$urlSparklineConversions}<span>
+ {'%s conversions'|translate:"<strong>$nb_conversions</strong>"}</span></p>
+ {if $revenue != 0 }
+ <p>{sparkline src=$urlSparklineRevenue}<span>
+ {'%s overall revenue'|translate:"<strong>$currency$revenue</strong>"}</span></p>
+ {/if}
+ </td><td valign="top">
+ <p>{sparkline src=$urlSparklineConversionRate}<span>
+ {'%s overall conversion rate (visits with a completed goal)'|translate:"<strong>$conversion_rate%</strong>"}</span></p>
+ </td></tr>
+</table>
+