PluginProbe
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.13.0
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.13.0
5.13.0 5.12.1 5.12.0 5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 All 83 releases
matomo / app / core / API / DataTableManipulator.php
DataTableManipulator.php
191 lines 7.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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\API;
10
11 use Exception;
12 use Piwik\Archive\DataTableFactory;
13 use Piwik\Container\StaticContainer;
14 use Piwik\DataTable\Row;
15 use Piwik\DataTable;
16 use Piwik\Period\Range;
17 use Piwik\Plugins\API\API;
18 use Piwik\Url;
19 /**
20 * Base class for manipulating data tables.
21 * It provides generic mechanisms like iteration and loading subtables.
22 *
23 * The manipulators are used in ResponseBuilder and are triggered by
24 * API parameters. They are not filters because they don't work on the pre-
25 * fetched nested data tables. Instead, they load subtables using this base
26 * class. This way, they can only load the tables they really need instead
27 * of using expanded=1. Another difference between manipulators and filters
28 * is that filters keep the overall structure of the table intact while
29 * manipulators can change the entire thing.
30 */
31 abstract class DataTableManipulator
32 {
33 protected $apiModule;
34 protected $apiMethod;
35 protected $request;
36 protected $apiMethodForSubtable;
37 /**
38 * @param string|bool $apiModule
39 * @param string|bool $apiMethod
40 * @param array $request
41 */
42 public function __construct($apiModule = \false, $apiMethod = \false, $request = array())
43 {
44 $this->apiModule = $apiModule;
45 $this->apiMethod = $apiMethod;
46 $this->request = $request;
47 }
48 /**
49 * This method can be used by subclasses to iterate over data tables that might be
50 * data table maps. It calls back the template method self::doManipulate for each table.
51 * This way, data table arrays can be handled in a transparent fashion.
52 *
53 * @param DataTable\Map|DataTable $dataTable
54 * @throws Exception
55 * @return DataTable\Map|DataTable
56 */
57 protected function manipulate($dataTable)
58 {
59 if ($dataTable instanceof DataTable\Map) {
60 return $this->manipulateDataTableMap($dataTable);
61 } elseif ($dataTable instanceof DataTable) {
62 return $this->manipulateDataTable($dataTable);
63 } else {
64 return $dataTable;
65 }
66 }
67 /**
68 * Manipulates child DataTables of a DataTable\Map. See @manipulate for more info.
69 *
70 * @param DataTable\Map $dataTable
71 * @return DataTable\Map
72 */
73 protected function manipulateDataTableMap($dataTable)
74 {
75 $result = $dataTable->getEmptyClone();
76 foreach ($dataTable->getDataTables() as $tableLabel => $childTable) {
77 $newTable = $this->manipulate($childTable);
78 $result->addTable($newTable, $tableLabel);
79 }
80 return $result;
81 }
82 /**
83 * Manipulates a single DataTable instance. Derived classes must define
84 * this function.
85 */
86 protected abstract function manipulateDataTable($dataTable);
87 /**
88 * Load the subtable for a row.
89 * Returns null if none is found.
90 *
91 * @param DataTable $dataTable
92 * @param Row $row
93 *
94 * @return DataTable|null
95 */
96 protected function loadSubtable($dataTable, $row)
97 {
98 if (!($this->apiModule && $this->apiMethod && count($this->request))) {
99 return null;
100 }
101 $request = $this->request;
102 $idSubTable = $row->getIdSubDataTable();
103 if ($idSubTable === null) {
104 return null;
105 }
106 $request['idSubtable'] = $idSubTable;
107 if ($dataTable) {
108 $period = $dataTable->getMetadata(DataTableFactory::TABLE_METADATA_PERIOD_INDEX);
109 if ($period instanceof Range) {
110 $request['date'] = $period->getDateStart() . ',' . $period->getDateEnd();
111 } else {
112 $request['date'] = $period->getDateStart()->toString();
113 }
114 }
115 $method = $this->getApiMethodForSubtable($request);
116 return $this->callApiAndReturnDataTable($this->apiModule, $method, $request);
117 }
118 /**
119 * In this method, subclasses can clean up the request array for loading subtables
120 * in order to make ResponseBuilder behave correctly (e.g. not trigger the
121 * manipulator again).
122 *
123 * @param array $request
124 * @return array
125 */
126 protected abstract function manipulateSubtableRequest($request);
127 /**
128 * Extract the API method for loading subtables from the meta data
129 *
130 * @throws Exception
131 * @return string
132 */
133 protected function getApiMethodForSubtable($request)
134 {
135 if (!$this->apiMethodForSubtable) {
136 if (!empty($request['idSite'])) {
137 $idSite = $request['idSite'];
138 } else {
139 $idSite = 'all';
140 }
141 $apiParameters = array();
142 $entityNames = StaticContainer::get('entities.idNames');
143 foreach ($entityNames as $idName) {
144 if (!empty($request[$idName])) {
145 $apiParameters[$idName] = $request[$idName];
146 }
147 }
148 $meta = API::getInstance()->getMetadata($idSite, $this->apiModule, $this->apiMethod, $apiParameters);
149 if (empty($meta) && array_key_exists('idGoal', $apiParameters)) {
150 unset($apiParameters['idGoal']);
151 $meta = API::getInstance()->getMetadata($idSite, $this->apiModule, $this->apiMethod, $apiParameters);
152 }
153 if (empty($meta)) {
154 throw new Exception(sprintf("The DataTable cannot be manipulated: Metadata for report %s.%s could not be found. You can define the metadata in a hook, see example at: %s", $this->apiModule, $this->apiMethod, Url::addCampaignParametersToMatomoLink('https://developer.matomo.org/api-reference/events#apigetreportmetadata')));
155 }
156 if (isset($meta[0]['actionToLoadSubTables'])) {
157 $this->apiMethodForSubtable = $meta[0]['actionToLoadSubTables'];
158 } else {
159 $this->apiMethodForSubtable = $this->apiMethod;
160 }
161 }
162 return $this->apiMethodForSubtable;
163 }
164 protected function callApiAndReturnDataTable($apiModule, $method, $request)
165 {
166 $class = \Piwik\API\Request::getClassNameAPI($apiModule);
167 $request = $this->manipulateSubtableRequest($request);
168 $request['serialize'] = 0;
169 $request['expanded'] = 0;
170 $request['format'] = 'original';
171 $request['format_metrics'] = 0;
172 $request['compare'] = 0;
173 // don't want to run recursive filters on the subtables as they are loaded,
174 // otherwise the result will be empty in places (or everywhere). instead we
175 // run it on the flattened table.
176 unset($request['filter_pattern_recursive']);
177 $dataTable = \Piwik\API\Proxy::getInstance()->call($class, $method, $request);
178 $response = new \Piwik\API\ResponseBuilder($format = 'original', $request);
179 $response->disableSendHeader();
180 $dataTable = $response->getResponse($dataTable, $apiModule, $method);
181 // save API method name so it can be used by filters
182 if ($dataTable instanceof DataTable\DataTableInterface) {
183 $dataTable->filter(function (DataTable $table) use($apiModule, $method) {
184 $table->setMetadata('apiModule', $apiModule);
185 $table->setMetadata('apiMethod', $method);
186 });
187 }
188 return $dataTable;
189 }
190 }
191