diff options
author | Thomas Steur <thomas.steur@gmail.com> | 2015-07-03 03:54:27 +0300 |
---|---|---|
committer | sgiehl <stefan@piwik.org> | 2015-10-06 18:25:13 +0300 |
commit | 9ba8f216fd7856ce5fef06bf82ecb8f8a2e7e630 (patch) | |
tree | 6ce07d18a85d00b39ab720abe042361c0775aead /plugins/API | |
parent | 8ccc9dc05da021325cdbf141a548637fa52f16b2 (diff) |
generate pages instead of implementing them in each controller
Diffstat (limited to 'plugins/API')
-rw-r--r-- | plugins/API/API.php | 162 | ||||
-rw-r--r-- | plugins/API/ProcessedReport.php | 86 | ||||
-rw-r--r-- | plugins/API/Reports/Get.php | 6 | ||||
-rw-r--r-- | plugins/API/SegmentMetadata.php | 167 | ||||
-rw-r--r-- | plugins/API/WidgetMetadata.php | 282 | ||||
-rw-r--r-- | plugins/API/tests/Unit/WidgetMetadataTest.php | 278 |
6 files changed, 799 insertions, 182 deletions
diff --git a/plugins/API/API.php b/plugins/API/API.php index 4ea0cb4055..8245d0712e 100644 --- a/plugins/API/API.php +++ b/plugins/API/API.php @@ -10,7 +10,10 @@ namespace Piwik\Plugins\API; use Piwik\API\Proxy; use Piwik\API\Request; -use Piwik\Columns\Dimension; +use Piwik\Cache; +use Piwik\CacheId; +use Piwik\Category\CategoryList; +use Piwik\Common; use Piwik\Config; use Piwik\Container\StaticContainer; use Piwik\DataTable; @@ -23,11 +26,13 @@ use Piwik\Period; use Piwik\Period\Range; use Piwik\Piwik; use Piwik\Plugin\Dimension\VisitDimension; +use Piwik\Plugin\Report; use Piwik\Plugins\API\DataTable\MergeDataTables; use Piwik\Plugins\CoreAdminHome\CustomLogo; use Piwik\Translation\Translator; use Piwik\Measurable\Type\TypeManager; use Piwik\Version; +use Piwik\Widget\WidgetsList; require_once PIWIK_INCLUDE_PATH . '/core/Config.php'; @@ -122,83 +127,20 @@ class API extends \Piwik\Plugin\API { $isAuthenticatedWithViewAccess = Piwik::isUserHasViewAccess($idSites) && !Piwik::isUserIsAnonymous(); - $segments = array(); - foreach (Dimension::getAllDimensions() as $dimension) { - foreach ($dimension->getSegments() as $segment) { - if ($segment->isRequiresAtLeastViewAccess()) { - $segment->setPermission($isAuthenticatedWithViewAccess); - } + $sites = (is_array($idSites) ? implode('.', $idSites) : (int) $idSites); + $cache = Cache::getTransientCache(); + $cachKey = 'API.getSegmentsMetadata' . $sites . '_' . (int) $_hideImplementationData . '_' . (int) $isAuthenticatedWithViewAccess; + $cachKey = CacheId::pluginAware($cachKey); - $segments[] = $segment->toArray(); - } + if ($cache->contains($cachKey)) { + return $cache->fetch($cachKey); } - /** - * Triggered when gathering all available segment dimensions. - * - * This event can be used to make new segment dimensions available. - * - * **Example** - * - * public function getSegmentsMetadata(&$segments, $idSites) - * { - * $segments[] = array( - * 'type' => 'dimension', - * 'category' => Piwik::translate('General_Visit'), - * 'name' => 'General_VisitorIP', - * 'segment' => 'visitIp', - * 'acceptedValues' => '13.54.122.1, etc.', - * 'sqlSegment' => 'log_visit.location_ip', - * 'sqlFilter' => array('Piwik\IP', 'P2N'), - * 'permission' => $isAuthenticatedWithViewAccess, - * ); - * } - * - * @param array &$dimensions The list of available segment dimensions. Append to this list to add - * new segments. Each element in this list must contain the - * following information: - * - * - **type**: Either `'metric'` or `'dimension'`. `'metric'` means - * the value is a numeric and `'dimension'` means it is - * a string. Also, `'metric'` values will be displayed - * under **Visit (metrics)** in the Segment Editor. - * - **category**: The segment category name. This can be an existing - * segment category visible in the segment editor. - * - **name**: The pretty name of the segment. Can be a translation token. - * - **segment**: The segment name, eg, `'visitIp'` or `'searches'`. - * - **acceptedValues**: A string describing one or two exacmple values, eg - * `'13.54.122.1, etc.'`. - * - **sqlSegment**: The table column this segment will segment by. - * For example, `'log_visit.location_ip'` for the - * **visitIp** segment. - * - **sqlFilter**: A PHP callback to apply to segment values before - * they are used in SQL. - * - **permission**: True if the current user has view access to this - * segment, false if otherwise. - * @param array $idSites The list of site IDs we're getting the available segments - * for. Some segments (such as Goal segments) depend on the - * site. - */ - Piwik::postEvent('API.getSegmentDimensionMetadata', array(&$segments, $idSites)); - - foreach ($segments as &$segment) { - $segment['name'] = Piwik::translate($segment['name']); - $segment['category'] = Piwik::translate($segment['category']); - - if ($_hideImplementationData) { - unset($segment['sqlFilter']); - unset($segment['sqlFilterValue']); - unset($segment['sqlSegment']); - - if (isset($segment['suggestedValuesCallback']) - && !is_string($segment['suggestedValuesCallback']) - ) { - unset($segment['suggestedValuesCallback']); - } - } - } + $metadata = new SegmentMetadata(); + $segments = $metadata->getSegmentsMetadata($idSites, $_hideImplementationData, $isAuthenticatedWithViewAccess); + + $cache->save($cachKey, $segments); - usort($segments, array($this, 'sortSegments')); return $segments; } @@ -219,32 +161,6 @@ class API extends \Piwik\Plugin\API return $values; } - private function sortSegments($row1, $row2) - { - $customVarCategory = Piwik::translate('CustomVariables_CustomVariables'); - - $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'] == $customVarCategory - && $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) * @@ -343,6 +259,41 @@ class API extends \Piwik\Plugin\API } /** + * Get a list of all pages that shall be shown in a Piwik UI including a list of all widgets that shall + * be shown within each page. + * + * @param int $idSite + * @return array + */ + public function getReportPagesMetadata($idSite) + { + Piwik::checkUserHasViewAccess($idSite); + + $widgetsList = WidgetsList::get(); + $categoryList = CategoryList::get(); + $metadata = new WidgetMetadata(); + + return $metadata->getPagesMetadata($categoryList, $widgetsList); + } + + /** + * Get a list of all widgetizable widgets. + * + * @param int $idSite + * @return array + */ + public function getWidgetMetadata($idSite) + { + Piwik::checkUserHasViewAccess($idSite); + + $widgetsList = WidgetsList::get(); + $categoryList = CategoryList::get(); + $metadata = new WidgetMetadata(); + + return $metadata->getWidgetMetadata($categoryList, $widgetsList); + } + + /** * Get a combined report of the *.get API methods. */ public function get($idSite, $period, $date, $segment = false, $columns = false) @@ -555,7 +506,12 @@ class API extends \Piwik\Plugin\API if ($suggestedValuesCallbackRequiresTable) { $values = call_user_func($segmentFound['suggestedValuesCallback'], $idSite, $maxSuggestionsToReturn, $table); } else { - $values = $this->getSegmentValuesFromVisitorLog($segmentName, $table); + // Cleanup data to return the top suggested (non empty) labels for this segment + $values = $table->getColumn($segmentName); + + // Select also flattened keys (custom variables "page" scope, page URLs for one visit, page titles for one visit) + $valuesBis = $table->getColumnsStartingWith($segmentName . ColumnDelete::APPEND_TO_COLUMN_NAME_TO_KEEP); + $values = array_merge($values, $valuesBis); } $values = $this->getMostFrequentValues($values); @@ -600,9 +556,7 @@ class API extends \Piwik\Plugin\API // If you update this, also update flattenVisitorDetailsArray $segmentsNeedActionsInfo = array('visitConvertedGoalId', 'pageUrl', 'pageTitle', 'siteSearchKeyword', - 'entryPageTitle', 'entryPageUrl', 'exitPageTitle', 'exitPageUrl', - 'outlinkUrl', 'downloadUrl' - ); + 'entryPageTitle', 'entryPageUrl', 'exitPageTitle', 'exitPageUrl'); $isCustomVariablePage = stripos($segmentName, 'customVariablePage') !== false; $isEventSegment = stripos($segmentName, 'event') !== false; $isContentSegment = stripos($segmentName, 'content') !== false; diff --git a/plugins/API/ProcessedReport.php b/plugins/API/ProcessedReport.php index d9d315b4c0..65b59a2051 100644 --- a/plugins/API/ProcessedReport.php +++ b/plugins/API/ProcessedReport.php @@ -23,9 +23,11 @@ use Piwik\Metrics\Formatter; use Piwik\Period; use Piwik\Piwik; use Piwik\Plugin\Report; +use Piwik\Plugin\Reports; use Piwik\Site; use Piwik\Timer; use Piwik\Url; +use Piwik\Category\Category; class ProcessedReport { @@ -166,77 +168,14 @@ class ProcessedReport $availableReports = array(); - foreach (Report::getAllReports() as $report) { + $reports = new Reports(); + foreach ($reports->getAllReports() as $report) { $report->configureReportMetadata($availableReports, $parameters); } - /** - * Triggered when gathering metadata for all available reports. - * - * Plugins that define new reports should use this event to make them available in via - * the metadata API. By doing so, the report will become available in scheduled reports - * as well as in the Piwik Mobile App. In fact, any third party app that uses the metadata - * API will automatically have access to the new report. - * - * @param string &$availableReports The list of available reports. Append to this list - * to make a report available. - * - * Every element of this array must contain the following - * information: - * - * - **category**: A translated string describing the report's category. - * - **name**: The translated display title of the report. - * - **module**: The plugin of the report. - * - **action**: The API method that serves the report. - * - * The following information is optional: - * - * - **dimension**: The report's [dimension](/guides/all-about-analytics-data#dimensions) if any. - * - **metrics**: An array mapping metric names with their display names. - * - **metricsDocumentation**: An array mapping metric names with their - * translated documentation. - * - **processedMetrics**: The array of metrics in the report that are - * calculated using existing metrics. Can be set to - * `false` if the report contains no processed - * metrics. - * - **order**: The order of the report in the list of reports - * with the same category. - * - * @param array $parameters Contains the values of the sites and period we are - * getting reports for. Some reports depend on this data. - * For example, Goals reports depend on the site IDs being - * requested. Contains the following information: - * - * - **idSites**: The array of site IDs we are getting reports for. - * - **period**: The period type, eg, `'day'`, `'week'`, `'month'`, - * `'year'`, `'range'`. - * - **date**: A string date within the period or a date range, eg, - * `'2013-01-01'` or `'2012-01-01,2013-01-01'`. - * - * TODO: put dimensions section in all about analytics data - * @deprecated since 2.5.0 Use Report Classes instead. - * @ignore - */ - Piwik::postEvent('API.getReportMetadata', array(&$availableReports, $parameters)); - - // TODO we can remove this one once we remove API.getReportMetadata event (except hideMetricsDoc) foreach ($availableReports as &$availableReport) { - // can be removed once we remove hook API.getReportMetadata - if (!isset($availableReport['metrics'])) { - $availableReport['metrics'] = Metrics::getDefaultMetrics(); - } - // can be removed once we remove hook API.getReportMetadata - if (!isset($availableReport['processedMetrics'])) { - $availableReport['processedMetrics'] = Metrics::getDefaultProcessedMetrics(); - } - - if ($hideMetricsDoc) // remove metric documentation if it's not wanted - { + if ($hideMetricsDoc) { unset($availableReport['metricsDocumentation']); - } else if (!isset($availableReport['metricsDocumentation'])) { - // set metric documentation to default if it's not set - // can be removed once we remove hook API.getReportMetadata - $availableReport['metricsDocumentation'] = Metrics::getDefaultMetricsDocumentation(); } } @@ -270,6 +209,9 @@ class ProcessedReport $columnsToRemove = $this->getColumnsToRemove(); foreach ($availableReports as &$availableReport) { + $availableReport['category'] = Piwik::translate($availableReport['category']); + $availableReport['subcategory'] = Piwik::translate($availableReport['subcategory']); + // Ensure all metrics have a translation $metrics = $availableReport['metrics']; $cleanedMetrics = array(); @@ -349,16 +291,8 @@ class ProcessedReport */ private static function sortReports($a, $b) { - static $order = null; - if (is_null($order)) { - $order = array(); - foreach (Report::$orderOfReports as $category) { - $order[] = Piwik::translate($category); - } - } - return ($category = strcmp(array_search($a['category'], $order), array_search($b['category'], $order))) == 0 - ? (@$a['order'] < @$b['order'] ? -1 : 1) - : $category; + $reports = new Reports(); + return $reports->compareCategories($a['category'], $a['subcategory'], $a['order'], $b['category'], $b['subcategory'], $b['order']); } public function getProcessedReport($idSite, $period, $date, $apiModule, $apiAction, $segment = false, diff --git a/plugins/API/Reports/Get.php b/plugins/API/Reports/Get.php index de087d0a86..61cded3856 100644 --- a/plugins/API/Reports/Get.php +++ b/plugins/API/Reports/Get.php @@ -10,6 +10,7 @@ namespace Piwik\Plugins\API\Reports; use Piwik\Piwik; use Piwik\Plugin\Report; +use Piwik\Plugin\Reports; class Get extends Report { @@ -29,7 +30,7 @@ class Get extends Report $this->module = 'API'; $this->action = 'get'; - $this->category = 'API'; + $this->categoryId = 'API'; $this->name = Piwik::translate('General_MainMetrics'); $this->documentation = ''; @@ -80,8 +81,9 @@ class Get extends Report */ private function getReportsToMerge() { + $reports = new Reports(); $result = array(); - foreach (Report::getAllReportClasses() as $reportClass) { + foreach ($reports->getAllReportClasses() as $reportClass) { if ($reportClass == 'Piwik\\Plugins\\API\\Reports\\Get') { continue; } diff --git a/plugins/API/SegmentMetadata.php b/plugins/API/SegmentMetadata.php new file mode 100644 index 0000000000..9d7e563bfb --- /dev/null +++ b/plugins/API/SegmentMetadata.php @@ -0,0 +1,167 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + */ +namespace Piwik\Plugins\API; + +use Piwik\Columns\Dimension; +use Piwik\Piwik; + +class SegmentMetadata +{ + public function getSegmentsMetadata($idSites = array(), $_hideImplementationData = true, $isAuthenticatedWithViewAccess) + { + $segments = array(); + + foreach (Dimension::getAllDimensions() as $dimension) { + foreach ($dimension->getSegments() as $segment) { + $segments[] = $segment->toArray(); + } + } + + /** + * Triggered when gathering all available segment dimensions. + * + * This event can be used to make new segment dimensions available. + * + * **Example** + * + * public function getSegmentsMetadata(&$segments, $idSites) + * { + * $segments[] = array( + * 'type' => 'dimension', + * 'category' => Piwik::translate('General_Visit'), + * 'name' => 'General_VisitorIP', + * 'segment' => 'visitIp', + * 'acceptedValues' => '13.54.122.1, etc.', + * 'sqlSegment' => 'log_visit.location_ip', + * 'sqlFilter' => array('Piwik\IP', 'P2N'), + * 'permission' => $isAuthenticatedWithViewAccess, + * ); + * } + * + * @param array &$dimensions The list of available segment dimensions. Append to this list to add + * new segments. Each element in this list must contain the + * following information: + * + * - **type**: Either `'metric'` or `'dimension'`. `'metric'` means + * the value is a numeric and `'dimension'` means it is + * a string. Also, `'metric'` values will be displayed + * under **Visit (metrics)** in the Segment Editor. + * - **category**: The segment category name. This can be an existing + * segment category visible in the segment editor. + * - **name**: The pretty name of the segment. Can be a translation token. + * - **segment**: The segment name, eg, `'visitIp'` or `'searches'`. + * - **acceptedValues**: A string describing one or two exacmple values, eg + * `'13.54.122.1, etc.'`. + * - **sqlSegment**: The table column this segment will segment by. + * For example, `'log_visit.location_ip'` for the + * **visitIp** segment. + * - **sqlFilter**: A PHP callback to apply to segment values before + * they are used in SQL. + * - **permission**: True if the current user has view access to this + * segment, false if otherwise. + * @param array $idSites The list of site IDs we're getting the available segments + * for. Some segments (such as Goal segments) depend on the + * site. + */ + Piwik::postEvent('API.getSegmentDimensionMetadata', array(&$segments, $idSites)); + + $segments[] = array( + 'type' => 'dimension', + 'category' => Piwik::translate('General_Visit'), + 'name' => 'General_UserId', + 'segment' => 'userId', + 'acceptedValues' => 'any non empty unique string identifying the user (such as an email address or a username).', + 'sqlSegment' => 'log_visit.user_id', + 'permission' => $isAuthenticatedWithViewAccess, + ); + + $segments[] = array( + 'type' => 'dimension', + 'category' => Piwik::translate('General_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', + 'sqlFilterValue' => array('Piwik\Common', 'convertVisitorIdToBin'), + 'permission' => $isAuthenticatedWithViewAccess, + ); + + $segments[] = array( + 'type' => 'dimension', + 'category' => Piwik::translate('General_Visit'), + 'name' => Piwik::translate('General_Visit') . " ID", + 'segment' => 'visitId', + 'acceptedValues' => 'Any integer. ', + 'sqlSegment' => 'log_visit.idvisit', + 'permission' => $isAuthenticatedWithViewAccess, + ); + + $segments[] = array( + 'type' => 'metric', + 'category' => Piwik::translate('General_Visit'), + 'name' => 'General_VisitorIP', + 'segment' => 'visitIp', + 'acceptedValues' => '13.54.122.1. </code>Select IP ranges with notation: <code>visitIp>13.54.122.0;visitIp<13.54.122.255', + 'sqlSegment' => 'log_visit.location_ip', + 'sqlFilterValue' => array('Piwik\Network\IPUtils', 'stringToBinaryIP'), + 'permission' => $isAuthenticatedWithViewAccess, + ); + + foreach ($segments as &$segment) { + $segment['name'] = Piwik::translate($segment['name']); + $segment['category'] = Piwik::translate($segment['category']); + + if ($_hideImplementationData) { + unset($segment['sqlFilter']); + unset($segment['sqlFilterValue']); + unset($segment['sqlSegment']); + } + + if (isset($segment['suggestedValuesCallback']) + && !is_string($segment['suggestedValuesCallback']) + ) { + unset($segment['suggestedValuesCallback']); + } + } + + usort($segments, array($this, 'sortSegments')); + + return $segments; + } + + private function sortSegments($row1, $row2) + { + $customVarCategory = Piwik::translate('CustomVariables_CustomVariables'); + + $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'] == $customVarCategory + && $row1['category'] == $row2['category'] + ) { + $compare = strcmp($row1['segment'], $row2['segment']); + return $compare; + } + + if ($compare != 0) { + return $compare; + } + } + + return $compare; + } + +}
\ No newline at end of file diff --git a/plugins/API/WidgetMetadata.php b/plugins/API/WidgetMetadata.php new file mode 100644 index 0000000000..ef12800a39 --- /dev/null +++ b/plugins/API/WidgetMetadata.php @@ -0,0 +1,282 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + */ +namespace Piwik\Plugins\API; + +use Piwik\Category\CategoryList; +use Piwik\Piwik; +use Piwik\Report\ReportWidgetConfig; +use Piwik\Category\Category; +use Piwik\Category\Subcategory; +use Piwik\Widget\WidgetContainerConfig; +use Piwik\Widget\WidgetConfig; +use Piwik\Widget\WidgetsList; + +class WidgetMetadata +{ + public function getPagesMetadata(CategoryList $categoryList, WidgetsList $widgetsList) + { + $this->createMissingCategoriesAndSubcategories($categoryList, $widgetsList->getWidgetConfigs()); + + return $this->buildPagesMetadata($categoryList, $widgetsList); + } + + public function getWidgetMetadata(CategoryList $categoryList, WidgetsList $widgetsList) + { + $this->createMissingCategoriesAndSubcategories($categoryList, $widgetsList->getWidgetConfigs()); + + $flat = array(); + + foreach ($widgetsList->getWidgetConfigs() as $widgetConfig) { + + /** @var WidgetConfig[] $widgets */ + $widgets = array($widgetConfig); + if ($widgetConfig instanceof WidgetContainerConfig) { + // so far we go only one level down, in theory these widgetConfigs could have again containers containing configs + $widgets = array_merge($widgets, $widgetConfig->getWidgetConfigs()); + } + + foreach ($widgets as $widget) { + // make sure to include only widgetizable widgets + if (!$widget->isWidgetizeable() || !$widget->getName()) { + continue; + } + + $flat[] = $this->buildWidgetMetadata($widget, $categoryList); + } + } + + usort($flat, array($this, 'sortWidgets')); + + return $flat; + } + + /** + * @param WidgetConfig $widget + * @param CategoryList|null $categoryList If null, no category information will be added to the widgets in first + * level (they will be added to nested widgets as potentially needed eg for + * widgets in ByDimensionView where they are needed to build the left menu) + * @return array + */ + public function buildWidgetMetadata(WidgetConfig $widget, $categoryList = null) + { + $item = array( + 'name' => Piwik::translate($widget->getName()) + ); + + if (isset($categoryList)) { + $category = $categoryList->getCategory($widget->getCategoryId()); + $subcategory = $category ? $category->getSubcategory($widget->getSubcategoryId()) : null; + + $item['category'] = $this->buildCategoryMetadata($category); + $item['subcategory'] = $this->buildSubcategoryMetadata($subcategory); + } + + $item['module'] = $widget->getModule(); + $item['action'] = $widget->getAction(); + $item['order'] = $widget->getOrder(); + $item['parameters'] = $widget->getParameters(); + $item['uniqueId'] = $widget->getUniqueId(); + + $middleware = $widget->getMiddlewareParameters(); + + if (!empty($middleware)) { + $item['middlewareParameters'] = $middleware; + } + + if ($widget instanceof ReportWidgetConfig) { + $item['viewDataTable'] = $widget->getViewDataTable(); + $item['isReport'] = true; + } + + if ($widget instanceof WidgetContainerConfig) { + $item['layout'] = $widget->getLayout(); + $item['isContainer'] = true; + + // we do not want to create categories to the inital categoryList. Otherwise we'd maybe display more pages + // etc. + $subCategoryList = new CategoryList(); + $this->createMissingCategoriesAndSubcategories($subCategoryList, $widget->getWidgetConfigs()); + + $children = array(); + foreach ($widget->getWidgetConfigs() as $widgetConfig) { + $children[] = $this->buildWidgetMetadata($widgetConfig, $subCategoryList); + } + $item['widgets'] = $children; + } + + return $item; + } + + private function sortWidgets($widgetA, $widgetB) { + $orderA = $widgetA['category']['order']; + $orderB = $widgetB['category']['order']; + + if ($orderA === $orderB) { + if (!empty($widgetA['subcategory']['order']) && !empty($widgetB['subcategory']['order'])) { + + $subOrderA = $widgetA['subcategory']['order']; + $subOrderB = $widgetB['subcategory']['order']; + + if ($subOrderA === $subOrderB) { + return 0; + } + + return $subOrderA > $subOrderB ? 1 : -1; + + } elseif (!empty($orderA)) { + + return 1; + } + + return -1; + } + + return $orderA > $orderB ? 1 : -1; + } + + /** + * @param Category|null $category + * @return array + */ + private function buildCategoryMetadata($category) + { + if (!isset($category)) { + return null; + } + + return array( + 'id' => (string) $category->getId(), + 'name' => Piwik::translate($category->getId()), + 'order' => $category->getOrder(), + ); + } + + /** + * @param Subcategory|null $subcategory + * @return array + */ + private function buildSubcategoryMetadata($subcategory) + { + if (!isset($subcategory)) { + return null; + } + + return array( + 'id' => (string) $subcategory->getId(), + 'name' => Piwik::translate($subcategory->getName()), + 'order' => $subcategory->getOrder(), + ); + } + + /** + * @param CategoryList $categoryList + * @param WidgetConfig[] $widgetConfigs + */ + private function createMissingCategoriesAndSubcategories($categoryList, $widgetConfigs) + { + // move reports into categories/subcategories and create missing ones if needed + foreach ($widgetConfigs as $widgetConfig) { + $categoryId = $widgetConfig->getCategoryId(); + $subcategoryId = $widgetConfig->getSubcategoryId(); + + if (!$categoryId) { + continue; + } + + if ($widgetConfig instanceof WidgetContainerConfig && !$widgetConfig->getWidgetConfigs()) { + // if a container does not contain any widgets, ignore it + continue; + } + + if (!$categoryList->hasCategory($categoryId)) { + $categoryList->addCategory($this->createCategory($categoryId)); + } + + if (!$subcategoryId) { + continue; + } + + $category = $categoryList->getCategory($categoryId); + + if (!$category->hasSubcategory($subcategoryId)) { + $category->addSubcategory($this->createSubcategory($categoryId, $subcategoryId)); + } + } + } + + private function createCategory($categoryId) + { + $category = new Category(); + $category->setId($categoryId); + return $category; + } + + private function createSubcategory($categoryId, $subcategoryId) + { + $subcategory = new Subcategory(); + $subcategory->setCategoryId($categoryId); + $subcategory->setId($subcategoryId); + return $subcategory; + } + + /** + * @param CategoryList $categoryList + * @param WidgetsList $widgetsList + * @return array + */ + private function buildPagesMetadata(CategoryList $categoryList, WidgetsList $widgetsList) + { + $pages = array(); + + $widgets = array(); + foreach ($widgetsList->getWidgetConfigs() as $config) { + $pageId = $this->buildPageId($config->getCategoryId(), $config->getSubcategoryId()); + + if (!isset($widgets[$pageId])) { + $widgets[$pageId] = array(); + } + + $widgets[$pageId][] = $config; + } + + foreach ($categoryList->getCategories() as $category) { + foreach ($category->getSubcategories() as $subcategory) { + $pageId = $this->buildPageId($category->getId(), $subcategory->getId()); + + if (!empty($widgets[$pageId])) { + $pages[] = $this->buildPageMetadata($category, $subcategory, $widgets[$pageId]); + } + } + } + + return $pages; + } + + private function buildPageId($categoryId, $subcategoryId) + { + return $categoryId . '.' . $subcategoryId; + } + + public function buildPageMetadata(Category $category, Subcategory $subcategory, $widgetConfigs) + { + $ca = array( + 'uniqueId' => $this->buildPageId($category->getId(), $subcategory->getId()), + 'category' => $this->buildCategoryMetadata($category), + 'subcategory' => $this->buildSubcategoryMetadata($subcategory), + 'widgets' => array() + ); + + foreach ($widgetConfigs as $config) { + $ca['widgets'][] = $this->buildWidgetMetadata($config); + } + + return $ca; + } + +}
\ No newline at end of file diff --git a/plugins/API/tests/Unit/WidgetMetadataTest.php b/plugins/API/tests/Unit/WidgetMetadataTest.php new file mode 100644 index 0000000000..bb4379a010 --- /dev/null +++ b/plugins/API/tests/Unit/WidgetMetadataTest.php @@ -0,0 +1,278 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +namespace Piwik\Plugins\API\tests\Unit; + +use Piwik\Category\Category; +use Piwik\Category\CategoryList; +use Piwik\Category\Subcategory; +use Piwik\DataTable; +use Piwik\Plugins\API\Renderer\Console; +use Piwik\Plugins\API\WidgetMetadata; +use Piwik\Plugins\CoreHome\CoreHome; +use Piwik\Report\ReportWidgetConfig; +use Piwik\Widget\WidgetConfig; +use Piwik\Widget\WidgetContainerConfig; + +/** + * @group Widget + * @group Widgets + * @group WidgetMetadata + * @group WidgetMetadataTest + */ +class WidgetMetadataTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var WidgetMetadata + */ + private $metadata; + + public function setUp() + { + $this->metadata = new WidgetMetadata(); + } + + public function test_buildWidgetMetadata_ShouldGenerateMetadata() + { + $config = $this->createWidgetConfig('Test', 'CategoryId', 'SubcategoryId'); + $list = $this->createCategoryList(array('CategoryId' => array('SubcategoryId'))); + $metadata = $this->metadata->buildWidgetMetadata($config, $list); + + $this->assertEquals(array( + 'name' => 'Test', + 'category' => array( + 'id' => 'CategoryId', + 'name' => 'CategoryId', + 'order' => 99, + ), + 'subcategory' => array( + 'id' => 'SubcategoryId', + 'name' => 'SubcategoryIdName', + 'order' => 99, + ), + 'module' => 'CoreHome', + 'action' => 'render', + 'order' => 99, + 'parameters' => array ( + 'module' => 'CoreHome', + 'action' => 'render' + ), + 'uniqueId' => 'widgetCoreHomerender' + ), $metadata); + } + + public function test_buildWidgetMetadata_ShouldSetCategoryAndSubcategoryToNull_IfBothGivenButNotExistInList() + { + $config = $this->createWidgetConfig('Test', 'CategoryId', 'SubcategoryId'); + $list = $this->createCategoryList(); + $metadata = $this->metadata->buildWidgetMetadata($config, $list); + + $this->assertNull($metadata['category']); + $this->assertNull($metadata['subcategory']); + } + + public function test_buildWidgetMetadata_ShouldSetSubcategoryToNull_IfCategoryGivenInListButSubcategoryNot() + { + $config = $this->createWidgetConfig('Test', 'CategoryId', 'SubcategoryId'); + $list = $this->createCategoryList(array('CategoryId' => array())); + $metadata = $this->metadata->buildWidgetMetadata($config, $list); + + $this->assertSame(array( + 'id' => 'CategoryId', + 'name' => 'CategoryId', + 'order' => 99, + ), $metadata['category']); + $this->assertNull($metadata['subcategory']); + } + + public function test_buildWidgetMetadata_ShouldNotAddCategoryAndSubcategoryToNull_IfNoCategoryListGiven() + { + $config = $this->createWidgetConfig('Test', 'CategoryId', 'SubcategoryId'); + $metadata = $this->metadata->buildWidgetMetadata($config); + + $this->assertArrayNotHasKey('category', $metadata); + $this->assertArrayNotHasKey('subcategory', $metadata); + } + + public function test_buildWidgetMetadata_ShouldAddOptionalMiddlewareParameters() + { + $config = $this->createWidgetConfig('Test', 'CategoryId', 'SubcategoryId'); + $config->setMiddlewareParameters(array('module' => 'Goals', 'action' => 'hasAnyConversions')); + $metadata = $this->metadata->buildWidgetMetadata($config); + + $this->assertSame(array('module' => 'Goals', 'action' => 'hasAnyConversions'), $metadata['middlewareParameters']); + } + + public function test_buildWidgetMetadata_ShouldAddReportInformtion_IfReportWidgetConfigGiven() + { + $config = new ReportWidgetConfig(); + $config->setDefaultViewDataTable('graph'); + $metadata = $this->metadata->buildWidgetMetadata($config); + + $this->assertSame('graph', $metadata['viewDataTable']); + $this->assertTrue($metadata['isReport']); + } + + public function test_buildWidgetMetadata_ShouldAddContainerInformtion_IfWidgetContainerConfigGiven() + { + $config = new WidgetContainerConfig(); + $config->setLayout('ByDimension'); + $config->addWidgetConfig($this->createWidgetConfig('NestedName1', 'NestedCategory1', 'NestedSubcategory1')); + $config->addWidgetConfig($this->createWidgetConfig('NestedName2', 'NestedCategory2', 'NestedSubcategory2')); + $metadata = $this->metadata->buildWidgetMetadata($config); + + $this->assertSame('ByDimension', $metadata['layout']); + $this->assertTrue($metadata['isContainer']); + $this->assertCount(2, $metadata['widgets']); + + $widget1 = $metadata['widgets'][0]; + $widget2 = $metadata['widgets'][1]; + $this->assertSame(array( + 'name' => 'NestedName1', + 'category' => array ( + 'id' => 'NestedCategory1', + 'name' => 'NestedCategory1', + 'order' => 99 + ), + 'subcategory' => array ( + 'id' => 'NestedSubcategory1', + 'name' => 'NestedSubcategory1', + 'order' => 99 + ), + 'module' => 'CoreHome', + 'action' => 'render', + 'order' => 99, + 'parameters' => array ( + 'module' => 'CoreHome', + 'action' => 'render', + ), + 'uniqueId' => 'widgetCoreHomerender' + ), $widget1); + $this->assertSame(array( + 'name' => 'NestedName2', + 'category' => array ( + 'id' => 'NestedCategory2', + 'name' => 'NestedCategory2', + 'order' => 99 + ), + 'subcategory' => array ( + 'id' => 'NestedSubcategory2', + 'name' => 'NestedSubcategory2', + 'order' => 99 + ), + 'module' => 'CoreHome', + 'action' => 'render', + 'order' => 99, + 'parameters' => array ( + 'module' => 'CoreHome', + 'action' => 'render', + ), + 'uniqueId' => 'widgetCoreHomerender' + ), $widget2); + } + + public function test_buildPageMetadata_ShouldAddContainerInformtion_IfWidgetContainerConfigGiven() + { + $config = new WidgetContainerConfig(); + $config->setLayout('ByDimension'); + + $widgets = array( + $this->createWidgetConfig('NestedName1', 'NestedCategory1', 'NestedSubcategory1'), + $this->createWidgetConfig('NestedName2', 'NestedCategory2', 'NestedSubcategory1'), + ); + + $category = $this->createCategory('NestedCategory1'); + $subcategory = $this->createSubcategory('NestedCategory1' ,'NestedSubcategory1'); + + $metadata = $this->metadata->buildPageMetadata($category, $subcategory, $widgets); + + $this->assertSame(array( + 'uniqueId' => 'NestedCategory1.NestedSubcategory1', + 'category' => array ( + 'id' => 'NestedCategory1', + 'name' => 'NestedCategory1', + 'order' => 99, + ), + 'subcategory' => array ( + 'id' => 'NestedSubcategory1', + 'name' => 'NestedSubcategory1Name', + 'order' => 99, + ), + 'widgets' => array ( + 0 => array ( // widgets should not have category / subcategory again, it's already present above + 'name' => 'NestedName1', + 'module' => 'CoreHome', + 'action' => 'render', + 'order' => 99, + 'parameters' => array ( + 'module' => 'CoreHome', + 'action' => 'render', + ), + 'uniqueId' => 'widgetCoreHomerender', + ), array ( + 'name' => 'NestedName2', + 'module' => 'CoreHome', + 'action' => 'render', + 'order' => 99, + 'parameters' => array ( + 'module' => 'CoreHome', + 'action' => 'render', + ), + 'uniqueId' => 'widgetCoreHomerender' + ) + ) + ), $metadata); + } + + private function createWidgetConfig($name, $categoryId, $subcategoryId = '') + { + $widgetConfig = new WidgetConfig(); + $widgetConfig->setName($name); + $widgetConfig->setCategoryId($categoryId); + $widgetConfig->setSubcategoryId($subcategoryId); + $widgetConfig->setModule('CoreHome'); + $widgetConfig->setAction('render'); + + return $widgetConfig; + } + + private function createCategoryList($categories = array()) + { + $list = new CategoryList(); + + foreach ($categories as $categoryId => $subcategoryIds) { + $category = $this->createCategory($categoryId); + $list->addCategory($category); + + foreach ($subcategoryIds as $subcategoryId) { + $subcategory = $this->createSubcategory($categoryId, $subcategoryId); + $category->addSubcategory($subcategory); + } + } + + return $list; + } + + private function createSubcategory($categoryId, $subcategoryId) + { + $subcategory = new Subcategory(); + $subcategory->setCategoryId($categoryId); + $subcategory->setId($subcategoryId); + $subcategory->setName($subcategoryId . 'Name'); + + return $subcategory; + } + + private function createCategory($categoryId) + { + $category = new Category(); + $category->setId($categoryId); + return $category; + } + +} |