Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/matomo-org/matomo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'core/Archive.php')
-rw-r--r--core/Archive.php799
1 files changed, 492 insertions, 307 deletions
diff --git a/core/Archive.php b/core/Archive.php
index 5492c1b4a8..07695eab35 100644
--- a/core/Archive.php
+++ b/core/Archive.php
@@ -12,9 +12,14 @@
/**
* The archive object is used to query specific data for a day or a period of statistics for a given website.
*
+ * Limitations:
+ * - If you query w/ a range period, you can only query for ONE at a time.
+ * - If you query w/ a non-range period, you can query for multiple periods, but they must
+ * all be of the same type (ie, day, week, month, year).
+ *
* Example:
* <pre>
- * $archive = Piwik_Archive::build($idSite = 1, $period = 'week', '2008-03-08' );
+ * $archive = Piwik_Archive::build($idSite = 1, $period = 'week', '2008-03-08');
* $dataTable = $archive->getDataTable('Provider_hostnameExt');
* $dataTable->queueFilter('ReplaceColumnNames');
* return $dataTable;
@@ -22,313 +27,248 @@
*
* Example bis:
* <pre>
- * $archive = Piwik_Archive::build($idSite = 3, $period = 'day', $date = 'today' );
+ * $archive = Piwik_Archive::build($idSite = 3, $period = 'day', $date = 'today');
* $nbVisits = $archive->getNumeric('nb_visits');
* return $nbVisits;
* </pre>
*
- * If the requested statistics are not yet processed, Archive uses ArchiveProcessing to archive the statistics.
- *
+ * If the requested statistics are not yet processed, Archive uses ArchiveProcessor to archive the statistics.
+ *
+ * TODO: create ticket for this: when building archives, should use each site's timezone (ONLY FOR 'now').
+ *
* @package Piwik
* @subpackage Piwik_Archive
*/
-abstract class Piwik_Archive
+class Piwik_Archive
{
- /**
- * When saving DataTables in the DB, we sometimes replace the columns name by these IDs so we save up lots of bytes
- * Eg. INDEX_NB_UNIQ_VISITORS is an integer: 4 bytes, but 'nb_uniq_visitors' is 16 bytes at least
- * (in php it's actually even much more)
- *
- */
- const INDEX_NB_UNIQ_VISITORS = 1;
- const INDEX_NB_VISITS = 2;
- const INDEX_NB_ACTIONS = 3;
- const INDEX_MAX_ACTIONS = 4;
- const INDEX_SUM_VISIT_LENGTH = 5;
- const INDEX_BOUNCE_COUNT = 6;
- const INDEX_NB_VISITS_CONVERTED = 7;
- const INDEX_NB_CONVERSIONS = 8;
- const INDEX_REVENUE = 9;
- const INDEX_GOALS = 10;
- const INDEX_SUM_DAILY_NB_UNIQ_VISITORS = 11;
-
- // Specific to the Actions reports
- const INDEX_PAGE_NB_HITS = 12;
- const INDEX_PAGE_SUM_TIME_SPENT = 13;
-
- const INDEX_PAGE_EXIT_NB_UNIQ_VISITORS = 14;
- const INDEX_PAGE_EXIT_NB_VISITS = 15;
- const INDEX_PAGE_EXIT_SUM_DAILY_NB_UNIQ_VISITORS = 16;
-
- const INDEX_PAGE_ENTRY_NB_UNIQ_VISITORS = 17;
- const INDEX_PAGE_ENTRY_SUM_DAILY_NB_UNIQ_VISITORS = 18;
- const INDEX_PAGE_ENTRY_NB_VISITS = 19;
- const INDEX_PAGE_ENTRY_NB_ACTIONS = 20;
- const INDEX_PAGE_ENTRY_SUM_VISIT_LENGTH = 21;
- const INDEX_PAGE_ENTRY_BOUNCE_COUNT = 22;
-
- // Ecommerce Items reports
- const INDEX_ECOMMERCE_ITEM_REVENUE = 23;
- const INDEX_ECOMMERCE_ITEM_QUANTITY = 24;
- const INDEX_ECOMMERCE_ITEM_PRICE = 25;
- const INDEX_ECOMMERCE_ORDERS = 26;
- const INDEX_ECOMMERCE_ITEM_PRICE_VIEWED = 27;
-
- // Site Search
- const INDEX_SITE_SEARCH_HAS_NO_RESULT = 28;
- const INDEX_PAGE_IS_FOLLOWING_SITE_SEARCH_NB_HITS = 29;
-
- // Performance Analytics
- const INDEX_PAGE_SUM_TIME_GENERATION = 30;
- const INDEX_PAGE_NB_HITS_WITH_TIME_GENERATION = 31;
- const INDEX_PAGE_MIN_TIME_GENERATION = 32;
- const INDEX_PAGE_MAX_TIME_GENERATION = 33;
-
- // Goal reports
- const INDEX_GOAL_NB_CONVERSIONS = 1;
- const INDEX_GOAL_REVENUE = 2;
- const INDEX_GOAL_NB_VISITS_CONVERTED = 3;
-
- const INDEX_GOAL_ECOMMERCE_REVENUE_SUBTOTAL = 4;
- const INDEX_GOAL_ECOMMERCE_REVENUE_TAX = 5;
- const INDEX_GOAL_ECOMMERCE_REVENUE_SHIPPING = 6;
- const INDEX_GOAL_ECOMMERCE_REVENUE_DISCOUNT = 7;
- const INDEX_GOAL_ECOMMERCE_ITEMS = 8;
-
- public static $mappingFromIdToName = array(
- Piwik_Archive::INDEX_NB_UNIQ_VISITORS => 'nb_uniq_visitors',
- Piwik_Archive::INDEX_NB_VISITS => 'nb_visits',
- Piwik_Archive::INDEX_NB_ACTIONS => 'nb_actions',
- Piwik_Archive::INDEX_MAX_ACTIONS => 'max_actions',
- Piwik_Archive::INDEX_SUM_VISIT_LENGTH => 'sum_visit_length',
- Piwik_Archive::INDEX_BOUNCE_COUNT => 'bounce_count',
- Piwik_Archive::INDEX_NB_VISITS_CONVERTED => 'nb_visits_converted',
- Piwik_Archive::INDEX_NB_CONVERSIONS => 'nb_conversions',
- Piwik_Archive::INDEX_REVENUE => 'revenue',
- Piwik_Archive::INDEX_GOALS => 'goals',
- Piwik_Archive::INDEX_SUM_DAILY_NB_UNIQ_VISITORS => 'sum_daily_nb_uniq_visitors',
-
- // Actions metrics
- Piwik_Archive::INDEX_PAGE_NB_HITS => 'nb_hits',
- Piwik_Archive::INDEX_PAGE_SUM_TIME_SPENT => 'sum_time_spent',
- Piwik_Archive::INDEX_PAGE_SUM_TIME_GENERATION => 'sum_time_generation',
- Piwik_Archive::INDEX_PAGE_NB_HITS_WITH_TIME_GENERATION => 'nb_hits_with_time_generation',
- Piwik_Archive::INDEX_PAGE_MIN_TIME_GENERATION => 'min_time_generation',
- Piwik_Archive::INDEX_PAGE_MAX_TIME_GENERATION => 'max_time_generation',
-
- Piwik_Archive::INDEX_PAGE_EXIT_NB_UNIQ_VISITORS => 'exit_nb_uniq_visitors',
- Piwik_Archive::INDEX_PAGE_EXIT_NB_VISITS => 'exit_nb_visits',
- Piwik_Archive::INDEX_PAGE_EXIT_SUM_DAILY_NB_UNIQ_VISITORS => 'sum_daily_exit_nb_uniq_visitors',
-
- Piwik_Archive::INDEX_PAGE_ENTRY_NB_UNIQ_VISITORS => 'entry_nb_uniq_visitors',
- Piwik_Archive::INDEX_PAGE_ENTRY_SUM_DAILY_NB_UNIQ_VISITORS => 'sum_daily_entry_nb_uniq_visitors',
- Piwik_Archive::INDEX_PAGE_ENTRY_NB_VISITS => 'entry_nb_visits',
- Piwik_Archive::INDEX_PAGE_ENTRY_NB_ACTIONS => 'entry_nb_actions',
- Piwik_Archive::INDEX_PAGE_ENTRY_SUM_VISIT_LENGTH => 'entry_sum_visit_length',
- Piwik_Archive::INDEX_PAGE_ENTRY_BOUNCE_COUNT => 'entry_bounce_count',
- Piwik_Archive::INDEX_PAGE_IS_FOLLOWING_SITE_SEARCH_NB_HITS => 'nb_hits_following_search',
-
- // Items reports metrics
- Piwik_Archive::INDEX_ECOMMERCE_ITEM_REVENUE => 'revenue',
- Piwik_Archive::INDEX_ECOMMERCE_ITEM_QUANTITY => 'quantity',
- Piwik_Archive::INDEX_ECOMMERCE_ITEM_PRICE => 'price',
- Piwik_Archive::INDEX_ECOMMERCE_ITEM_PRICE_VIEWED => 'price_viewed',
- Piwik_Archive::INDEX_ECOMMERCE_ORDERS => 'orders',
- );
-
- public static $mappingFromIdToNameGoal = array(
- Piwik_Archive::INDEX_GOAL_NB_CONVERSIONS => 'nb_conversions',
- Piwik_Archive::INDEX_GOAL_NB_VISITS_CONVERTED => 'nb_visits_converted',
- Piwik_Archive::INDEX_GOAL_REVENUE => 'revenue',
- Piwik_Archive::INDEX_GOAL_ECOMMERCE_REVENUE_SUBTOTAL => 'revenue_subtotal',
- Piwik_Archive::INDEX_GOAL_ECOMMERCE_REVENUE_TAX => 'revenue_tax',
- Piwik_Archive::INDEX_GOAL_ECOMMERCE_REVENUE_SHIPPING => 'revenue_shipping',
- Piwik_Archive::INDEX_GOAL_ECOMMERCE_REVENUE_DISCOUNT => 'revenue_discount',
- Piwik_Archive::INDEX_GOAL_ECOMMERCE_ITEMS => 'items',
- );
+ const REQUEST_ALL_WEBSITES_FLAG = 'all';
+ const ARCHIVE_ALL_PLUGINS_FLAG = 'all';
+ const ID_SUBTABLE_LOAD_ALL_SUBTABLES = 'all';
/**
- * string indexed column name => Integer indexed column name
+ * List of archive IDs for the site, periods and segment we are querying with.
+ * Archive IDs are indexed by done flag and period, ie:
+ *
+ * array(
+ * 'done.Referers' => array(
+ * '2010-01-01' => 1,
+ * '2010-01-02' => 2,
+ * ),
+ * 'done.VisitsSummary' => array(
+ * '2010-01-01' => 3,
+ * '2010-01-02' => 4,
+ * ),
+ * )
+ *
+ * or,
+ *
+ * array(
+ * 'done.all' => array(
+ * '2010-01-01' => 1,
+ * '2010-01-02' => 2
+ * )
+ * )
+ *
* @var array
*/
- public static $mappingFromNameToId = array(
- 'nb_uniq_visitors' => Piwik_Archive::INDEX_NB_UNIQ_VISITORS,
- 'nb_visits' => Piwik_Archive::INDEX_NB_VISITS,
- 'nb_actions' => Piwik_Archive::INDEX_NB_ACTIONS,
- 'max_actions' => Piwik_Archive::INDEX_MAX_ACTIONS,
- 'sum_visit_length' => Piwik_Archive::INDEX_SUM_VISIT_LENGTH,
- 'bounce_count' => Piwik_Archive::INDEX_BOUNCE_COUNT,
- 'nb_visits_converted' => Piwik_Archive::INDEX_NB_VISITS_CONVERTED,
- 'nb_conversions' => Piwik_Archive::INDEX_NB_CONVERSIONS,
- 'revenue' => Piwik_Archive::INDEX_REVENUE,
- 'goals' => Piwik_Archive::INDEX_GOALS,
- 'sum_daily_nb_uniq_visitors' => Piwik_Archive::INDEX_SUM_DAILY_NB_UNIQ_VISITORS,
- );
-
+ private $idarchives = array();
+
/**
- * Metrics calculated and archived by the Actions plugin.
- *
- * @var array
+ * If set to true, the result of all get functions (ie, getNumeric, getBlob, etc.)
+ * will be indexed by the site ID, even if we're only querying data for one site.
+ *
+ * @var bool
*/
- public static $actionsMetrics = array(
- 'nb_pageviews',
- 'nb_uniq_pageviews',
- 'nb_downloads',
- 'nb_uniq_downloads',
- 'nb_outlinks',
- 'nb_uniq_outlinks',
- 'nb_searches',
- 'nb_keywords',
- 'nb_hits',
- 'nb_hits_following_search',
- );
-
- const LABEL_ECOMMERCE_CART = 'ecommerceAbandonedCart';
- const LABEL_ECOMMERCE_ORDER = 'ecommerceOrder';
-
+ private $forceIndexedBySite;
+
/**
- * Website Piwik_Site
- *
- * @var Piwik_Site
+ * If set to true, the result of all get functions (ie, getNumeric, getBlob, etc.)
+ * will be indexed by the period, even if we're only querying data for one period.
+ *
+ * @var bool
*/
- protected $site = null;
+ private $forceIndexedByDate;
/**
- * Segment applied to the visits set
- * @var Piwik_Segment
+ * @var Piwik_Archive_Parameters
*/
- protected $segment = false;
-
+ private $params;
+
/**
- * Builds an Archive object or returns the same archive if previously built.
- *
- * @param int|string $idSite integer, or comma separated list of integer
- * @param string $period 'week' 'day' etc.
- * @param Piwik_Date|string $strDate 'YYYY-MM-DD' or magic keywords 'today' @see Piwik_Date::factory()
- * @param bool|string $segment Segment definition - defaults to false for Backward Compatibility
- * @param bool|string $_restrictSitesToLogin Used only when running as a scheduled task
- * @return Piwik_Archive
+ * @param Piwik_Archive_Parameters $params
+ * @param bool $forceIndexedBySite Whether to force index the result of a query by site ID.
+ * @param bool $forceIndexedByDate Whether to force index the result of a query by period.
*/
- public static function build($idSite, $period, $strDate, $segment = false, $_restrictSitesToLogin = false)
+ protected function __construct(Piwik_Archive_Parameters $params, $forceIndexedBySite = false,
+ $forceIndexedByDate = false)
{
- if ($idSite === 'all') {
- $sites = Piwik_SitesManager_API::getInstance()->getSitesIdWithAtLeastViewAccess($_restrictSitesToLogin);
- } else {
- $sites = Piwik_Site::getIdSitesFromIdSitesString($idSite);
- }
-
- if (!($segment instanceof Piwik_Segment)) {
- $segment = new Piwik_Segment($segment, $idSite);
- }
-
- // idSite=1,3 or idSite=all
- if ($idSite === 'all'
- || is_array($idSite)
- || count($sites) > 1
- ) {
- $archive = new Piwik_Archive_Array_IndexedBySite($sites, $period, $strDate, $segment, $_restrictSitesToLogin);
- } // if a period date string is detected: either 'last30', 'previous10' or 'YYYY-MM-DD,YYYY-MM-DD'
- elseif (is_string($strDate) && self::isMultiplePeriod($strDate, $period)) {
- $oSite = new Piwik_Site($idSite);
- $archive = new Piwik_Archive_Array_IndexedByDate($oSite, $period, $strDate, $segment);
- } // case we request a single archive
- else {
- $oSite = new Piwik_Site($idSite);
- $oPeriod = Piwik_Archive::makePeriodFromQueryParams($oSite, $period, $strDate);
-
- $archive = new Piwik_Archive_Single();
- $archive->setPeriod($oPeriod);
- $archive->setSite($oSite);
- $archive->setSegment($segment);
- }
- return $archive;
+ $this->params = $params;
+ $this->forceIndexedBySite = $forceIndexedBySite;
+ $this->forceIndexedByDate = $forceIndexedByDate;
}
/**
- * Creates a period instance using a Piwik_Site instance and two strings describing
- * the period & date.
+ * Builds an Archive object using query parameter values.
*
- * @param Piwik_Site $site
- * @param string $strPeriod The period string: day, week, month, year, range
- * @param string $strDate The date or date range string.
- * @return Piwik_Period
+ * @param $idSites
+ * @param string $period 'day', 'week', 'month', 'year' or 'range'
+ * @param Piwik_Date|string $strDate 'YYYY-MM-DD', magic keywords (ie, 'today'; @see Piwik_Date::factory())
+ * or date range (ie, 'YYYY-MM-DD,YYYY-MM-DD').
+ * @param bool|string $segment Segment definition - defaults to false for backward compatibility.
+ * @param bool|string $_restrictSitesToLogin Used only when running as a scheduled task.
+ * @return Piwik_Archive
*/
- public static function makePeriodFromQueryParams($site, $strPeriod, $strDate)
+ public static function build($idSites, $period, $strDate, $segment = false, $_restrictSitesToLogin = false)
{
- $tz = $site->getTimezone();
+ $websiteIds = Piwik_Site::getIdSitesFromIdSitesString($idSites, $_restrictSitesToLogin);
- if ($strPeriod == 'range') {
- $oPeriod = new Piwik_Period_Range('range', $strDate, $tz, Piwik_Date::factory('today', $tz));
+ if (Piwik_Period::isMultiplePeriod($strDate, $period)) {
+ $oPeriod = new Piwik_Period_Range($period, $strDate);
+ $allPeriods = $oPeriod->getSubperiods();
} else {
- $oDate = $strDate;
- if (!($strDate instanceof Piwik_Date)) {
- if ($strDate == 'now' || $strDate == 'today') {
- $strDate = date('Y-m-d', Piwik_Date::factory('now', $tz)->getTimestamp());
- } elseif ($strDate == 'yesterday' || $strDate == 'yesterdaySameTime') {
- $strDate = date('Y-m-d', Piwik_Date::factory('now', $tz)->subDay(1)->getTimestamp());
- }
- $oDate = Piwik_Date::factory($strDate);
- }
- $date = $oDate->toString();
- $oPeriod = Piwik_Period::factory($strPeriod, $oDate);
+ $timezone = count($websiteIds) == 1 ? Piwik_Site::getTimezoneFor($websiteIds[0]) : false;
+ $oPeriod = Piwik_Period::makePeriodFromQueryParams($timezone, $period, $strDate);
+ $allPeriods = array($oPeriod);
}
-
- return $oPeriod;
+ $segment = new Piwik_Segment($segment, $websiteIds);
+ $idSiteIsAll = $idSites == self::REQUEST_ALL_WEBSITES_FLAG;
+ $isMultipleDate = Piwik_Period::isMultiplePeriod($strDate, $period);
+ return Piwik_Archive::factory($segment, $allPeriods, $websiteIds, $idSiteIsAll, $isMultipleDate);
}
- abstract public function prepareArchive();
+ public static function factory(Piwik_Segment $segment, array $periods, $idSites, $idSiteIsAll = false, $isMultipleDate = false)
+ {
+ $forceIndexedBySite = false;
+ $forceIndexedByDate = false;
+ if ($idSiteIsAll || count($idSites) > 1) {
+ $forceIndexedBySite = true;
+ }
+ if (count($periods) > 1 || $isMultipleDate) {
+ $forceIndexedByDate = true;
+ }
+
+ $params = new Piwik_Archive_Parameters();
+ $params->setIdSites($idSites);
+ $params->setPeriods($periods);
+ $params->setSegment($segment);
+ return new Piwik_Archive($params, $forceIndexedBySite, $forceIndexedByDate);
+ }
+
+
/**
- * Returns the value of the element $name from the current archive
+ * Returns the value of the element $name from the current archive
* The value to be returned is a numeric value and is stored in the archive_numeric_* tables
*
- * @param string $name For example Referers_distinctKeywords
- * @return float|int|false False if no value with the given name
+ * @param string|array $names One or more archive names, eg, 'nb_visits', 'Referers_distinctKeywords',
+ * etc.
+ * @return numeric|array|false False if no value with the given name, numeric if only one site
+ * and date and we're not forcing an index, and array if multiple
+ * sites/dates are queried.
*/
- abstract public function getNumeric($name);
+ public function getNumeric($names)
+ {
+ $data = $this->get($names, 'numeric');
+
+ $resultIndices = $this->getResultIndices();
+ $result = $data->getArray($resultIndices);
+
+ // if only one metric is returned, just return it as a numeric value
+ if (empty($resultIndices)
+ && count($result) <= 1
+ && (!is_array($names) || count($names) == 1)
+ ) {
+ $result = (float)reset($result); // convert to float in case $result is empty
+ }
+
+ return $result;
+ }
/**
- * Returns the value of the element $name from the current archive
+ * Returns the value of the elements in $names from the current archive.
*
- * The value to be returned is a blob value and is stored in the archive_numeric_* tables
+ * The value to be returned is a blob value and is stored in the archive_blob_* tables.
*
* It can return anything from strings, to serialized PHP arrays or PHP objects, etc.
*
- * @param string $name For example Referers_distinctKeywords
- * @return mixed False if no value with the given name
+ * @param string|array $names One or more archive names, eg, 'Referers_keywordBySearchEngine'.
+ * @param null $idSubtable
+ * @return string|array|false False if no value with the given name, numeric if only one site
+ * and date and we're not forcing an index, and array if multiple
+ * sites/dates are queried.
*/
- abstract public function getBlob($name);
-
+ public function getBlob($names, $idSubtable = null)
+ {
+ $data = $this->get($names, 'blob', $idSubtable);
+ return $data->getArray($this->getResultIndices());
+ }
+
/**
- *
- * @param $fields
- * @return Piwik_DataTable
+ * Returns the numeric values of the elements in $names as a DataTable.
+ *
+ * Note: Every DataTable instance returned will have at most one row in it.
+ *
+ * @param string|array $names One or more archive names, eg, 'nb_visits', 'Referers_distinctKeywords',
+ * etc.
+ * @return Piwik_DataTable|false False if no value with the given names. Based on the number
+ * of sites/periods, the result can be a DataTable_Array, which
+ * contains DataTable instances.
*/
- abstract public function getDataTableFromNumeric($fields);
+ public function getDataTableFromNumeric($names)
+ {
+ $data = $this->get($names, 'numeric');
+ return $data->getDataTable($this->getResultIndices());
+ }
/**
* This method will build a dataTable from the blob value $name in the current archive.
+ *
+ * For example $name = 'Referers_searchEngineByKeyword' will return a
+ * Piwik_DataTable containing all the keywords. If a $idSubtable is given, the method
+ * will return the subTable of $name. If 'all' is supplied for $idSubtable every subtable
+ * will be returned.
+ *
+ * @param string $name The name of the record to get.
+ * @param int|string|null $idSubtable The subtable ID (if any) or 'all' if requesting every datatable.
+ * @return Piwik_DataTable|false
+ */
+ public function getDataTable($name, $idSubtable = null)
+ {
+ $data = $this->get($name, 'blob', $idSubtable);
+ return $data->getDataTable($this->getResultIndices());
+ }
+
+ /**
+ * Same as getDataTable() except that it will also load in memory all the subtables
+ * for the DataTable $name. You can then access the subtables by using the
+ * Piwik_DataTable_Manager::getTable() function.
*
- * For example $name = 'Referers_searchEngineByKeyword' will return a Piwik_DataTable containing all the keywords
- * If a idSubTable is given, the method will return the subTable of $name
- *
- * @param string $name
- * @param int $idSubTable or null if requesting the parent table
+ * @param string $name The name of the record to get.
+ * @param int|string|null $idSubtable The subtable ID (if any) or self::ID_SUBTABLE_LOAD_ALL_SUBTABLES if requesting every datatable.
+ * @param bool $addMetadataSubtableId Whether to add the DB subtable ID as metadata to each datatable,
+ * or not.
* @return Piwik_DataTable
- * @throws exception If the value cannot be found
*/
- abstract public function getDataTable($name, $idSubTable = null);
+ public function getDataTableExpanded($name, $idSubtable = null, $addMetadataSubtableId = true)
+ {
+ $data = $this->get($name, 'blob', self::ID_SUBTABLE_LOAD_ALL_SUBTABLES);
+ return $data->getExpandedDataTable($this->getResultIndices(), $idSubtable, $addMetadataSubtableId);
+ }
/**
- * Same as getDataTable() except that it will also load in memory
- * all the subtables for the DataTable $name.
- * You can then access the subtables by using the Piwik_DataTable_Manager getTable()
- *
- * @param string $name
- * @param int|null $idSubTable null if requesting the parent table
- * @return Piwik_DataTable
+ * Returns the list of plugins that archive the given reports.
+ *
+ * @param array $archiveNames
+ * @return array
*/
- abstract public function getDataTableExpanded($name, $idSubTable = null);
+ public function getRequestedPlugins($archiveNames)
+ {
+ $result = array();
+ foreach ($archiveNames as $name) {
+ $result[] = self::getPluginForReport($name);
+ }
+ return array_unique($result);
+ }
/**
@@ -364,97 +304,342 @@ abstract class Piwik_Archive
return $dataTable;
}
- protected function formatNumericValue($value)
+ private function appendIdSubtable($recordName, $id)
{
- // If there is no dot, we return as is
- // Note: this could be an integer bigger than 32 bits
- if (strpos($value, '.') === false) {
- if ($value === false) {
- return 0;
+ return $recordName . "_" . $id;
+ }
+ /**
+ * Queries archive tables for data and returns the result.
+ * @return Piwik_Archive_DataCollection
+ */
+ private function get($archiveNames, $archiveDataType, $idSubtable = null)
+ {
+ if (!is_array($archiveNames)) {
+ $archiveNames = array($archiveNames);
+ }
+
+ // apply idSubtable
+ if ($idSubtable !== null
+ && $idSubtable != self::ID_SUBTABLE_LOAD_ALL_SUBTABLES
+ ) {
+ foreach ($archiveNames as &$name) {
+ $name = $this->appendIdsubtable($name, $idSubtable);
}
- return (float)$value;
+ }
+
+ $result = new Piwik_Archive_DataCollection(
+ $archiveNames, $archiveDataType, $this->params->getIdSites(), $this->params->getPeriods(), $defaultRow = null);
+
+ $archiveIds = $this->getArchiveIds($archiveNames);
+ if (empty($archiveIds)) {
+ return $result;
}
- // Round up the value with 2 decimals
- // we cast the result as float because returns false when no visitors
- $value = round((float)$value, 2);
- return $value;
+ $loadAllSubtables = $idSubtable == self::ID_SUBTABLE_LOAD_ALL_SUBTABLES;
+ $archiveData = Piwik_DataAccess_ArchiveSelector::getArchiveData($archiveIds, $archiveNames, $archiveDataType, $loadAllSubtables);
+ foreach ($archiveData as $row) {
+ // values are grouped by idsite (site ID), date1-date2 (date range), then name (field name)
+ $idSite = $row['idsite'];
+ $periodStr = $row['date1'].",".$row['date2'];
+
+ if ($archiveDataType == 'numeric') {
+ $value = $this->formatNumericValue($row['value']);
+ } else {
+ $value = $this->uncompress($row['value']);
+ $result->addMetadata($idSite, $periodStr, 'ts_archived', $row['ts_archived']);
+ }
+ //FIXMEA
+ $resultRow = &$result->get($idSite, $periodStr);
+ $resultRow[$row['name']] = $value;
+ }
+
+ return $result;
}
-
- public function getSegment()
+
+ /**
+ * Returns archive IDs for the sites, periods and archive names that are being
+ * queried. This function will use the idarchive cache if it has the right data,
+ * query archive tables for IDs w/o launching archiving, or launch archiving and
+ * get the idarchive from Piwik_ArchiveProcessor instances.
+ */
+ private function getArchiveIds($archiveNames)
{
- return $this->segment;
+ $plugins = $this->getRequestedPlugins($archiveNames);
+
+ // figure out which archives haven't been processed (if an archive has been processed,
+ // then we have the archive IDs in $this->idarchives)
+ $doneFlags = array();
+ $archiveGroups = array();
+ foreach ($plugins as $plugin) {
+ $doneFlag = $this->getDoneStringForPlugin($plugin);
+
+ $doneFlags[$doneFlag] = true;
+ if (!isset($this->idarchives[$doneFlag])) {
+ $archiveGroups[] = $this->getArchiveGroupOfPlugin($plugin);
+ }
+ }
+
+ $archiveGroups = array_unique($archiveGroups);
+
+ // cache id archives for plugins we haven't processed yet
+ if (!empty($archiveGroups)) {
+ if (!Piwik_ArchiveProcessor_Rules::isArchivingDisabledFor($this->params->getSegment(), $this->getPeriodLabel())) {
+ $this->cacheArchiveIdsAfterLaunching($archiveGroups, $plugins);
+ } else {
+ $this->cacheArchiveIdsWithoutLaunching($plugins);
+ }
+ }
+
+ // order idarchives by the table month they belong to
+ $idArchivesByMonth = array();
+ foreach (array_keys($doneFlags) as $doneFlag) {
+ if (empty($this->idarchives[$doneFlag])) {
+ continue;
+ }
+
+ foreach ($this->idarchives[$doneFlag] as $dateRange => $idarchives) {
+ foreach ($idarchives as $id) {
+ $idArchivesByMonth[$dateRange][] = $id;
+ }
+ }
+ }
+
+ return $idArchivesByMonth;
}
- public function setSegment(Piwik_Segment $segment)
+ /**
+ * @return Piwik_Archive_Parameters
+ */
+ public function getParams()
{
- $this->segment = $segment;
+ return $this->params;
}
/**
- * Sets the site
- *
- * @param Piwik_Site $site
+ * Gets the IDs of the archives we're querying for and stores them in $this->archives.
+ * This function will launch the archiving process for each period/site/plugin if
+ * metrics/reports have not been calculated/archived already.
+ *
+ * @param array $archiveGroups @see getArchiveGroupOfReport
+ * @param array $plugins List of plugin names to archive.
*/
- public function setSite(Piwik_Site $site)
+ private function cacheArchiveIdsAfterLaunching($archiveGroups, $plugins)
{
- $this->site = $site;
+ $today = Piwik_Date::today();
+
+ /* @var Piwik_Period $period */
+ foreach ($this->params->getPeriods() as $period) {
+ $periodStr = $period->getRangeString();
+
+ $twoDaysBeforePeriod = $period->getDateStart()->subDay(2);
+ $twoDaysAfterPeriod = $period->getDateEnd()->addDay(2);
+
+ foreach ($this->params->getIdSites() as $idSite) {
+ $site = new Piwik_Site($idSite);
+
+ // if the END of the period is BEFORE the website creation date
+ // we already know there are no stats for this period
+ // we add one day to make sure we don't miss the day of the website creation
+ if ($twoDaysAfterPeriod->isEarlier($site->getCreationDate())) {
+ $archiveDesc = $this->getArchiveDescriptor($idSite, $period);
+ Piwik::log("Archive $archiveDesc skipped, archive is before the website was created.");
+ continue;
+ }
+
+ // if the starting date is in the future we know there is no visit
+ if ($twoDaysBeforePeriod->isLater($today)) {
+ $archiveDesc = $this->getArchiveDescriptor($idSite, $period);
+ Piwik::log("Archive $archiveDesc skipped, archive is after today.");
+ continue;
+ }
+
+
+ if($period->getLabel() == 'day') {
+ $processing = new Piwik_ArchiveProcessor_Day($period, $site, $this->params->getSegment());
+ } else {
+ $processing = new Piwik_ArchiveProcessor_Period($period, $site, $this->params->getSegment());
+ }
+
+ // process for each plugin as well
+ foreach ($archiveGroups as $plugin) {
+ if ($plugin == self::ARCHIVE_ALL_PLUGINS_FLAG) {
+ $plugin = reset($plugins);
+ }
+
+ $doneFlag = $this->getDoneStringForPlugin($plugin);
+ $this->initializeArchiveIdCache($doneFlag);
+
+ $idArchive = $processing->preProcessArchive($plugin);
+
+ $visits = $processing->getNumberOfVisits();
+ if($visits > 0) {
+ $this->idarchives[$doneFlag][$periodStr][] = $idArchive;
+ }
+ }
+ }
+ }
}
/**
- * Gets the site
- *
- * @return Piwik_Site
+ * Gets the IDs of the archives we're querying for and stores them in $this->archives.
+ * This function will not launch the archiving process (and is thus much, much faster
+ * than cacheArchiveIdsAfterLaunching).
+ *
+ * @param array $plugins List of plugin names from which data is being requested.
*/
- public function getSite()
+ private function cacheArchiveIdsWithoutLaunching($plugins)
{
- return $this->site;
- }
+ $idarchivesByReport = Piwik_DataAccess_ArchiveSelector::getArchiveIds(
+ $this->params->getIdSites(), $this->params->getPeriods(), $this->params->getSegment(), $plugins);
+ // initialize archive ID cache for each report
+ foreach ($plugins as $plugin) {
+ $doneFlag = $this->getDoneStringForPlugin($plugin);
+ $this->initializeArchiveIdCache($doneFlag);
+ }
+
+ foreach ($idarchivesByReport as $doneFlag => $idarchivesByDate) {
+ foreach ($idarchivesByDate as $dateRange => $idarchives) {
+ foreach ($idarchives as $idarchive) {
+ $this->idarchives[$doneFlag][$dateRange][] = $idarchive;
+ }
+ }
+ }
+ }
+
/**
- * Returns the Id site associated with this archive
- *
- * @return int
+ * Returns the done string flag for a plugin using this instance's segment & periods.
+ * @param string $plugin
+ * @return string
*/
- public function getIdSite()
+ private function getDoneStringForPlugin($plugin)
{
- return $this->site->getId();
+ return Piwik_ArchiveProcessor_Rules::getDoneStringFlagFor($this->params->getSegment(), $this->getPeriodLabel(), $plugin);
}
+ private function getPeriodLabel()
+ {
+ $periods = $this->params->getPeriods();
+ return reset($periods)->getLabel();
+ }
+
/**
- * Returns true if Segmentation is allowed for this user
- *
- * @return bool
+ * Returns an array describing what metadata to use when indexing a query result.
+ * For use with Piwik_Archive_DataCollection.
+ *
+ * @return array
*/
- public static function isSegmentationEnabled()
+ private function getResultIndices()
+ {
+ $indices = array();
+
+ if (count($this->params->getIdSites()) > 1
+ || $this->forceIndexedBySite
+ ) {
+ $indices['site'] = 'idSite';
+ }
+
+ if (count($this->params->getPeriods()) > 1
+ || $this->forceIndexedByDate
+ ) {
+ $indices['period'] = 'date';
+ }
+
+ return $indices;
+ }
+
+ private function formatNumericValue($value)
+ {
+ // If there is no dot, we return as is
+ // Note: this could be an integer bigger than 32 bits
+ if (strpos($value, '.') === false) {
+ if ($value === false) {
+ return 0;
+ }
+ return (float)$value;
+ }
+
+ // Round up the value with 2 decimals
+ // we cast the result as float because returns false when no visitors
+ return round((float)$value, 2);
+ }
+
+ private function getArchiveDescriptor($idSite, $period)
+ {
+ return "site $idSite, {$period->getLabel()} ({$period->getPrettyString()})";
+ }
+
+ private function uncompress($data)
{
- return !Piwik::isUserIsAnonymous()
- || Piwik_Config::getInstance()->General['anonymous_user_enable_use_segments_API'];
+ return @gzuncompress($data);
}
/**
- * Indicate if $dateString and $period correspond to multiple periods
- *
- * @static
- * @param $dateString
- * @param $period
- * @return boolean
+ * Initializes the archive ID cache ($this->idarchives) for a particular 'done' flag.
+ *
+ * It is necessary that each archive ID caching function call this method for each
+ * unique 'done' flag it encounters, since the getArchiveIds function determines
+ * whether archiving should be launched based on whether $this->idarchives has a
+ * an entry for a specific 'done' flag.
+ *
+ * If this function is not called, then periods with no visits will not add
+ * entries to the cache. If the archive is used again, SQL will be executed to
+ * try and find the archive IDs even though we know there are none.
*/
- public static function isMultiplePeriod($dateString, $period)
+ private function initializeArchiveIdCache($doneFlag)
{
- return (preg_match('/^(last|previous){1}([0-9]*)$/D', $dateString, $regs)
- || Piwik_Period_Range::parseDateRange($dateString))
- && $period != 'range';
+ if (!isset($this->idarchives[$doneFlag])) {
+ $this->idarchives[$doneFlag] = array();
+ }
}
-
+
/**
- * Indicate if $idSiteString corresponds to multiple sites.
- *
- * @param string $idSiteString
- * @return bool
+ * Returns the archiving group identifier given a plugin.
+ *
+ * More than one plugin can be called at once when archiving. In such a case
+ * we don't want to launch archiving three times for three plugins if doing
+ * it once is enough, so getArchiveIds makes sure to get the archive group of
+ * all reports.
+ *
+ * If the period isn't a range, then all plugins' archiving code is executed.
+ * If the period is a range, then archiving code is executed individually for
+ * each plugin.
*/
- public static function isMultipleSites($idSiteString)
+ private function getArchiveGroupOfPlugin($plugin)
{
- return $idSiteString == 'all' || strpos($idSiteString, ',') !== false;
+ if ($this->getPeriodLabel() != 'range') {
+ return self::ARCHIVE_ALL_PLUGINS_FLAG;
+ }
+
+ return $plugin;
+ }
+
+ /**
+ * Returns the name of the plugin that archives a given report.
+ *
+ * @param string $report Archive data name, ie, 'nb_visits', 'UserSettings_...', etc.
+ * @return string
+ */
+ public static function getPluginForReport($report)
+ {
+ // Core metrics are always processed in Core, for the requested date/period/segment
+ if (in_array($report, Piwik_Metrics::getVisitsMetricNames())) {
+ $report = 'VisitsSummary_CoreMetrics';
+ }
+ // Goal_* metrics are processed by the Goals plugin (HACK)
+ else if(strpos($report, 'Goal_') === 0) {
+ $report = 'Goals_Metrics';
+ }
+
+ $plugin = substr($report, 0, strpos($report, '_'));
+ if (empty($plugin)
+ || !Piwik_PluginsManager::getInstance()->isPluginActivated($plugin)
+ ) {
+ $pluginStr = empty($plugin) ? '' : "($plugin)";
+ throw new Exception("Error: The report '$report' was requested but it is not available "
+ . "at this stage. You may also disable the related plugin $pluginStr "
+ . "to avoid this error.");
+ }
+ return $plugin;
}
}