PageRenderTime 57ms CodeModel.GetById 31ms RepoModel.GetById 0ms app.codeStats 0ms

/analytics/core/Archive/DataCollection.php

https://gitlab.com/yasminmostfa/thomas-site
PHP | 375 lines | 139 code | 43 blank | 193 comment | 19 complexity | 80010277e191ff708368a0cd45fd1ed8 MD5 | raw file
  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\Archive;
  10. use Exception;
  11. use Piwik\DataTable;
  12. /**
  13. * This class is used to hold and transform archive data for the Archive class.
  14. *
  15. * Archive data is loaded into an instance of this type, can be indexed by archive
  16. * metadata (such as the site ID, period string, etc.), and can be transformed into
  17. * DataTable and Map instances.
  18. */
  19. class DataCollection
  20. {
  21. const METADATA_CONTAINER_ROW_KEY = '_metadata';
  22. /**
  23. * The archive data, indexed first by site ID and then by period date range. Eg,
  24. *
  25. * array(
  26. * '0' => array(
  27. * array(
  28. * '2012-01-01,2012-01-01' => array(...),
  29. * '2012-01-02,2012-01-02' => array(...),
  30. * )
  31. * ),
  32. * '1' => array(
  33. * array(
  34. * '2012-01-01,2012-01-01' => array(...),
  35. * )
  36. * )
  37. * )
  38. *
  39. * Archive data can be either a numeric value or a serialized string blob. Every
  40. * piece of archive data is associated by it's archive name. For example,
  41. * the array(...) above could look like:
  42. *
  43. * array(
  44. * 'nb_visits' => 1,
  45. * 'nb_actions' => 2
  46. * )
  47. *
  48. * There is a special element '_metadata' in data rows that holds values treated
  49. * as DataTable metadata.
  50. */
  51. private $data = array();
  52. /**
  53. * The whole list of metric/record names that were used in the archive query.
  54. *
  55. * @var array
  56. */
  57. private $dataNames;
  58. /**
  59. * The type of data that was queried for (ie, "blob" or "numeric").
  60. *
  61. * @var string
  62. */
  63. private $dataType;
  64. /**
  65. * The default values to use for each metric/record name that's being queried
  66. * for.
  67. *
  68. * @var array
  69. */
  70. private $defaultRow;
  71. /**
  72. * The list of all site IDs that were queried for.
  73. *
  74. * @var array
  75. */
  76. private $sitesId;
  77. /**
  78. * The list of all periods that were queried for. Each period is associated with
  79. * the period's range string. Eg,
  80. *
  81. * array(
  82. * '2012-01-01,2012-01-31' => new Period(...),
  83. * '2012-02-01,2012-02-28' => new Period(...),
  84. * )
  85. *
  86. * @var \Piwik\Period[]
  87. */
  88. private $periods;
  89. /**
  90. * Constructor.
  91. *
  92. * @param array $dataNames @see $this->dataNames
  93. * @param string $dataType @see $this->dataType
  94. * @param array $sitesId @see $this->sitesId
  95. * @param \Piwik\Period[] $periods @see $this->periods
  96. * @param array $defaultRow @see $this->defaultRow
  97. */
  98. public function __construct($dataNames, $dataType, $sitesId, $periods, $defaultRow = null)
  99. {
  100. $this->dataNames = $dataNames;
  101. $this->dataType = $dataType;
  102. if ($defaultRow === null) {
  103. $defaultRow = array_fill_keys($dataNames, 0);
  104. }
  105. $this->sitesId = $sitesId;
  106. foreach ($periods as $period) {
  107. $this->periods[$period->getRangeString()] = $period;
  108. }
  109. $this->defaultRow = $defaultRow;
  110. }
  111. /**
  112. * Returns a reference to the data for a specific site & period. If there is
  113. * no data for the given site ID & period, it is set to the default row.
  114. *
  115. * @param int $idSite
  116. * @param string $period eg, '2012-01-01,2012-01-31'
  117. */
  118. public function &get($idSite, $period)
  119. {
  120. if (!isset($this->data[$idSite][$period])) {
  121. $this->data[$idSite][$period] = $this->defaultRow;
  122. }
  123. return $this->data[$idSite][$period];
  124. }
  125. /**
  126. * Set data for a specific site & period. If there is no data for the given site ID & period,
  127. * it is set to the default row.
  128. *
  129. * @param int $idSite
  130. * @param string $period eg, '2012-01-01,2012-01-31'
  131. * @param string $name eg 'nb_visits'
  132. * @param string $value eg 5
  133. */
  134. public function set($idSite, $period, $name, $value)
  135. {
  136. $row = & $this->get($idSite, $period);
  137. $row[$name] = $value;
  138. }
  139. /**
  140. * Adds a new metadata to the data for specific site & period. If there is no
  141. * data for the given site ID & period, it is set to the default row.
  142. *
  143. * Note: Site ID and period range string are two special types of metadata. Since
  144. * the data stored in this class is indexed by site & period, this metadata is not
  145. * stored in individual data rows.
  146. *
  147. * @param int $idSite
  148. * @param string $period eg, '2012-01-01,2012-01-31'
  149. * @param string $name The metadata name.
  150. * @param mixed $value The metadata name.
  151. */
  152. public function addMetadata($idSite, $period, $name, $value)
  153. {
  154. $row = & $this->get($idSite, $period);
  155. $row[self::METADATA_CONTAINER_ROW_KEY][$name] = $value;
  156. }
  157. /**
  158. * Returns archive data as an array indexed by metadata.
  159. *
  160. * @param array $resultIndices An array mapping metadata names to pretty labels
  161. * for them. Each archive data row will be indexed
  162. * by the metadata specified here.
  163. *
  164. * Eg, array('site' => 'idSite', 'period' => 'Date')
  165. * @return array
  166. */
  167. public function getIndexedArray($resultIndices)
  168. {
  169. $indexKeys = array_keys($resultIndices);
  170. $result = $this->createOrderedIndex($indexKeys);
  171. foreach ($this->data as $idSite => $rowsByPeriod) {
  172. foreach ($rowsByPeriod as $period => $row) {
  173. // FIXME: This hack works around a strange bug that occurs when getting
  174. // archive IDs through ArchiveProcessing instances. When a table
  175. // does not already exist, for some reason the archive ID for
  176. // today (or from two days ago) will be added to the Archive
  177. // instances list. The Archive instance will then select data
  178. // for periods outside of the requested set.
  179. // working around the bug here, but ideally, we need to figure
  180. // out why incorrect idarchives are being selected.
  181. if (empty($this->periods[$period])) {
  182. continue;
  183. }
  184. $this->putRowInIndex($result, $indexKeys, $row, $idSite, $period);
  185. }
  186. }
  187. return $result;
  188. }
  189. /**
  190. * Returns archive data as a DataTable indexed by metadata. Indexed data will
  191. * be represented by Map instances.
  192. *
  193. * @param array $resultIndices An array mapping metadata names to pretty labels
  194. * for them. Each archive data row will be indexed
  195. * by the metadata specified here.
  196. *
  197. * Eg, array('site' => 'idSite', 'period' => 'Date')
  198. * @return DataTable|DataTable\Map
  199. */
  200. public function getDataTable($resultIndices)
  201. {
  202. $dataTableFactory = new DataTableFactory(
  203. $this->dataNames, $this->dataType, $this->sitesId, $this->periods, $this->defaultRow);
  204. $index = $this->getIndexedArray($resultIndices);
  205. return $dataTableFactory->make($index, $resultIndices);
  206. }
  207. /**
  208. * See {@link DataTableFactory::makeMerged()}
  209. *
  210. * @param array $resultIndices
  211. * @return DataTable|DataTable\Map
  212. * @throws Exception
  213. */
  214. public function getMergedDataTable($resultIndices)
  215. {
  216. $dataTableFactory = new DataTableFactory(
  217. $this->dataNames, $this->dataType, $this->sitesId, $this->periods, $this->defaultRow);
  218. $index = $this->getIndexedArray($resultIndices);
  219. return $dataTableFactory->makeMerged($index, $resultIndices);
  220. }
  221. /**
  222. * Returns archive data as a DataTable indexed by metadata. Indexed data will
  223. * be represented by Map instances. Each DataTable will have
  224. * its subtable IDs set.
  225. *
  226. * This function will only work if blob data was loaded and only one record
  227. * was loaded (not including subtables of the record).
  228. *
  229. * @param array $resultIndices An array mapping metadata names to pretty labels
  230. * for them. Each archive data row will be indexed
  231. * by the metadata specified here.
  232. *
  233. * Eg, array('site' => 'idSite', 'period' => 'Date')
  234. * @param int|null $idSubTable The subtable to return.
  235. * @param int|null $depth max depth for subtables.
  236. * @param bool $addMetadataSubTableId Whether to add the DB subtable ID as metadata
  237. * to each datatable, or not.
  238. * @throws Exception
  239. * @return DataTable|DataTable\Map
  240. */
  241. public function getExpandedDataTable($resultIndices, $idSubTable = null, $depth = null, $addMetadataSubTableId = false)
  242. {
  243. if ($this->dataType != 'blob') {
  244. throw new Exception("DataCollection: cannot call getExpandedDataTable with "
  245. . "{$this->dataType} data types. Only works with blob data.");
  246. }
  247. if (count($this->dataNames) !== 1) {
  248. throw new Exception("DataCollection: cannot call getExpandedDataTable with "
  249. . "more than one record.");
  250. }
  251. $dataTableFactory = new DataTableFactory(
  252. $this->dataNames, 'blob', $this->sitesId, $this->periods, $this->defaultRow);
  253. $dataTableFactory->expandDataTable($depth, $addMetadataSubTableId);
  254. $dataTableFactory->useSubtable($idSubTable);
  255. $index = $this->getIndexedArray($resultIndices);
  256. return $dataTableFactory->make($index, $resultIndices);
  257. }
  258. /**
  259. * Returns metadata for a data row.
  260. *
  261. * @param array $data The data row.
  262. * @return array
  263. */
  264. public static function getDataRowMetadata($data)
  265. {
  266. if (isset($data[self::METADATA_CONTAINER_ROW_KEY])) {
  267. return $data[self::METADATA_CONTAINER_ROW_KEY];
  268. } else {
  269. return array();
  270. }
  271. }
  272. /**
  273. * Removes all table metadata from a data row.
  274. *
  275. * @param array $data The data row.
  276. */
  277. public static function removeMetadataFromDataRow(&$data)
  278. {
  279. unset($data[self::METADATA_CONTAINER_ROW_KEY]);
  280. }
  281. /**
  282. * Creates an empty index using a list of metadata names. If the 'site' and/or
  283. * 'period' metadata names are supplied, empty rows are added for every site/period
  284. * that was queried for.
  285. *
  286. * Using this function ensures consistent ordering in the indexed result.
  287. *
  288. * @param array $metadataNamesToIndexBy List of metadata names to index archive data by.
  289. * @return array
  290. */
  291. private function createOrderedIndex($metadataNamesToIndexBy)
  292. {
  293. $result = array();
  294. if (!empty($metadataNamesToIndexBy)) {
  295. $metadataName = array_shift($metadataNamesToIndexBy);
  296. if ($metadataName == DataTableFactory::TABLE_METADATA_SITE_INDEX) {
  297. $indexKeyValues = array_values($this->sitesId);
  298. } elseif ($metadataName == DataTableFactory::TABLE_METADATA_PERIOD_INDEX) {
  299. $indexKeyValues = array_keys($this->periods);
  300. }
  301. if (empty($metadataNamesToIndexBy)) {
  302. $result = array_fill_keys($indexKeyValues, array());
  303. } else {
  304. foreach ($indexKeyValues as $key) {
  305. $result[$key] = $this->createOrderedIndex($metadataNamesToIndexBy);
  306. }
  307. }
  308. }
  309. return $result;
  310. }
  311. /**
  312. * Puts an archive data row in an index.
  313. */
  314. private function putRowInIndex(&$index, $metadataNamesToIndexBy, $row, $idSite, $period)
  315. {
  316. $currentLevel = & $index;
  317. foreach ($metadataNamesToIndexBy as $metadataName) {
  318. if ($metadataName == DataTableFactory::TABLE_METADATA_SITE_INDEX) {
  319. $key = $idSite;
  320. } elseif ($metadataName == DataTableFactory::TABLE_METADATA_PERIOD_INDEX) {
  321. $key = $period;
  322. } else {
  323. $key = $row[self::METADATA_CONTAINER_ROW_KEY][$metadataName];
  324. }
  325. if (!isset($currentLevel[$key])) {
  326. $currentLevel[$key] = array();
  327. }
  328. $currentLevel = & $currentLevel[$key];
  329. }
  330. $currentLevel = $row;
  331. }
  332. }