1<?php
2/**
3 * Matomo - free/libre analytics platform
4 *
5 * @link https://matomo.org
6 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
7 *
8 */
9namespace Piwik\Plugins\Goals;
10
11use Piwik\API\Request;
12use Piwik\Columns\ComputedMetricFactory;
13use Piwik\Columns\Dimension;
14use Piwik\Columns\MetricsList;
15use Piwik\Common;
16use Piwik\Piwik;
17use Piwik\Plugin\ArchivedMetric;
18use Piwik\Plugin\ComputedMetric;
19use Piwik\Plugin\ReportsProvider;
20use Piwik\Plugins\CoreHome\SystemSummary;
21use Piwik\Tracker\GoalManager;
22use Piwik\Category\Subcategory;
23
24/**
25 *
26 */
27class Goals extends \Piwik\Plugin
28{
29    public static function getReportsWithGoalMetrics()
30    {
31        $dimensions = self::getAllReportsWithGoalMetrics();
32
33        $dimensionsByGroup = array();
34        foreach ($dimensions as $dimension) {
35            $group = $dimension['category'];
36            // move "Custom Variables" report to the "Goals/Sales by User attribute" category
37            if ($dimension['module'] === 'CustomVariables'
38                || $dimension['action'] == 'getVisitInformationPerServerTime') {
39                $group = 'VisitsSummary_VisitsSummary';
40            }
41            unset($dimension['category']);
42            $dimensionsByGroup[$group][] = $dimension;
43        }
44
45        return $dimensionsByGroup;
46    }
47
48    public static function getGoalIdFromGoalColumn($columnName)
49    {
50        if (strpos($columnName, 'goal_') === 0) {
51            $column = str_replace(array('goal_'), '', $columnName);
52            return (int) $column;
53        }
54    }
55
56    public static function makeGoalColumn($idGoal, $column, $forceInt = true)
57    {
58        if ($forceInt) { // in non-archiver code idGoal can be, eg, ecommerceOrder
59            $idGoal = (int) $idGoal;
60        }
61
62        return 'goal_'. $idGoal . '_' . $column;
63    }
64
65    public static function getGoalColumns($idGoal)
66    {
67        $columns = array(
68            'nb_conversions',
69            'nb_visits_converted',
70            'revenue',
71        );
72        if ($idGoal === false) {
73            return $columns;
74        }
75        // Orders
76        if ($idGoal === GoalManager::IDGOAL_ORDER) {
77            $columns = array_merge($columns, array(
78                                                  'revenue_subtotal',
79                                                  'revenue_tax',
80                                                  'revenue_shipping',
81                                                  'revenue_discount',
82                                             ));
83        }
84        // Abandoned carts & orders
85        if ($idGoal <= GoalManager::IDGOAL_ORDER) {
86            $columns[] = 'items';
87        }
88        return $columns;
89    }
90
91    /**
92     * @see \Piwik\Plugin::registerEvents
93     */
94    public function registerEvents()
95    {
96        $hooks = array(
97            'AssetManager.getJavaScriptFiles'        => 'getJsFiles',
98            'AssetManager.getStylesheetFiles'        => 'getStylesheetFiles',
99            'Tracker.Cache.getSiteAttributes'        => 'fetchGoalsFromDb',
100            'API.getReportMetadata.end'              => 'getReportMetadataEnd',
101            'SitesManager.deleteSite.end'            => 'deleteSiteGoals',
102            'Translate.getClientSideTranslationKeys' => 'getClientSideTranslationKeys',
103            'Metrics.getDefaultMetricTranslations'   => 'addMetricTranslations',
104            'Category.addSubcategories'              => 'addSubcategories',
105            'Metric.addMetrics'                      => 'addMetrics',
106            'Metric.addComputedMetrics'              => 'addComputedMetrics',
107            'System.addSystemSummaryItems'           => 'addSystemSummaryItems',
108        );
109        return $hooks;
110    }
111
112    public function addSystemSummaryItems(&$systemSummary)
113    {
114        $goalModel = new Model();
115        $numGoals = $goalModel->getActiveGoalCount();
116
117        $systemSummary[] = new SystemSummary\Item($key = 'goals', Piwik::translate('Goals_NGoals', $numGoals), $value = null, array('module' => 'Goals', 'action' => 'manage'), $icon = 'icon-goal', $order = 7);
118    }
119
120    public function addComputedMetrics(MetricsList $list, ComputedMetricFactory $computedMetricFactory)
121    {
122        $idSite = Common::getRequestVar('idSite', 0, 'int');
123        $goals = Request::processRequest('Goals.getGoals', ['idSite' => $idSite, 'filter_limit' => '-1'], $default = []);
124
125        foreach ($goals as $goal) {
126            $metric = $computedMetricFactory->createComputedMetric('goal_' .  $goal['idgoal'] . '_conversion', 'nb_uniq_visitors', ComputedMetric::AGGREGATION_RATE);
127            $goalName = '"' . Piwik::translate('Goals_GoalX', $goal['name']) . '"';
128            $metricName = Piwik::translate('Goals_ConversionRate', $goalName);
129            $metric->setTranslatedName($metricName);
130            $list->addMetric($metric);
131        }
132    }
133
134    public function addMetrics(MetricsList $metricsList)
135    {
136        $idSite = Common::getRequestVar('idSite', 0, 'int');
137        $goals = Request::processRequest('Goals.getGoals', ['idSite' => $idSite, 'filter_limit' => '-1'], $default = []);
138
139        foreach ($goals as $goal) {
140            $custom = new GoalDimension($goal, 'idgoal', 'Conversions goal "' . $goal['name'] . '" (ID ' . $goal['idgoal'] .' )');
141            $custom->setType(Dimension::TYPE_NUMBER);
142            $custom->setSqlSegment('count(distinct log_conversion.idvisit, log_conversion.buster)');
143
144            $metric = new ArchivedMetric($custom, ArchivedMetric::AGGREGATION_SUM);
145            $metric->setQuery('count(distinct log_conversion.idvisit, log_conversion.buster)');
146            $metric->setTranslatedName($custom->getName());
147            $metric->setDocumentation('The number of times this goal was converted.');
148            $metric->setCategory($custom->getCategoryId());
149            $metric->setName('goal_' . $goal['idgoal'] . '_conversion');
150            $metricsList->addMetric($metric);
151
152            $custom = new GoalDimension($goal, 'revenue', 'Revenue goal "' . $goal['name'] . '" (ID ' . $goal['idgoal'] .' )');
153            $custom->setType(Dimension::TYPE_MONEY);
154            $metric = new ArchivedMetric($custom, ArchivedMetric::AGGREGATION_SUM);
155            $metric->setTranslatedName($custom->getName());
156            $metric->setName('goal_' . $goal['idgoal'] . '_revenue');
157            $metric->setDocumentation('The amount of revenue that was generated by converting this goal.');
158            $metric->setCategory($custom->getCategoryId());
159            $metricsList->addMetric($metric);
160
161            $custom = new GoalDimension($goal, 'visitor_seconds_since_first', 'Days to conversion goal "' . $goal['name'] . '" (ID ' . $goal['idgoal'] .' )');
162            $custom->setType(Dimension::TYPE_NUMBER);
163            $metric = new ArchivedMetric($custom, ArchivedMetric::AGGREGATION_SUM);
164            $metric->setTranslatedName($custom->getName());
165            $metric->setCategory($custom->getCategoryId());
166            $metric->setDocumentation('The number of days it took a visitor to convert this goal.');
167            $metric->setName('goal_' . $goal['idgoal'] . '_daystoconversion');
168            $metric->setQuery('sum(floor(log_visit.visitor_seconds_since_first / 86400))');
169            $metricsList->addMetric($metric);
170
171            $custom = new GoalDimension($goal, 'visitor_count_visits', 'Visits to conversion goal "' . $goal['name'] . '" (ID ' . $goal['idgoal'] .' )');
172            $custom->setType(Dimension::TYPE_NUMBER);
173            $metric = new ArchivedMetric($custom, ArchivedMetric::AGGREGATION_SUM);
174            $metric->setTranslatedName($custom->getName());
175            $metric->setCategory($custom->getCategoryId());
176            $metric->setDocumentation('The number of visits it took a visitor to convert this goal.');
177            $metric->setName('goal_' . $goal['idgoal'] . '_visitstoconversion');
178            $metricsList->addMetric($metric);
179        }
180    }
181
182    public function addSubcategories(&$subcategories)
183    {
184        $idSite = Common::getRequestVar('idSite', 0, 'int');
185
186        if (!$idSite) {
187            // fallback for eg API.getReportMetadata which uses idSites
188            $idSite = Common::getRequestVar('idSites', 0, 'int');
189
190            if (!$idSite) {
191                return;
192            }
193        }
194
195        $goals = Request::processRequest('Goals.getGoals', ['idSite' => $idSite, 'filter_limit' => '-1'], $default = []);
196
197        $order = 900;
198        foreach ($goals as $goal) {
199            $category = new Subcategory();
200            $category->setName($goal['name']);
201            $category->setCategoryId('Goals_Goals');
202            $category->setId($goal['idgoal']);
203            $category->setOrder($order++);
204            $subcategories[] = $category;
205        }
206    }
207
208    public function addMetricTranslations(&$translations)
209    {
210        $metrics = array(
211            'orders'            => 'General_EcommerceOrders',
212            'ecommerce_revenue' => 'General_ProductRevenue',
213            'revenue_per_visit' => 'General_ColumnValuePerVisit',
214            'quantity'          => 'General_Quantity',
215            'avg_price'         => 'General_AveragePrice',
216            'avg_quantity'      => 'General_AverageQuantity',
217            'revenue_subtotal'  => 'General_Subtotal',
218            'revenue_tax'       => 'General_Tax',
219            'revenue_shipping'  => 'General_Shipping',
220            'revenue_discount'  => 'General_Discount',
221            'avg_order_revenue' => 'General_AverageOrderValue'
222        );
223
224        $metrics = array_map(array('\\Piwik\\Piwik', 'translate'), $metrics);
225
226        $translations = array_merge($translations, $metrics);
227    }
228
229    /**
230     * Delete goals recorded for this site
231     */
232    public function deleteSiteGoals($idSite)
233    {
234        $model = new Model();
235        $model->deleteGoalsForSite($idSite);
236    }
237
238    /**
239     * Returns the Metadata for the Goals plugin API.
240     * The API returns general Goal metrics: conv, conv rate and revenue globally
241     * and for each goal.
242     *
243     * Also, this will update metadata of all other reports that have Goal segmentation
244     */
245    public function getReportMetadataEnd(&$reports, $info)
246    {
247        // Processed in AddColumnsProcessedMetricsGoal
248        // These metrics will also be available for some reports, for each goal
249        // Example: Conversion rate for Goal 2 for the keyword 'piwik'
250        $goalProcessedMetrics = array(
251            'revenue_per_visit' => Piwik::translate('General_ColumnValuePerVisit'),
252        );
253
254        $goalMetrics = array(
255            'nb_conversions'  => Piwik::translate('Goals_ColumnConversions'),
256            'conversion_rate' => Piwik::translate('General_ColumnConversionRate'),
257            'revenue'         => Piwik::translate('General_ColumnRevenue')
258        );
259
260        $reportsWithGoals = self::getAllReportsWithGoalMetrics();
261
262        foreach ($reportsWithGoals as $reportWithGoals) {
263            // Select this report from the API metadata array
264            // and add the Goal metrics to it
265            foreach ($reports as &$apiReportToUpdate) {
266                if ($apiReportToUpdate['module'] == $reportWithGoals['module']
267                    && $apiReportToUpdate['action'] == $reportWithGoals['action']
268                    && empty($apiReportToUpdate['parameters'])) {
269                    $apiReportToUpdate['metricsGoal'] = $goalMetrics;
270                    $apiReportToUpdate['processedMetricsGoal'] = $goalProcessedMetrics;
271                    break;
272                }
273            }
274        }
275    }
276
277    private static function getAllReportsWithGoalMetrics()
278    {
279        $reportsWithGoals = array();
280
281        $reports = new ReportsProvider();
282
283        foreach ($reports->getAllReports() as $report) {
284            if ($report->hasGoalMetrics() && $report->isEnabled()) {
285                $reportsWithGoals[] = array(
286                    'category' => $report->getCategoryId(),
287                    'name'     => $report->getName(),
288                    'module'   => $report->getModule(),
289                    'action'   => $report->getAction(),
290                    'parameters' => $report->getParameters()
291                );
292            }
293        }
294
295        $reportsWithGoals[] = array('category' => 'General_Visit',
296            'name'     => Piwik::translate('Goals_VisitsUntilConv'),
297            'module'   => 'Goals',
298            'action'   => 'getVisitsUntilConversion',
299            'viewDataTable' => 'table',
300        );
301        $reportsWithGoals[] = array('category' => 'General_Visit',
302            'name'     => Piwik::translate('Goals_DaysToConv'),
303            'module'   => 'Goals',
304            'action'   => 'getDaysToConversion',
305            'viewDataTable' => 'table',
306        );
307
308        /**
309         * Triggered when gathering all reports that contain Goal metrics. The list of reports
310         * will be displayed on the left column of the bottom of every _Goals_ page.
311         *
312         * If plugins define reports that contain goal metrics (such as **conversions** or **revenue**),
313         * they can use this event to make sure their reports can be viewed on Goals pages.
314         *
315         * **Example**
316         *
317         *     public function getReportsWithGoalMetrics(&$reports)
318         *     {
319         *         $reports[] = array(
320         *             'category' => Piwik::translate('MyPlugin_myReportCategory'),
321         *             'name' => Piwik::translate('MyPlugin_myReportDimension'),
322         *             'module' => 'MyPlugin',
323         *             'action' => 'getMyReport'
324         *         );
325         *     }
326         *
327         * @param array &$reportsWithGoals The list of arrays describing reports that have Goal metrics.
328         *                                 Each element of this array must be an array with the following
329         *                                 properties:
330         *
331         *                                 - **category**: The report category. This should be a translated string.
332         *                                 - **name**: The report's translated name.
333         *                                 - **module**: The plugin the report is in, eg, `'UserCountry'`.
334         *                                 - **action**: The API method of the report, eg, `'getCountry'`.
335         * @ignore
336         * @deprecated since 2.5.0
337         */
338        Piwik::postEvent('Goals.getReportsWithGoalMetrics', array(&$reportsWithGoals));
339
340        return $reportsWithGoals;
341    }
342
343    public function getJsFiles(&$jsFiles)
344    {
345        $jsFiles[] = "plugins/Goals/angularjs/common/directives/goal-page-link.js";
346        $jsFiles[] = "plugins/Goals/angularjs/manage-goals/manage-goals.controller.js";
347        $jsFiles[] = "plugins/Goals/angularjs/manage-goals/manage-goals.directive.js";
348    }
349
350    public function getStylesheetFiles(&$stylesheets)
351    {
352        $stylesheets[] = "plugins/Goals/stylesheets/goals.css";
353    }
354
355    public function fetchGoalsFromDb(&$array, $idSite)
356    {
357        // add the 'goal' entry in the website array
358        $array['goals'] = API::getInstance()->getGoals($idSite);
359    }
360
361    public function getClientSideTranslationKeys(&$translationKeys)
362    {
363        $translationKeys[] = 'Goals_AddGoal';
364        $translationKeys[] = 'Goals_AddNewGoal';
365        $translationKeys[] = 'Goals_UpdateGoal';
366        $translationKeys[] = 'Goals_DeleteGoalConfirm';
367        $translationKeys[] = 'Goals_UpdateGoal';
368        $translationKeys[] = 'Goals_DeleteGoalConfirm';
369        $translationKeys[] = 'Goals_Ecommerce';
370        $translationKeys[] = 'Goals_Optional';
371        $translationKeys[] = 'Goals_TimeInMinutes';
372        $translationKeys[] = 'Goals_Pattern';
373        $translationKeys[] = 'Goals_ClickToViewThisGoal';
374    }
375}
376