Skip to content
Extraits de code Groupes Projets
API.php 62,2 ko
Newer Older
  • Learn to ignore specific revisions
  • <?php
    
    /**
     * Piwik - Open source web analytics
    
     * @link http://piwik.org
    
    robocoder's avatar
    robocoder a validé
     * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
    
    robocoder's avatar
    robocoder a validé
     * @category Piwik_Plugins
     * @package Piwik_API
    
    robocoder's avatar
    robocoder a validé
    /**
     * @package Piwik_API
     */
    
    class Piwik_API extends Piwik_Plugin
    {
    
        public function getInformation()
        {
            return array(
                'description'     => Piwik_Translate('API_PluginDescription'),
                'author'          => 'Piwik',
                'author_homepage' => 'http://piwik.org/',
                'version'         => Piwik_Version::VERSION,
            );
        }
    
        public function getListHooksRegistered()
        {
            return array(
                'AssetManager.getCssFiles' => 'getCssFiles',
                'TopMenu.add'              => 'addTopMenu',
            );
        }
    
        public function addTopMenu()
        {
            $apiUrlParams = array('module' => 'API', 'action' => 'listAllAPI');
            $tooltip = Piwik_Translate('API_TopLinkTooltip');
    
            Piwik_AddTopMenu('General_API', $apiUrlParams, true, 7, $isHTML = false, $tooltip);
    
            $this->addTopMenuMobileApp();
        }
    
        protected function addTopMenuMobileApp()
        {
            if (empty($_SERVER['HTTP_USER_AGENT'])) {
                return;
            }
            require_once PIWIK_INCLUDE_PATH . '/libs/UserAgentParser/UserAgentParser.php';
            $os = UserAgentParser::getOperatingSystem($_SERVER['HTTP_USER_AGENT']);
            if ($os && in_array($os['id'], array('AND', 'IPD', 'IPA', 'IPH'))) {
                Piwik_AddTopMenu('Piwik Mobile App', array('module' => 'Proxy', 'action' => 'redirect', 'url' => 'http://piwik.org/mobile/'), true, 4);
            }
        }
    
        /**
         * @param Piwik_Event_Notification $notification  notification object
         */
        public function getCssFiles($notification)
        {
            $cssFiles = & $notification->getNotificationObject();
    
            $cssFiles[] = "plugins/API/css/styles.css";
        }
    
    mattpiwik's avatar
    mattpiwik a validé
    
    
    
    robocoder's avatar
    robocoder a validé
    /**
    
    mattpiwik's avatar
    mattpiwik a validé
     * This API is the <a href='http://piwik.org/docs/analytics-api/metadata/' target='_blank'>Metadata API</a>: it gives information about all other available APIs methods, as well as providing
    
     * human readable and more complete outputs than normal API methods.
     *
     * Some of the information that is returned by the Metadata API:
    
    mattpiwik's avatar
    mattpiwik a validé
     * <ul>
    
     * <li>the dynamically generated list of all API methods via "getReportMetadata"</li>
    
    mattpiwik's avatar
    mattpiwik a validé
     * <li>the list of metrics that will be returned by each method, along with their human readable name, via "getDefaultMetrics" and "getDefaultProcessedMetrics"</li>
     * <li>the list of segments metadata supported by all functions that have a 'segment' parameter</li>
    
     * <li>the (truly magic) method "getProcessedReport" will return a human readable version of any other report, and include the processed metrics such as
     * conversion rate, time on site, etc. which are not directly available in other methods.
    
    mattpiwik's avatar
    mattpiwik a validé
     * </ul>
     * The Metadata API is for example used by the Piwik Mobile App to automatically display all Piwik reports, with translated report & columns names and nicely formatted values.
     * More information on the <a href='http://piwik.org/docs/analytics-api/metadata/' target='_blank'>Metadata API documentation page</a>
    
    robocoder's avatar
    robocoder a validé
     * @package Piwik_API
     */
    
    class Piwik_API_API
    
    mattpiwik's avatar
    mattpiwik a validé
    {
    
        static private $instance = null;
    
        /**
         * @return Piwik_API_API
         */
        static public function getInstance()
        {
            if (self::$instance == null) {
                self::$instance = new self;
            }
            return self::$instance;
        }
    
        /**
         * Get Piwik version
         * @return string
         */
        public function getPiwikVersion()
        {
            Piwik::checkUserHasSomeViewAccess();
            return Piwik_Version::VERSION;
        }
    
        /**
         * Returns the section [APISettings] if defined in config.ini.php
         * @return array
         */
        public function getSettings()
        {
            return Piwik_Config::getInstance()->APISettings;
        }
    
        /**
         * Derive the unit name from a column name
         * @param $column
         * @param $idSite
         * @return string
         * @ignore
         */
        static public function getUnit($column, $idSite)
        {
            $nameToUnit = array(
                '_rate'   => '%',
                'revenue' => Piwik::getCurrency($idSite),
                '_time_'  => 's'
            );
    
            foreach ($nameToUnit as $pattern => $type) {
                if (strpos($column, $pattern) !== false) {
                    return $type;
                }
            }
    
            return '';
        }
    
        /**
         * Is a lower value for a given column better?
         * @param $column
         * @return bool
         *
         * @ignore
         */
        static public function isLowerValueBetter($column)
        {
            $lowerIsBetterPatterns = array(
                'bounce', 'exit'
            );
    
            foreach ($lowerIsBetterPatterns as $pattern) {
                if (strpos($column, $pattern) !== false) {
                    return true;
                }
            }
    
            return false;
        }
    
        /**
         * Default translations for many core metrics.
         * This is used for exports with translated labels. The exports contain columns that
         * are not visible in the UI and not present in the API meta data. These columns are
         * translated here.
         * @return array
         */
        static public function getDefaultMetricTranslations()
        {
            $trans = array(
                'label'                         => 'General_ColumnLabel',
                'date'                          => 'General_Date',
                'avg_time_on_page'              => 'General_ColumnAverageTimeOnPage',
                'sum_time_spent'                => 'General_ColumnSumVisitLength',
                'sum_visit_length'              => 'General_ColumnSumVisitLength',
                'bounce_count'                  => 'General_ColumnBounces',
                'bounce_count_returning'        => 'VisitFrequency_ColumnBounceCountForReturningVisits',
                'max_actions'                   => 'General_ColumnMaxActions',
                'max_actions_returning'         => 'VisitFrequency_ColumnMaxActionsInReturningVisit',
                'nb_visits_converted_returning' => 'VisitFrequency_ColumnNbReturningVisitsConverted',
                'sum_visit_length_returning'    => 'VisitFrequency_ColumnSumVisitLengthReturning',
                'nb_visits_converted'           => 'General_ColumnVisitsWithConversions',
                'nb_conversions'                => 'Goals_ColumnConversions',
                'revenue'                       => 'Goals_ColumnRevenue',
                'nb_hits'                       => 'General_ColumnPageviews',
                'entry_nb_visits'               => 'General_ColumnEntrances',
                'entry_nb_uniq_visitors'        => 'General_ColumnUniqueEntrances',
                'exit_nb_visits'                => 'General_ColumnExits',
                'exit_nb_uniq_visitors'         => 'General_ColumnUniqueExits',
                'entry_bounce_count'            => 'General_ColumnBounces',
                'exit_bounce_count'             => 'General_ColumnBounces',
                'exit_rate'                     => 'General_ColumnExitRate'
            );
    
            $trans = array_map('Piwik_Translate', $trans);
    
            $dailySum = ' (' . Piwik_Translate('General_DailySum') . ')';
            $afterEntry = ' ' . Piwik_Translate('General_AfterEntry');
    
            $trans['sum_daily_nb_uniq_visitors'] = Piwik_Translate('General_ColumnNbUniqVisitors') . $dailySum;
            $trans['sum_daily_entry_nb_uniq_visitors'] = Piwik_Translate('General_ColumnUniqueEntrances') . $dailySum;
            $trans['sum_daily_exit_nb_uniq_visitors'] = Piwik_Translate('General_ColumnUniqueExits') . $dailySum;
            $trans['entry_nb_actions'] = Piwik_Translate('General_ColumnNbActions') . $afterEntry;
            $trans['entry_sum_visit_length'] = Piwik_Translate('General_ColumnSumVisitLength') . $afterEntry;
    
            $api = self::getInstance();
            $trans = array_merge($api->getDefaultMetrics(), $api->getDefaultProcessedMetrics(), $trans);
    
            return $trans;
        }
    
        public function getDefaultMetrics()
        {
            $translations = array(
                // Standard metrics
                'nb_visits'        => 'General_ColumnNbVisits',
                'nb_uniq_visitors' => 'General_ColumnNbUniqVisitors',
                'nb_actions'       => 'General_ColumnNbActions',
    
    mattpiwik's avatar
    mattpiwik a validé
    // Do not display these in reports, as they are not so relevant
    
    mattpiwik's avatar
    mattpiwik a validé
    // They are used to process metrics below
    //			'nb_visits_converted' => 'General_ColumnVisitsWithConversions',
    
    mattpiwik's avatar
    mattpiwik a validé
    //    		'max_actions' => 'General_ColumnMaxActions',
    //    		'sum_visit_length' => 'General_ColumnSumVisitLength',
    //			'bounce_count'
    
            );
            $translations = array_map('Piwik_Translate', $translations);
            return $translations;
        }
    
        public function getDefaultProcessedMetrics()
        {
            $translations = array(
                // Processed in AddColumnsProcessedMetrics
                'nb_actions_per_visit' => 'General_ColumnActionsPerVisit',
                'avg_time_on_site'     => 'General_ColumnAvgTimeOnSite',
                'bounce_rate'          => 'General_ColumnBounceRate',
                'conversion_rate'      => 'General_ColumnConversionRate',
            );
            return array_map('Piwik_Translate', $translations);
        }
    
        public function getDefaultMetricsDocumentation()
        {
            $documentation = array(
                'nb_visits'            => 'General_ColumnNbVisitsDocumentation',
                'nb_uniq_visitors'     => 'General_ColumnNbUniqVisitorsDocumentation',
                'nb_actions'           => 'General_ColumnNbActionsDocumentation',
                'nb_actions_per_visit' => 'General_ColumnActionsPerVisitDocumentation',
                'avg_time_on_site'     => 'General_ColumnAvgTimeOnSiteDocumentation',
                'bounce_rate'          => 'General_ColumnBounceRateDocumentation',
                'conversion_rate'      => 'General_ColumnConversionRateDocumentation',
                'avg_time_on_page'     => 'General_ColumnAverageTimeOnPageDocumentation',
                'nb_hits'              => 'General_ColumnPageviewsDocumentation',
                'exit_rate'            => 'General_ColumnExitRateDocumentation'
            );
            return array_map('Piwik_Translate', $documentation);
        }
    
        public function getSegmentsMetadata($idSites = array(), $_hideImplementationData = true)
    
    mattpiwik's avatar
    mattpiwik a validé
        {
    
    268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710
            $segments = array();
            Piwik_PostEvent('API.getSegmentsMetadata', $segments, $idSites);
    
            $segments[] = array(
                'type'           => 'dimension',
                'category'       => 'Visit',
                'name'           => 'General_VisitorIP',
                'segment'        => 'visitIp',
                'acceptedValues' => '13.54.122.1, etc.',
                'sqlSegment'     => 'log_visit.location_ip',
                'sqlFilter'      => array('Piwik_IP', 'P2N'),
                'permission'     => Piwik::isUserHasAdminAccess($idSites),
            );
            $segments[] = array(
                'type'           => 'dimension',
                'category'       => 'Visit',
                'name'           => 'General_VisitorID',
                'segment'        => 'visitorId',
                'acceptedValues' => '34c31e04394bdc63 - any 16 Hexadecimal chars ID, which can be fetched using the Tracking API function getVisitorId()',
                'sqlSegment'     => 'log_visit.idvisitor',
                'sqlFilter'      => array('Piwik_Common', 'convertVisitorIdToBin'),
            );
            $segments[] = array(
                'type'       => 'metric',
                'category'   => 'Visit',
                'name'       => 'General_NbActions',
                'segment'    => 'actions',
                'sqlSegment' => 'log_visit.visit_total_actions',
            );
            $segments[] = array(
                'type'           => 'metric',
                'category'       => 'Visit',
                'name'           => 'General_NbSearches',
                'segment'        => 'searches',
                'sqlSegment'     => 'log_visit.visit_total_searches',
                'acceptedValues' => 'To select all visits who used internal Site Search, use: &segment=searches>0',
            );
            $segments[] = array(
                'type'       => 'metric',
                'category'   => 'Visit',
                'name'       => 'General_ColumnVisitDuration',
                'segment'    => 'visitDuration',
                'sqlSegment' => 'log_visit.visit_total_time',
            );
            $segments[] = array(
                'type'           => 'dimension',
                'category'       => 'Visit',
                'name'           => Piwik_Translate('General_VisitType') . ". " . Piwik_Translate('General_VisitTypeExample', '"&segment=visitorType==returning,visitorType==returningCustomer"'),
                'segment'        => 'visitorType',
                'acceptedValues' => 'new, returning, returningCustomer',
                'sqlSegment'     => 'log_visit.visitor_returning',
                'sqlFilter'      => create_function('$type', 'return $type == "new" ? 0 : ($type == "returning" ? 1 : 2);'),
            );
            $segments[] = array(
                'type'       => 'metric',
                'category'   => 'Visit',
                'name'       => 'General_DaysSinceLastVisit',
                'segment'    => 'daysSinceLastVisit',
                'sqlSegment' => 'log_visit.visitor_days_since_last',
            );
            $segments[] = array(
                'type'       => 'metric',
                'category'   => 'Visit',
                'name'       => 'General_DaysSinceFirstVisit',
                'segment'    => 'daysSinceFirstVisit',
                'sqlSegment' => 'log_visit.visitor_days_since_first',
            );
            $segments[] = array(
                'type'       => 'metric',
                'category'   => 'Visit',
                'name'       => 'General_NumberOfVisits',
                'segment'    => 'visitCount',
                'sqlSegment' => 'log_visit.visitor_count_visits',
            );
    
            $segments[] = array(
                'type'           => 'dimension',
                'category'       => 'Visit',
                'name'           => 'General_VisitConvertedGoal',
                'segment'        => 'visitConverted',
                'acceptedValues' => '0, 1',
                'sqlSegment'     => 'log_visit.visit_goal_converted',
            );
    
            $segments[] = array(
                'type'           => 'dimension',
                'category'       => 'Visit',
                'name'           => Piwik_Translate('General_EcommerceVisitStatus', '"&segment=visitEcommerceStatus==ordered,visitEcommerceStatus==orderedThenAbandonedCart"'),
                'segment'        => 'visitEcommerceStatus',
                'acceptedValues' => implode(", ", self::$visitEcommerceStatus),
                'sqlSegment'     => 'log_visit.visit_goal_buyer',
                'sqlFilter'      => array('Piwik_API_API', 'getVisitEcommerceStatus'),
            );
    
            $segments[] = array(
                'type'       => 'metric',
                'category'   => 'Visit',
                'name'       => 'General_DaysSinceLastEcommerceOrder',
                'segment'    => 'daysSinceLastEcommerceOrder',
                'sqlSegment' => 'log_visit.visitor_days_since_order',
            );
    
            foreach ($segments as &$segment) {
                $segment['name'] = Piwik_Translate($segment['name']);
                $segment['category'] = Piwik_Translate($segment['category']);
    
                if ($_hideImplementationData) {
                    unset($segment['sqlFilter']);
                    unset($segment['sqlSegment']);
                }
            }
    
            usort($segments, array($this, 'sortSegments'));
            return $segments;
        }
    
        static protected $visitEcommerceStatus = array(
            Piwik_Tracker_GoalManager::TYPE_BUYER_NONE                  => 'none',
            Piwik_Tracker_GoalManager::TYPE_BUYER_ORDERED               => 'ordered',
            Piwik_Tracker_GoalManager::TYPE_BUYER_OPEN_CART             => 'abandonedCart',
            Piwik_Tracker_GoalManager::TYPE_BUYER_ORDERED_AND_OPEN_CART => 'orderedThenAbandonedCart',
        );
    
        /**
         * @ignore
         */
        static public function getVisitEcommerceStatusFromId($id)
        {
            if (!isset(self::$visitEcommerceStatus[$id])) {
                throw new Exception("Unexpected ECommerce status value ");
            }
            return self::$visitEcommerceStatus[$id];
        }
    
        /**
         * @ignore
         */
        static public function getVisitEcommerceStatus($status)
        {
            $id = array_search($status, self::$visitEcommerceStatus);
            if ($id === false) {
                throw new Exception("Invalid 'visitEcommerceStatus' segment value");
            }
            return $id;
        }
    
        private function sortSegments($row1, $row2)
        {
            $columns = array('type', 'category', 'name', 'segment');
            foreach ($columns as $column) {
                // Keep segments ordered alphabetically inside categories..
                $type = -1;
                if ($column == 'name') $type = 1;
                $compare = $type * strcmp($row1[$column], $row2[$column]);
    
                // hack so that custom variables "page" are grouped together in the doc
                if ($row1['category'] == Piwik_Translate('CustomVariables_CustomVariables')
                    && $row1['category'] == $row2['category']
                ) {
                    $compare = strcmp($row1['segment'], $row2['segment']);
                    return $compare;
                }
                if ($compare != 0) {
                    return $compare;
                }
            }
            return $compare;
        }
    
        /**
         * Returns the url to application logo (~280x110px)
         *
         * @param bool $pathOnly If true, returns path relative to doc root. Otherwise, returns a URL.
         * @return string
         */
        public function getLogoUrl($pathOnly = false)
        {
            $logo = 'themes/default/images/logo.png';
            if (Piwik_Config::getInstance()->branding['use_custom_logo'] == 1
                && file_exists(Piwik_Common::getPathToPiwikRoot() . '/themes/logo.png')
            ) {
                $logo = 'themes/logo.png';
            }
            if (!$pathOnly) {
                return Piwik::getPiwikUrl() . $logo;
            }
            return Piwik_Common::getPathToPiwikRoot() . '/' . $logo;
        }
    
        /**
         * Returns the url to header logo (~127x50px)
         *
         * @param bool $pathOnly If true, returns path relative to doc root. Otherwise, returns a URL.
         * @return string
         */
        public function getHeaderLogoUrl($pathOnly = false)
        {
            $logo = 'themes/default/images/logo-header.png';
            if (Piwik_Config::getInstance()->branding['use_custom_logo'] == 1
                && file_exists(Piwik_Common::getPathToPiwikRoot() . '/themes/logo-header.png')
            ) {
                $logo = 'themes/logo-header.png';
            }
            if (!$pathOnly) {
                return Piwik::getPiwikUrl() . $logo;
            }
            return Piwik_Common::getPathToPiwikRoot() . '/' . $logo;
        }
    
        /**
         * Returns the URL to application SVG Logo
         *
         * @param bool $pathOnly If true, returns path relative to doc root. Otherwise, returns a URL.
         * @return string
         */
        public function getSVGLogoUrl($pathOnly = false)
        {
            $logo = 'themes/default/images/logo.svg';
            if (Piwik_Config::getInstance()->branding['use_custom_logo'] == 1
                && file_exists(Piwik_Common::getPathToPiwikRoot() . '/themes/logo.svg')
            ) {
                $logo = 'themes/logo.svg';
            }
            if (!$pathOnly) {
                return Piwik::getPiwikUrl() . $logo;
            }
            return Piwik_Common::getPathToPiwikRoot() . '/' . $logo;
        }
    
        /**
         * Returns whether there is an SVG Logo available.
         *
         * @return bool
         */
        public function hasSVGLogo()
        {
            if (Piwik_Config::getInstance()->branding['use_custom_logo'] == 0) {
                /* We always have our application logo */
                return true;
            } else if (Piwik_Config::getInstance()->branding['use_custom_logo'] == 1
                && file_exists(Piwik_Common::getPathToPiwikRoot() . '/themes/logo.svg')
            ) {
                return true;
            }
    
            return false;
        }
    
        /**
         * Loads reports metadata, then return the requested one,
         * matching optional API parameters.
         */
        public function getMetadata($idSite, $apiModule, $apiAction, $apiParameters = array(), $language = false,
                                    $period = false, $date = false, $hideMetricsDoc = false, $showSubtableReports = false)
        {
            Piwik_Translate::getInstance()->reloadLanguage($language);
            $reportsMetadata = $this->getReportMetadata($idSite, $period, $date, $hideMetricsDoc, $showSubtableReports);
    
            foreach ($reportsMetadata as $report) {
                // See ArchiveProcessing/Period.php - unique visitors are not processed for period != day
                if (($period && $period != 'day') && !($apiModule == 'VisitsSummary' && $apiAction == 'get')) {
                    unset($report['metrics']['nb_uniq_visitors']);
                }
                if ($report['module'] == $apiModule
                    && $report['action'] == $apiAction
                ) {
                    // No custom parameters
                    if (empty($apiParameters)
                        && empty($report['parameters'])
                    ) {
                        return array($report);
                    }
                    if (empty($report['parameters'])) {
                        continue;
                    }
                    $diff = array_diff($report['parameters'], $apiParameters);
                    if (empty($diff)) {
                        return array($report);
                    }
                }
            }
            return false;
        }
    
        /**
         * Triggers a hook to ask plugins for available Reports.
         * Returns metadata information about each report (category, name, dimension, metrics, etc.)
         *
         * @param string $idSites Comma separated list of website Ids
         * @return array
         */
        public function getReportMetadata($idSites = '', $period = false, $date = false, $hideMetricsDoc = false,
                                          $showSubtableReports = false)
        {
            $idSites = Piwik_Site::getIdSitesFromIdSitesString($idSites);
            if (!empty($idSites)) {
                Piwik::checkUserHasViewAccess($idSites);
            }
    
            $parameters = array('idSites' => $idSites, 'period' => $period, 'date' => $date);
    
            $availableReports = array();
            Piwik_PostEvent('API.getReportMetadata', $availableReports, $parameters);
            foreach ($availableReports as &$availableReport) {
                if (!isset($availableReport['metrics'])) {
                    $availableReport['metrics'] = $this->getDefaultMetrics();
                }
                if (!isset($availableReport['processedMetrics'])) {
                    $availableReport['processedMetrics'] = $this->getDefaultProcessedMetrics();
                }
    
                if ($hideMetricsDoc) // remove metric documentation if it's not wanted
                {
                    unset($availableReport['metricsDocumentation']);
                } else if (!isset($availableReport['metricsDocumentation'])) {
                    // set metric documentation to default if it's not set
                    $availableReport['metricsDocumentation'] = $this->getDefaultMetricsDocumentation();
                }
    
                // if hide/show columns specified, hide/show metrics & docs
                $availableReport['metrics'] = $this->hideShowMetrics($availableReport['metrics']);
                $availableReport['processedMetrics'] = $this->hideShowMetrics($availableReport['processedMetrics']);
                if (isset($availableReport['metricsDocumentation'])) {
                    $availableReport['metricsDocumentation'] =
                        $this->hideShowMetrics($availableReport['metricsDocumentation']);
                }
            }
    
            // Some plugins need to add custom metrics after all plugins hooked in
            Piwik_PostEvent('API.getReportMetadata.end', $availableReports, $parameters);
            // Oh this is not pretty! Until we have event listeners order parameter...
            Piwik_PostEvent('API.getReportMetadata.end.end', $availableReports, $parameters);
    
            // Sort results to ensure consistent order
            usort($availableReports, array($this, 'sort'));
    
            // Add the magic API.get report metadata aggregating all plugins API.get API calls automatically
            $this->addApiGetMetdata($availableReports);
    
            $knownMetrics = array_merge($this->getDefaultMetrics(), $this->getDefaultProcessedMetrics());
            foreach ($availableReports as &$availableReport) {
                // Ensure all metrics have a translation
                $metrics = $availableReport['metrics'];
                $cleanedMetrics = array();
                foreach ($metrics as $metricId => $metricTranslation) {
                    // When simply the column name was given, ie 'metric' => array( 'nb_visits' )
                    // $metricTranslation is in this case nb_visits. We look for a known translation.
                    if (is_numeric($metricId)
                        && isset($knownMetrics[$metricTranslation])
                    ) {
                        $metricId = $metricTranslation;
                        $metricTranslation = $knownMetrics[$metricTranslation];
                    }
                    $cleanedMetrics[$metricId] = $metricTranslation;
                }
                $availableReport['metrics'] = $cleanedMetrics;
    
                // Remove array elements that are false (to clean up API output)
                foreach ($availableReport as $attributeName => $attributeValue) {
                    if (empty($attributeValue)) {
                        unset($availableReport[$attributeName]);
                    }
                }
                // when there are per goal metrics, don't display conversion_rate since it can differ from per goal sum
                if (isset($availableReport['metricsGoal'])) {
                    unset($availableReport['processedMetrics']['conversion_rate']);
                    unset($availableReport['metricsGoal']['conversion_rate']);
                }
    
                // Processing a uniqueId for each report,
                // can be used by UIs as a key to match a given report
                $uniqueId = $availableReport['module'] . '_' . $availableReport['action'];
                if (!empty($availableReport['parameters'])) {
                    foreach ($availableReport['parameters'] as $key => $value) {
                        $uniqueId .= '_' . $key . '--' . $value;
                    }
                }
                $availableReport['uniqueId'] = $uniqueId;
    
                // Order is used to order reports internally, but not meant to be used outside
                unset($availableReport['order']);
            }
    
            // remove subtable reports
            if (!$showSubtableReports) {
                foreach ($availableReports as $idx => $report) {
                    if (isset($report['isSubtableReport']) && $report['isSubtableReport']) {
                        unset($availableReports[$idx]);
                    }
                }
            }
    
            return array_values($availableReports); // make sure array has contiguous key values
        }
    
    
        /**
         * Add the metadata for the API.get report
         * In other plugins, this would hook on 'API.getReportMetadata'
         */
        private function addApiGetMetdata(&$availableReports)
        {
            $metadata = array(
                'category'             => Piwik_Translate('General_API'),
                'name'                 => Piwik_Translate('General_MainMetrics'),
                'module'               => 'API',
                'action'               => 'get',
                'metrics'              => array(),
                'processedMetrics'     => array(),
                'metricsDocumentation' => array(),
                'order'                => 1
            );
    
            $indexesToMerge = array('metrics', 'processedMetrics', 'metricsDocumentation');
    
            foreach ($availableReports as $report) {
                if ($report['action'] == 'get') {
                    foreach ($indexesToMerge as $index) {
                        if (isset($report[$index])
                            && is_array($report[$index])
                        ) {
                            $metadata[$index] = array_merge($metadata[$index], $report[$index]);
                        }
                    }
                }
            }
    
            $availableReports[] = $metadata;
        }
    
        public function getProcessedReport($idSite, $period, $date, $apiModule, $apiAction, $segment = false,
                                           $apiParameters = false, $idGoal = false, $language = false,
                                           $showTimer = true, $hideMetricsDoc = false, $idSubtable = false, $showRawMetrics = false)
        {
            $timer = new Piwik_Timer();
            if ($apiParameters === false) {
                $apiParameters = array();
            }
            if (!empty($idGoal)
                && empty($apiParameters['idGoal'])
            ) {
                $apiParameters['idGoal'] = $idGoal;
            }
    
    mattpiwik's avatar
    mattpiwik a validé
            // Is this report found in the Metadata available reports?
    
            $reportMetadata = $this->getMetadata($idSite, $apiModule, $apiAction, $apiParameters, $language,
    
                $period, $date, $hideMetricsDoc, $showSubtableReports = true);
            if (empty($reportMetadata)) {
                throw new Exception("Requested report $apiModule.$apiAction for Website id=$idSite not found in the list of available reports. \n");
    
    mattpiwik's avatar
    mattpiwik a validé
            }
    
            $reportMetadata = reset($reportMetadata);
    
            // Generate Api call URL passing custom parameters
            $parameters = array_merge($apiParameters, array(
                                                           'method'     => $apiModule . '.' . $apiAction,
                                                           'idSite'     => $idSite,
                                                           'period'     => $period,
                                                           'date'       => $date,
                                                           'format'     => 'original',
                                                           'serialize'  => '0',
                                                           'language'   => $language,
                                                           'idSubtable' => $idSubtable,
                                                      ));
            if (!empty($segment)) $parameters['segment'] = $segment;
    
            $url = Piwik_Url::getQueryStringFromParameters($parameters);
    
    mattpiwik's avatar
    mattpiwik a validé
            $request = new Piwik_API_Request($url);
            try {
    
                /** @var Piwik_DataTable */
                $dataTable = $request->process();
            } catch (Exception $e) {
                throw new Exception("API returned an error: " . $e->getMessage() . "\n");
            }
    
            list($newReport, $columns, $rowsMetadata) = $this->handleTableReport($idSite, $dataTable, $reportMetadata, isset($reportMetadata['dimension']), $showRawMetrics);
            foreach ($columns as $columnId => &$name) {
                $name = ucfirst($name);
            }
            $website = new Piwik_Site($idSite);
    
    mattpiwik's avatar
    mattpiwik a validé
    //    	$segment = new Piwik_Segment($segment, $idSite);
    
            $period = Piwik_Period::advancedFactory($period, $date);
            $period = $period->getLocalizedLongString();
    
            $return = array(
                'website'        => $website->getName(),
                'prettyDate'     => $period,
    
    mattpiwik's avatar
    mattpiwik a validé
    //    			'prettySegment' => $segment->getPrettyString(),
    
                'metadata'       => $reportMetadata,
                'columns'        => $columns,
                'reportData'     => $newReport,
                'reportMetadata' => $rowsMetadata,
            );
            if ($showTimer) {
                $return['timerMillis'] = $timer->getTimeMs(0);
            }
            return $return;
    
    mattpiwik's avatar
    mattpiwik a validé
        }
    
        /**
         * Enhance a $dataTable using metadata :
         *
         * - remove metrics based on $reportMetadata['metrics']
         * - add 0 valued metrics if $dataTable doesn't provide all $reportMetadata['metrics']
         * - format metric values to a 'human readable' format
         * - extract row metadata to a separate Piwik_DataTable_Simple|Piwik_DataTable_Array : $rowsMetadata
         * - translate metric names to a separate array : $columns
         *
         * @param int $idSite enables monetary value formatting based on site currency
         * @param Piwik_DataTable|Piwik_DataTable_Array $dataTable
         * @param array $reportMetadata
         * @param boolean $hasDimension
         * @return array Piwik_DataTable_Simple|Piwik_DataTable_Array $newReport with human readable format & array $columns list of translated column names & Piwik_DataTable_Simple|Piwik_DataTable_Array $rowsMetadata
         **/
    
        private function handleTableReport($idSite, $dataTable, &$reportMetadata, $hasDimension, $showRawMetrics = false)
    
    mattpiwik's avatar
    mattpiwik a validé
        {
    
            $columns = $reportMetadata['metrics'];
    
            if ($hasDimension) {
                $columns = array_merge(
                    array('label' => $reportMetadata['dimension']),
                    $columns
                );
    
                if (isset($reportMetadata['processedMetrics'])) {
                    $processedMetricsAdded = $this->getDefaultProcessedMetrics();
                    foreach ($processedMetricsAdded as $processedMetricId => $processedMetricTranslation) {
                        // this processed metric can be displayed for this report
                        if (isset($reportMetadata['processedMetrics'][$processedMetricId])) {
                            $columns[$processedMetricId] = $processedMetricTranslation;
                        }
                    }
                }
    
                // Display the global Goal metrics
                if (isset($reportMetadata['metricsGoal'])) {
                    $metricsGoalDisplay = array('revenue');
                    // Add processed metrics to be displayed for this report
                    foreach ($metricsGoalDisplay as $goalMetricId) {
                        if (isset($reportMetadata['metricsGoal'][$goalMetricId])) {
                            $columns[$goalMetricId] = $reportMetadata['metricsGoal'][$goalMetricId];
                        }
                    }
                }
    
                if (isset($reportMetadata['processedMetrics'])) {
                    // Add processed metrics
                    $dataTable->filter('AddColumnsProcessedMetrics', array($deleteRowsWithNoVisit = false));
                }
            }
    
            $columns = $this->hideShowMetrics($columns);
    
            // $dataTable is an instance of Piwik_DataTable_Array when multiple periods requested
            if ($dataTable instanceof Piwik_DataTable_Array) {
                // Need a new Piwik_DataTable_Array to store the 'human readable' values
                $newReport = new Piwik_DataTable_Array();
                $newReport->setKeyName("prettyDate");
    
                // Need a new Piwik_DataTable_Array to store report metadata
                $rowsMetadata = new Piwik_DataTable_Array();
                $rowsMetadata->setKeyName("prettyDate");
    
                // Process each Piwik_DataTable_Simple entry
                foreach ($dataTable->getArray() as $label => $simpleDataTable) {
                    $this->removeEmptyColumns($columns, $reportMetadata, $simpleDataTable);
    
                    list($enhancedSimpleDataTable, $rowMetadata) = $this->handleSimpleDataTable($idSite, $simpleDataTable, $columns, $hasDimension, $showRawMetrics);
                    $enhancedSimpleDataTable->metadata = $simpleDataTable->metadata;
    
                    $period = $simpleDataTable->metadata['period']->getLocalizedLongString();
                    $newReport->addTable($enhancedSimpleDataTable, $period);
                    $rowsMetadata->addTable($rowMetadata, $period);
                }
            } else {
                $this->removeEmptyColumns($columns, $reportMetadata, $dataTable);
                list($newReport, $rowsMetadata) = $this->handleSimpleDataTable($idSite, $dataTable, $columns, $hasDimension, $showRawMetrics);
            }
    
            return array(
                $newReport,
                $columns,
                $rowsMetadata
            );
        }
    
        /**
         * Removes metrics from the list of columns and the report meta data if they are marked empty
         * in the data table meta data.
         */
        private function removeEmptyColumns(&$columns, &$reportMetadata, $dataTable)
        {
            $emptyColumns = $dataTable->getMetadata(Piwik_DataTable::EMPTY_COLUMNS_METADATA_NAME);
    
            if (!is_array($emptyColumns)) {
                return;
            }
    
            $columns = $this->hideShowMetrics($columns, $emptyColumns);
    
            if (isset($reportMetadata['metrics'])) {
                $reportMetadata['metrics'] = $this->hideShowMetrics($reportMetadata['metrics'], $emptyColumns);
            }
    
            if (isset($reportMetadata['metricsDocumentation'])) {
                $reportMetadata['metricsDocumentation'] = $this->hideShowMetrics($reportMetadata['metricsDocumentation'], $emptyColumns);
            }
    
    mattpiwik's avatar
    mattpiwik a validé
        }
    
         * Removes column names from an array based on the values in the hideColumns,
         * showColumns query parameters. This is a hack that provides the ColumnDelete
         * filter functionality in processed reports.
    
         * @param array $columns List of metrics shown in a processed report.
    
         * @param array $emptyColumns Empty columns from the data table meta data.
    
         * @return array Filtered list of metrics.
         */
    
        private function hideShowMetrics($columns, $emptyColumns = array())
        {
            if (!is_array($columns)) {
                return $columns;
            }
    
            // remove columns if hideColumns query parameters exist
            $columnsToRemove = Piwik_Common::getRequestVar('hideColumns', '');
            if ($columnsToRemove != '') {
                $columnsToRemove = explode(',', $columnsToRemove);
                foreach ($columnsToRemove as $name) {
                    // if a column to remove is in the column list, remove it
                    if (isset($columns[$name])) {
                        unset($columns[$name]);
                    }
                }
            }
    
            // remove columns if showColumns query parameters exist
            $columnsToKeep = Piwik_Common::getRequestVar('showColumns', '');
            if ($columnsToKeep != '') {
                $columnsToKeep = explode(',', $columnsToKeep);
                $columnsToKeep[] = 'label';
    
                foreach ($columns as $name => $ignore) {
                    // if the current column should not be kept, remove it
                    $idx = array_search($name, $columnsToKeep);
                    if ($idx === FALSE) // if $name is not in $columnsToKeep
                    {
                        unset($columns[$name]);
                    }
                }
            }
    
            // remove empty columns
            if (is_array($emptyColumns)) {
                foreach ($emptyColumns as $column) {
                    if (isset($columns[$column])) {
                        unset($columns[$column]);
                    }
                }
            }
    
            return $columns;
        }
    
        /**
         * Enhance $simpleDataTable using metadata :
         *
         * - remove metrics based on $reportMetadata['metrics']
         * - add 0 valued metrics if $simpleDataTable doesn't provide all $reportMetadata['metrics']
         * - format metric values to a 'human readable' format
         * - extract row metadata to a separate Piwik_DataTable_Simple $rowsMetadata
         *
         * @param int $idSite enables monetary value formatting based on site currency
         * @param Piwik_DataTable_Simple $simpleDataTable
         * @param array $metadataColumns
         * @param boolean $hasDimension
         * @param bool $returnRawMetrics If set to true, the original metrics will be returned
         *
         * @return array Piwik_DataTable $enhancedDataTable filtered metrics with human readable format & Piwik_DataTable_Simple $rowsMetadata
         */
        private function handleSimpleDataTable($idSite, $simpleDataTable, $metadataColumns, $hasDimension, $returnRawMetrics = false)
        {
            // new DataTable to store metadata
            $rowsMetadata = new Piwik_DataTable();
    
            // new DataTable to store 'human readable' values
            if ($hasDimension) {
                $enhancedDataTable = new Piwik_DataTable();
            } else {
                $enhancedDataTable = new Piwik_DataTable_Simple();
            }
    
            // add missing metrics
            foreach ($simpleDataTable->getRows() as $row) {
                $rowMetrics = $row->getColumns();
                foreach ($metadataColumns as $id => $name) {
                    if (!isset($rowMetrics[$id])) {
                        $row->addColumn($id, 0);
                    }
                }
            }
    
            foreach ($simpleDataTable->getRows() as $row) {
                $enhancedRow = new Piwik_DataTable_Row();
                $enhancedDataTable->addRow($enhancedRow);
                $rowMetrics = $row->getColumns();
                foreach ($rowMetrics as $columnName => $columnValue) {
                    // filter metrics according to metadata definition
                    if (isset($metadataColumns[$columnName])) {
                        // generate 'human readable' metric values
    
                        $prettyValue = Piwik::getPrettyValue($idSite, $columnName, $columnValue, $htmlAllowed = false);
    
                        $enhancedRow->addColumn($columnName, $prettyValue);
                    } // For example the Maps Widget requires the raw metrics to do advanced datavis
                    elseif ($returnRawMetrics) {
                        $enhancedRow->addColumn($columnName, $columnValue);
                    }
                }
    
                // If report has a dimension, extract metadata into a distinct DataTable
                if ($hasDimension) {
                    $rowMetadata = $row->getMetadata();
                    $idSubDataTable = $row->getIdSubDataTable();
    
                    // Create a row metadata only if there are metadata to insert
                    if (count($rowMetadata) > 0 || !is_null($idSubDataTable)) {
                        $metadataRow = new Piwik_DataTable_Row();
                        $rowsMetadata->addRow($metadataRow);
    
                        foreach ($rowMetadata as $metadataKey => $metadataValue) {
                            $metadataRow->addColumn($metadataKey, $metadataValue);
                        }
    
                        if (!is_null($idSubDataTable)) {
                            $metadataRow->addColumn('idsubdatatable', $idSubDataTable);