PageRenderTime 41ms CodeModel.GetById 12ms RepoModel.GetById 1ms app.codeStats 0ms

/core/ArchiveProcessing.php

https://github.com/quarkness/piwik
PHP | 1018 lines | 571 code | 111 blank | 336 comment | 63 complexity | e322c564cbaf6ad59da0d4c0de2209cc 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. * The ArchiveProcessing module is a module that reads the Piwik logs from the DB and
  14. * compute all the reports, which are then stored in the database.
  15. *
  16. * The ArchiveProcessing class is used by the Archive object to make sure the given Archive is processed and available in the DB.
  17. *
  18. * A record in the Database for a given report is defined by
  19. * - idarchive = unique ID that is associated to all the data of this archive (idsite+period+date)
  20. * - idsite = the ID of the website
  21. * - date1 = starting day of the period
  22. * - date2 = ending day of the period
  23. * - period = integer that defines the period (day/week/etc.). @see period::getId()
  24. * - ts_archived = timestamp when the archive was processed (UTC)
  25. * - name = the name of the report (ex: uniq_visitors or search_keywords_by_search_engines)
  26. * - value = the actual data
  27. *
  28. * @package Piwik
  29. * @subpackage Piwik_ArchiveProcessing
  30. */
  31. abstract class Piwik_ArchiveProcessing
  32. {
  33. /**
  34. * Flag stored at the end of the archiving
  35. *
  36. * @var int
  37. */
  38. const DONE_OK = 1;
  39. /**
  40. * Flag stored at the start of the archiving
  41. * When requesting an Archive, we make sure that non-finished archive are not considered valid
  42. *
  43. * @var int
  44. */
  45. const DONE_ERROR = 2;
  46. /**
  47. * Flag indicates the archive is over a period that is not finished, eg. the current day, current week, etc.
  48. * Archives flagged will be regularly purged from the DB.
  49. *
  50. * @var int
  51. */
  52. const DONE_OK_TEMPORARY = 3;
  53. /**
  54. * Idarchive in the DB for the requested archive
  55. *
  56. * @var int
  57. */
  58. protected $idArchive;
  59. /**
  60. * Period id @see Piwik_Period::getId()
  61. *
  62. * @var int
  63. */
  64. protected $periodId;
  65. /**
  66. * Timestamp for the first date of the period
  67. *
  68. * @var int unix timestamp
  69. */
  70. protected $timestampDateStart = null;
  71. /**
  72. * Starting date of the archive
  73. *
  74. * @var Piwik_Date
  75. */
  76. protected $dateStart;
  77. /**
  78. * Ending date of the archive
  79. *
  80. * @var Piwik_Date
  81. */
  82. protected $dateEnd;
  83. /**
  84. * Object used to generate (depending on the $dateStart) the name of the DB table to use to store numeric values
  85. *
  86. * @var Piwik_TablePartitioning
  87. */
  88. protected $tableArchiveNumeric;
  89. /**
  90. * Object used to generate (depending on the $dateStart) the name of the DB table to use to store numeric values
  91. *
  92. * @var Piwik_TablePartitioning
  93. */
  94. protected $tableArchiveBlob;
  95. /**
  96. * Minimum timestamp looked at for processed archives
  97. *
  98. * @var int
  99. */
  100. protected $minDatetimeArchiveProcessedUTC = false;
  101. /**
  102. * Compress blobs
  103. *
  104. * @var bool
  105. */
  106. protected $compressBlob;
  107. /**
  108. * Is the current archive temporary. ie.
  109. * - today
  110. * - current week / month / year
  111. */
  112. protected $temporaryArchive;
  113. /**
  114. * Id of the current site
  115. * Can be accessed by plugins (that is why it's public)
  116. *
  117. * @var int
  118. */
  119. public $idsite = null;
  120. /**
  121. * Period of the current archive
  122. * Can be accessed by plugins (that is why it's public)
  123. *
  124. * @var Piwik_Period
  125. */
  126. public $period = null;
  127. /**
  128. * Site of the current archive
  129. * Can be accessed by plugins (that is why it's public)
  130. *
  131. * @var Piwik_Site
  132. */
  133. public $site = null;
  134. /**
  135. * @var Piwik_Segment
  136. */
  137. protected $segment = null;
  138. /**
  139. * Current time.
  140. * This value is cached.
  141. *
  142. * @var int
  143. */
  144. public $time = null;
  145. /**
  146. * Starting datetime in UTC
  147. *
  148. * @var string
  149. */
  150. public $startDatetimeUTC;
  151. /**
  152. * Ending date in UTC
  153. *
  154. * @var string
  155. */
  156. public $strDateEnd;
  157. /**
  158. * Name of the DB table _log_visit
  159. *
  160. * @var string
  161. */
  162. public $logTable;
  163. /**
  164. * When set to true, we always archive, even if the archive is already available.
  165. * You can change this settings automatically in the config/global.ini.php always_archive_data under the [Debug] section
  166. *
  167. * @var bool
  168. */
  169. public $debugAlwaysArchive = false;
  170. /**
  171. * If the archive has at least 1 visit, this is set to true.
  172. *
  173. * @var bool
  174. */
  175. public $isThereSomeVisits = null;
  176. protected $startTimestampUTC;
  177. protected $endTimestampUTC;
  178. /**
  179. * Flag that will forcefully disable the archiving process. Only set by the tests.
  180. */
  181. public static $forceDisableArchiving = false;
  182. /**
  183. * Constructor
  184. */
  185. public function __construct()
  186. {
  187. $this->time = time();
  188. }
  189. /**
  190. * Returns the Piwik_ArchiveProcessing_Day or Piwik_ArchiveProcessing_Period object
  191. * depending on $name period string
  192. *
  193. * @param string $name day|week|month|year
  194. * @return Piwik_ArchiveProcessing Piwik_ArchiveProcessing_Day|Piwik_ArchiveProcessing_Period
  195. */
  196. static function factory($name)
  197. {
  198. switch($name)
  199. {
  200. case 'day':
  201. $process = new Piwik_ArchiveProcessing_Day();
  202. $process->debugAlwaysArchive = Zend_Registry::get('config')->Debug->always_archive_data_day;
  203. break;
  204. case 'week':
  205. case 'month':
  206. case 'year':
  207. $process = new Piwik_ArchiveProcessing_Period();
  208. $process->debugAlwaysArchive = Zend_Registry::get('config')->Debug->always_archive_data_period;
  209. break;
  210. case 'range':
  211. $process = new Piwik_ArchiveProcessing_Period();
  212. $process->debugAlwaysArchive = Zend_Registry::get('config')->Debug->always_archive_data_range;
  213. break;
  214. default:
  215. throw new Exception("Unknown Archiving period specified '$name'");
  216. break;
  217. }
  218. return $process;
  219. }
  220. const OPTION_TODAY_ARCHIVE_TTL = 'todayArchiveTimeToLive';
  221. const OPTION_BROWSER_TRIGGER_ARCHIVING = 'enableBrowserTriggerArchiving';
  222. static public function getCoreMetrics()
  223. {
  224. return array(
  225. 'nb_uniq_visitors',
  226. 'nb_visits',
  227. 'nb_actions',
  228. 'sum_visit_length',
  229. 'bounce_count',
  230. 'nb_visits_converted',
  231. );
  232. }
  233. static public function setTodayArchiveTimeToLive($timeToLiveSeconds)
  234. {
  235. $timeToLiveSeconds = (int)$timeToLiveSeconds;
  236. if($timeToLiveSeconds <= 0)
  237. {
  238. throw new Exception(Piwik_TranslateException('General_ExceptionInvalidArchiveTimeToLive'));
  239. }
  240. Piwik_SetOption(self::OPTION_TODAY_ARCHIVE_TTL, $timeToLiveSeconds, $autoload = true);
  241. }
  242. static public function getTodayArchiveTimeToLive()
  243. {
  244. $timeToLive = Piwik_GetOption(self::OPTION_TODAY_ARCHIVE_TTL);
  245. if($timeToLive !== false)
  246. {
  247. return $timeToLive;
  248. }
  249. return Zend_Registry::get('config')->General->time_before_today_archive_considered_outdated;
  250. }
  251. static public function setBrowserTriggerArchiving($enabled)
  252. {
  253. if(!is_bool($enabled))
  254. {
  255. throw new Exception('Browser trigger archiving must be set to true or false.');
  256. }
  257. Piwik_SetOption(self::OPTION_BROWSER_TRIGGER_ARCHIVING, (int)$enabled, $autoload = true);
  258. Piwik_Common::clearCacheGeneral();
  259. }
  260. static public function isBrowserTriggerArchivingEnabled()
  261. {
  262. $browserArchivingEnabled = Piwik_GetOption(self::OPTION_BROWSER_TRIGGER_ARCHIVING);
  263. if($browserArchivingEnabled !== false)
  264. {
  265. return (bool)$browserArchivingEnabled;
  266. }
  267. return (bool)Zend_Registry::get('config')->General->enable_browser_archiving_triggering;
  268. }
  269. public function getIdArchive()
  270. {
  271. return $this->idArchive;
  272. }
  273. /**
  274. * Sets object attributes that will be used throughout the process
  275. */
  276. public function init()
  277. {
  278. $this->idsite = $this->site->getId();
  279. $this->periodId = $this->period->getId();
  280. $dateStartLocalTimezone = $this->period->getDateStart();
  281. $dateEndLocalTimezone = $this->period->getDateEnd();
  282. $this->tableArchiveNumeric = self::makeNumericArchiveTable($this->period);
  283. $this->tableArchiveBlob = self::makeBlobArchiveTable($this->period);
  284. $dateStartUTC = $dateStartLocalTimezone->setTimezone($this->site->getTimezone());
  285. $dateEndUTC = $dateEndLocalTimezone->setTimezone($this->site->getTimezone());
  286. $this->startDatetimeUTC = $dateStartUTC->getDateStartUTC();
  287. $this->endDatetimeUTC = $dateEndUTC->getDateEndUTC();
  288. $this->startTimestampUTC = $dateStartUTC->getTimestamp();
  289. $this->endTimestampUTC = strtotime($this->endDatetimeUTC);
  290. $this->minDatetimeArchiveProcessedUTC = $this->getMinTimeArchivedProcessed();
  291. $db = Zend_Registry::get('db');
  292. $this->compressBlob = $db->hasBlobDataType();
  293. }
  294. /**
  295. * Utility function which creates a TablePartitioning instance for the numeric
  296. * archive data of a given period.
  297. *
  298. * @param $period The time period of the archive data.
  299. * @return Piwik_TablePartitioning_Monthly
  300. */
  301. public static function makeNumericArchiveTable($period)
  302. {
  303. $result = new Piwik_TablePartitioning_Monthly('archive_numeric');
  304. $result->setTimestamp($period->getDateStart()->getTimestamp());
  305. return $result;
  306. }
  307. /**
  308. * Utility function which creates a TablePartitioning instance for the blob
  309. * archive data of a given period.
  310. *
  311. * @param $period The time period of the archive data.
  312. * @return Piwik_TablePartitioning_Monthly
  313. */
  314. public static function makeBlobArchiveTable($period)
  315. {
  316. $result = new Piwik_TablePartitioning_Monthly('archive_blob');
  317. $result->setTimestamp($period->getDateStart()->getTimestamp());
  318. return $result;
  319. }
  320. public function getStartDatetimeUTC()
  321. {
  322. return $this->startDatetimeUTC;
  323. }
  324. public function getEndDatetimeUTC()
  325. {
  326. return $this->endDatetimeUTC;
  327. }
  328. public function isArchiveTemporary()
  329. {
  330. return $this->temporaryArchive;
  331. }
  332. /**
  333. * Returns the minimum archive processed datetime to look at
  334. *
  335. * @return string Datetime string, or false if must look at any archive available
  336. */
  337. public function getMinTimeArchivedProcessed()
  338. {
  339. $this->temporaryArchive = false;
  340. // if the current archive is a DAY and if it's today,
  341. // we set this minDatetimeArchiveProcessedUTC that defines the lifetime value of today's archive
  342. if( $this->period->getNumberOfSubperiods() == 0
  343. && ($this->startTimestampUTC <= $this->time && $this->endTimestampUTC > $this->time)
  344. )
  345. {
  346. $this->temporaryArchive = true;
  347. $minDatetimeArchiveProcessedUTC = $this->time - self::getTodayArchiveTimeToLive();
  348. // see #1150; if new archives are not triggered from the browser,
  349. // we still want to try and return the latest archive available for today (rather than return nothing)
  350. if($this->isArchivingDisabled())
  351. {
  352. return false;
  353. }
  354. }
  355. // - if the period we are looking for is finished, we look for a ts_archived that
  356. // is greater than the last day of the archive
  357. elseif($this->endTimestampUTC <= $this->time)
  358. {
  359. $minDatetimeArchiveProcessedUTC = $this->endTimestampUTC+1;
  360. }
  361. // - if the period we're looking for is not finished, we look for a recent enough archive
  362. else
  363. {
  364. $this->temporaryArchive = true;
  365. // We choose to only look at archives that are newer than the specified timeout
  366. $minDatetimeArchiveProcessedUTC = $this->time - self::getTodayArchiveTimeToLive();
  367. // However, if archiving is disabled for this request, we shall
  368. // accept any archive that was processed today after 00:00:01 this morning
  369. if($this->isArchivingDisabled())
  370. {
  371. $timezone = $this->site->getTimezone();
  372. $minDatetimeArchiveProcessedUTC = Piwik_Date::factory(Piwik_Date::factory('now', $timezone)->getDateStartUTC())->setTimezone($timezone)->getTimestamp();
  373. }
  374. }
  375. return $minDatetimeArchiveProcessedUTC;
  376. }
  377. /**
  378. * This method returns the idArchive ; if necessary, it triggers the archiving process.
  379. *
  380. * If the archive was not processed yet, it will launch the archiving process.
  381. * If the current archive needs sub-archives (eg. a month archive needs all the days archive)
  382. * it will recursively launch the archiving (using this loadArchive() on the sub-periods)
  383. *
  384. * @return int|false The idarchive of the archive, false if the archive is not archived yet
  385. */
  386. public function loadArchive()
  387. {
  388. $this->init();
  389. if($this->debugAlwaysArchive)
  390. {
  391. return false;
  392. }
  393. $this->idArchive = $this->isArchived();
  394. if($this->idArchive === false)
  395. {
  396. return false;
  397. }
  398. return $this->idArchive;
  399. }
  400. /**
  401. * @see loadArchive()
  402. */
  403. public function launchArchiving()
  404. {
  405. if (!Piwik::getArchiveProcessingLock($this->idsite, $this->period, $this->segment))
  406. {
  407. // unable to get lock
  408. Piwik::log('Unable to get lock for idSite = ' . $this->idsite
  409. . ', period = ' . $this->period->getLabel()
  410. . ', UTC datetime [' . $this->startDatetimeUTC . ' -> ' . $this->endDatetimeUTC . ' ]...');
  411. return;
  412. }
  413. $this->initCompute();
  414. $this->compute();
  415. $this->postCompute();
  416. // we execute again the isArchived that does some initialization work
  417. $this->idArchive = $this->isArchived();
  418. Piwik::releaseArchiveProcessingLock($this->idsite, $this->period, $this->segment);
  419. }
  420. /**
  421. * This methods reads the subperiods if necessary,
  422. * and computes the archive of the current period.
  423. */
  424. abstract protected function compute();
  425. abstract public function isThereSomeVisits();
  426. /**
  427. * Returns the name of the archive field used to tell the status of an archive, (ie,
  428. * whether the archive was created succesfully or not).
  429. *
  430. * @param bool $flagArchiveAsAllPlugins
  431. * @return string
  432. */
  433. public function getDoneStringFlag($flagArchiveAsAllPlugins = false)
  434. {
  435. return self::getDoneStringFlagFor(
  436. $this->getSegment(), $this->period, $this->getRequestedReport(), $flagArchiveAsAllPlugins);
  437. }
  438. /**
  439. * Returns the name of the archive field used to tell the status of an archive, (ie,
  440. * whether the archive was created succesfully or not).
  441. *
  442. * @param bool $flagArchiveAsAllPlugins
  443. * @return string
  444. */
  445. public static function getDoneStringFlagFor($segment, $period, $requestedReport, $flagArchiveAsAllPlugins = false)
  446. {
  447. $segmentHash = $segment->getHash();
  448. if(!self::shouldProcessReportsAllPluginsFor($segment, $period))
  449. {
  450. $pluginProcessed = self::getPluginBeingProcessed($requestedReport);
  451. if(!Piwik_PluginsManager::getInstance()->isPluginLoaded($pluginProcessed)
  452. || $flagArchiveAsAllPlugins
  453. )
  454. {
  455. $pluginProcessed = 'all';
  456. }
  457. $segmentHash .= '.'.$pluginProcessed;
  458. }
  459. return 'done' . $segmentHash;
  460. }
  461. /**
  462. * Init the object before launching the real archive processing
  463. */
  464. protected function initCompute()
  465. {
  466. $this->loadNextIdarchive();
  467. $done = $this->getDoneStringFlag();
  468. $this->insertNumericRecord($done, Piwik_ArchiveProcessing::DONE_ERROR);
  469. // Can be removed when GeoIp is in core
  470. $this->logTable = Piwik_Common::prefixTable('log_visit');
  471. $temporary = 'definitive archive';
  472. if($this->isArchiveTemporary())
  473. {
  474. $temporary = 'temporary archive';
  475. }
  476. Piwik::log("'" . $this->period->getLabel() . "'"
  477. .", idSite = ". $this->idsite." ($temporary)"
  478. .", segment = '". $this->getSegment()->getString()."'"
  479. .", report = '". $this->getRequestedReport()."'"
  480. .", UTC datetime [".$this->startDatetimeUTC." -> ".$this->endDatetimeUTC." ]...");
  481. }
  482. /**
  483. * Post processing called at the end of the main archive processing.
  484. * Makes sure the new archive is marked as "successful" in the DB
  485. *
  486. * We also try to delete some stuff from memory but really there is still a lot...
  487. */
  488. protected function postCompute()
  489. {
  490. // delete the first done = ERROR
  491. $done = $this->getDoneStringFlag();
  492. Piwik_Query("DELETE FROM ".$this->tableArchiveNumeric->getTableName()."
  493. WHERE idarchive = ? AND name = '".$done."'",
  494. array($this->idArchive)
  495. );
  496. $flag = Piwik_ArchiveProcessing::DONE_OK;
  497. if($this->isArchiveTemporary())
  498. {
  499. $flag = Piwik_ArchiveProcessing::DONE_OK_TEMPORARY;
  500. }
  501. $this->insertNumericRecord($done, $flag);
  502. }
  503. /**
  504. * Returns the name of the numeric table where the archive numeric values are stored
  505. *
  506. * @return string
  507. */
  508. public function getTableArchiveNumericName()
  509. {
  510. return $this->tableArchiveNumeric->getTableName();
  511. }
  512. /**
  513. * Returns the name of the blob table where the archive blob values are stored
  514. *
  515. * @return string
  516. */
  517. public function getTableArchiveBlobName()
  518. {
  519. return $this->tableArchiveBlob->getTableName();
  520. }
  521. /**
  522. * Set the period
  523. *
  524. * @param Piwik_Period $period
  525. */
  526. public function setPeriod( Piwik_Period $period )
  527. {
  528. $this->period = $period;
  529. }
  530. public function setSegment( Piwik_Segment $segment)
  531. {
  532. $this->segment = $segment;
  533. }
  534. public function getSegment()
  535. {
  536. return $this->segment;
  537. }
  538. /**
  539. * Set the site
  540. *
  541. * @param Piwik_Site $site
  542. */
  543. public function setSite( Piwik_Site $site )
  544. {
  545. $this->site = $site;
  546. }
  547. public function setRequestedReport($requestedReport)
  548. {
  549. $this->requestedReport = $requestedReport;
  550. }
  551. protected function getRequestedReport()
  552. {
  553. return $this->requestedReport;
  554. }
  555. static public function getPluginBeingProcessed( $requestedReport )
  556. {
  557. return substr($requestedReport, 0, strpos($requestedReport, '_'));
  558. }
  559. /**
  560. * Returns the timestamp of the first date of the period
  561. *
  562. * @return int
  563. */
  564. public function getTimestampStartDate()
  565. {
  566. return $this->timestampDateStart;
  567. }
  568. // exposing the number of visits publicly (number used to compute conversions rates)
  569. protected $nb_visits = null;
  570. protected $nb_visits_converted = null;
  571. protected function setNumberOfVisits($nb_visits)
  572. {
  573. $this->nb_visits = $nb_visits;
  574. }
  575. public function getNumberOfVisits()
  576. {
  577. return $this->nb_visits;
  578. }
  579. protected function setNumberOfVisitsConverted($nb_visits_converted)
  580. {
  581. $this->nb_visits_converted = $nb_visits_converted;
  582. }
  583. public function getNumberOfVisitsConverted()
  584. {
  585. return $this->nb_visits_converted;
  586. }
  587. /**
  588. * Returns the idArchive we will use for the current archive
  589. *
  590. * @return int IdArchive to use when saving the current Archive
  591. */
  592. protected function loadNextIdarchive()
  593. {
  594. $db = Zend_Registry::get('db');
  595. $id = $db->fetchOne("SELECT max(idarchive)
  596. FROM ".$this->tableArchiveNumeric->getTableName());
  597. if(empty($id))
  598. {
  599. $id = 0;
  600. }
  601. $this->idArchive = $id + 1;
  602. }
  603. /**
  604. * @param string $name
  605. * @param int|float $value
  606. */
  607. public function insertNumericRecord($name, $value)
  608. {
  609. $value = round($value, 2);
  610. return $this->insertRecord($name, $value);
  611. }
  612. /**
  613. * @param string $name
  614. * @param string|array of string $aValues
  615. * @return true
  616. */
  617. public function insertBlobRecord($name, $values)
  618. {
  619. if(is_array($values))
  620. {
  621. $clean = array();
  622. foreach($values as $id => $value)
  623. {
  624. // for the parent Table we keep the name
  625. // for example for the Table of searchEngines we keep the name 'referer_search_engine'
  626. // but for the child table of 'Google' which has the ID = 9 the name would be 'referer_search_engine_9'
  627. $newName = $name;
  628. if($id != 0)
  629. {
  630. $newName = $name . '_' . $id;
  631. }
  632. if($this->compressBlob)
  633. {
  634. $value = $this->compress($value);
  635. }
  636. $clean[] = array($newName, $value);
  637. }
  638. return $this->insertBulkRecords($clean);
  639. }
  640. if($this->compressBlob)
  641. {
  642. $values = $this->compress($values);
  643. }
  644. $this->insertRecord($name, $values);
  645. return array($name => $values);
  646. }
  647. protected function compress($data)
  648. {
  649. return gzcompress($data);
  650. }
  651. protected function insertBulkRecords($records)
  652. {
  653. // Using standard plain INSERT if there is only one record to insert
  654. if($DEBUG_DO_NOT_USE_BULK_INSERT = false
  655. || count($records) == 1)
  656. {
  657. foreach($records as $record)
  658. {
  659. $this->insertRecord($record[0], $record[1]);
  660. }
  661. return ;
  662. }
  663. $bindSql = $this->getBindArray();
  664. $values = array();
  665. foreach($records as $record)
  666. {
  667. // don't record zero
  668. if(empty($record[1])) continue;
  669. $bind = $bindSql;
  670. $bind[] = $record[0]; // name
  671. $bind[] = $record[1]; // value
  672. $values[] = $bind;
  673. }
  674. if(empty($values)) return ;
  675. if(is_numeric($record[1]))
  676. {
  677. $table = $this->tableArchiveNumeric;
  678. }
  679. else
  680. {
  681. $table = $this->tableArchiveBlob;
  682. }
  683. Piwik::tableInsertBatch($table->getTableName(), $this->getInsertFields(), $values);
  684. return true;
  685. }
  686. protected function getBindArray()
  687. {
  688. return array( $this->idArchive,
  689. $this->idsite,
  690. $this->period->getDateStart()->toString('Y-m-d'),
  691. $this->period->getDateEnd()->toString('Y-m-d'),
  692. $this->periodId,
  693. date("Y-m-d H:i:s"));
  694. }
  695. protected function getInsertFields()
  696. {
  697. return array('idarchive', 'idsite', 'date1', 'date2', 'period', 'ts_archived', 'name', 'value');
  698. }
  699. /**
  700. * Inserts a record in the right table (either NUMERIC or BLOB)
  701. *
  702. */
  703. protected function insertRecord($name, $value)
  704. {
  705. // table to use to save the data
  706. if(is_numeric($value))
  707. {
  708. // We choose not to record records with a value of 0
  709. if($value == 0)
  710. {
  711. return;
  712. }
  713. $table = $this->tableArchiveNumeric;
  714. }
  715. else
  716. {
  717. $table = $this->tableArchiveBlob;
  718. }
  719. // duplicate idarchives are Ignored, see http://dev.piwik.org/trac/ticket/987
  720. $query = "INSERT IGNORE INTO ".$table->getTableName()."
  721. (". implode(", ", $this->getInsertFields()).")
  722. VALUES (?,?,?,?,?,?,?,?)";
  723. $bindSql = $this->getBindArray();
  724. $bindSql[] = $name;
  725. $bindSql[] = $value;
  726. // var_dump($bindSql);
  727. Piwik_Query($query, $bindSql);
  728. }
  729. /**
  730. * Returns the idArchive if the archive is available in the database.
  731. * Returns false if the archive needs to be computed.
  732. *
  733. * An archive is available if
  734. * - for today, the archive was computed less than minDatetimeArchiveProcessedUTC seconds ago
  735. * - for any other day, if the archive was computed once this day was finished
  736. * - for other periods, if the archive was computed once the period was finished
  737. *
  738. * @return int|false
  739. */
  740. protected function isArchived()
  741. {
  742. $bindSQL = array( $this->idsite,
  743. $this->period->getDateStart()->toString('Y-m-d'),
  744. $this->period->getDateEnd()->toString('Y-m-d'),
  745. $this->periodId,
  746. );
  747. $timeStampWhere = '';
  748. if($this->minDatetimeArchiveProcessedUTC)
  749. {
  750. $timeStampWhere = " AND ts_archived >= ? ";
  751. $bindSQL[] = Piwik_Date::factory($this->minDatetimeArchiveProcessedUTC)->getDatetime();
  752. }
  753. // When a Segment is specified, we try and only process the requested report in the archive
  754. // As a limitation, we don't know all the time which plugin should process which report
  755. // There is a catch all flag 'all' appended to archives containing all reports already
  756. // We look for this 'done.ABCDEFG.all', or for an archive that contains only our plugin data 'done.ABDCDEFG.Referers'
  757. $done = $this->getDoneStringFlag();
  758. $doneAllPluginsProcessed = $this->getDoneStringFlag($flagArchiveAsAllPlugins = true);
  759. $sqlSegmentsFindArchiveAllPlugins = '';
  760. if($done != $doneAllPluginsProcessed)
  761. {
  762. $sqlSegmentsFindArchiveAllPlugins = "OR (name = '".$doneAllPluginsProcessed."' AND value = ".Piwik_ArchiveProcessing::DONE_OK.")
  763. OR (name = '".$doneAllPluginsProcessed."' AND value = ".Piwik_ArchiveProcessing::DONE_OK_TEMPORARY.")";
  764. }
  765. $sqlQuery = " SELECT idarchive, value, name, date1 as startDate
  766. FROM ".$this->tableArchiveNumeric->getTableName()."
  767. WHERE idsite = ?
  768. AND date1 = ?
  769. AND date2 = ?
  770. AND period = ?
  771. AND ( (name = '".$done."' AND value = ".Piwik_ArchiveProcessing::DONE_OK.")
  772. OR (name = '".$done."' AND value = ".Piwik_ArchiveProcessing::DONE_OK_TEMPORARY.")
  773. $sqlSegmentsFindArchiveAllPlugins
  774. OR name = 'nb_visits')
  775. $timeStampWhere
  776. ORDER BY ts_archived DESC";
  777. $results = Piwik_FetchAll($sqlQuery, $bindSQL );
  778. if(empty($results))
  779. {
  780. return false;
  781. }
  782. $idarchive = false;
  783. // we look for the more recent idarchive
  784. foreach($results as $result)
  785. {
  786. if($result['name'] == $done
  787. || $result['name'] == $doneAllPluginsProcessed)
  788. {
  789. $idarchive = $result['idarchive'];
  790. $this->timestampDateStart = Piwik_Date::factory($result['startDate'])->getTimestamp();
  791. break;
  792. }
  793. }
  794. // case when we have a nb_visits entry in the archive, but the process is not finished yet or failed to finish
  795. // therefore we don't have the done=OK
  796. if($idarchive === false)
  797. {
  798. return false;
  799. }
  800. if($this->getPluginBeingProcessed($this->getRequestedReport()) == 'VisitsSummary')
  801. {
  802. $this->isThereSomeVisits = false;
  803. }
  804. // we look for the nb_visits result for this most recent archive
  805. foreach($results as $result)
  806. {
  807. if($result['name'] == 'nb_visits'
  808. && $result['idarchive'] == $idarchive)
  809. {
  810. $this->isThereSomeVisits = ($result['value'] > 0);
  811. $this->setNumberOfVisits($result['value']);
  812. break;
  813. }
  814. }
  815. return $idarchive;
  816. }
  817. /**
  818. * Returns true if, for some reasons, triggering the archiving is disabled.
  819. * Note that when a segment is passed to the function, archiving will always occur
  820. * (since segments are by default not pre-processed)
  821. *
  822. * @return bool
  823. */
  824. public function isArchivingDisabled()
  825. {
  826. return self::isArchivingDisabledFor($this->getSegment(), $this->period);
  827. }
  828. public static function isArchivingDisabledFor($segment, $period)
  829. {
  830. $processOneReportOnly = !self::shouldProcessReportsAllPluginsFor($segment, $period);
  831. if($processOneReportOnly)
  832. {
  833. // When there is a segment, archiving is not necessary allowed
  834. // If browser archiving is allowed, then archiving is enabled
  835. // if browser archiving is not allowed, then archiving is disabled
  836. if(!$segment->isEmpty()
  837. && !self::isRequestAuthorizedToArchive()
  838. && Zend_Registry::get('config')->General->browser_archiving_disabled_enforce
  839. )
  840. {
  841. Piwik::log("Archiving is disabled because of config setting browser_archiving_disabled_enforce=1");
  842. return true;
  843. }
  844. return false;
  845. }
  846. $isDisabled = !self::isRequestAuthorizedToArchive();
  847. return $isDisabled;
  848. }
  849. protected static function isRequestAuthorizedToArchive()
  850. {
  851. return !self::$forceDisableArchiving &&
  852. (self::isBrowserTriggerArchivingEnabled()
  853. || Piwik_Common::isPhpCliMode()
  854. || (Piwik::isUserIsSuperUser()
  855. && Piwik_Common::isArchivePhpTriggered()))
  856. ;
  857. }
  858. /**
  859. * Returns true when
  860. * - there is no segment and period is not range
  861. * - there is a segment that is part of the preprocessed [Segments] list
  862. */
  863. protected function shouldProcessReportsAllPlugins($segment, $period)
  864. {
  865. return self::shouldProcessReportsAllPluginsFor($segment, $period);
  866. }
  867. protected static function shouldProcessReportsAllPluginsFor($segment, $period)
  868. {
  869. if($segment->isEmpty() && $period->getLabel() != 'range')
  870. {
  871. return true;
  872. }
  873. $segmentsToProcess = Piwik::getKnownSegmentsToArchive();
  874. if(!empty($segmentsToProcess))
  875. {
  876. // If the requested segment is one of the segments to pre-process
  877. // we ensure that any call to the API will trigger archiving of all reports for this segment
  878. $segment = $segment->getString();
  879. if(in_array($segment, $segmentsToProcess))
  880. {
  881. return true;
  882. }
  883. }
  884. return false;
  885. }
  886. /**
  887. * When a segment is set, we shall only process the requested report (no more).
  888. * The requested data set will return a lot faster if we only process these reports rather than all plugins.
  889. * Similarly, when a period=range is requested, we shall only process the requested report for the range itself.
  890. *
  891. * @param string $pluginName
  892. * @return bool
  893. */
  894. public function shouldProcessReportsForPlugin($pluginName)
  895. {
  896. if($this->shouldProcessReportsAllPlugins($this->getSegment(), $this->period))
  897. {
  898. return true;
  899. }
  900. // If any other segment, only process if the requested report belong to this plugin
  901. // or process all plugins if the requested report plugin couldn't be guessed
  902. $pluginBeingProcessed = self::getPluginBeingProcessed($this->getRequestedReport());
  903. return $pluginBeingProcessed == $pluginName
  904. || !Piwik_PluginsManager::getInstance()->isPluginLoaded($pluginBeingProcessed)
  905. ;
  906. }
  907. }