Teknik is a suite of services with attractive and functional interfaces. https://www.teknik.io/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

DataTableManipulator.php 6.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. <?php
  2. /**
  3. * Piwik - free/libre analytics platform
  4. *
  5. * @link http://piwik.org
  6. * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  7. *
  8. */
  9. namespace Piwik\API;
  10. use Exception;
  11. use Piwik\Archive\DataTableFactory;
  12. use Piwik\Common;
  13. use Piwik\DataTable\Row;
  14. use Piwik\DataTable;
  15. use Piwik\Period\Range;
  16. use Piwik\Plugins\API\API;
  17. /**
  18. * Base class for manipulating data tables.
  19. * It provides generic mechanisms like iteration and loading subtables.
  20. *
  21. * The manipulators are used in ResponseBuilder and are triggered by
  22. * API parameters. They are not filters because they don't work on the pre-
  23. * fetched nested data tables. Instead, they load subtables using this base
  24. * class. This way, they can only load the tables they really need instead
  25. * of using expanded=1. Another difference between manipulators and filters
  26. * is that filters keep the overall structure of the table intact while
  27. * manipulators can change the entire thing.
  28. */
  29. abstract class DataTableManipulator
  30. {
  31. protected $apiModule;
  32. protected $apiMethod;
  33. protected $request;
  34. private $apiMethodForSubtable;
  35. /**
  36. * Constructor
  37. *
  38. * @param bool $apiModule
  39. * @param 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. } else if ($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
  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();
  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 $request
  124. * @return
  125. */
  126. protected abstract function manipulateSubtableRequest($request);
  127. /**
  128. * Extract the API method for loading subtables from the meta data
  129. *
  130. * @return string
  131. */
  132. private function getApiMethodForSubtable()
  133. {
  134. if (!$this->apiMethodForSubtable) {
  135. $meta = API::getInstance()->getMetadata('all', $this->apiModule, $this->apiMethod);
  136. if(empty($meta)) {
  137. throw new Exception(sprintf(
  138. "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: http://developer.piwik.org/api-reference/events#apigetreportmetadata",
  139. $this->apiModule, $this->apiMethod
  140. ));
  141. }
  142. if (isset($meta[0]['actionToLoadSubTables'])) {
  143. $this->apiMethodForSubtable = $meta[0]['actionToLoadSubTables'];
  144. } else {
  145. $this->apiMethodForSubtable = $this->apiMethod;
  146. }
  147. }
  148. return $this->apiMethodForSubtable;
  149. }
  150. protected function callApiAndReturnDataTable($apiModule, $method, $request)
  151. {
  152. $class = Request::getClassNameAPI($apiModule);
  153. $request = $this->manipulateSubtableRequest($request);
  154. $request['serialize'] = 0;
  155. $request['expanded'] = 0;
  156. // don't want to run recursive filters on the subtables as they are loaded,
  157. // otherwise the result will be empty in places (or everywhere). instead we
  158. // run it on the flattened table.
  159. unset($request['filter_pattern_recursive']);
  160. $dataTable = Proxy::getInstance()->call($class, $method, $request);
  161. $response = new ResponseBuilder($format = 'original', $request);
  162. $dataTable = $response->getResponse($dataTable);
  163. if (Common::getRequestVar('disable_queued_filters', 0, 'int', $request) == 0) {
  164. if (method_exists($dataTable, 'applyQueuedFilters')) {
  165. $dataTable->applyQueuedFilters();
  166. }
  167. }
  168. return $dataTable;
  169. }
  170. }