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