PageRenderTime 56ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/system/application/stats/core/Archive/Single.php

https://github.com/ibnoe/Microweber
PHP | 565 lines | 310 code | 65 blank | 190 comment | 50 complexity | 1f6751d65538f7c752af07396c9d9228 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: Single.php 4272 2011-04-01 00:33:56Z vipsoft $
  8. *
  9. *
  10. * @category Piwik
  11. * @package Piwik
  12. */
  13. /**
  14. * Piwik_Archive_Single is used to store the data of a single archive,
  15. * for example the statistics for the 'day' '2008-02-21' for the website idSite '2'
  16. *
  17. * @package Piwik
  18. * @subpackage Piwik_Archive
  19. */
  20. class Piwik_Archive_Single extends Piwik_Archive
  21. {
  22. /**
  23. * The Piwik_ArchiveProcessing object used to check that the archive is available
  24. * and launch the processing if the archive was not yet processed
  25. *
  26. * @var Piwik_ArchiveProcessing
  27. */
  28. public $archiveProcessing = null;
  29. /**
  30. * @var bool Set to true if the archive has at least 1 visit
  31. */
  32. public $isThereSomeVisits = null;
  33. /**
  34. * Period of this Archive
  35. *
  36. * @var Piwik_Period
  37. */
  38. protected $period = null;
  39. /**
  40. * Set to true will activate numeric value caching for this archive.
  41. *
  42. * @var bool
  43. */
  44. protected $cacheEnabledForNumeric = true;
  45. /**
  46. * Array of cached numeric values, used to make requests faster
  47. * when requesting the same value again and again
  48. *
  49. * @var array of numeric
  50. */
  51. protected $numericCached = array();
  52. /**
  53. * Array of cached blob, used to make requests faster when requesting the same blob again and again
  54. *
  55. * @var array of mixed
  56. */
  57. protected $blobCached = array();
  58. /**
  59. * idarchive of this Archive in the database
  60. *
  61. * @var int
  62. */
  63. protected $idArchive = null;
  64. /**
  65. * Flag set to true once the archive has been checked (when we make sure it is archived)
  66. *
  67. * @var bool
  68. */
  69. protected $alreadyChecked = array();
  70. protected function clearCache()
  71. {
  72. foreach($this->blobCached as $name => $blob)
  73. {
  74. $this->freeBlob($name);
  75. }
  76. $this->blobCached = array();
  77. }
  78. public function __destruct()
  79. {
  80. $this->clearCache();
  81. }
  82. /**
  83. * Returns the pretty date of this Archive, eg. 'Thursday 20th March 2008'
  84. *
  85. * @return string
  86. */
  87. public function getPrettyDate()
  88. {
  89. return $this->period->getPrettyString();
  90. }
  91. /**
  92. * Returns the idarchive of this Archive used to index this archive in the DB
  93. *
  94. * @return int
  95. */
  96. public function getIdArchive()
  97. {
  98. if(is_null($this->idArchive))
  99. {
  100. throw new Exception("idArchive is null");
  101. }
  102. return $this->idArchive;
  103. }
  104. /**
  105. * Set the period
  106. *
  107. * @param Piwik_Period $period
  108. */
  109. public function setPeriod( Piwik_Period $period )
  110. {
  111. $this->period = $period;
  112. }
  113. public function getPeriod()
  114. {
  115. return $this->period;
  116. }
  117. /**
  118. * Returns the timestamp of the first date in the period for this Archive.
  119. * This is used to sort archives by date when working on a Archive_Array
  120. *
  121. * @return int Unix timestamp
  122. */
  123. public function getTimestampStartDate()
  124. {
  125. if(!is_null($this->archiveProcessing))
  126. {
  127. $timestamp = $this->archiveProcessing->getTimestampStartDate();
  128. if(!empty($timestamp))
  129. {
  130. return $timestamp;
  131. }
  132. }
  133. return $this->period->getDateStart()->getTimestamp();
  134. }
  135. /**
  136. * Prepares the archive. Gets the idarchive from the ArchiveProcessing.
  137. *
  138. * This will possibly launch the archiving process if the archive was not available.
  139. */
  140. public function prepareArchive()
  141. {
  142. $archiveJustProcessed = false;
  143. $periodString = $this->period->getLabel();
  144. $plugin = Piwik_ArchiveProcessing::getPluginBeingProcessed($this->getRequestedReport());
  145. $cacheKey = 'all';
  146. if($periodString == 'range')
  147. {
  148. $cacheKey = $plugin;
  149. }
  150. if(!isset($this->alreadyChecked[$cacheKey]))
  151. {
  152. $this->isThereSomeVisits = false;
  153. $this->alreadyChecked[$cacheKey] = true;
  154. $dayString = $this->period->getPrettyString();
  155. $logMessage = "Preparing archive: " . $periodString . "(" . $dayString . "), plugin $plugin ";
  156. // if the END of the period is BEFORE the website creation date
  157. // we already know there are no stats for this period
  158. // we add one day to make sure we don't miss the day of the website creation
  159. if( $this->period->getDateEnd()->addDay(2)->isEarlier( $this->site->getCreationDate() ) )
  160. {
  161. Piwik::log("$logMessage skipped, archive is before the website was created.");
  162. return;
  163. }
  164. // if the starting date is in the future we know there is no visit
  165. if( $this->period->getDateStart()->subDay(2)->isLater( Piwik_Date::today() ) )
  166. {
  167. Piwik::log("$logMessage skipped, archive is after today.");
  168. return;
  169. }
  170. // we make sure the archive is available for the given date
  171. $periodLabel = $this->period->getLabel();
  172. $this->archiveProcessing = Piwik_ArchiveProcessing::factory($periodLabel);
  173. $this->archiveProcessing->setSite($this->site);
  174. $this->archiveProcessing->setPeriod($this->period);
  175. $this->archiveProcessing->setSegment($this->segment);
  176. $this->archiveProcessing->init();
  177. $this->archiveProcessing->setRequestedReport( $this->getRequestedReport() );
  178. $archivingDisabledArchiveNotProcessed = false;
  179. $idArchive = $this->archiveProcessing->loadArchive();
  180. if(empty($idArchive))
  181. {
  182. if($this->archiveProcessing->isArchivingDisabled())
  183. {
  184. $archivingDisabledArchiveNotProcessed = true;
  185. $logMessage = "* ARCHIVING DISABLED, for $logMessage";
  186. }
  187. else
  188. {
  189. Piwik::log("* PROCESSING $logMessage, not archived yet...");
  190. $archiveJustProcessed = true;
  191. // Process the reports
  192. $this->archiveProcessing->launchArchiving();
  193. $idArchive = $this->archiveProcessing->getIdArchive();
  194. $logMessage = "PROCESSED: idArchive = ".$idArchive.", for $logMessage";
  195. }
  196. }
  197. else
  198. {
  199. $logMessage = "* ALREADY PROCESSED, Fetching [idArchive = $idArchive], for $logMessage";
  200. }
  201. Piwik::log("$logMessage, Visits = ". $this->archiveProcessing->getNumberOfVisits());
  202. $this->isThereSomeVisits = !$archivingDisabledArchiveNotProcessed
  203. && $this->archiveProcessing->isThereSomeVisits();
  204. $this->idArchive = $idArchive;
  205. }
  206. return $archiveJustProcessed;
  207. }
  208. /**
  209. * Returns a value from the current archive with the name = $name
  210. * Method used by getNumeric or getBlob
  211. *
  212. * @param string $name
  213. * @param string $typeValue numeric|blob
  214. * @return mixed|false if no result
  215. */
  216. protected function get( $name, $typeValue = 'numeric' )
  217. {
  218. $this->setRequestedReport($name);
  219. $this->prepareArchive();
  220. // values previously "get" and now cached
  221. if($typeValue == 'numeric'
  222. && $this->cacheEnabledForNumeric
  223. && isset($this->numericCached[$name])
  224. )
  225. {
  226. return $this->numericCached[$name];
  227. }
  228. // During archiving we prefetch the blobs recursively
  229. // and we get them faster from memory after
  230. if($typeValue == 'blob'
  231. && isset($this->blobCached[$name]))
  232. {
  233. return $this->blobCached[$name];
  234. }
  235. if($name == 'idarchive')
  236. {
  237. return $this->idArchive;
  238. }
  239. if(!$this->isThereSomeVisits)
  240. {
  241. return false;
  242. }
  243. // select the table to use depending on the type of the data requested
  244. switch($typeValue)
  245. {
  246. case 'blob':
  247. $table = $this->archiveProcessing->getTableArchiveBlobName();
  248. break;
  249. case 'numeric':
  250. default:
  251. $table = $this->archiveProcessing->getTableArchiveNumericName();
  252. break;
  253. }
  254. $db = Zend_Registry::get('db');
  255. $value = $db->fetchOne("SELECT value
  256. FROM $table
  257. WHERE idarchive = ?
  258. AND name = ?",
  259. array( $this->idArchive , $name)
  260. );
  261. if($value === false)
  262. {
  263. if($typeValue == 'numeric'
  264. && $this->cacheEnabledForNumeric)
  265. {
  266. $this->numericCached[$name] = false;
  267. }
  268. return $value;
  269. }
  270. // uncompress when selecting from the BLOB table
  271. if($typeValue == 'blob' && $db->hasBlobDataType())
  272. {
  273. $value = $this->uncompress($value);
  274. }
  275. if($typeValue == 'numeric'
  276. && $this->cacheEnabledForNumeric)
  277. {
  278. $this->numericCached[$name] = $value;
  279. }
  280. return $value;
  281. }
  282. /**
  283. * This method loads in memory all the subtables for the main table called $name.
  284. * You have to give it the parent table $dataTableToLoad so we can lookup the sub tables ids to load.
  285. *
  286. * If $addMetadataSubtableId set to true, it will add for each row a 'metadata' called 'databaseSubtableId'
  287. * containing the child ID of the subtable associated to this row.
  288. *
  289. * @param string $name
  290. * @param Piwik_DataTable $dataTableToLoad
  291. * @param bool $addMetadataSubtableId
  292. */
  293. public function loadSubDataTables($name, Piwik_DataTable $dataTableToLoad, $addMetadataSubtableId = false)
  294. {
  295. // we have to recursively load all the subtables associated to this table's rows
  296. // and update the subtableID so that it matches the newly instanciated table
  297. foreach($dataTableToLoad->getRows() as $row)
  298. {
  299. $subTableID = $row->getIdSubDataTable();
  300. if($subTableID !== null)
  301. {
  302. $subDataTableLoaded = $this->getDataTable($name, $subTableID);
  303. $this->loadSubDataTables($name, $subDataTableLoaded, $addMetadataSubtableId);
  304. // we edit the subtable ID so that it matches the newly table created in memory
  305. // NB: we dont overwrite the datatableid in the case we are displaying the table expanded.
  306. if($addMetadataSubtableId)
  307. {
  308. // this will be written back to the column 'idsubdatatable' just before rendering, see Renderer/Php.php
  309. $row->addMetadata('idsubdatatable_in_db', $row->getIdSubDataTable());
  310. }
  311. $row->setSubtable( $subDataTableLoaded );
  312. }
  313. }
  314. }
  315. /**
  316. * Free the blob cache memory array
  317. */
  318. public function freeBlob( $name )
  319. {
  320. $this->blobCached[$name] = null;
  321. unset($this->blobCached[$name]);
  322. }
  323. protected function uncompress($data)
  324. {
  325. return @gzuncompress($data);
  326. }
  327. /**
  328. * Fetches all blob fields name_* at once for the current archive for performance reasons.
  329. *
  330. * @return false if no visits
  331. */
  332. public function preFetchBlob( $name )
  333. {
  334. $this->setRequestedReport($name);
  335. $this->prepareArchive();
  336. if(!$this->isThereSomeVisits) { return; }
  337. $tableBlob = $this->archiveProcessing->getTableArchiveBlobName();
  338. $db = Zend_Registry::get('db');
  339. $hasBlobs = $db->hasBlobDataType();
  340. $query = $db->query("SELECT value, name
  341. FROM $tableBlob
  342. WHERE idarchive = ?
  343. AND name LIKE '$name%'",
  344. array( $this->idArchive )
  345. );
  346. while($row = $query->fetch())
  347. {
  348. $value = $row['value'];
  349. $name = $row['name'];
  350. if($hasBlobs)
  351. {
  352. $this->blobCached[$name] = $this->uncompress($value);
  353. if($this->blobCached[$name] === false)
  354. {
  355. //throw new Exception("Error gzuncompress $name ");
  356. }
  357. }
  358. else
  359. {
  360. $this->blobCached[$name] = $value;
  361. }
  362. }
  363. }
  364. /**
  365. * Returns a numeric value from this Archive, with the name '$name'
  366. *
  367. * @param string $name
  368. * @return int|float
  369. */
  370. public function getNumeric( $name )
  371. {
  372. // we cast the result as float because returns false when no visitors
  373. return (float)$this->get($name, 'numeric');
  374. }
  375. /**
  376. * Returns a blob value from this Archive, with the name '$name'
  377. * Blob values are all values except int and float.
  378. *
  379. * @param string $name
  380. * @return mixed
  381. */
  382. public function getBlob( $name )
  383. {
  384. return $this->get($name, 'blob');
  385. }
  386. /**
  387. * Given a list of fields defining numeric values, it will return a Piwik_DataTable_Simple
  388. * containing one row per field name.
  389. *
  390. * For example $fields = array( 'max_actions',
  391. * 'nb_uniq_visitors',
  392. * 'nb_visits',
  393. * 'nb_actions',
  394. * 'sum_visit_length',
  395. * 'bounce_count',
  396. * 'nb_visits_converted'
  397. * );
  398. *
  399. * @param string|array $fields Name or array of names of Archive fields
  400. *
  401. * @return Piwik_DataTable_Simple
  402. */
  403. public function getDataTableFromNumeric( $fields )
  404. {
  405. if(!is_array($fields))
  406. {
  407. $fields = array($fields);
  408. }
  409. $values = array();
  410. foreach($fields as $field)
  411. {
  412. $values[$field] = $this->getNumeric($field);
  413. }
  414. $table = new Piwik_DataTable_Simple();
  415. $table->addRowsFromArray($values);
  416. return $table;
  417. }
  418. /**
  419. * Returns a DataTable that has the name '$name' from the current Archive.
  420. * If $idSubTable is specified, returns the subDataTable called '$name_$idSubTable'
  421. *
  422. * @param string $name
  423. * @param int $idSubTable optional id SubDataTable
  424. * @return Piwik_DataTable
  425. */
  426. public function getDataTable( $name, $idSubTable = null )
  427. {
  428. if(!is_null($idSubTable))
  429. {
  430. $name .= "_$idSubTable";
  431. }
  432. $this->setRequestedReport($name);
  433. $data = $this->get($name, 'blob');
  434. $table = new Piwik_DataTable();
  435. if($data !== false)
  436. {
  437. $table->addRowsFromSerializedArray($data);
  438. }
  439. if($data === false
  440. && $idSubTable !== null)
  441. {
  442. // This is not expected, but somehow happens in some unknown cases and very rarely.
  443. // Do not throw error in this case
  444. // throw new Exception("not expected");
  445. return new Piwik_DataTable();
  446. }
  447. return $table;
  448. }
  449. public function setRequestedReport($requestedReport )
  450. {
  451. $this->requestedReport = $requestedReport;
  452. }
  453. protected function getRequestedReport()
  454. {
  455. // Core metrics are always processed in Core, for the requested date/period/segment
  456. if(in_array($this->requestedReport, Piwik_ArchiveProcessing::getCoreMetrics())
  457. || $this->requestedReport == 'max_actions')
  458. {
  459. return 'VisitsSummary_CoreMetrics';
  460. }
  461. // VisitFrequency metrics don't follow the same naming convention (HACK)
  462. if(strpos($this->requestedReport, '_returning') > 0
  463. // ignore Goal_visitor_returning_1_1_nb_conversions
  464. && strpos($this->requestedReport, 'Goal_') === false)
  465. {
  466. return 'VisitFrequency_Metrics';
  467. }
  468. // Goal_* metrics are processed by the Goals plugin (HACK)
  469. if(strpos($this->requestedReport, 'Goal_') === 0)
  470. {
  471. return 'Goals_Metrics';
  472. }
  473. return $this->requestedReport;
  474. }
  475. /**
  476. * Returns a DataTable that has the name '$name' from the current Archive.
  477. * Also loads in memory all subDataTable for this DataTable.
  478. *
  479. * For example, if $name = 'Referers_keywordBySearchEngine' it will load all DataTable
  480. * named 'Referers_keywordBySearchEngine_*' and they will be set as subDataTable to the
  481. * rows. You can then go through the rows
  482. * $rows = DataTable->getRows();
  483. * and for each row request the subDataTable (in this case the DataTable of the keywords for each search engines)
  484. * $idSubTable = $row->getIdSubDataTable();
  485. * $subTable = Piwik_DataTable_Manager::getInstance()->getTable($idSubTable);
  486. *
  487. * @param string $name
  488. * @param int $idSubTable Optional subDataTable to load instead of loading the parent DataTable
  489. * @return Piwik_DataTable
  490. */
  491. public function getDataTableExpanded($name, $idSubTable = null)
  492. {
  493. $this->preFetchBlob($name);
  494. $dataTableToLoad = $this->getDataTable($name, $idSubTable);
  495. $this->loadSubDataTables($name, $dataTableToLoad, $addMetadataSubtableId = true);
  496. $dataTableToLoad->enableRecursiveFilters();
  497. $this->freeBlob($name);
  498. return $dataTableToLoad;
  499. }
  500. }