ConsoleCommand
1 month ago
Dimension
1 month ago
API.php
6 months ago
AggregatedMetric.php
2 years ago
ArchivedMetric.php
1 year ago
Archiver.php
1 month ago
Categories.php
2 years ago
ComponentFactory.php
3 months ago
ComputedMetric.php
1 year ago
ConsoleCommand.php
1 month ago
Controller.php
1 month ago
ControllerAdmin.php
2 weeks ago
Dependency.php
1 month ago
LogTablesProvider.php
2 years ago
Manager.php
1 month ago
Menu.php
1 month ago
MetadataLoader.php
1 month ago
Metric.php
1 month ago
PluginException.php
1 year ago
ProcessedMetric.php
3 months ago
ReleaseChannels.php
3 months ago
Report.php
1 month ago
ReportsProvider.php
2 years ago
RequestProcessors.php
4 months ago
Segment.php
3 months ago
SettingsProvider.php
1 month ago
Tasks.php
1 month ago
ThemeStyles.php
2 weeks ago
ViewDataTable.php
3 months ago
Visualization.php
1 year ago
WidgetsProvider.php
3 months ago
ReportsProvider.php
247 lines
| 1 | <?php |
| 2 | |
| 3 | /** |
| 4 | * Matomo - free/libre analytics platform |
| 5 | * |
| 6 | * @link https://matomo.org |
| 7 | * @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later |
| 8 | */ |
| 9 | namespace Piwik\Plugin; |
| 10 | |
| 11 | use Piwik\Cache; |
| 12 | use Piwik\CacheId; |
| 13 | use Piwik\Category\CategoryList; |
| 14 | use Piwik\Common; |
| 15 | use Piwik\Piwik; |
| 16 | use Piwik\Plugin; |
| 17 | use Piwik\Cache as PiwikCache; |
| 18 | use Piwik\Site; |
| 19 | /** |
| 20 | * Get reports that are defined by plugins. |
| 21 | */ |
| 22 | class ReportsProvider |
| 23 | { |
| 24 | private $categoryList; |
| 25 | /** |
| 26 | * Get an instance of a specific report belonging to the given module and having the given action. |
| 27 | * @param string $module |
| 28 | * @param string $action |
| 29 | * @return null|\Piwik\Plugin\Report |
| 30 | * @api |
| 31 | */ |
| 32 | public static function factory($module, $action) |
| 33 | { |
| 34 | $listApiToReport = self::getMapOfModuleActionsToReport(); |
| 35 | $api = $module . '.' . ucfirst($action); |
| 36 | if (!array_key_exists($api, $listApiToReport)) { |
| 37 | return null; |
| 38 | } |
| 39 | $klassName = $listApiToReport[$api]; |
| 40 | return new $klassName(); |
| 41 | } |
| 42 | private static function getMapOfModuleActionsToReport() |
| 43 | { |
| 44 | $cacheKey = 'ReportFactoryMap'; |
| 45 | $idSite = Common::getRequestVar('idSite', 0, 'int'); |
| 46 | if (!empty($idSite)) { |
| 47 | // some reports may be per site! |
| 48 | $cacheKey .= '_' . (int) $idSite; |
| 49 | } |
| 50 | // fallback eg fror API.getReportMetadata and API.getSegmentsMetadata |
| 51 | $idSites = Common::getRequestVar('idSites', '', $type = null); |
| 52 | if (!empty($idSites)) { |
| 53 | $transientCache = Cache::getTransientCache(); |
| 54 | $transientCacheKey = 'ReportIdSitesParam'; |
| 55 | if ($transientCache->contains($transientCacheKey)) { |
| 56 | $idSites = $transientCache->fetch($transientCacheKey); |
| 57 | } else { |
| 58 | // this may be called 100 times during one page request and may go to DB, therefore have to cache |
| 59 | $idSites = Site::getIdSitesFromIdSitesString($idSites); |
| 60 | sort($idSites); |
| 61 | // we sort to reuse the cache key as often as possible |
| 62 | $transientCache->save($transientCacheKey, $idSites); |
| 63 | } |
| 64 | // it is important to not use either idsite, or idsites in the cache key but to include both for security reasons |
| 65 | // otherwise someone may specify idSite=5&idSites=7 and if then a plugin is eg only looking at idSites param |
| 66 | // we could return a wrong result (eg API.getSegmentsMetadata) |
| 67 | if (count($idSites) <= 5) { |
| 68 | $cacheKey .= '_' . implode('_', $idSites); |
| 69 | // we keep the cache key readable when possible |
| 70 | } else { |
| 71 | $cacheKey .= '_' . md5(implode('_', $idSites)); |
| 72 | // we need to shorten it |
| 73 | } |
| 74 | } |
| 75 | $lazyCacheId = CacheId::pluginAware($cacheKey); |
| 76 | $cache = PiwikCache::getLazyCache(); |
| 77 | $mapApiToReport = $cache->fetch($lazyCacheId); |
| 78 | if (empty($mapApiToReport)) { |
| 79 | $reports = new static(); |
| 80 | $reports = $reports->getAllReports(); |
| 81 | $mapApiToReport = array(); |
| 82 | foreach ($reports as $report) { |
| 83 | $key = $report->getModule() . '.' . ucfirst($report->getAction()); |
| 84 | if (isset($mapApiToReport[$key]) && $report->getParameters()) { |
| 85 | // sometimes there are multiple reports with same module/action but different parameters. |
| 86 | // we might pick the "wrong" one. At some point we should compare all parameters and if there is |
| 87 | // a report which parameters mach $_REQUEST then we should prefer that report |
| 88 | continue; |
| 89 | } |
| 90 | $mapApiToReport[$key] = get_class($report); |
| 91 | } |
| 92 | $cache->save($lazyCacheId, $mapApiToReport, $lifeTime = 3600); |
| 93 | } |
| 94 | return $mapApiToReport; |
| 95 | } |
| 96 | /** |
| 97 | * Returns a list of all available reports. Even not enabled reports will be returned. They will be already sorted |
| 98 | * depending on the order and category of the report. |
| 99 | * @return \Piwik\Plugin\Report[] |
| 100 | * @api |
| 101 | */ |
| 102 | public function getAllReports() |
| 103 | { |
| 104 | $reports = $this->getAllReportClasses(); |
| 105 | $cacheId = CacheId::siteAware(CacheId::languageAware('Reports' . md5(implode('', $reports)))); |
| 106 | $cache = PiwikCache::getTransientCache(); |
| 107 | if (!$cache->contains($cacheId)) { |
| 108 | $instances = array(); |
| 109 | /** |
| 110 | * Triggered to add new reports that cannot be picked up automatically by the platform. |
| 111 | * This is useful if the plugin allows a user to create reports / dimensions dynamically. For example |
| 112 | * CustomDimensions or CustomVariables. There are a variable number of dimensions in this case and it |
| 113 | * wouldn't be really possible to create a report file for one of these dimensions as it is not known |
| 114 | * how many Custom Dimensions will exist. |
| 115 | * |
| 116 | * **Example** |
| 117 | * |
| 118 | * public function addReport(&$reports) |
| 119 | * { |
| 120 | * $reports[] = new MyCustomReport(); |
| 121 | * } |
| 122 | * |
| 123 | * @param Report[] $reports An array of reports |
| 124 | */ |
| 125 | Piwik::postEvent('Report.addReports', array(&$instances)); |
| 126 | foreach ($reports as $report) { |
| 127 | $instances[] = new $report(); |
| 128 | } |
| 129 | /** |
| 130 | * Triggered to filter / restrict reports. |
| 131 | * |
| 132 | * **Example** |
| 133 | * |
| 134 | * public function filterReports(&$reports) |
| 135 | * { |
| 136 | * foreach ($reports as $index => $report) { |
| 137 | * if ($report->getCategoryId() === 'General_Actions') { |
| 138 | * unset($reports[$index]); // remove all reports having this action |
| 139 | * } |
| 140 | * } |
| 141 | * } |
| 142 | * |
| 143 | * @param Report[] $reports An array of reports |
| 144 | */ |
| 145 | Piwik::postEvent('Report.filterReports', array(&$instances)); |
| 146 | @usort($instances, array($this, 'sort')); |
| 147 | $cache->save($cacheId, $instances); |
| 148 | } |
| 149 | return $cache->fetch($cacheId); |
| 150 | } |
| 151 | /** |
| 152 | * API metadata are sorted by category/name, |
| 153 | * with a little tweak to replicate the standard Piwik category ordering |
| 154 | * |
| 155 | * @param Report $a |
| 156 | * @param Report $b |
| 157 | * @return int |
| 158 | */ |
| 159 | private function sort($a, $b) |
| 160 | { |
| 161 | $result = $this->compareCategories($a->getCategoryId(), $a->getSubcategoryId(), $a->getOrder(), $b->getCategoryId(), $b->getSubcategoryId(), $b->getOrder()); |
| 162 | // if categories are equal, sort by ID |
| 163 | if (!$result) { |
| 164 | $aId = $a->getId(); |
| 165 | $bId = $b->getId(); |
| 166 | if ($aId == $bId) { |
| 167 | return 0; |
| 168 | } |
| 169 | return $aId < $bId ? -1 : 1; |
| 170 | } |
| 171 | return $result; |
| 172 | } |
| 173 | /** |
| 174 | * @param string|null $catIdA |
| 175 | * @param string|null $subcatIdA |
| 176 | * @param int $orderA |
| 177 | * @param string|null $catIdB |
| 178 | * @param string|null $subcatIdB |
| 179 | * @param int $orderB |
| 180 | * |
| 181 | * @return int |
| 182 | */ |
| 183 | public function compareCategories($catIdA, $subcatIdA, $orderA, $catIdB, $subcatIdB, $orderB) |
| 184 | { |
| 185 | if (!isset($this->categoryList)) { |
| 186 | $this->categoryList = CategoryList::get(); |
| 187 | } |
| 188 | $catA = $this->categoryList->getCategory($catIdA); |
| 189 | $catB = $this->categoryList->getCategory($catIdB); |
| 190 | // in case there is a category class for both reports |
| 191 | if (isset($catA) && isset($catB)) { |
| 192 | if ($catA->getOrder() == $catB->getOrder()) { |
| 193 | // same category order, compare subcategory order |
| 194 | $subcatA = $catA->getSubcategory($subcatIdA); |
| 195 | $subcatB = $catB->getSubcategory($subcatIdB); |
| 196 | // both reports have a subcategory with custom subcategory class |
| 197 | if ($subcatA && $subcatB) { |
| 198 | if ($subcatA->getOrder() == $subcatB->getOrder()) { |
| 199 | // same subcategory order, compare order of report |
| 200 | if ($orderA == $orderB) { |
| 201 | return 0; |
| 202 | } |
| 203 | return $orderA < $orderB ? -1 : 1; |
| 204 | } |
| 205 | return $subcatA->getOrder() < $subcatB->getOrder() ? -1 : 1; |
| 206 | } elseif ($subcatA) { |
| 207 | return 1; |
| 208 | } elseif ($subcatB) { |
| 209 | return -1; |
| 210 | } |
| 211 | if ($orderA == $orderB) { |
| 212 | return 0; |
| 213 | } |
| 214 | return $orderA < $orderB ? -1 : 1; |
| 215 | } |
| 216 | return $catA->getOrder() < $catB->getOrder() ? -1 : 1; |
| 217 | } elseif (isset($catA)) { |
| 218 | return -1; |
| 219 | } elseif (isset($catB)) { |
| 220 | return 1; |
| 221 | } |
| 222 | if ($catIdA === $catIdB) { |
| 223 | // both have same category, compare order |
| 224 | if ($orderA == $orderB) { |
| 225 | return 0; |
| 226 | } |
| 227 | return $orderA < $orderB ? -1 : 1; |
| 228 | } |
| 229 | return strnatcasecmp($catIdA ?? '', $catIdB ?? ''); |
| 230 | } |
| 231 | /** |
| 232 | * Returns class names of all Report metadata classes. |
| 233 | * |
| 234 | * @return string[] |
| 235 | * @api |
| 236 | */ |
| 237 | public function getAllReportClasses() |
| 238 | { |
| 239 | return Plugin\Manager::getInstance()->findMultipleComponents('Reports', '\\Piwik\\Plugin\\Report'); |
| 240 | } |
| 241 | //Added this to trigger reset of category list as the list never gets rest after setting up due to isset check and affects testcases |
| 242 | public function unsetCategoryList() |
| 243 | { |
| 244 | unset($this->categoryList); |
| 245 | } |
| 246 | } |
| 247 |