PageRenderTime 47ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/core/DataTable/Row.php

https://github.com/quarkness/piwik
PHP | 490 lines | 258 code | 38 blank | 194 comment | 37 complexity | d920e7d2729079f79bf49dbc34a5836f MD5 | raw file
  1. <?php
  2. /**
  3. * Piwik - Open source web analytics
  4. *
  5. * @link http://piwik.org
  6. * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  7. * @version $Id$
  8. *
  9. * @category Piwik
  10. * @package Piwik
  11. */
  12. /**
  13. * A DataTable is composed of rows.
  14. *
  15. * A row is composed of:
  16. * - columns often at least a 'label' column containing the description
  17. * of the row, and some numeric values ('nb_visits', etc.)
  18. * - metadata: other information never to be shown as 'columns'
  19. * - idSubtable: a row can be linked to a SubTable
  20. *
  21. * IMPORTANT: Make sure that the column named 'label' contains at least one non-numeric character.
  22. * Otherwise the method addDataTable() or sumRow() would fail because they would consider
  23. * the 'label' as being a numeric column to sum.
  24. *
  25. * PERFORMANCE: Do *not* add new fields except if necessary in this object. New fields will be
  26. * serialized and recorded in the DB millions of times. This object size is critical and must be under control.
  27. *
  28. * @package Piwik
  29. * @subpackage Piwik_DataTable
  30. */
  31. class Piwik_DataTable_Row
  32. {
  33. /**
  34. * This array contains the row information:
  35. * - array indexed by self::COLUMNS contains the columns, pairs of (column names, value)
  36. * - (optional) array indexed by self::METADATA contains the metadata, pairs of (metadata name, value)
  37. * - (optional) integer indexed by self::DATATABLE_ASSOCIATED contains the ID of the Piwik_DataTable associated to this row.
  38. * This ID can be used to read the DataTable from the DataTable_Manager.
  39. *
  40. * @var array
  41. * @see constructor for more information
  42. */
  43. public $c = array();
  44. const COLUMNS = 0;
  45. const METADATA = 1;
  46. const DATATABLE_ASSOCIATED = 3;
  47. /**
  48. * Efficient load of the Row structure from a well structured php array
  49. *
  50. * @param array The row array has the structure
  51. * array(
  52. * Piwik_DataTable_Row::COLUMNS => array(
  53. * 'label' => 'Piwik',
  54. * 'column1' => 42,
  55. * 'visits' => 657,
  56. * 'time_spent' => 155744,
  57. * ),
  58. * Piwik_DataTable_Row::METADATA => array(
  59. * 'logo' => 'test.png'
  60. * ),
  61. * Piwik_DataTable_Row::DATATABLE_ASSOCIATED => #Piwik_DataTable object (but in the row only the ID will be stored)
  62. * )
  63. */
  64. public function __construct( $row = array() )
  65. {
  66. $this->c[self::COLUMNS] = array();
  67. $this->c[self::METADATA] = array();
  68. $this->c[self::DATATABLE_ASSOCIATED] = null;
  69. if(isset($row[self::COLUMNS]))
  70. {
  71. $this->c[self::COLUMNS] = $row[self::COLUMNS];
  72. }
  73. if(isset($row[self::METADATA]))
  74. {
  75. $this->c[self::METADATA] = $row[self::METADATA];
  76. }
  77. if(isset($row[self::DATATABLE_ASSOCIATED])
  78. && $row[self::DATATABLE_ASSOCIATED] instanceof Piwik_DataTable)
  79. {
  80. $this->c[self::DATATABLE_ASSOCIATED] = $row[self::DATATABLE_ASSOCIATED]->getId();
  81. }
  82. }
  83. /**
  84. * When destroyed, a row destroys its associated subTable if there is one
  85. */
  86. public function __destruct()
  87. {
  88. $idSubtable = $this->c[self::DATATABLE_ASSOCIATED];
  89. if($idSubtable !== null)
  90. {
  91. Piwik_DataTable_Manager::getInstance()->deleteTable($idSubtable);
  92. $idSubtable = null;
  93. }
  94. }
  95. /**
  96. * Applys a basic rendering to the Row and returns the output
  97. *
  98. * @return string characterizing the row. Example: - 1 ['label' => 'piwik', 'nb_uniq_visitors' => 1685, 'nb_visits' => 1861, 'nb_actions' => 2271, 'max_actions' => 13, 'sum_visit_length' => 920131, 'bounce_count' => 1599] [] [idsubtable = 1375]
  99. */
  100. public function __toString()
  101. {
  102. $columns = array();
  103. foreach($this->getColumns() as $column => $value)
  104. {
  105. if(is_string($value)) $value = "'$value'";
  106. elseif(is_array($value)) $value = var_export($value, true);
  107. $columns[] = "'$column' => $value";
  108. }
  109. $columns = implode(", ", $columns);
  110. $metadata = array();
  111. foreach($this->getMetadata() as $name => $value)
  112. {
  113. if(is_string($value)) $value = "'$value'";
  114. elseif(is_array($value)) $value = var_export($value, true);
  115. $metadata[] = "'$name' => $value";
  116. }
  117. $metadata = implode(", ", $metadata);
  118. $output = "# [".$columns."] [".$metadata."] [idsubtable = " . $this->getIdSubDataTable()."]<br />\n";
  119. return $output;
  120. }
  121. /**
  122. * Deletes the given column
  123. *
  124. * @param string Column name
  125. * @return bool True on success, false if the column didn't exist
  126. */
  127. public function deleteColumn( $name )
  128. {
  129. if(!isset($this->c[self::COLUMNS][$name]))
  130. {
  131. return false;
  132. }
  133. unset($this->c[self::COLUMNS][$name]);
  134. return true;
  135. }
  136. public function renameColumn($oldName, $newName)
  137. {
  138. if(isset($this->c[self::COLUMNS][$oldName]))
  139. {
  140. $this->c[self::COLUMNS][$newName] = $this->c[self::COLUMNS][$oldName];
  141. }
  142. // outside the if() since we want to delete nulled columns
  143. unset($this->c[self::COLUMNS][$oldName]);
  144. }
  145. /**
  146. * Returns the given column
  147. *
  148. * @param string Column name
  149. * @return mixed|false The column value
  150. */
  151. public function getColumn( $name )
  152. {
  153. if(!isset($this->c[self::COLUMNS][$name]))
  154. {
  155. return false;
  156. }
  157. return $this->c[self::COLUMNS][$name];
  158. }
  159. /**
  160. * Returns the array of all metadata,
  161. * or the specified metadata
  162. *
  163. * @param string Metadata name
  164. * @return mixed|array|false
  165. */
  166. public function getMetadata( $name = null )
  167. {
  168. if(is_null($name))
  169. {
  170. return $this->c[self::METADATA];
  171. }
  172. if(!isset($this->c[self::METADATA][$name]))
  173. {
  174. return false;
  175. }
  176. return $this->c[self::METADATA][$name];
  177. }
  178. /**
  179. * Returns the array containing all the columns
  180. *
  181. * @return array Example: array(
  182. * 'column1' => VALUE,
  183. * 'label' => 'www.php.net'
  184. * 'nb_visits' => 15894,
  185. * )
  186. */
  187. public function getColumns()
  188. {
  189. return $this->c[self::COLUMNS];
  190. }
  191. /**
  192. * Returns the ID of the subDataTable.
  193. * If there is no such a table, returns null.
  194. *
  195. * @return int|null
  196. */
  197. public function getIdSubDataTable()
  198. {
  199. return $this->c[self::DATATABLE_ASSOCIATED];
  200. }
  201. /**
  202. * Sums a DataTable to this row subDataTable.
  203. * If this row doesn't have a SubDataTable yet, we create a new one.
  204. * Then we add the values of the given DataTable to this row's DataTable.
  205. *
  206. * @param Piwik_DataTable Table to sum to this row's subDatatable
  207. * @see Piwik_DataTable::addDataTable() for the algorithm used for the sum
  208. */
  209. public function sumSubtable(Piwik_DataTable $subTable)
  210. {
  211. $thisSubtableID = $this->getIdSubDataTable();
  212. if($thisSubtableID === null)
  213. {
  214. $thisSubTable = new Piwik_DataTable();
  215. $this->addSubtable($thisSubTable);
  216. }
  217. else
  218. {
  219. $thisSubTable = Piwik_DataTable_Manager::getInstance()->getTable( $thisSubtableID );
  220. }
  221. $thisSubTable->addDataTable($subTable);
  222. }
  223. /**
  224. * Set a DataTable to be associated to this row.
  225. * If the row already has a DataTable associated to it, throws an Exception.
  226. *
  227. * @param Piwik_DataTable DataTable to associate to this row
  228. * @throws Exception
  229. *
  230. */
  231. public function addSubtable(Piwik_DataTable $subTable)
  232. {
  233. if(!is_null($this->c[self::DATATABLE_ASSOCIATED]))
  234. {
  235. throw new Exception("Adding a subtable to the row, but it already has a subtable associated.");
  236. }
  237. $this->c[self::DATATABLE_ASSOCIATED] = $subTable->getId();
  238. }
  239. /**
  240. * Set a DataTable to this row. If there is already
  241. * a DataTable associated, it is simply overwritten.
  242. *
  243. * @param Piwik_DataTable DataTable to associate to this row
  244. */
  245. public function setSubtable(Piwik_DataTable $subTable)
  246. {
  247. $this->c[self::DATATABLE_ASSOCIATED] = $subTable->getId();
  248. }
  249. /**
  250. * Set all the columns at once. Overwrites previously set columns.
  251. *
  252. * @param array array(
  253. * 'label' => 'www.php.net'
  254. * 'nb_visits' => 15894,
  255. * )
  256. */
  257. public function setColumns( $columns )
  258. {
  259. $this->c[self::COLUMNS] = $columns;
  260. }
  261. /**
  262. * Set the value $value to the column called $name.
  263. *
  264. * @param string $name of the column to set
  265. * @param mixed $value of the column to set
  266. */
  267. public function setColumn($name, $value)
  268. {
  269. $this->c[self::COLUMNS][$name] = $value;
  270. }
  271. /**
  272. * Set the value $value to the metadata called $name.
  273. *
  274. * @param string $name of the metadata to set
  275. * @param mixed $value of the metadata to set
  276. */
  277. public function setMetadata($name, $value)
  278. {
  279. $this->c[self::METADATA][$name] = $value;
  280. }
  281. /**
  282. * Add a new column to the row. If the column already exists, throws an exception
  283. *
  284. * @param string $name of the column to add
  285. * @param mixed $value of the column to set
  286. * @throws Exception
  287. */
  288. public function addColumn($name, $value)
  289. {
  290. if(isset($this->c[self::COLUMNS][$name]))
  291. {
  292. // debug_print_backtrace();
  293. throw new Exception("Column $name already in the array!");
  294. }
  295. $this->c[self::COLUMNS][$name] = $value;
  296. }
  297. /**
  298. * Add columns to the row
  299. *
  300. * @param array $columns Name/Value pairs, e.g., array( name => value , ...)
  301. * @return void
  302. */
  303. public function addColumns($columns)
  304. {
  305. foreach($columns as $name => $value)
  306. {
  307. $this->addColumn($name, $value);
  308. }
  309. }
  310. /**
  311. * Add a new metadata to the row. If the column already exists, throws an exception.
  312. *
  313. * @param string $name of the metadata to add
  314. * @param mixed $value of the metadata to set
  315. * @throws Exception
  316. */
  317. public function addMetadata($name, $value)
  318. {
  319. if(isset($this->c[self::METADATA][$name]))
  320. {
  321. throw new Exception("Metadata $name already in the array!");
  322. }
  323. $this->c[self::METADATA][$name] = $value;
  324. }
  325. /**
  326. * Sums the given $row columns values to the existing row' columns values.
  327. * It will sum only the int or float values of $row.
  328. * It will not sum the column 'label' even if it has a numeric value.
  329. *
  330. * If a given column doesn't exist in $this then it is added with the value of $row.
  331. * If the column already exists in $this then we have
  332. * this.columns[idThisCol] += $row.columns[idThisCol]
  333. */
  334. public function sumRow( Piwik_DataTable_Row $rowToSum )
  335. {
  336. foreach($rowToSum->getColumns() as $columnToSumName => $columnToSumValue)
  337. {
  338. if($columnToSumName != 'label')
  339. {
  340. $thisColumnValue = $this->getColumn($columnToSumName);
  341. // Max operation
  342. if($columnToSumName == Piwik_Archive::INDEX_MAX_ACTIONS )
  343. {
  344. $newValue = max($thisColumnValue, $columnToSumValue);
  345. }
  346. else
  347. {
  348. $newValue = $this->sumRowArray($thisColumnValue, $columnToSumValue);
  349. }
  350. $this->setColumn( $columnToSumName, $newValue);
  351. }
  352. }
  353. }
  354. protected function sumRowArray( $thisColumnValue, $columnToSumValue )
  355. {
  356. if(is_numeric($columnToSumValue))
  357. {
  358. if($thisColumnValue === false)
  359. {
  360. $thisColumnValue = 0;
  361. }
  362. return $thisColumnValue + $columnToSumValue;
  363. }
  364. if(is_array($columnToSumValue))
  365. {
  366. if($thisColumnValue == false)
  367. {
  368. return $columnToSumValue;
  369. }
  370. $newValue = $thisColumnValue;
  371. foreach($columnToSumValue as $arrayIndex => $arrayValue)
  372. {
  373. if(!isset($newValue[$arrayIndex]))
  374. {
  375. $newValue[$arrayIndex] = false;
  376. }
  377. $newValue[$arrayIndex] = $this->sumRowArray($newValue[$arrayIndex], $arrayValue);
  378. }
  379. return $newValue;
  380. }
  381. return 0;
  382. }
  383. /**
  384. * Helper function to compare array elements
  385. *
  386. * @param mixed $elem1
  387. * @param mixed $elem2
  388. * @return bool
  389. */
  390. static public function compareElements($elem1, $elem2)
  391. {
  392. if (is_array($elem1)) {
  393. if (is_array($elem2))
  394. {
  395. return strcmp(serialize($elem1), serialize($elem2));
  396. }
  397. return 1;
  398. }
  399. if (is_array($elem2))
  400. return -1;
  401. if ((string)$elem1 === (string)$elem2)
  402. return 0;
  403. return ((string)$elem1 > (string)$elem2) ? 1 : -1;
  404. }
  405. /**
  406. * Helper function to test if two rows are equal.
  407. *
  408. * Two rows are equal
  409. * - if they have exactly the same columns / metadata
  410. * - if they have a subDataTable associated, then we check that both of them are the same.
  411. *
  412. * @param Piwik_DataTable_Row row1 to compare
  413. * @param Piwik_DataTable_Row row2 to compare
  414. *
  415. * @return bool
  416. */
  417. static public function isEqual( Piwik_DataTable_Row $row1, Piwik_DataTable_Row $row2 )
  418. {
  419. //same columns
  420. $cols1 = $row1->getColumns();
  421. $cols2 = $row2->getColumns();
  422. $diff1 = array_udiff($cols1, $cols2, array(__CLASS__, 'compareElements'));
  423. $diff2 = array_udiff($cols2, $cols1, array(__CLASS__, 'compareElements'));
  424. if($diff1 != $diff2)
  425. {
  426. return false;
  427. }
  428. $dets1 = $row1->getMetadata();
  429. $dets2 = $row2->getMetadata();
  430. ksort($dets1);
  431. ksort($dets2);
  432. if($dets1 != $dets2)
  433. {
  434. return false;
  435. }
  436. // either both are null
  437. // or both have a value
  438. if( !(is_null($row1->getIdSubDataTable())
  439. && is_null($row2->getIdSubDataTable())
  440. )
  441. )
  442. {
  443. $subtable1 = Piwik_DataTable_Manager::getInstance()->getTable($row1->getIdSubDataTable());
  444. $subtable2 = Piwik_DataTable_Manager::getInstance()->getTable($row2->getIdSubDataTable());
  445. if(!Piwik_DataTable::isEqual($subtable1, $subtable2))
  446. {
  447. return false;
  448. }
  449. }
  450. return true;
  451. }
  452. }