add icons for Character groups
This commit is contained in:
commit
2d9a41a5fe
3461 changed files with 594457 additions and 0 deletions
596
www/analytics/plugins/Actions/API.php
Normal file
596
www/analytics/plugins/Actions/API.php
Normal file
|
|
@ -0,0 +1,596 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\Actions;
|
||||
|
||||
use Exception;
|
||||
use Piwik\API\Request;
|
||||
use Piwik\Archive;
|
||||
use Piwik\Common;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\Date;
|
||||
use Piwik\Metrics;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Plugins\CustomVariables\API as APICustomVariables;
|
||||
use Piwik\Tracker\Action;
|
||||
use Piwik\Tracker\ActionSiteSearch;
|
||||
use Piwik\Tracker\PageUrl;
|
||||
|
||||
/**
|
||||
* The Actions API lets you request reports for all your Visitor Actions: Page URLs, Page titles (Piwik Events),
|
||||
* File Downloads and Clicks on external websites.
|
||||
*
|
||||
* For example, "getPageTitles" will return all your page titles along with standard <a href='http://piwik.org/docs/analytics-api/reference/#toc-metric-definitions' target='_blank'>Actions metrics</a> for each row.
|
||||
*
|
||||
* It is also possible to request data for a specific Page Title with "getPageTitle"
|
||||
* and setting the parameter pageName to the page title you wish to request.
|
||||
* Similarly, you can request metrics for a given Page URL via "getPageUrl", a Download file via "getDownload"
|
||||
* and an outlink via "getOutlink".
|
||||
*
|
||||
* Note: pageName, pageUrl, outlinkUrl, downloadUrl parameters must be URL encoded before you call the API.
|
||||
* @method static \Piwik\Plugins\Actions\API getInstance()
|
||||
*/
|
||||
class API extends \Piwik\Plugin\API
|
||||
{
|
||||
/**
|
||||
* Returns the list of metrics (pages, downloads, outlinks)
|
||||
*
|
||||
* @param int $idSite
|
||||
* @param string $period
|
||||
* @param string $date
|
||||
* @param bool|string $segment
|
||||
* @param bool|array $columns
|
||||
* @return DataTable
|
||||
*/
|
||||
public function get($idSite, $period, $date, $segment = false, $columns = false)
|
||||
{
|
||||
Piwik::checkUserHasViewAccess($idSite);
|
||||
$archive = Archive::build($idSite, $period, $date, $segment);
|
||||
|
||||
$metrics = Archiver::$actionsAggregateMetrics;
|
||||
$metrics['Actions_avg_time_generation'] = 'avg_time_generation';
|
||||
|
||||
// get requested columns
|
||||
$columns = Piwik::getArrayFromApiParameter($columns);
|
||||
if (!empty($columns)) {
|
||||
// get the columns that are available and requested
|
||||
$columns = array_intersect($columns, array_values($metrics));
|
||||
$columns = array_values($columns); // make sure indexes are right
|
||||
$nameReplace = array();
|
||||
foreach ($columns as $i => $column) {
|
||||
$fullColumn = array_search($column, $metrics);
|
||||
$columns[$i] = $fullColumn;
|
||||
$nameReplace[$fullColumn] = $column;
|
||||
}
|
||||
|
||||
if (false !== ($avgGenerationTimeRequested = array_search('Actions_avg_time_generation', $columns))) {
|
||||
unset($columns[$avgGenerationTimeRequested]);
|
||||
$avgGenerationTimeRequested = true;
|
||||
}
|
||||
} else {
|
||||
// get all columns
|
||||
unset($metrics['Actions_avg_time_generation']);
|
||||
$columns = array_keys($metrics);
|
||||
$nameReplace = & $metrics;
|
||||
$avgGenerationTimeRequested = true;
|
||||
}
|
||||
|
||||
if ($avgGenerationTimeRequested) {
|
||||
$tempColumns[] = Archiver::METRIC_SUM_TIME_RECORD_NAME;
|
||||
$tempColumns[] = Archiver::METRIC_HITS_TIMED_RECORD_NAME;
|
||||
$columns = array_merge($columns, $tempColumns);
|
||||
$columns = array_unique($columns);
|
||||
|
||||
$nameReplace[Archiver::METRIC_SUM_TIME_RECORD_NAME] = 'sum_time_generation';
|
||||
$nameReplace[Archiver::METRIC_HITS_TIMED_RECORD_NAME] = 'nb_hits_with_time_generation';
|
||||
}
|
||||
|
||||
$table = $archive->getDataTableFromNumeric($columns);
|
||||
|
||||
// replace labels (remove Actions_)
|
||||
$table->filter('ReplaceColumnNames', array($nameReplace));
|
||||
|
||||
// compute avg generation time
|
||||
if ($avgGenerationTimeRequested) {
|
||||
$table->filter('ColumnCallbackAddColumnQuotient', array('avg_time_generation', 'sum_time_generation', 'nb_hits_with_time_generation', 3));
|
||||
$table->deleteColumns(array('sum_time_generation', 'nb_hits_with_time_generation'));
|
||||
}
|
||||
|
||||
return $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $idSite
|
||||
* @param string $period
|
||||
* @param Date $date
|
||||
* @param bool $segment
|
||||
* @param bool $expanded
|
||||
* @param bool|int $idSubtable
|
||||
* @param bool|int $depth
|
||||
*
|
||||
* @return DataTable|DataTable\Map
|
||||
*/
|
||||
public function getPageUrls($idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false,
|
||||
$depth = false)
|
||||
{
|
||||
$dataTable = $this->getDataTableFromArchive('Actions_actions_url', $idSite, $period, $date, $segment, $expanded, $idSubtable, $depth);
|
||||
$this->filterPageDatatable($dataTable);
|
||||
$this->filterActionsDataTable($dataTable, $expanded);
|
||||
return $dataTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $idSite
|
||||
* @param string $period
|
||||
* @param Date $date
|
||||
* @param bool $segment
|
||||
* @param bool $expanded
|
||||
* @param bool $idSubtable
|
||||
*
|
||||
* @return DataTable|DataTable\Map
|
||||
*/
|
||||
public function getPageUrlsFollowingSiteSearch($idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false)
|
||||
{
|
||||
$dataTable = $this->getPageUrls($idSite, $period, $date, $segment, $expanded, $idSubtable);
|
||||
$this->keepPagesFollowingSearch($dataTable);
|
||||
return $dataTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $idSite
|
||||
* @param string $period
|
||||
* @param Date $date
|
||||
* @param bool $segment
|
||||
* @param bool $expanded
|
||||
* @param bool $idSubtable
|
||||
*
|
||||
* @return DataTable|DataTable\Map
|
||||
*/
|
||||
public function getPageTitlesFollowingSiteSearch($idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false)
|
||||
{
|
||||
$dataTable = $this->getPageTitles($idSite, $period, $date, $segment, $expanded, $idSubtable);
|
||||
$this->keepPagesFollowingSearch($dataTable);
|
||||
return $dataTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTable $dataTable
|
||||
*/
|
||||
protected function keepPagesFollowingSearch($dataTable)
|
||||
{
|
||||
// Keep only pages which are following site search
|
||||
$dataTable->filter('ColumnCallbackDeleteRow', array(
|
||||
'nb_hits_following_search',
|
||||
function ($value) {
|
||||
return $value <= 0;
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a DataTable with analytics information for every unique entry page URL, for
|
||||
* the specified site, period & segment.
|
||||
*/
|
||||
public function getEntryPageUrls($idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false)
|
||||
{
|
||||
$dataTable = $this->getPageUrls($idSite, $period, $date, $segment, $expanded, $idSubtable);
|
||||
$this->filterNonEntryActions($dataTable);
|
||||
return $dataTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a DataTable with analytics information for every unique exit page URL, for
|
||||
* the specified site, period & segment.
|
||||
*/
|
||||
public function getExitPageUrls($idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false)
|
||||
{
|
||||
$dataTable = $this->getPageUrls($idSite, $period, $date, $segment, $expanded, $idSubtable);
|
||||
$this->filterNonExitActions($dataTable);
|
||||
return $dataTable;
|
||||
}
|
||||
|
||||
public function getPageUrl($pageUrl, $idSite, $period, $date, $segment = false)
|
||||
{
|
||||
$callBackParameters = array('Actions_actions_url', $idSite, $period, $date, $segment, $expanded = false, $idSubtable = false);
|
||||
$dataTable = $this->getFilterPageDatatableSearch($callBackParameters, $pageUrl, Action::TYPE_PAGE_URL);
|
||||
$this->filterPageDatatable($dataTable);
|
||||
$this->filterActionsDataTable($dataTable);
|
||||
return $dataTable;
|
||||
}
|
||||
|
||||
public function getPageTitles($idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false)
|
||||
{
|
||||
$dataTable = $this->getDataTableFromArchive('Actions_actions', $idSite, $period, $date, $segment, $expanded, $idSubtable);
|
||||
$this->filterPageDatatable($dataTable);
|
||||
$this->filterActionsDataTable($dataTable, $expanded);
|
||||
return $dataTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a DataTable with analytics information for every unique entry page title
|
||||
* for the given site, time period & segment.
|
||||
*/
|
||||
public function getEntryPageTitles($idSite, $period, $date, $segment = false, $expanded = false,
|
||||
$idSubtable = false)
|
||||
{
|
||||
$dataTable = $this->getPageTitles($idSite, $period, $date, $segment, $expanded, $idSubtable);
|
||||
$this->filterNonEntryActions($dataTable);
|
||||
return $dataTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a DataTable with analytics information for every unique exit page title
|
||||
* for the given site, time period & segment.
|
||||
*/
|
||||
public function getExitPageTitles($idSite, $period, $date, $segment = false, $expanded = false,
|
||||
$idSubtable = false)
|
||||
{
|
||||
$dataTable = $this->getPageTitles($idSite, $period, $date, $segment, $expanded, $idSubtable);
|
||||
$this->filterNonExitActions($dataTable);
|
||||
return $dataTable;
|
||||
}
|
||||
|
||||
public function getPageTitle($pageName, $idSite, $period, $date, $segment = false)
|
||||
{
|
||||
$callBackParameters = array('Actions_actions', $idSite, $period, $date, $segment, $expanded = false, $idSubtable = false);
|
||||
$dataTable = $this->getFilterPageDatatableSearch($callBackParameters, $pageName, Action::TYPE_PAGE_TITLE);
|
||||
$this->filterPageDatatable($dataTable);
|
||||
$this->filterActionsDataTable($dataTable);
|
||||
return $dataTable;
|
||||
}
|
||||
|
||||
public function getDownloads($idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false)
|
||||
{
|
||||
$dataTable = $this->getDataTableFromArchive('Actions_downloads', $idSite, $period, $date, $segment, $expanded, $idSubtable);
|
||||
$this->filterActionsDataTable($dataTable, $expanded);
|
||||
return $dataTable;
|
||||
}
|
||||
|
||||
public function getDownload($downloadUrl, $idSite, $period, $date, $segment = false)
|
||||
{
|
||||
$callBackParameters = array('Actions_downloads', $idSite, $period, $date, $segment, $expanded = false, $idSubtable = false);
|
||||
$dataTable = $this->getFilterPageDatatableSearch($callBackParameters, $downloadUrl, Action::TYPE_DOWNLOAD);
|
||||
$this->filterActionsDataTable($dataTable);
|
||||
return $dataTable;
|
||||
}
|
||||
|
||||
public function getOutlinks($idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false)
|
||||
{
|
||||
$dataTable = $this->getDataTableFromArchive('Actions_outlink', $idSite, $period, $date, $segment, $expanded, $idSubtable);
|
||||
$this->filterActionsDataTable($dataTable, $expanded);
|
||||
return $dataTable;
|
||||
}
|
||||
|
||||
public function getOutlink($outlinkUrl, $idSite, $period, $date, $segment = false)
|
||||
{
|
||||
$callBackParameters = array('Actions_outlink', $idSite, $period, $date, $segment, $expanded = false, $idSubtable = false);
|
||||
$dataTable = $this->getFilterPageDatatableSearch($callBackParameters, $outlinkUrl, Action::TYPE_OUTLINK);
|
||||
$this->filterActionsDataTable($dataTable);
|
||||
return $dataTable;
|
||||
}
|
||||
|
||||
public function getSiteSearchKeywords($idSite, $period, $date, $segment = false)
|
||||
{
|
||||
$dataTable = $this->getSiteSearchKeywordsRaw($idSite, $period, $date, $segment);
|
||||
$dataTable->deleteColumn(Metrics::INDEX_SITE_SEARCH_HAS_NO_RESULT);
|
||||
$this->filterPageDatatable($dataTable);
|
||||
$this->filterActionsDataTable($dataTable);
|
||||
$this->addPagesPerSearchColumn($dataTable);
|
||||
return $dataTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Visitors can search, and then click "next" to view more results. This is the average number of search results pages viewed for this keyword.
|
||||
*
|
||||
* @param DataTable|DataTable\Simple|DataTable\Map $dataTable
|
||||
* @param string $columnToRead
|
||||
*/
|
||||
protected function addPagesPerSearchColumn($dataTable, $columnToRead = 'nb_hits')
|
||||
{
|
||||
$dataTable->filter('ColumnCallbackAddColumnQuotient', array('nb_pages_per_search', $columnToRead, 'nb_visits', $precision = 1));
|
||||
}
|
||||
|
||||
protected function getSiteSearchKeywordsRaw($idSite, $period, $date, $segment)
|
||||
{
|
||||
$dataTable = $this->getDataTableFromArchive('Actions_sitesearch', $idSite, $period, $date, $segment, $expanded = false);
|
||||
return $dataTable;
|
||||
}
|
||||
|
||||
public function getSiteSearchNoResultKeywords($idSite, $period, $date, $segment = false)
|
||||
{
|
||||
$dataTable = $this->getSiteSearchKeywordsRaw($idSite, $period, $date, $segment);
|
||||
// Delete all rows that have some results
|
||||
$dataTable->filter('ColumnCallbackDeleteRow',
|
||||
array(
|
||||
Metrics::INDEX_SITE_SEARCH_HAS_NO_RESULT,
|
||||
function ($value) {
|
||||
return $value < 1;
|
||||
}
|
||||
));
|
||||
$dataTable->deleteRow(DataTable::ID_SUMMARY_ROW);
|
||||
$dataTable->deleteColumn(Metrics::INDEX_SITE_SEARCH_HAS_NO_RESULT);
|
||||
$this->filterPageDatatable($dataTable);
|
||||
$this->filterActionsDataTable($dataTable);
|
||||
$this->addPagesPerSearchColumn($dataTable);
|
||||
return $dataTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $idSite
|
||||
* @param string $period
|
||||
* @param Date $date
|
||||
* @param bool $segment
|
||||
*
|
||||
* @return DataTable|DataTable\Map
|
||||
*/
|
||||
public function getSiteSearchCategories($idSite, $period, $date, $segment = false)
|
||||
{
|
||||
Actions::checkCustomVariablesPluginEnabled();
|
||||
$customVariables = APICustomVariables::getInstance()->getCustomVariables($idSite, $period, $date, $segment, $expanded = false, $_leavePiwikCoreVariables = true);
|
||||
|
||||
$customVarNameToLookFor = ActionSiteSearch::CVAR_KEY_SEARCH_CATEGORY;
|
||||
|
||||
$dataTable = new DataTable();
|
||||
// Handle case where date=last30&period=day
|
||||
// FIXMEA: this logic should really be refactored somewhere, this is ugly!
|
||||
if ($customVariables instanceof DataTable\Map) {
|
||||
$dataTable = $customVariables->getEmptyClone();
|
||||
|
||||
$customVariableDatatables = $customVariables->getDataTables();
|
||||
$dataTables = $dataTable->getDataTables();
|
||||
foreach ($customVariableDatatables as $key => $customVariableTableForDate) {
|
||||
// we do not enter the IF, in the case idSite=1,3 AND period=day&date=datefrom,dateto,
|
||||
if ($customVariableTableForDate instanceof DataTable
|
||||
&& $customVariableTableForDate->getMetadata(Archive\DataTableFactory::TABLE_METADATA_PERIOD_INDEX)
|
||||
) {
|
||||
$row = $customVariableTableForDate->getRowFromLabel($customVarNameToLookFor);
|
||||
if ($row) {
|
||||
$dateRewrite = $customVariableTableForDate->getMetadata(Archive\DataTableFactory::TABLE_METADATA_PERIOD_INDEX)->getDateStart()->toString();
|
||||
$idSubtable = $row->getIdSubDataTable();
|
||||
$categories = APICustomVariables::getInstance()->getCustomVariablesValuesFromNameId($idSite, $period, $dateRewrite, $idSubtable, $segment);
|
||||
$dataTable->addTable($categories, $key);
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif ($customVariables instanceof DataTable) {
|
||||
$row = $customVariables->getRowFromLabel($customVarNameToLookFor);
|
||||
if ($row) {
|
||||
$idSubtable = $row->getIdSubDataTable();
|
||||
$dataTable = APICustomVariables::getInstance()->getCustomVariablesValuesFromNameId($idSite, $period, $date, $idSubtable, $segment);
|
||||
}
|
||||
}
|
||||
$this->filterActionsDataTable($dataTable);
|
||||
$this->addPagesPerSearchColumn($dataTable, $columnToRead = 'nb_actions');
|
||||
return $dataTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will search in the DataTable for a Label matching the searched string
|
||||
* and return only the matching row, or an empty datatable
|
||||
*/
|
||||
protected function getFilterPageDatatableSearch($callBackParameters, $search, $actionType, $table = false,
|
||||
$searchTree = false)
|
||||
{
|
||||
if ($searchTree === false) {
|
||||
// build the query parts that are searched inside the tree
|
||||
if ($actionType == Action::TYPE_PAGE_TITLE) {
|
||||
$searchedString = Common::unsanitizeInputValue($search);
|
||||
} else {
|
||||
$idSite = $callBackParameters[1];
|
||||
try {
|
||||
$searchedString = PageUrl::excludeQueryParametersFromUrl($search, $idSite);
|
||||
} catch (Exception $e) {
|
||||
$searchedString = $search;
|
||||
}
|
||||
}
|
||||
ArchivingHelper::reloadConfig();
|
||||
$searchTree = ArchivingHelper::getActionExplodedNames($searchedString, $actionType);
|
||||
}
|
||||
|
||||
if ($table === false) {
|
||||
// fetch the data table
|
||||
$table = call_user_func_array(array($this, 'getDataTableFromArchive'), $callBackParameters);
|
||||
|
||||
if ($table instanceof DataTable\Map) {
|
||||
// search an array of tables, e.g. when using date=last30
|
||||
// note that if the root is an array, we filter all children
|
||||
// if an array occurs inside the nested table, we only look for the first match (see below)
|
||||
$dataTableMap = $table->getEmptyClone();
|
||||
|
||||
foreach ($table->getDataTables() as $label => $subTable) {
|
||||
$newSubTable = $this->doFilterPageDatatableSearch($callBackParameters, $subTable, $searchTree);
|
||||
|
||||
$dataTableMap->addTable($newSubTable, $label);
|
||||
}
|
||||
|
||||
return $dataTableMap;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->doFilterPageDatatableSearch($callBackParameters, $table, $searchTree);
|
||||
}
|
||||
|
||||
/**
|
||||
* This looks very similar to LabelFilter.php should it be refactored somehow? FIXME
|
||||
*/
|
||||
protected function doFilterPageDatatableSearch($callBackParameters, $table, $searchTree)
|
||||
{
|
||||
// filter a data table array
|
||||
if ($table instanceof DataTable\Map) {
|
||||
foreach ($table->getDataTables() as $subTable) {
|
||||
$filteredSubTable = $this->doFilterPageDatatableSearch($callBackParameters, $subTable, $searchTree);
|
||||
|
||||
if ($filteredSubTable->getRowsCount() > 0) {
|
||||
// match found in a sub table, return and stop searching the others
|
||||
return $filteredSubTable;
|
||||
}
|
||||
}
|
||||
|
||||
// nothing found in all sub tables
|
||||
return new DataTable;
|
||||
}
|
||||
|
||||
// filter regular data table
|
||||
if ($table instanceof DataTable) {
|
||||
// search for the first part of the tree search
|
||||
$search = array_shift($searchTree);
|
||||
$row = $table->getRowFromLabel($search);
|
||||
if ($row === false) {
|
||||
// not found
|
||||
$result = new DataTable;
|
||||
$result->setAllTableMetadata($table->getAllTableMetadata());
|
||||
return $result;
|
||||
}
|
||||
|
||||
// end of tree search reached
|
||||
if (count($searchTree) == 0) {
|
||||
$result = new DataTable();
|
||||
$result->addRow($row);
|
||||
$result->setAllTableMetadata($table->getAllTableMetadata());
|
||||
return $result;
|
||||
}
|
||||
|
||||
// match found on this level and more levels remaining: go deeper
|
||||
$idSubTable = $row->getIdSubDataTable();
|
||||
$callBackParameters[6] = $idSubTable;
|
||||
$table = call_user_func_array(array($this, 'getDataTableFromArchive'), $callBackParameters);
|
||||
return $this->doFilterPageDatatableSearch($callBackParameters, $table, $searchTree);
|
||||
}
|
||||
|
||||
throw new Exception("For this API function, DataTable " . get_class($table) . " is not supported");
|
||||
}
|
||||
|
||||
/**
|
||||
* Common filters for Page URLs and Page Titles
|
||||
*
|
||||
* @param DataTable|DataTable\Simple|DataTable\Map $dataTable
|
||||
*/
|
||||
protected function filterPageDatatable($dataTable)
|
||||
{
|
||||
$columnsToRemove = array('bounce_rate');
|
||||
$dataTable->queueFilter('ColumnDelete', array($columnsToRemove));
|
||||
|
||||
// Average time on page = total time on page / number visits on that page
|
||||
$dataTable->queueFilter('ColumnCallbackAddColumnQuotient',
|
||||
array('avg_time_on_page',
|
||||
'sum_time_spent',
|
||||
'nb_visits',
|
||||
0)
|
||||
);
|
||||
|
||||
// Bounce rate = single page visits on this page / visits started on this page
|
||||
$dataTable->queueFilter('ColumnCallbackAddColumnPercentage',
|
||||
array('bounce_rate',
|
||||
'entry_bounce_count',
|
||||
'entry_nb_visits',
|
||||
0));
|
||||
|
||||
// % Exit = Number of visits that finished on this page / visits on this page
|
||||
$dataTable->queueFilter('ColumnCallbackAddColumnPercentage',
|
||||
array('exit_rate',
|
||||
'exit_nb_visits',
|
||||
'nb_visits',
|
||||
0)
|
||||
);
|
||||
|
||||
// Handle performance analytics
|
||||
$hasTimeGeneration = (array_sum($dataTable->getColumn(Metrics::INDEX_PAGE_SUM_TIME_GENERATION)) > 0);
|
||||
if ($hasTimeGeneration) {
|
||||
// Average generation time = total generation time / number of pageviews
|
||||
$precisionAvgTimeGeneration = 3;
|
||||
$dataTable->queueFilter('ColumnCallbackAddColumnQuotient',
|
||||
array('avg_time_generation',
|
||||
'sum_time_generation',
|
||||
'nb_hits_with_time_generation',
|
||||
$precisionAvgTimeGeneration)
|
||||
);
|
||||
$dataTable->queueFilter('ColumnDelete', array(array('sum_time_generation')));
|
||||
} else {
|
||||
// No generation time: remove it from the API output and add it to empty_columns metadata, so that
|
||||
// the columns can also be removed from the view
|
||||
$dataTable->filter('ColumnDelete', array(array(
|
||||
Metrics::INDEX_PAGE_SUM_TIME_GENERATION,
|
||||
Metrics::INDEX_PAGE_NB_HITS_WITH_TIME_GENERATION,
|
||||
Metrics::INDEX_PAGE_MIN_TIME_GENERATION,
|
||||
Metrics::INDEX_PAGE_MAX_TIME_GENERATION
|
||||
)));
|
||||
|
||||
if ($dataTable instanceof DataTable) {
|
||||
$emptyColumns = $dataTable->getMetadata(DataTable::EMPTY_COLUMNS_METADATA_NAME);
|
||||
if (!is_array($emptyColumns)) {
|
||||
$emptyColumns = array();
|
||||
}
|
||||
$emptyColumns[] = 'sum_time_generation';
|
||||
$emptyColumns[] = 'avg_time_generation';
|
||||
$emptyColumns[] = 'min_time_generation';
|
||||
$emptyColumns[] = 'max_time_generation';
|
||||
$dataTable->setMetadata(DataTable::EMPTY_COLUMNS_METADATA_NAME, $emptyColumns);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Common filters for all Actions API
|
||||
*
|
||||
* @param DataTable|DataTable\Simple|DataTable\Map $dataTable
|
||||
* @param bool $expanded
|
||||
*/
|
||||
protected function filterActionsDataTable($dataTable, $expanded = false)
|
||||
{
|
||||
// Must be applied before Sort in this case, since the DataTable can contain both int and strings indexes
|
||||
// (in the transition period between pre 1.2 and post 1.2 datatable structure)
|
||||
$dataTable->filter('ReplaceColumnNames');
|
||||
$dataTable->filter('Sort', array('nb_visits', 'desc', $naturalSort = false, $expanded));
|
||||
|
||||
$dataTable->queueFilter('ReplaceSummaryRowLabel');
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes DataTable rows referencing actions that were never the first action of a visit.
|
||||
*
|
||||
* @param DataTable $dataTable
|
||||
*/
|
||||
private function filterNonEntryActions($dataTable)
|
||||
{
|
||||
$dataTable->filter('ColumnCallbackDeleteRow',
|
||||
array('entry_nb_visits',
|
||||
function ($visits) {
|
||||
return !strlen($visits);
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes DataTable rows referencing actions that were never the last action of a visit.
|
||||
*
|
||||
* @param DataTable $dataTable
|
||||
*/
|
||||
private function filterNonExitActions($dataTable)
|
||||
{
|
||||
$dataTable->filter('ColumnCallbackDeleteRow',
|
||||
array('exit_nb_visits',
|
||||
function ($visits) {
|
||||
return !strlen($visits);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
protected function getDataTableFromArchive($name, $idSite, $period, $date, $segment, $expanded = false, $idSubtable = null, $depth = null)
|
||||
{
|
||||
$skipAggregationOfSubTables = false;
|
||||
if ($period == 'range'
|
||||
&& empty($idSubtable)
|
||||
&& empty($expanded)
|
||||
&& !Request::shouldLoadFlatten()
|
||||
) {
|
||||
$skipAggregationOfSubTables = false;
|
||||
}
|
||||
return Archive::getDataTableFromArchive($name, $idSite, $period, $date, $segment, $expanded, $idSubtable, $skipAggregationOfSubTables, $depth);
|
||||
}
|
||||
}
|
||||
937
www/analytics/plugins/Actions/Actions.php
Normal file
937
www/analytics/plugins/Actions/Actions.php
Normal file
|
|
@ -0,0 +1,937 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\Actions;
|
||||
|
||||
use Piwik\API\Request;
|
||||
use Piwik\ArchiveProcessor;
|
||||
use Piwik\Common;
|
||||
use Piwik\Db;
|
||||
use Piwik\Menu\MenuMain;
|
||||
use Piwik\MetricsFormatter;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Plugin\ViewDataTable;
|
||||
use Piwik\Plugins\CoreVisualizations\Visualizations\HtmlTable;
|
||||
use Piwik\Site;
|
||||
use Piwik\WidgetsList;
|
||||
|
||||
/**
|
||||
* Actions plugin
|
||||
*
|
||||
* Reports about the page views, the outlinks and downloads.
|
||||
*
|
||||
*/
|
||||
class Actions extends \Piwik\Plugin
|
||||
{
|
||||
const ACTIONS_REPORT_ROWS_DISPLAY = 100;
|
||||
|
||||
/**
|
||||
* @see Piwik\Plugin::getListHooksRegistered
|
||||
*/
|
||||
public function getListHooksRegistered()
|
||||
{
|
||||
$hooks = array(
|
||||
'WidgetsList.addWidgets' => 'addWidgets',
|
||||
'Menu.Reporting.addItems' => 'addMenus',
|
||||
'API.getReportMetadata' => 'getReportMetadata',
|
||||
'API.getSegmentDimensionMetadata' => 'getSegmentsMetadata',
|
||||
'ViewDataTable.configure' => 'configureViewDataTable',
|
||||
'AssetManager.getStylesheetFiles' => 'getStylesheetFiles',
|
||||
'AssetManager.getJavaScriptFiles' => 'getJsFiles',
|
||||
'Insights.addReportToOverview' => 'addReportToInsightsOverview'
|
||||
);
|
||||
return $hooks;
|
||||
}
|
||||
|
||||
public function addReportToInsightsOverview(&$reports)
|
||||
{
|
||||
$reports['Actions_getPageUrls'] = array();
|
||||
$reports['Actions_getPageTitles'] = array();
|
||||
$reports['Actions_getDownloads'] = array('flat' => 1);
|
||||
}
|
||||
|
||||
public function getStylesheetFiles(&$stylesheets)
|
||||
{
|
||||
$stylesheets[] = "plugins/Actions/stylesheets/dataTableActions.less";
|
||||
}
|
||||
|
||||
public function getJsFiles(&$jsFiles)
|
||||
{
|
||||
$jsFiles[] = "plugins/Actions/javascripts/actionsDataTable.js";
|
||||
}
|
||||
|
||||
public function getSegmentsMetadata(&$segments)
|
||||
{
|
||||
$sqlFilter = '\\Piwik\\Tracker\\TableLogAction::getIdActionFromSegment';
|
||||
|
||||
// entry and exit pages of visit
|
||||
$segments[] = array(
|
||||
'type' => 'dimension',
|
||||
'category' => 'General_Actions',
|
||||
'name' => 'Actions_ColumnEntryPageURL',
|
||||
'segment' => 'entryPageUrl',
|
||||
'sqlSegment' => 'log_visit.visit_entry_idaction_url',
|
||||
'sqlFilter' => $sqlFilter,
|
||||
);
|
||||
$segments[] = array(
|
||||
'type' => 'dimension',
|
||||
'category' => 'General_Actions',
|
||||
'name' => 'Actions_ColumnEntryPageTitle',
|
||||
'segment' => 'entryPageTitle',
|
||||
'sqlSegment' => 'log_visit.visit_entry_idaction_name',
|
||||
'sqlFilter' => $sqlFilter,
|
||||
);
|
||||
$segments[] = array(
|
||||
'type' => 'dimension',
|
||||
'category' => 'General_Actions',
|
||||
'name' => 'Actions_ColumnExitPageURL',
|
||||
'segment' => 'exitPageUrl',
|
||||
'sqlSegment' => 'log_visit.visit_exit_idaction_url',
|
||||
'sqlFilter' => $sqlFilter,
|
||||
);
|
||||
$segments[] = array(
|
||||
'type' => 'dimension',
|
||||
'category' => 'General_Actions',
|
||||
'name' => 'Actions_ColumnExitPageTitle',
|
||||
'segment' => 'exitPageTitle',
|
||||
'sqlSegment' => 'log_visit.visit_exit_idaction_name',
|
||||
'sqlFilter' => $sqlFilter,
|
||||
);
|
||||
|
||||
// single pages
|
||||
$segments[] = array(
|
||||
'type' => 'dimension',
|
||||
'category' => 'General_Actions',
|
||||
'name' => 'Actions_ColumnPageURL',
|
||||
'segment' => 'pageUrl',
|
||||
'sqlSegment' => 'log_link_visit_action.idaction_url',
|
||||
'sqlFilter' => $sqlFilter,
|
||||
'acceptedValues' => "All these segments must be URL encoded, for example: " . urlencode('http://example.com/path/page?query'),
|
||||
);
|
||||
$segments[] = array(
|
||||
'type' => 'dimension',
|
||||
'category' => 'General_Actions',
|
||||
'name' => 'Actions_ColumnPageName',
|
||||
'segment' => 'pageTitle',
|
||||
'sqlSegment' => 'log_link_visit_action.idaction_name',
|
||||
'sqlFilter' => $sqlFilter,
|
||||
);
|
||||
$segments[] = array(
|
||||
'type' => 'dimension',
|
||||
'category' => 'General_Actions',
|
||||
'name' => 'Actions_SiteSearchKeyword',
|
||||
'segment' => 'siteSearchKeyword',
|
||||
'sqlSegment' => 'log_link_visit_action.idaction_name',
|
||||
'sqlFilter' => $sqlFilter,
|
||||
);
|
||||
}
|
||||
|
||||
public function getReportMetadata(&$reports)
|
||||
{
|
||||
$reports[] = array(
|
||||
'category' => Piwik::translate('General_Actions'),
|
||||
'name' => Piwik::translate('General_Actions') . ' - ' . Piwik::translate('General_MainMetrics'),
|
||||
'module' => 'Actions',
|
||||
'action' => 'get',
|
||||
'metrics' => array(
|
||||
'nb_pageviews' => Piwik::translate('General_ColumnPageviews'),
|
||||
'nb_uniq_pageviews' => Piwik::translate('General_ColumnUniquePageviews'),
|
||||
'nb_downloads' => Piwik::translate('General_Downloads'),
|
||||
'nb_uniq_downloads' => Piwik::translate('Actions_ColumnUniqueDownloads'),
|
||||
'nb_outlinks' => Piwik::translate('General_Outlinks'),
|
||||
'nb_uniq_outlinks' => Piwik::translate('Actions_ColumnUniqueOutlinks'),
|
||||
'nb_searches' => Piwik::translate('Actions_ColumnSearches'),
|
||||
'nb_keywords' => Piwik::translate('Actions_ColumnSiteSearchKeywords'),
|
||||
'avg_time_generation' => Piwik::translate('General_ColumnAverageGenerationTime'),
|
||||
),
|
||||
'metricsDocumentation' => array(
|
||||
'nb_pageviews' => Piwik::translate('General_ColumnPageviewsDocumentation'),
|
||||
'nb_uniq_pageviews' => Piwik::translate('General_ColumnUniquePageviewsDocumentation'),
|
||||
'nb_downloads' => Piwik::translate('Actions_ColumnClicksDocumentation'),
|
||||
'nb_uniq_downloads' => Piwik::translate('Actions_ColumnUniqueClicksDocumentation'),
|
||||
'nb_outlinks' => Piwik::translate('Actions_ColumnClicksDocumentation'),
|
||||
'nb_uniq_outlinks' => Piwik::translate('Actions_ColumnUniqueClicksDocumentation'),
|
||||
'nb_searches' => Piwik::translate('Actions_ColumnSearchesDocumentation'),
|
||||
'avg_time_generation' => Piwik::translate('General_ColumnAverageGenerationTimeDocumentation'),
|
||||
// 'nb_keywords' => Piwik::translate('Actions_ColumnSiteSearchKeywords'),
|
||||
),
|
||||
'processedMetrics' => false,
|
||||
'order' => 1
|
||||
);
|
||||
|
||||
$metrics = array(
|
||||
'nb_hits' => Piwik::translate('General_ColumnPageviews'),
|
||||
'nb_visits' => Piwik::translate('General_ColumnUniquePageviews'),
|
||||
'bounce_rate' => Piwik::translate('General_ColumnBounceRate'),
|
||||
'avg_time_on_page' => Piwik::translate('General_ColumnAverageTimeOnPage'),
|
||||
'exit_rate' => Piwik::translate('General_ColumnExitRate'),
|
||||
'avg_time_generation' => Piwik::translate('General_ColumnAverageGenerationTime')
|
||||
);
|
||||
|
||||
$documentation = array(
|
||||
'nb_hits' => Piwik::translate('General_ColumnPageviewsDocumentation'),
|
||||
'nb_visits' => Piwik::translate('General_ColumnUniquePageviewsDocumentation'),
|
||||
'bounce_rate' => Piwik::translate('General_ColumnPageBounceRateDocumentation'),
|
||||
'avg_time_on_page' => Piwik::translate('General_ColumnAverageTimeOnPageDocumentation'),
|
||||
'exit_rate' => Piwik::translate('General_ColumnExitRateDocumentation'),
|
||||
'avg_time_generation' => Piwik::translate('General_ColumnAverageGenerationTimeDocumentation'),
|
||||
);
|
||||
|
||||
// pages report
|
||||
$reports[] = array(
|
||||
'category' => Piwik::translate('General_Actions'),
|
||||
'name' => Piwik::translate('Actions_PageUrls'),
|
||||
'module' => 'Actions',
|
||||
'action' => 'getPageUrls',
|
||||
'dimension' => Piwik::translate('Actions_ColumnPageURL'),
|
||||
'metrics' => $metrics,
|
||||
'metricsDocumentation' => $documentation,
|
||||
'documentation' => Piwik::translate('Actions_PagesReportDocumentation', '<br />')
|
||||
. '<br />' . Piwik::translate('General_UsePlusMinusIconsDocumentation'),
|
||||
'processedMetrics' => false,
|
||||
'actionToLoadSubTables' => 'getPageUrls',
|
||||
'order' => 2
|
||||
);
|
||||
|
||||
// entry pages report
|
||||
$reports[] = array(
|
||||
'category' => Piwik::translate('General_Actions'),
|
||||
'name' => Piwik::translate('Actions_SubmenuPagesEntry'),
|
||||
'module' => 'Actions',
|
||||
'action' => 'getEntryPageUrls',
|
||||
'dimension' => Piwik::translate('Actions_ColumnPageURL'),
|
||||
'metrics' => array(
|
||||
'entry_nb_visits' => Piwik::translate('General_ColumnEntrances'),
|
||||
'entry_bounce_count' => Piwik::translate('General_ColumnBounces'),
|
||||
'bounce_rate' => Piwik::translate('General_ColumnBounceRate'),
|
||||
),
|
||||
'metricsDocumentation' => array(
|
||||
'entry_nb_visits' => Piwik::translate('General_ColumnEntrancesDocumentation'),
|
||||
'entry_bounce_count' => Piwik::translate('General_ColumnBouncesDocumentation'),
|
||||
'bounce_rate' => Piwik::translate('General_ColumnBounceRateForPageDocumentation')
|
||||
),
|
||||
'documentation' => Piwik::translate('Actions_EntryPagesReportDocumentation', '<br />')
|
||||
. ' ' . Piwik::translate('General_UsePlusMinusIconsDocumentation'),
|
||||
'processedMetrics' => false,
|
||||
'actionToLoadSubTables' => 'getEntryPageUrls',
|
||||
'order' => 3
|
||||
);
|
||||
|
||||
// exit pages report
|
||||
$reports[] = array(
|
||||
'category' => Piwik::translate('General_Actions'),
|
||||
'name' => Piwik::translate('Actions_SubmenuPagesExit'),
|
||||
'module' => 'Actions',
|
||||
'action' => 'getExitPageUrls',
|
||||
'dimension' => Piwik::translate('Actions_ColumnPageURL'),
|
||||
'metrics' => array(
|
||||
'exit_nb_visits' => Piwik::translate('General_ColumnExits'),
|
||||
'nb_visits' => Piwik::translate('General_ColumnUniquePageviews'),
|
||||
'exit_rate' => Piwik::translate('General_ColumnExitRate')
|
||||
),
|
||||
'metricsDocumentation' => array(
|
||||
'exit_nb_visits' => Piwik::translate('General_ColumnExitsDocumentation'),
|
||||
'nb_visits' => Piwik::translate('General_ColumnUniquePageviewsDocumentation'),
|
||||
'exit_rate' => Piwik::translate('General_ColumnExitRateDocumentation')
|
||||
),
|
||||
'documentation' => Piwik::translate('Actions_ExitPagesReportDocumentation', '<br />')
|
||||
. ' ' . Piwik::translate('General_UsePlusMinusIconsDocumentation'),
|
||||
'processedMetrics' => false,
|
||||
'actionToLoadSubTables' => 'getExitPageUrls',
|
||||
'order' => 4
|
||||
);
|
||||
|
||||
// page titles report
|
||||
$reports[] = array(
|
||||
'category' => Piwik::translate('General_Actions'),
|
||||
'name' => Piwik::translate('Actions_SubmenuPageTitles'),
|
||||
'module' => 'Actions',
|
||||
'action' => 'getPageTitles',
|
||||
'dimension' => Piwik::translate('Actions_ColumnPageName'),
|
||||
'metrics' => $metrics,
|
||||
'metricsDocumentation' => $documentation,
|
||||
'documentation' => Piwik::translate('Actions_PageTitlesReportDocumentation', array('<br />', htmlentities('<title>'))),
|
||||
'processedMetrics' => false,
|
||||
'actionToLoadSubTables' => 'getPageTitles',
|
||||
'order' => 5,
|
||||
|
||||
);
|
||||
|
||||
// entry page titles report
|
||||
$reports[] = array(
|
||||
'category' => Piwik::translate('General_Actions'),
|
||||
'name' => Piwik::translate('Actions_EntryPageTitles'),
|
||||
'module' => 'Actions',
|
||||
'action' => 'getEntryPageTitles',
|
||||
'dimension' => Piwik::translate('Actions_ColumnPageName'),
|
||||
'metrics' => array(
|
||||
'entry_nb_visits' => Piwik::translate('General_ColumnEntrances'),
|
||||
'entry_bounce_count' => Piwik::translate('General_ColumnBounces'),
|
||||
'bounce_rate' => Piwik::translate('General_ColumnBounceRate'),
|
||||
),
|
||||
'metricsDocumentation' => array(
|
||||
'entry_nb_visits' => Piwik::translate('General_ColumnEntrancesDocumentation'),
|
||||
'entry_bounce_count' => Piwik::translate('General_ColumnBouncesDocumentation'),
|
||||
'bounce_rate' => Piwik::translate('General_ColumnBounceRateForPageDocumentation')
|
||||
),
|
||||
'documentation' => Piwik::translate('Actions_ExitPageTitlesReportDocumentation', '<br />')
|
||||
. ' ' . Piwik::translate('General_UsePlusMinusIconsDocumentation'),
|
||||
'processedMetrics' => false,
|
||||
'actionToLoadSubTables' => 'getEntryPageTitles',
|
||||
'order' => 6
|
||||
);
|
||||
|
||||
// exit page titles report
|
||||
$reports[] = array(
|
||||
'category' => Piwik::translate('General_Actions'),
|
||||
'name' => Piwik::translate('Actions_ExitPageTitles'),
|
||||
'module' => 'Actions',
|
||||
'action' => 'getExitPageTitles',
|
||||
'dimension' => Piwik::translate('Actions_ColumnPageName'),
|
||||
'metrics' => array(
|
||||
'exit_nb_visits' => Piwik::translate('General_ColumnExits'),
|
||||
'nb_visits' => Piwik::translate('General_ColumnUniquePageviews'),
|
||||
'exit_rate' => Piwik::translate('General_ColumnExitRate')
|
||||
),
|
||||
'metricsDocumentation' => array(
|
||||
'exit_nb_visits' => Piwik::translate('General_ColumnExitsDocumentation'),
|
||||
'nb_visits' => Piwik::translate('General_ColumnUniquePageviewsDocumentation'),
|
||||
'exit_rate' => Piwik::translate('General_ColumnExitRateDocumentation')
|
||||
),
|
||||
'documentation' => Piwik::translate('Actions_EntryPageTitlesReportDocumentation', '<br />')
|
||||
. ' ' . Piwik::translate('General_UsePlusMinusIconsDocumentation'),
|
||||
'processedMetrics' => false,
|
||||
'actionToLoadSubTables' => 'getExitPageTitles',
|
||||
'order' => 7
|
||||
);
|
||||
|
||||
$documentation = array(
|
||||
'nb_visits' => Piwik::translate('Actions_ColumnUniqueClicksDocumentation'),
|
||||
'nb_hits' => Piwik::translate('Actions_ColumnClicksDocumentation')
|
||||
);
|
||||
|
||||
// outlinks report
|
||||
$reports[] = array(
|
||||
'category' => Piwik::translate('General_Actions'),
|
||||
'name' => Piwik::translate('General_Outlinks'),
|
||||
'module' => 'Actions',
|
||||
'action' => 'getOutlinks',
|
||||
'dimension' => Piwik::translate('Actions_ColumnClickedURL'),
|
||||
'metrics' => array(
|
||||
'nb_visits' => Piwik::translate('Actions_ColumnUniqueClicks'),
|
||||
'nb_hits' => Piwik::translate('Actions_ColumnClicks')
|
||||
),
|
||||
'metricsDocumentation' => $documentation,
|
||||
'documentation' => Piwik::translate('Actions_OutlinksReportDocumentation') . ' '
|
||||
. Piwik::translate('Actions_OutlinkDocumentation') . '<br />'
|
||||
. Piwik::translate('General_UsePlusMinusIconsDocumentation'),
|
||||
'processedMetrics' => false,
|
||||
'actionToLoadSubTables' => 'getOutlinks',
|
||||
'order' => 8,
|
||||
);
|
||||
|
||||
// downloads report
|
||||
$reports[] = array(
|
||||
'category' => Piwik::translate('General_Actions'),
|
||||
'name' => Piwik::translate('General_Downloads'),
|
||||
'module' => 'Actions',
|
||||
'action' => 'getDownloads',
|
||||
'dimension' => Piwik::translate('Actions_ColumnDownloadURL'),
|
||||
'metrics' => array(
|
||||
'nb_visits' => Piwik::translate('Actions_ColumnUniqueDownloads'),
|
||||
'nb_hits' => Piwik::translate('General_Downloads')
|
||||
),
|
||||
'metricsDocumentation' => $documentation,
|
||||
'documentation' => Piwik::translate('Actions_DownloadsReportDocumentation', '<br />'),
|
||||
'processedMetrics' => false,
|
||||
'actionToLoadSubTables' => 'getDownloads',
|
||||
'order' => 9,
|
||||
);
|
||||
|
||||
if ($this->isSiteSearchEnabled()) {
|
||||
// Search Keywords
|
||||
$reports[] = array(
|
||||
'category' => Piwik::translate('Actions_SubmenuSitesearch'),
|
||||
'name' => Piwik::translate('Actions_WidgetSearchKeywords'),
|
||||
'module' => 'Actions',
|
||||
'action' => 'getSiteSearchKeywords',
|
||||
'dimension' => Piwik::translate('General_ColumnKeyword'),
|
||||
'metrics' => array(
|
||||
'nb_visits' => Piwik::translate('Actions_ColumnSearches'),
|
||||
'nb_pages_per_search' => Piwik::translate('Actions_ColumnPagesPerSearch'),
|
||||
'exit_rate' => Piwik::translate('Actions_ColumnSearchExits'),
|
||||
),
|
||||
'metricsDocumentation' => array(
|
||||
'nb_visits' => Piwik::translate('Actions_ColumnSearchesDocumentation'),
|
||||
'nb_pages_per_search' => Piwik::translate('Actions_ColumnPagesPerSearchDocumentation'),
|
||||
'exit_rate' => Piwik::translate('Actions_ColumnSearchExitsDocumentation'),
|
||||
),
|
||||
'documentation' => Piwik::translate('Actions_SiteSearchKeywordsDocumentation') . '<br/><br/>' . Piwik::translate('Actions_SiteSearchIntro') . '<br/><br/>'
|
||||
. '<a href="http://piwik.org/docs/site-search/" target="_blank">' . Piwik::translate('Actions_LearnMoreAboutSiteSearchLink') . '</a>',
|
||||
'processedMetrics' => false,
|
||||
'order' => 15
|
||||
);
|
||||
// No Result Search Keywords
|
||||
$reports[] = array(
|
||||
'category' => Piwik::translate('Actions_SubmenuSitesearch'),
|
||||
'name' => Piwik::translate('Actions_WidgetSearchNoResultKeywords'),
|
||||
'module' => 'Actions',
|
||||
'action' => 'getSiteSearchNoResultKeywords',
|
||||
'dimension' => Piwik::translate('Actions_ColumnNoResultKeyword'),
|
||||
'metrics' => array(
|
||||
'nb_visits' => Piwik::translate('Actions_ColumnSearches'),
|
||||
'exit_rate' => Piwik::translate('Actions_ColumnSearchExits'),
|
||||
),
|
||||
'metricsDocumentation' => array(
|
||||
'nb_visits' => Piwik::translate('Actions_ColumnSearchesDocumentation'),
|
||||
'exit_rate' => Piwik::translate('Actions_ColumnSearchExitsDocumentation'),
|
||||
),
|
||||
'documentation' => Piwik::translate('Actions_SiteSearchIntro') . '<br /><br />' . Piwik::translate('Actions_SiteSearchKeywordsNoResultDocumentation'),
|
||||
'processedMetrics' => false,
|
||||
'order' => 16
|
||||
);
|
||||
|
||||
if (self::isCustomVariablesPluginsEnabled()) {
|
||||
// Search Categories
|
||||
$reports[] = array(
|
||||
'category' => Piwik::translate('Actions_SubmenuSitesearch'),
|
||||
'name' => Piwik::translate('Actions_WidgetSearchCategories'),
|
||||
'module' => 'Actions',
|
||||
'action' => 'getSiteSearchCategories',
|
||||
'dimension' => Piwik::translate('Actions_ColumnSearchCategory'),
|
||||
'metrics' => array(
|
||||
'nb_visits' => Piwik::translate('Actions_ColumnSearches'),
|
||||
'nb_pages_per_search' => Piwik::translate('Actions_ColumnPagesPerSearch'),
|
||||
'exit_rate' => Piwik::translate('Actions_ColumnSearchExits'),
|
||||
),
|
||||
'metricsDocumentation' => array(
|
||||
'nb_visits' => Piwik::translate('Actions_ColumnSearchesDocumentation'),
|
||||
'nb_pages_per_search' => Piwik::translate('Actions_ColumnPagesPerSearchDocumentation'),
|
||||
'exit_rate' => Piwik::translate('Actions_ColumnSearchExitsDocumentation'),
|
||||
),
|
||||
'documentation' => Piwik::translate('Actions_SiteSearchCategories1') . '<br/>' . Piwik::translate('Actions_SiteSearchCategories2'),
|
||||
'processedMetrics' => false,
|
||||
'order' => 17
|
||||
);
|
||||
}
|
||||
|
||||
$documentation = Piwik::translate('Actions_SiteSearchFollowingPagesDoc') . '<br/>' . Piwik::translate('General_UsePlusMinusIconsDocumentation');
|
||||
// Pages URLs following Search
|
||||
$reports[] = array(
|
||||
'category' => Piwik::translate('Actions_SubmenuSitesearch'),
|
||||
'name' => Piwik::translate('Actions_WidgetPageUrlsFollowingSearch'),
|
||||
'module' => 'Actions',
|
||||
'action' => 'getPageUrlsFollowingSiteSearch',
|
||||
'dimension' => Piwik::translate('General_ColumnDestinationPage'),
|
||||
'metrics' => array(
|
||||
'nb_hits_following_search' => Piwik::translate('General_ColumnViewedAfterSearch'),
|
||||
'nb_hits' => Piwik::translate('General_ColumnTotalPageviews'),
|
||||
),
|
||||
'metricsDocumentation' => array(
|
||||
'nb_hits_following_search' => Piwik::translate('General_ColumnViewedAfterSearchDocumentation'),
|
||||
'nb_hits' => Piwik::translate('General_ColumnPageviewsDocumentation'),
|
||||
),
|
||||
'documentation' => $documentation,
|
||||
'processedMetrics' => false,
|
||||
'order' => 18
|
||||
);
|
||||
// Pages Titles following Search
|
||||
$reports[] = array(
|
||||
'category' => Piwik::translate('Actions_SubmenuSitesearch'),
|
||||
'name' => Piwik::translate('Actions_WidgetPageTitlesFollowingSearch'),
|
||||
'module' => 'Actions',
|
||||
'action' => 'getPageTitlesFollowingSiteSearch',
|
||||
'dimension' => Piwik::translate('General_ColumnDestinationPage'),
|
||||
'metrics' => array(
|
||||
'nb_hits_following_search' => Piwik::translate('General_ColumnViewedAfterSearch'),
|
||||
'nb_hits' => Piwik::translate('General_ColumnTotalPageviews'),
|
||||
),
|
||||
'metricsDocumentation' => array(
|
||||
'nb_hits_following_search' => Piwik::translate('General_ColumnViewedAfterSearchDocumentation'),
|
||||
'nb_hits' => Piwik::translate('General_ColumnPageviewsDocumentation'),
|
||||
),
|
||||
'documentation' => $documentation,
|
||||
'processedMetrics' => false,
|
||||
'order' => 19
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function addWidgets()
|
||||
{
|
||||
WidgetsList::add('General_Actions', 'General_Pages', 'Actions', 'getPageUrls');
|
||||
WidgetsList::add('General_Actions', 'Actions_WidgetPageTitles', 'Actions', 'getPageTitles');
|
||||
WidgetsList::add('General_Actions', 'General_Outlinks', 'Actions', 'getOutlinks');
|
||||
WidgetsList::add('General_Actions', 'General_Downloads', 'Actions', 'getDownloads');
|
||||
WidgetsList::add('General_Actions', 'Actions_WidgetPagesEntry', 'Actions', 'getEntryPageUrls');
|
||||
WidgetsList::add('General_Actions', 'Actions_WidgetPagesExit', 'Actions', 'getExitPageUrls');
|
||||
WidgetsList::add('General_Actions', 'Actions_WidgetEntryPageTitles', 'Actions', 'getEntryPageTitles');
|
||||
WidgetsList::add('General_Actions', 'Actions_WidgetExitPageTitles', 'Actions', 'getExitPageTitles');
|
||||
|
||||
if ($this->isSiteSearchEnabled()) {
|
||||
WidgetsList::add('Actions_SubmenuSitesearch', 'Actions_WidgetSearchKeywords', 'Actions', 'getSiteSearchKeywords');
|
||||
|
||||
if (self::isCustomVariablesPluginsEnabled()) {
|
||||
WidgetsList::add('Actions_SubmenuSitesearch', 'Actions_WidgetSearchCategories', 'Actions', 'getSiteSearchCategories');
|
||||
}
|
||||
WidgetsList::add('Actions_SubmenuSitesearch', 'Actions_WidgetSearchNoResultKeywords', 'Actions', 'getSiteSearchNoResultKeywords');
|
||||
WidgetsList::add('Actions_SubmenuSitesearch', 'Actions_WidgetPageUrlsFollowingSearch', 'Actions', 'getPageUrlsFollowingSiteSearch');
|
||||
WidgetsList::add('Actions_SubmenuSitesearch', 'Actions_WidgetPageTitlesFollowingSearch', 'Actions', 'getPageTitlesFollowingSiteSearch');
|
||||
}
|
||||
}
|
||||
|
||||
function addMenus()
|
||||
{
|
||||
MenuMain::getInstance()->add('General_Actions', '', array('module' => 'Actions', 'action' => 'indexPageUrls'), true, 15);
|
||||
MenuMain::getInstance()->add('General_Actions', 'General_Pages', array('module' => 'Actions', 'action' => 'indexPageUrls'), true, 1);
|
||||
MenuMain::getInstance()->add('General_Actions', 'Actions_SubmenuPagesEntry', array('module' => 'Actions', 'action' => 'indexEntryPageUrls'), true, 2);
|
||||
MenuMain::getInstance()->add('General_Actions', 'Actions_SubmenuPagesExit', array('module' => 'Actions', 'action' => 'indexExitPageUrls'), true, 3);
|
||||
MenuMain::getInstance()->add('General_Actions', 'Actions_SubmenuPageTitles', array('module' => 'Actions', 'action' => 'indexPageTitles'), true, 4);
|
||||
MenuMain::getInstance()->add('General_Actions', 'General_Outlinks', array('module' => 'Actions', 'action' => 'indexOutlinks'), true, 6);
|
||||
MenuMain::getInstance()->add('General_Actions', 'General_Downloads', array('module' => 'Actions', 'action' => 'indexDownloads'), true, 7);
|
||||
|
||||
if ($this->isSiteSearchEnabled()) {
|
||||
MenuMain::getInstance()->add('General_Actions', 'Actions_SubmenuSitesearch', array('module' => 'Actions', 'action' => 'indexSiteSearch'), true, 5);
|
||||
}
|
||||
}
|
||||
|
||||
protected function isSiteSearchEnabled()
|
||||
{
|
||||
$idSite = Common::getRequestVar('idSite', 0, 'int');
|
||||
$idSites = Common::getRequestVar('idSites', '', 'string');
|
||||
$idSites = Site::getIdSitesFromIdSitesString($idSites, true);
|
||||
|
||||
if (!empty($idSite)) {
|
||||
$idSites[] = $idSite;
|
||||
}
|
||||
|
||||
if (empty($idSites)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($idSites as $idSite) {
|
||||
if (!Site::isSiteSearchEnabledFor($idSite)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static public function checkCustomVariablesPluginEnabled()
|
||||
{
|
||||
if (!self::isCustomVariablesPluginsEnabled()) {
|
||||
throw new \Exception("To Track Site Search Categories, please ask the Piwik Administrator to enable the 'Custom Variables' plugin in Settings > Plugins.");
|
||||
}
|
||||
}
|
||||
|
||||
static protected function isCustomVariablesPluginsEnabled()
|
||||
{
|
||||
return \Piwik\Plugin\Manager::getInstance()->isPluginActivated('CustomVariables');
|
||||
}
|
||||
|
||||
|
||||
public function configureViewDataTable(ViewDataTable $view)
|
||||
{
|
||||
switch ($view->requestConfig->apiMethodToRequestDataTable) {
|
||||
case 'Actions.getPageUrls':
|
||||
$this->configureViewForPageUrls($view);
|
||||
break;
|
||||
case 'Actions.getEntryPageUrls':
|
||||
$this->configureViewForEntryPageUrls($view);
|
||||
break;
|
||||
case 'Actions.getExitPageUrls':
|
||||
$this->configureViewForExitPageUrls($view);
|
||||
break;
|
||||
case 'Actions.getSiteSearchKeywords':
|
||||
$this->configureViewForSiteSearchKeywords($view);
|
||||
break;
|
||||
case 'Actions.getSiteSearchNoResultKeywords':
|
||||
$this->configureViewForSiteSearchNoResultKeywords($view);
|
||||
break;
|
||||
case 'Actions.getSiteSearchCategories':
|
||||
$this->configureViewForSiteSearchCategories($view);
|
||||
break;
|
||||
case 'Actions.getPageUrlsFollowingSiteSearch':
|
||||
$this->configureViewForGetPageUrlsOrTitlesFollowingSiteSearch($view, false);
|
||||
break;
|
||||
case 'Actions.getPageTitlesFollowingSiteSearch':
|
||||
$this->configureViewForGetPageUrlsOrTitlesFollowingSiteSearch($view, true);
|
||||
break;
|
||||
case 'Actions.getPageTitles':
|
||||
$this->configureViewForGetPageTitles($view);
|
||||
break;
|
||||
case 'Actions.getEntryPageTitles':
|
||||
$this->configureViewForGetEntryPageTitles($view);
|
||||
break;
|
||||
case 'Actions.getExitPageTitles':
|
||||
$this->configureViewForGetExitPageTitles($view);
|
||||
break;
|
||||
case 'Actions.getDownloads':
|
||||
$this->configureViewForGetDownloads($view);
|
||||
break;
|
||||
case 'Actions.getOutlinks':
|
||||
$this->configureViewForGetOutlinks($view);
|
||||
break;
|
||||
}
|
||||
|
||||
if ($this->pluginName == $view->requestConfig->getApiModuleToRequest()) {
|
||||
if ($view->isRequestingSingleDataTable()) {
|
||||
// make sure custom visualizations are shown on actions reports
|
||||
$view->config->show_all_views_icons = true;
|
||||
$view->config->show_bar_chart = false;
|
||||
$view->config->show_pie_chart = false;
|
||||
$view->config->show_tag_cloud = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function addBaseDisplayProperties(ViewDataTable $view)
|
||||
{
|
||||
$view->config->datatable_js_type = 'ActionsDataTable';
|
||||
$view->config->search_recursive = true;
|
||||
$view->config->show_table_all_columns = false;
|
||||
$view->requestConfig->filter_limit = self::ACTIONS_REPORT_ROWS_DISPLAY;
|
||||
$view->config->show_all_views_icons = false;
|
||||
|
||||
if ($view->isViewDataTableId(HtmlTable::ID)) {
|
||||
$view->config->show_embedded_subtable = true;
|
||||
}
|
||||
|
||||
// if the flat parameter is not provided, make sure it is set to 0 in the URL,
|
||||
// so users can see that they can set it to 1 (see #3365)
|
||||
$view->config->custom_parameters = array('flat' => 0);
|
||||
|
||||
if (Request::shouldLoadExpanded()) {
|
||||
|
||||
if ($view->isViewDataTableId(HtmlTable::ID)) {
|
||||
$view->config->show_expanded = true;
|
||||
}
|
||||
|
||||
$view->config->filters[] = function ($dataTable) {
|
||||
Actions::setDataTableRowLevels($dataTable);
|
||||
};
|
||||
}
|
||||
|
||||
$view->config->filters[] = function ($dataTable) use ($view) {
|
||||
if ($view->isViewDataTableId(HtmlTable::ID)) {
|
||||
$view->config->datatable_css_class = 'dataTableActions';
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Piwik\DataTable $dataTable
|
||||
* @param int $level
|
||||
*/
|
||||
public static function setDataTableRowLevels($dataTable, $level = 0)
|
||||
{
|
||||
foreach ($dataTable->getRows() as $row) {
|
||||
$row->setMetadata('css_class', 'level' . $level);
|
||||
|
||||
$subtable = $row->getSubtable();
|
||||
if ($subtable) {
|
||||
self::setDataTableRowLevels($subtable, $level + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function addExcludeLowPopDisplayProperties(ViewDataTable $view)
|
||||
{
|
||||
if (Common::getRequestVar('enable_filter_excludelowpop', '0', 'string') != '0') {
|
||||
$view->requestConfig->filter_excludelowpop = 'nb_hits';
|
||||
$view->requestConfig->filter_excludelowpop_value = function () {
|
||||
// computing minimum value to exclude (2 percent of the total number of actions)
|
||||
$visitsInfo = \Piwik\Plugins\VisitsSummary\Controller::getVisitsSummary()->getFirstRow();
|
||||
$nbActions = $visitsInfo->getColumn('nb_actions');
|
||||
$nbActionsLowPopulationThreshold = floor(0.02 * $nbActions);
|
||||
|
||||
// we remove 1 to make sure some actions/downloads are displayed in the case we have a very few of them
|
||||
// and each of them has 1 or 2 hits...
|
||||
return min($visitsInfo->getColumn('max_actions') - 1, $nbActionsLowPopulationThreshold - 1);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private function addPageDisplayProperties(ViewDataTable $view)
|
||||
{
|
||||
$view->config->addTranslations(array(
|
||||
'nb_hits' => Piwik::translate('General_ColumnPageviews'),
|
||||
'nb_visits' => Piwik::translate('General_ColumnUniquePageviews'),
|
||||
'avg_time_on_page' => Piwik::translate('General_ColumnAverageTimeOnPage'),
|
||||
'bounce_rate' => Piwik::translate('General_ColumnBounceRate'),
|
||||
'exit_rate' => Piwik::translate('General_ColumnExitRate'),
|
||||
'avg_time_generation' => Piwik::translate('General_ColumnAverageGenerationTime'),
|
||||
));
|
||||
|
||||
// prettify avg_time_on_page column
|
||||
$getPrettyTimeFromSeconds = '\Piwik\MetricsFormatter::getPrettyTimeFromSeconds';
|
||||
$view->config->filters[] = array('ColumnCallbackReplace', array('avg_time_on_page', $getPrettyTimeFromSeconds));
|
||||
|
||||
// prettify avg_time_generation column
|
||||
$avgTimeCallback = function ($time) {
|
||||
return $time ? MetricsFormatter::getPrettyTimeFromSeconds($time, true, true, false) : "-";
|
||||
};
|
||||
$view->config->filters[] = array('ColumnCallbackReplace', array('avg_time_generation', $avgTimeCallback));
|
||||
|
||||
// add avg_generation_time tooltip
|
||||
$tooltipCallback = function ($hits, $min, $max) {
|
||||
if (!$hits) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Piwik::translate("Actions_AvgGenerationTimeTooltip", array(
|
||||
$hits,
|
||||
"<br />",
|
||||
MetricsFormatter::getPrettyTimeFromSeconds($min),
|
||||
MetricsFormatter::getPrettyTimeFromSeconds($max)
|
||||
));
|
||||
};
|
||||
$view->config->filters[] = array('ColumnCallbackAddMetadata',
|
||||
array(
|
||||
array('nb_hits_with_time_generation', 'min_time_generation', 'max_time_generation'),
|
||||
'avg_time_generation_tooltip',
|
||||
$tooltipCallback
|
||||
)
|
||||
);
|
||||
|
||||
$this->addExcludeLowPopDisplayProperties($view);
|
||||
}
|
||||
|
||||
public function configureViewForPageUrls(ViewDataTable $view)
|
||||
{
|
||||
$view->config->addTranslation('label', Piwik::translate('Actions_ColumnPageURL'));
|
||||
$view->config->columns_to_display = array('label', 'nb_hits', 'nb_visits', 'bounce_rate',
|
||||
'avg_time_on_page', 'exit_rate', 'avg_time_generation');
|
||||
|
||||
$this->addPageDisplayProperties($view);
|
||||
$this->addBaseDisplayProperties($view);
|
||||
}
|
||||
|
||||
public function configureViewForEntryPageUrls(ViewDataTable $view)
|
||||
{
|
||||
// link to the page, not just the report, but only if not a widget
|
||||
$widget = Common::getRequestVar('widget', false);
|
||||
|
||||
$view->config->self_url = Request::getCurrentUrlWithoutGenericFilters(array(
|
||||
'module' => 'Actions',
|
||||
'action' => $widget === false ? 'indexEntryPageUrls' : 'getEntryPageUrls'
|
||||
));
|
||||
|
||||
$view->config->addTranslations(array(
|
||||
'label' => Piwik::translate('Actions_ColumnEntryPageURL'),
|
||||
'entry_bounce_count' => Piwik::translate('General_ColumnBounces'),
|
||||
'entry_nb_visits' => Piwik::translate('General_ColumnEntrances'))
|
||||
);
|
||||
|
||||
$view->config->title = Piwik::translate('Actions_SubmenuPagesEntry');
|
||||
$view->config->addRelatedReport('Actions.getEntryPageTitles', Piwik::translate('Actions_EntryPageTitles'));
|
||||
$view->config->columns_to_display = array('label', 'entry_nb_visits', 'entry_bounce_count', 'bounce_rate');
|
||||
$view->requestConfig->filter_sort_column = 'entry_nb_visits';
|
||||
$view->requestConfig->filter_sort_order = 'desc';
|
||||
|
||||
$this->addPageDisplayProperties($view);
|
||||
$this->addBaseDisplayProperties($view);
|
||||
}
|
||||
|
||||
public function configureViewForExitPageUrls(ViewDataTable $view)
|
||||
{
|
||||
// link to the page, not just the report, but only if not a widget
|
||||
$widget = Common::getRequestVar('widget', false);
|
||||
|
||||
$view->config->self_url = Request::getCurrentUrlWithoutGenericFilters(array(
|
||||
'module' => 'Actions',
|
||||
'action' => $widget === false ? 'indexExitPageUrls' : 'getExitPageUrls'
|
||||
));
|
||||
|
||||
$view->config->addTranslations(array(
|
||||
'label' => Piwik::translate('Actions_ColumnExitPageURL'),
|
||||
'exit_nb_visits' => Piwik::translate('General_ColumnExits'))
|
||||
);
|
||||
|
||||
$view->config->title = Piwik::translate('Actions_SubmenuPagesExit');
|
||||
$view->config->addRelatedReport('Actions.getExitPageTitles', Piwik::translate('Actions_ExitPageTitles'));
|
||||
|
||||
$view->config->columns_to_display = array('label', 'exit_nb_visits', 'nb_visits', 'exit_rate');
|
||||
$view->requestConfig->filter_sort_column = 'exit_nb_visits';
|
||||
$view->requestConfig->filter_sort_order = 'desc';
|
||||
|
||||
$this->addPageDisplayProperties($view);
|
||||
$this->addBaseDisplayProperties($view);
|
||||
}
|
||||
|
||||
private function addSiteSearchDisplayProperties(ViewDataTable $view)
|
||||
{
|
||||
$view->config->addTranslations(array(
|
||||
'nb_visits' => Piwik::translate('Actions_ColumnSearches'),
|
||||
'exit_rate' => str_replace("% ", "% ", Piwik::translate('Actions_ColumnSearchExits')),
|
||||
'nb_pages_per_search' => Piwik::translate('Actions_ColumnPagesPerSearch')
|
||||
));
|
||||
|
||||
$view->config->show_bar_chart = false;
|
||||
$view->config->show_table_all_columns = false;
|
||||
}
|
||||
|
||||
public function configureViewForSiteSearchKeywords(ViewDataTable $view)
|
||||
{
|
||||
$view->config->addTranslation('label', Piwik::translate('General_ColumnKeyword'));
|
||||
$view->config->columns_to_display = array('label', 'nb_visits', 'nb_pages_per_search', 'exit_rate');
|
||||
|
||||
$this->addSiteSearchDisplayProperties($view);
|
||||
}
|
||||
|
||||
public function configureViewForSiteSearchNoResultKeywords(ViewDataTable $view)
|
||||
{
|
||||
$view->config->addTranslation('label', Piwik::translate('Actions_ColumnNoResultKeyword'));
|
||||
$view->config->columns_to_display = array('label', 'nb_visits', 'exit_rate');
|
||||
|
||||
$this->addSiteSearchDisplayProperties($view);
|
||||
}
|
||||
|
||||
public function configureViewForSiteSearchCategories(ViewDataTable $view)
|
||||
{
|
||||
$view->config->addTranslations(array(
|
||||
'label' => Piwik::translate('Actions_ColumnSearchCategory'),
|
||||
'nb_visits' => Piwik::translate('Actions_ColumnSearches'),
|
||||
'nb_pages_per_search' => Piwik::translate('Actions_ColumnPagesPerSearch')
|
||||
));
|
||||
|
||||
$view->config->columns_to_display = array('label', 'nb_visits', 'nb_pages_per_search');
|
||||
$view->config->show_table_all_columns = false;
|
||||
$view->config->show_bar_chart = false;
|
||||
|
||||
if ($view->isViewDataTableId(HtmlTable::ID)) {
|
||||
$view->config->disable_row_evolution = false;
|
||||
}
|
||||
}
|
||||
|
||||
public function configureViewForGetPageUrlsOrTitlesFollowingSiteSearch(ViewDataTable $view, $isTitle)
|
||||
{
|
||||
$title = $isTitle ? Piwik::translate('Actions_WidgetPageTitlesFollowingSearch')
|
||||
: Piwik::translate('Actions_WidgetPageUrlsFollowingSearch');
|
||||
|
||||
$relatedReports = array(
|
||||
'Actions.getPageTitlesFollowingSiteSearch' => Piwik::translate('Actions_WidgetPageTitlesFollowingSearch'),
|
||||
'Actions.getPageUrlsFollowingSiteSearch' => Piwik::translate('Actions_WidgetPageUrlsFollowingSearch'),
|
||||
);
|
||||
|
||||
$view->config->addRelatedReports($relatedReports);
|
||||
$view->config->addTranslations(array(
|
||||
'label' => Piwik::translate('General_ColumnDestinationPage'),
|
||||
'nb_hits_following_search' => Piwik::translate('General_ColumnViewedAfterSearch'),
|
||||
'nb_hits' => Piwik::translate('General_ColumnTotalPageviews')
|
||||
));
|
||||
|
||||
$view->config->title = $title;
|
||||
$view->config->columns_to_display = array('label', 'nb_hits_following_search', 'nb_hits');
|
||||
$view->config->show_exclude_low_population = false;
|
||||
$view->requestConfig->filter_sort_column = 'nb_hits_following_search';
|
||||
$view->requestConfig->filter_sort_order = 'desc';
|
||||
|
||||
$this->addExcludeLowPopDisplayProperties($view);
|
||||
$this->addBaseDisplayProperties($view);
|
||||
}
|
||||
|
||||
public function configureViewForGetPageTitles(ViewDataTable $view)
|
||||
{
|
||||
// link to the page, not just the report, but only if not a widget
|
||||
$widget = Common::getRequestVar('widget', false);
|
||||
|
||||
$view->config->self_url = Request::getCurrentUrlWithoutGenericFilters(array(
|
||||
'module' => 'Actions',
|
||||
'action' => $widget === false ? 'indexPageTitles' : 'getPageTitles'
|
||||
));
|
||||
|
||||
$view->config->title = Piwik::translate('Actions_SubmenuPageTitles');
|
||||
$view->config->addRelatedReports(array(
|
||||
'Actions.getEntryPageTitles' => Piwik::translate('Actions_EntryPageTitles'),
|
||||
'Actions.getExitPageTitles' => Piwik::translate('Actions_ExitPageTitles'),
|
||||
));
|
||||
|
||||
$view->config->addTranslation('label', Piwik::translate('Actions_ColumnPageName'));
|
||||
$view->config->columns_to_display = array('label', 'nb_hits', 'nb_visits', 'bounce_rate',
|
||||
'avg_time_on_page', 'exit_rate', 'avg_time_generation');
|
||||
|
||||
$this->addPageDisplayProperties($view);
|
||||
$this->addBaseDisplayProperties($view);
|
||||
}
|
||||
|
||||
public function configureViewForGetEntryPageTitles(ViewDataTable $view)
|
||||
{
|
||||
$entryPageUrlAction =
|
||||
Common::getRequestVar('widget', false) === false ? 'indexEntryPageUrls' : 'getEntryPageUrls';
|
||||
|
||||
$view->config->addTranslations(array(
|
||||
'label' => Piwik::translate('Actions_ColumnEntryPageTitle'),
|
||||
'entry_bounce_count' => Piwik::translate('General_ColumnBounces'),
|
||||
'entry_nb_visits' => Piwik::translate('General_ColumnEntrances'),
|
||||
));
|
||||
$view->config->addRelatedReports(array(
|
||||
'Actions.getPageTitles' => Piwik::translate('Actions_SubmenuPageTitles'),
|
||||
"Actions.$entryPageUrlAction" => Piwik::translate('Actions_SubmenuPagesEntry')
|
||||
));
|
||||
|
||||
$view->config->columns_to_display = array('label', 'entry_nb_visits', 'entry_bounce_count', 'bounce_rate');
|
||||
$view->config->title = Piwik::translate('Actions_EntryPageTitles');
|
||||
|
||||
$view->requestConfig->filter_sort_column = 'entry_nb_visits';
|
||||
|
||||
$this->addPageDisplayProperties($view);
|
||||
$this->addBaseDisplayProperties($view);
|
||||
}
|
||||
|
||||
public function configureViewForGetExitPageTitles(ViewDataTable $view)
|
||||
{
|
||||
$exitPageUrlAction =
|
||||
Common::getRequestVar('widget', false) === false ? 'indexExitPageUrls' : 'getExitPageUrls';
|
||||
|
||||
$view->config->addTranslations(array(
|
||||
'label' => Piwik::translate('Actions_ColumnExitPageTitle'),
|
||||
'exit_nb_visits' => Piwik::translate('General_ColumnExits'),
|
||||
));
|
||||
$view->config->addRelatedReports(array(
|
||||
'Actions.getPageTitles' => Piwik::translate('Actions_SubmenuPageTitles'),
|
||||
"Actions.$exitPageUrlAction" => Piwik::translate('Actions_SubmenuPagesExit'),
|
||||
));
|
||||
|
||||
$view->config->title = Piwik::translate('Actions_ExitPageTitles');
|
||||
$view->config->columns_to_display = array('label', 'exit_nb_visits', 'nb_visits', 'exit_rate');
|
||||
|
||||
$this->addPageDisplayProperties($view);
|
||||
$this->addBaseDisplayProperties($view);
|
||||
}
|
||||
|
||||
public function configureViewForGetDownloads(ViewDataTable $view)
|
||||
{
|
||||
$view->config->addTranslations(array(
|
||||
'label' => Piwik::translate('Actions_ColumnDownloadURL'),
|
||||
'nb_visits' => Piwik::translate('Actions_ColumnUniqueDownloads'),
|
||||
'nb_hits' => Piwik::translate('General_Downloads'),
|
||||
));
|
||||
|
||||
$view->config->columns_to_display = array('label', 'nb_visits', 'nb_hits');
|
||||
$view->config->show_exclude_low_population = false;
|
||||
|
||||
$this->addBaseDisplayProperties($view);
|
||||
}
|
||||
|
||||
public function configureViewForGetOutlinks(ViewDataTable $view)
|
||||
{
|
||||
$view->config->addTranslations(array(
|
||||
'label' => Piwik::translate('Actions_ColumnClickedURL'),
|
||||
'nb_visits' => Piwik::translate('Actions_ColumnUniqueClicks'),
|
||||
'nb_hits' => Piwik::translate('Actions_ColumnClicks'),
|
||||
));
|
||||
|
||||
$view->config->columns_to_display = array('label', 'nb_visits', 'nb_hits');
|
||||
$view->config->show_exclude_low_population = false;
|
||||
|
||||
$this->addBaseDisplayProperties($view);
|
||||
}
|
||||
}
|
||||
|
||||
548
www/analytics/plugins/Actions/Archiver.php
Normal file
548
www/analytics/plugins/Actions/Archiver.php
Normal file
|
|
@ -0,0 +1,548 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\Actions;
|
||||
|
||||
use Piwik\DataTable;
|
||||
use Piwik\Metrics;
|
||||
use Piwik\RankingQuery;
|
||||
use Piwik\Tracker\Action;
|
||||
use Piwik\Tracker\ActionSiteSearch;
|
||||
|
||||
/**
|
||||
* Class encapsulating logic to process Day/Period Archiving for the Actions reports
|
||||
*
|
||||
*/
|
||||
class Archiver extends \Piwik\Plugin\Archiver
|
||||
{
|
||||
const DOWNLOADS_RECORD_NAME = 'Actions_downloads';
|
||||
const OUTLINKS_RECORD_NAME = 'Actions_outlink';
|
||||
const PAGE_TITLES_RECORD_NAME = 'Actions_actions';
|
||||
const SITE_SEARCH_RECORD_NAME = 'Actions_sitesearch';
|
||||
const PAGE_URLS_RECORD_NAME = 'Actions_actions_url';
|
||||
|
||||
const METRIC_PAGEVIEWS_RECORD_NAME = 'Actions_nb_pageviews';
|
||||
const METRIC_UNIQ_PAGEVIEWS_RECORD_NAME = 'Actions_nb_uniq_pageviews';
|
||||
const METRIC_SUM_TIME_RECORD_NAME = 'Actions_sum_time_generation';
|
||||
const METRIC_HITS_TIMED_RECORD_NAME = 'Actions_nb_hits_with_time_generation';
|
||||
const METRIC_DOWNLOADS_RECORD_NAME = 'Actions_nb_downloads';
|
||||
const METRIC_UNIQ_DOWNLOADS_RECORD_NAME = 'Actions_nb_uniq_downloads';
|
||||
const METRIC_OUTLINKS_RECORD_NAME = 'Actions_nb_outlinks';
|
||||
const METRIC_UNIQ_OUTLINKS_RECORD_NAME = 'Actions_nb_uniq_outlinks';
|
||||
const METRIC_SEARCHES_RECORD_NAME = 'Actions_nb_searches';
|
||||
const METRIC_KEYWORDS_RECORD_NAME = 'Actions_nb_keywords';
|
||||
|
||||
/* Metrics in use by the API Actions.get */
|
||||
public static $actionsAggregateMetrics = array(
|
||||
self::METRIC_PAGEVIEWS_RECORD_NAME => 'nb_pageviews',
|
||||
self::METRIC_UNIQ_PAGEVIEWS_RECORD_NAME => 'nb_uniq_pageviews',
|
||||
self::METRIC_DOWNLOADS_RECORD_NAME => 'nb_downloads',
|
||||
self::METRIC_UNIQ_DOWNLOADS_RECORD_NAME => 'nb_uniq_downloads',
|
||||
self::METRIC_OUTLINKS_RECORD_NAME => 'nb_outlinks',
|
||||
self::METRIC_UNIQ_OUTLINKS_RECORD_NAME => 'nb_uniq_outlinks',
|
||||
self::METRIC_SEARCHES_RECORD_NAME => 'nb_searches',
|
||||
self::METRIC_KEYWORDS_RECORD_NAME => 'nb_keywords',
|
||||
);
|
||||
|
||||
public static $actionTypes = array(
|
||||
Action::TYPE_PAGE_URL,
|
||||
Action::TYPE_OUTLINK,
|
||||
Action::TYPE_DOWNLOAD,
|
||||
Action::TYPE_PAGE_TITLE,
|
||||
Action::TYPE_SITE_SEARCH,
|
||||
);
|
||||
static protected $columnsToRenameAfterAggregation = array(
|
||||
Metrics::INDEX_NB_UNIQ_VISITORS => Metrics::INDEX_SUM_DAILY_NB_UNIQ_VISITORS,
|
||||
Metrics::INDEX_PAGE_ENTRY_NB_UNIQ_VISITORS => Metrics::INDEX_PAGE_ENTRY_SUM_DAILY_NB_UNIQ_VISITORS,
|
||||
Metrics::INDEX_PAGE_EXIT_NB_UNIQ_VISITORS => Metrics::INDEX_PAGE_EXIT_SUM_DAILY_NB_UNIQ_VISITORS,
|
||||
);
|
||||
static public $columnsToDeleteAfterAggregation = array(
|
||||
Metrics::INDEX_NB_UNIQ_VISITORS,
|
||||
Metrics::INDEX_PAGE_ENTRY_NB_UNIQ_VISITORS,
|
||||
Metrics::INDEX_PAGE_EXIT_NB_UNIQ_VISITORS,
|
||||
);
|
||||
private static $columnsAggregationOperation = array(
|
||||
Metrics::INDEX_PAGE_MAX_TIME_GENERATION => 'max',
|
||||
Metrics::INDEX_PAGE_MIN_TIME_GENERATION => 'min'
|
||||
);
|
||||
protected $actionsTablesByType = null;
|
||||
protected $isSiteSearchEnabled = false;
|
||||
|
||||
function __construct($processor)
|
||||
{
|
||||
parent::__construct($processor);
|
||||
$this->isSiteSearchEnabled = $processor->getParams()->getSite()->isSiteSearchEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Archives Actions reports for a Day
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function aggregateDayReport()
|
||||
{
|
||||
$rankingQueryLimit = ArchivingHelper::getRankingQueryLimit();
|
||||
ArchivingHelper::reloadConfig();
|
||||
|
||||
$this->initActionsTables();
|
||||
$this->archiveDayActions($rankingQueryLimit);
|
||||
$this->archiveDayEntryActions($rankingQueryLimit);
|
||||
$this->archiveDayExitActions($rankingQueryLimit);
|
||||
$this->archiveDayActionsTime($rankingQueryLimit);
|
||||
|
||||
$this->insertDayReports();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function getMetricNames()
|
||||
{
|
||||
return array(
|
||||
self::METRIC_PAGEVIEWS_RECORD_NAME,
|
||||
self::METRIC_UNIQ_PAGEVIEWS_RECORD_NAME,
|
||||
self::METRIC_DOWNLOADS_RECORD_NAME,
|
||||
self::METRIC_UNIQ_DOWNLOADS_RECORD_NAME,
|
||||
self::METRIC_OUTLINKS_RECORD_NAME,
|
||||
self::METRIC_UNIQ_OUTLINKS_RECORD_NAME,
|
||||
self::METRIC_SEARCHES_RECORD_NAME,
|
||||
self::METRIC_SUM_TIME_RECORD_NAME,
|
||||
self::METRIC_HITS_TIMED_RECORD_NAME,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
static public function getWhereClauseActionIsNotEvent()
|
||||
{
|
||||
return " AND log_link_visit_action.idaction_event_category IS NULL";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $select
|
||||
* @param $from
|
||||
*/
|
||||
protected function updateQuerySelectFromForSiteSearch(&$select, &$from)
|
||||
{
|
||||
$selectFlagNoResultKeywords = ",
|
||||
CASE WHEN (MAX(log_link_visit_action.custom_var_v" . ActionSiteSearch::CVAR_INDEX_SEARCH_COUNT . ") = 0
|
||||
AND log_link_visit_action.custom_var_k" . ActionSiteSearch::CVAR_INDEX_SEARCH_COUNT . " = '" . ActionSiteSearch::CVAR_KEY_SEARCH_COUNT . "')
|
||||
THEN 1 ELSE 0 END
|
||||
AS `" . Metrics::INDEX_SITE_SEARCH_HAS_NO_RESULT . "`";
|
||||
|
||||
//we need an extra JOIN to know whether the referrer "idaction_name_ref" was a Site Search request
|
||||
$from[] = array(
|
||||
"table" => "log_action",
|
||||
"tableAlias" => "log_action_name_ref",
|
||||
"joinOn" => "log_link_visit_action.idaction_name_ref = log_action_name_ref.idaction"
|
||||
);
|
||||
|
||||
$selectPageIsFollowingSiteSearch = ",
|
||||
SUM( CASE WHEN log_action_name_ref.type = " . Action::TYPE_SITE_SEARCH . "
|
||||
THEN 1 ELSE 0 END)
|
||||
AS `" . Metrics::INDEX_PAGE_IS_FOLLOWING_SITE_SEARCH_NB_HITS . "`";
|
||||
|
||||
$select .= $selectFlagNoResultKeywords
|
||||
. $selectPageIsFollowingSiteSearch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the DataTables created by the archiveDay function.
|
||||
*/
|
||||
private function initActionsTables()
|
||||
{
|
||||
$this->actionsTablesByType = array();
|
||||
foreach (self::$actionTypes as $type) {
|
||||
$dataTable = new DataTable();
|
||||
$dataTable->setMaximumAllowedRows(ArchivingHelper::$maximumRowsInDataTableLevelZero);
|
||||
|
||||
if ($type == Action::TYPE_PAGE_URL
|
||||
|| $type == Action::TYPE_PAGE_TITLE
|
||||
) {
|
||||
// for page urls and page titles, performance metrics exist and have to be aggregated correctly
|
||||
$dataTable->setMetadata(DataTable::COLUMN_AGGREGATION_OPS_METADATA_NAME, self::$columnsAggregationOperation);
|
||||
}
|
||||
|
||||
$this->actionsTablesByType[$type] = $dataTable;
|
||||
}
|
||||
}
|
||||
|
||||
protected function archiveDayActions($rankingQueryLimit)
|
||||
{
|
||||
$select = "log_action.name,
|
||||
log_action.type,
|
||||
log_action.idaction,
|
||||
log_action.url_prefix,
|
||||
count(distinct log_link_visit_action.idvisit) as `" . Metrics::INDEX_NB_VISITS . "`,
|
||||
count(distinct log_link_visit_action.idvisitor) as `" . Metrics::INDEX_NB_UNIQ_VISITORS . "`,
|
||||
count(*) as `" . Metrics::INDEX_PAGE_NB_HITS . "`,
|
||||
sum(
|
||||
case when " . Action::DB_COLUMN_CUSTOM_FLOAT . " is null
|
||||
then 0
|
||||
else " . Action::DB_COLUMN_CUSTOM_FLOAT . "
|
||||
end
|
||||
) / 1000 as `" . Metrics::INDEX_PAGE_SUM_TIME_GENERATION . "`,
|
||||
sum(
|
||||
case when " . Action::DB_COLUMN_CUSTOM_FLOAT . " is null
|
||||
then 0
|
||||
else 1
|
||||
end
|
||||
) as `" . Metrics::INDEX_PAGE_NB_HITS_WITH_TIME_GENERATION . "`,
|
||||
min(" . Action::DB_COLUMN_CUSTOM_FLOAT . ") / 1000
|
||||
as `" . Metrics::INDEX_PAGE_MIN_TIME_GENERATION . "`,
|
||||
max(" . Action::DB_COLUMN_CUSTOM_FLOAT . ") / 1000
|
||||
as `" . Metrics::INDEX_PAGE_MAX_TIME_GENERATION . "`
|
||||
";
|
||||
|
||||
$from = array(
|
||||
"log_link_visit_action",
|
||||
array(
|
||||
"table" => "log_action",
|
||||
"joinOn" => "log_link_visit_action.%s = log_action.idaction"
|
||||
)
|
||||
);
|
||||
|
||||
$where = "log_link_visit_action.server_time >= ?
|
||||
AND log_link_visit_action.server_time <= ?
|
||||
AND log_link_visit_action.idsite = ?
|
||||
AND log_link_visit_action.%s IS NOT NULL"
|
||||
. $this->getWhereClauseActionIsNotEvent();
|
||||
|
||||
$groupBy = "log_action.idaction";
|
||||
$orderBy = "`" . Metrics::INDEX_PAGE_NB_HITS . "` DESC, name ASC";
|
||||
|
||||
$rankingQuery = false;
|
||||
if ($rankingQueryLimit > 0) {
|
||||
$rankingQuery = new RankingQuery($rankingQueryLimit);
|
||||
$rankingQuery->setOthersLabel(DataTable::LABEL_SUMMARY_ROW);
|
||||
$rankingQuery->addLabelColumn(array('idaction', 'name'));
|
||||
$rankingQuery->addColumn(array('url_prefix', Metrics::INDEX_NB_UNIQ_VISITORS));
|
||||
$rankingQuery->addColumn(array(Metrics::INDEX_PAGE_NB_HITS, Metrics::INDEX_NB_VISITS), 'sum');
|
||||
if ($this->isSiteSearchEnabled()) {
|
||||
$rankingQuery->addColumn(Metrics::INDEX_SITE_SEARCH_HAS_NO_RESULT, 'min');
|
||||
$rankingQuery->addColumn(Metrics::INDEX_PAGE_IS_FOLLOWING_SITE_SEARCH_NB_HITS, 'sum');
|
||||
}
|
||||
$rankingQuery->addColumn(Metrics::INDEX_PAGE_SUM_TIME_GENERATION, 'sum');
|
||||
$rankingQuery->addColumn(Metrics::INDEX_PAGE_NB_HITS_WITH_TIME_GENERATION, 'sum');
|
||||
$rankingQuery->addColumn(Metrics::INDEX_PAGE_MIN_TIME_GENERATION, 'min');
|
||||
$rankingQuery->addColumn(Metrics::INDEX_PAGE_MAX_TIME_GENERATION, 'max');
|
||||
$rankingQuery->partitionResultIntoMultipleGroups('type', array_keys($this->actionsTablesByType));
|
||||
}
|
||||
|
||||
// Special Magic to get
|
||||
// 1) No result Keywords
|
||||
// 2) For each page view, count number of times the referrer page was a Site Search
|
||||
if ($this->isSiteSearchEnabled()) {
|
||||
$this->updateQuerySelectFromForSiteSearch($select, $from);
|
||||
}
|
||||
|
||||
$this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, "idaction_name", $rankingQuery);
|
||||
|
||||
$this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, "idaction_url", $rankingQuery);
|
||||
}
|
||||
|
||||
protected function isSiteSearchEnabled()
|
||||
{
|
||||
return $this->isSiteSearchEnabled;
|
||||
}
|
||||
|
||||
protected function archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, $sprintfField, $rankingQuery = false)
|
||||
{
|
||||
$select = sprintf($select, $sprintfField);
|
||||
|
||||
// get query with segmentation
|
||||
$query = $this->getLogAggregator()->generateQuery($select, $from, $where, $groupBy, $orderBy);
|
||||
|
||||
// replace the rest of the %s
|
||||
$querySql = str_replace("%s", $sprintfField, $query['sql']);
|
||||
|
||||
// apply ranking query
|
||||
if ($rankingQuery) {
|
||||
$querySql = $rankingQuery->generateQuery($querySql);
|
||||
}
|
||||
|
||||
// get result
|
||||
$resultSet = $this->getLogAggregator()->getDb()->query($querySql, $query['bind']);
|
||||
$modified = ArchivingHelper::updateActionsTableWithRowQuery($resultSet, $sprintfField, $this->actionsTablesByType);
|
||||
return $modified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry actions for Page URLs and Page names
|
||||
*/
|
||||
protected function archiveDayEntryActions($rankingQueryLimit)
|
||||
{
|
||||
$rankingQuery = false;
|
||||
if ($rankingQueryLimit > 0) {
|
||||
$rankingQuery = new RankingQuery($rankingQueryLimit);
|
||||
$rankingQuery->setOthersLabel(DataTable::LABEL_SUMMARY_ROW);
|
||||
$rankingQuery->addLabelColumn('idaction');
|
||||
$rankingQuery->addColumn(Metrics::INDEX_PAGE_ENTRY_NB_UNIQ_VISITORS);
|
||||
$rankingQuery->addColumn(array(Metrics::INDEX_PAGE_ENTRY_NB_VISITS,
|
||||
Metrics::INDEX_PAGE_ENTRY_NB_ACTIONS,
|
||||
Metrics::INDEX_PAGE_ENTRY_SUM_VISIT_LENGTH,
|
||||
Metrics::INDEX_PAGE_ENTRY_BOUNCE_COUNT), 'sum');
|
||||
$rankingQuery->partitionResultIntoMultipleGroups('type', array_keys($this->actionsTablesByType));
|
||||
|
||||
$extraSelects = 'log_action.type, log_action.name,';
|
||||
$from = array(
|
||||
"log_visit",
|
||||
array(
|
||||
"table" => "log_action",
|
||||
"joinOn" => "log_visit.%s = log_action.idaction"
|
||||
)
|
||||
);
|
||||
$orderBy = "`" . Metrics::INDEX_PAGE_ENTRY_NB_ACTIONS . "` DESC, log_action.name ASC";
|
||||
} else {
|
||||
$extraSelects = false;
|
||||
$from = "log_visit";
|
||||
$orderBy = false;
|
||||
}
|
||||
|
||||
$select = "log_visit.%s as idaction, $extraSelects
|
||||
count(distinct log_visit.idvisitor) as `" . Metrics::INDEX_PAGE_ENTRY_NB_UNIQ_VISITORS . "`,
|
||||
count(*) as `" . Metrics::INDEX_PAGE_ENTRY_NB_VISITS . "`,
|
||||
sum(log_visit.visit_total_actions) as `" . Metrics::INDEX_PAGE_ENTRY_NB_ACTIONS . "`,
|
||||
sum(log_visit.visit_total_time) as `" . Metrics::INDEX_PAGE_ENTRY_SUM_VISIT_LENGTH . "`,
|
||||
sum(case log_visit.visit_total_actions when 1 then 1 when 0 then 1 else 0 end) as `" . Metrics::INDEX_PAGE_ENTRY_BOUNCE_COUNT . "`";
|
||||
|
||||
$where = "log_visit.visit_last_action_time >= ?
|
||||
AND log_visit.visit_last_action_time <= ?
|
||||
AND log_visit.idsite = ?
|
||||
AND log_visit.%s > 0";
|
||||
|
||||
$groupBy = "log_visit.%s, idaction";
|
||||
|
||||
$this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, "visit_entry_idaction_url", $rankingQuery);
|
||||
|
||||
$this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, "visit_entry_idaction_name", $rankingQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exit actions
|
||||
*/
|
||||
protected function archiveDayExitActions($rankingQueryLimit)
|
||||
{
|
||||
$rankingQuery = false;
|
||||
if ($rankingQueryLimit > 0) {
|
||||
$rankingQuery = new RankingQuery($rankingQueryLimit);
|
||||
$rankingQuery->setOthersLabel(DataTable::LABEL_SUMMARY_ROW);
|
||||
$rankingQuery->addLabelColumn('idaction');
|
||||
$rankingQuery->addColumn(Metrics::INDEX_PAGE_EXIT_NB_UNIQ_VISITORS);
|
||||
$rankingQuery->addColumn(Metrics::INDEX_PAGE_EXIT_NB_VISITS, 'sum');
|
||||
$rankingQuery->partitionResultIntoMultipleGroups('type', array_keys($this->actionsTablesByType));
|
||||
|
||||
$extraSelects = 'log_action.type, log_action.name,';
|
||||
$from = array(
|
||||
"log_visit",
|
||||
array(
|
||||
"table" => "log_action",
|
||||
"joinOn" => "log_visit.%s = log_action.idaction"
|
||||
)
|
||||
);
|
||||
$orderBy = "`" . Metrics::INDEX_PAGE_EXIT_NB_VISITS . "` DESC, log_action.name ASC";
|
||||
} else {
|
||||
$extraSelects = false;
|
||||
$from = "log_visit";
|
||||
$orderBy = false;
|
||||
}
|
||||
|
||||
$select = "log_visit.%s as idaction, $extraSelects
|
||||
count(distinct log_visit.idvisitor) as `" . Metrics::INDEX_PAGE_EXIT_NB_UNIQ_VISITORS . "`,
|
||||
count(*) as `" . Metrics::INDEX_PAGE_EXIT_NB_VISITS . "`";
|
||||
|
||||
$where = "log_visit.visit_last_action_time >= ?
|
||||
AND log_visit.visit_last_action_time <= ?
|
||||
AND log_visit.idsite = ?
|
||||
AND log_visit.%s > 0";
|
||||
|
||||
$groupBy = "log_visit.%s, idaction";
|
||||
|
||||
$this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, "visit_exit_idaction_url", $rankingQuery);
|
||||
|
||||
$this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, "visit_exit_idaction_name", $rankingQuery);
|
||||
return array($rankingQuery, $extraSelects, $from, $orderBy, $select, $where, $groupBy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Time per action
|
||||
*/
|
||||
protected function archiveDayActionsTime($rankingQueryLimit)
|
||||
{
|
||||
$rankingQuery = false;
|
||||
if ($rankingQueryLimit > 0) {
|
||||
$rankingQuery = new RankingQuery($rankingQueryLimit);
|
||||
$rankingQuery->setOthersLabel(DataTable::LABEL_SUMMARY_ROW);
|
||||
$rankingQuery->addLabelColumn('idaction');
|
||||
$rankingQuery->addColumn(Metrics::INDEX_PAGE_SUM_TIME_SPENT, 'sum');
|
||||
$rankingQuery->partitionResultIntoMultipleGroups('type', array_keys($this->actionsTablesByType));
|
||||
|
||||
$extraSelects = "log_action.type, log_action.name, count(*) as `" . Metrics::INDEX_PAGE_NB_HITS . "`,";
|
||||
$from = array(
|
||||
"log_link_visit_action",
|
||||
array(
|
||||
"table" => "log_action",
|
||||
"joinOn" => "log_link_visit_action.%s = log_action.idaction"
|
||||
)
|
||||
);
|
||||
$orderBy = "`" . Metrics::INDEX_PAGE_NB_HITS . "` DESC, log_action.name ASC";
|
||||
} else {
|
||||
$extraSelects = false;
|
||||
$from = "log_link_visit_action";
|
||||
$orderBy = false;
|
||||
}
|
||||
|
||||
$select = "log_link_visit_action.%s as idaction, $extraSelects
|
||||
sum(log_link_visit_action.time_spent_ref_action) as `" . Metrics::INDEX_PAGE_SUM_TIME_SPENT . "`";
|
||||
|
||||
$where = "log_link_visit_action.server_time >= ?
|
||||
AND log_link_visit_action.server_time <= ?
|
||||
AND log_link_visit_action.idsite = ?
|
||||
AND log_link_visit_action.time_spent_ref_action > 0
|
||||
AND log_link_visit_action.%s > 0"
|
||||
. $this->getWhereClauseActionIsNotEvent();
|
||||
|
||||
$groupBy = "log_link_visit_action.%s, idaction";
|
||||
|
||||
$this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, "idaction_url_ref", $rankingQuery);
|
||||
|
||||
$this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, "idaction_name_ref", $rankingQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* Records in the DB the archived reports for Page views, Downloads, Outlinks, and Page titles
|
||||
*/
|
||||
protected function insertDayReports()
|
||||
{
|
||||
ArchivingHelper::clearActionsCache();
|
||||
|
||||
$this->insertPageUrlsReports();
|
||||
$this->insertDownloadsReports();
|
||||
$this->insertOutlinksReports();
|
||||
$this->insertPageTitlesReports();
|
||||
$this->insertSiteSearchReports();
|
||||
}
|
||||
|
||||
protected function insertPageUrlsReports()
|
||||
{
|
||||
$dataTable = $this->getDataTable(Action::TYPE_PAGE_URL);
|
||||
$this->insertTable($dataTable, self::PAGE_URLS_RECORD_NAME);
|
||||
|
||||
$records = array(
|
||||
self::METRIC_PAGEVIEWS_RECORD_NAME => array_sum($dataTable->getColumn(Metrics::INDEX_PAGE_NB_HITS)),
|
||||
self::METRIC_UNIQ_PAGEVIEWS_RECORD_NAME => array_sum($dataTable->getColumn(Metrics::INDEX_NB_VISITS)),
|
||||
self::METRIC_SUM_TIME_RECORD_NAME => array_sum($dataTable->getColumn(Metrics::INDEX_PAGE_SUM_TIME_GENERATION)),
|
||||
self::METRIC_HITS_TIMED_RECORD_NAME => array_sum($dataTable->getColumn(Metrics::INDEX_PAGE_NB_HITS_WITH_TIME_GENERATION))
|
||||
);
|
||||
$this->getProcessor()->insertNumericRecords($records);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $typeId
|
||||
* @return DataTable
|
||||
*/
|
||||
protected function getDataTable($typeId)
|
||||
{
|
||||
return $this->actionsTablesByType[$typeId];
|
||||
}
|
||||
|
||||
protected function insertTable(DataTable $dataTable, $recordName)
|
||||
{
|
||||
ArchivingHelper::deleteInvalidSummedColumnsFromDataTable($dataTable);
|
||||
$report = $dataTable->getSerialized(ArchivingHelper::$maximumRowsInDataTableLevelZero, ArchivingHelper::$maximumRowsInSubDataTable, ArchivingHelper::$columnToSortByBeforeTruncation);
|
||||
$this->getProcessor()->insertBlobRecord($recordName, $report);
|
||||
}
|
||||
|
||||
|
||||
protected function insertDownloadsReports()
|
||||
{
|
||||
$dataTable = $this->getDataTable(Action::TYPE_DOWNLOAD);
|
||||
$this->insertTable($dataTable, self::DOWNLOADS_RECORD_NAME);
|
||||
|
||||
$this->getProcessor()->insertNumericRecord(self::METRIC_DOWNLOADS_RECORD_NAME, array_sum($dataTable->getColumn(Metrics::INDEX_PAGE_NB_HITS)));
|
||||
$this->getProcessor()->insertNumericRecord(self::METRIC_UNIQ_DOWNLOADS_RECORD_NAME, array_sum($dataTable->getColumn(Metrics::INDEX_NB_VISITS)));
|
||||
}
|
||||
|
||||
protected function insertOutlinksReports()
|
||||
{
|
||||
$dataTable = $this->getDataTable(Action::TYPE_OUTLINK);
|
||||
$this->insertTable($dataTable, self::OUTLINKS_RECORD_NAME);
|
||||
|
||||
$this->getProcessor()->insertNumericRecord(self::METRIC_OUTLINKS_RECORD_NAME, array_sum($dataTable->getColumn(Metrics::INDEX_PAGE_NB_HITS)));
|
||||
$this->getProcessor()->insertNumericRecord(self::METRIC_UNIQ_OUTLINKS_RECORD_NAME, array_sum($dataTable->getColumn(Metrics::INDEX_NB_VISITS)));
|
||||
}
|
||||
|
||||
protected function insertPageTitlesReports()
|
||||
{
|
||||
$dataTable = $this->getDataTable(Action::TYPE_PAGE_TITLE);
|
||||
$this->insertTable($dataTable, self::PAGE_TITLES_RECORD_NAME);
|
||||
}
|
||||
|
||||
protected function insertSiteSearchReports()
|
||||
{
|
||||
$dataTable = $this->getDataTable(Action::TYPE_SITE_SEARCH);
|
||||
$this->deleteUnusedColumnsFromKeywordsDataTable($dataTable);
|
||||
$this->insertTable($dataTable, self::SITE_SEARCH_RECORD_NAME);
|
||||
|
||||
$this->getProcessor()->insertNumericRecord(self::METRIC_SEARCHES_RECORD_NAME, array_sum($dataTable->getColumn(Metrics::INDEX_NB_VISITS)));
|
||||
$this->getProcessor()->insertNumericRecord(self::METRIC_KEYWORDS_RECORD_NAME, $dataTable->getRowsCount());
|
||||
}
|
||||
|
||||
protected function deleteUnusedColumnsFromKeywordsDataTable(DataTable $dataTable)
|
||||
{
|
||||
$columnsToDelete = array(
|
||||
Metrics::INDEX_NB_UNIQ_VISITORS,
|
||||
Metrics::INDEX_PAGE_IS_FOLLOWING_SITE_SEARCH_NB_HITS,
|
||||
Metrics::INDEX_PAGE_ENTRY_NB_UNIQ_VISITORS,
|
||||
Metrics::INDEX_PAGE_ENTRY_NB_ACTIONS,
|
||||
Metrics::INDEX_PAGE_ENTRY_SUM_VISIT_LENGTH,
|
||||
Metrics::INDEX_PAGE_ENTRY_NB_VISITS,
|
||||
Metrics::INDEX_PAGE_ENTRY_BOUNCE_COUNT,
|
||||
Metrics::INDEX_PAGE_EXIT_NB_UNIQ_VISITORS,
|
||||
);
|
||||
$dataTable->deleteColumns($columnsToDelete);
|
||||
}
|
||||
|
||||
public function aggregateMultipleReports()
|
||||
{
|
||||
ArchivingHelper::reloadConfig();
|
||||
$dataTableToSum = array(
|
||||
self::PAGE_TITLES_RECORD_NAME,
|
||||
self::PAGE_URLS_RECORD_NAME,
|
||||
);
|
||||
$this->getProcessor()->aggregateDataTableRecords($dataTableToSum,
|
||||
ArchivingHelper::$maximumRowsInDataTableLevelZero,
|
||||
ArchivingHelper::$maximumRowsInSubDataTable,
|
||||
ArchivingHelper::$columnToSortByBeforeTruncation,
|
||||
self::$columnsAggregationOperation,
|
||||
self::$columnsToRenameAfterAggregation
|
||||
);
|
||||
|
||||
$dataTableToSum = array(
|
||||
self::DOWNLOADS_RECORD_NAME,
|
||||
self::OUTLINKS_RECORD_NAME,
|
||||
self::SITE_SEARCH_RECORD_NAME,
|
||||
);
|
||||
$aggregation = null;
|
||||
$nameToCount = $this->getProcessor()->aggregateDataTableRecords($dataTableToSum,
|
||||
ArchivingHelper::$maximumRowsInDataTableLevelZero,
|
||||
ArchivingHelper::$maximumRowsInSubDataTable,
|
||||
ArchivingHelper::$columnToSortByBeforeTruncation,
|
||||
$aggregation,
|
||||
self::$columnsToRenameAfterAggregation
|
||||
);
|
||||
|
||||
$this->getProcessor()->aggregateNumericMetrics($this->getMetricNames());
|
||||
|
||||
// Unique Keywords can't be summed, instead we take the RowsCount() of the keyword table
|
||||
$this->getProcessor()->insertNumericRecord(self::METRIC_KEYWORDS_RECORD_NAME, $nameToCount[self::SITE_SEARCH_RECORD_NAME]['level0']);
|
||||
}
|
||||
}
|
||||
612
www/analytics/plugins/Actions/ArchivingHelper.php
Normal file
612
www/analytics/plugins/Actions/ArchivingHelper.php
Normal file
|
|
@ -0,0 +1,612 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\Actions;
|
||||
|
||||
use PDOStatement;
|
||||
use Piwik\Config;
|
||||
use Piwik\DataTable\Manager;
|
||||
use Piwik\DataTable\Row;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\DataTable\Row\DataTableSummaryRow;
|
||||
use Piwik\Metrics;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Tracker\Action;
|
||||
use Piwik\Tracker\PageUrl;
|
||||
use Zend_Db_Statement;
|
||||
|
||||
/**
|
||||
* This static class provides:
|
||||
* - logic to parse/cleanup Action names,
|
||||
* - logic to efficiently process aggregate the array data during Archiving
|
||||
*
|
||||
*/
|
||||
class ArchivingHelper
|
||||
{
|
||||
const OTHERS_ROW_KEY = '';
|
||||
|
||||
/**
|
||||
* Ideally this should use the DataArray object instead of custom data structure
|
||||
*
|
||||
* @param Zend_Db_Statement|PDOStatement $query
|
||||
* @param string|bool $fieldQueried
|
||||
* @param array $actionsTablesByType
|
||||
* @return int
|
||||
*/
|
||||
static public function updateActionsTableWithRowQuery($query, $fieldQueried, & $actionsTablesByType)
|
||||
{
|
||||
$rowsProcessed = 0;
|
||||
while ($row = $query->fetch()) {
|
||||
if (empty($row['idaction'])) {
|
||||
$row['type'] = ($fieldQueried == 'idaction_url' ? Action::TYPE_PAGE_URL : Action::TYPE_PAGE_TITLE);
|
||||
// This will be replaced with 'X not defined' later
|
||||
$row['name'] = '';
|
||||
// Yes, this is kind of a hack, so we don't mix 'page url not defined' with 'page title not defined' etc.
|
||||
$row['idaction'] = -$row['type'];
|
||||
}
|
||||
|
||||
if ($row['type'] != Action::TYPE_SITE_SEARCH) {
|
||||
unset($row[Metrics::INDEX_SITE_SEARCH_HAS_NO_RESULT]);
|
||||
}
|
||||
|
||||
// This will appear as <url /> in the API, which is actually very important to keep
|
||||
// eg. When there's at least one row in a report that does not have a URL, not having this <url/> would break HTML/PDF reports.
|
||||
$url = '';
|
||||
if ($row['type'] == Action::TYPE_SITE_SEARCH
|
||||
|| $row['type'] == Action::TYPE_PAGE_TITLE
|
||||
) {
|
||||
$url = null;
|
||||
} elseif (!empty($row['name'])
|
||||
&& $row['name'] != DataTable::LABEL_SUMMARY_ROW) {
|
||||
$url = PageUrl::reconstructNormalizedUrl((string)$row['name'], $row['url_prefix']);
|
||||
}
|
||||
|
||||
if (isset($row['name'])
|
||||
&& isset($row['type'])
|
||||
) {
|
||||
$actionName = $row['name'];
|
||||
$actionType = $row['type'];
|
||||
$urlPrefix = $row['url_prefix'];
|
||||
$idaction = $row['idaction'];
|
||||
|
||||
// in some unknown case, the type field is NULL, as reported in #1082 - we ignore this page view
|
||||
if (empty($actionType)) {
|
||||
if ($idaction != DataTable::LABEL_SUMMARY_ROW) {
|
||||
self::setCachedActionRow($idaction, $actionType, false);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$actionRow = self::getActionRow($actionName, $actionType, $urlPrefix, $actionsTablesByType);
|
||||
|
||||
self::setCachedActionRow($idaction, $actionType, $actionRow);
|
||||
} else {
|
||||
$actionRow = self::getCachedActionRow($row['idaction'], $row['type']);
|
||||
|
||||
// Action processed as "to skip" for some reasons
|
||||
if ($actionRow === false) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_null($actionRow)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Here we do ensure that, the Metadata URL set for a given row, is the one from the Pageview with the most hits.
|
||||
// This is to ensure that when, different URLs are loaded with the same page name.
|
||||
// For example http://piwik.org and http://id.piwik.org are reported in Piwik > Actions > Pages with /index
|
||||
// But, we must make sure http://piwik.org is used to link & for transitions
|
||||
// Note: this code is partly duplicated from Row->sumRowMetadata()
|
||||
if (!is_null($url)
|
||||
&& !$actionRow->isSummaryRow()
|
||||
) {
|
||||
if (($existingUrl = $actionRow->getMetadata('url')) !== false) {
|
||||
if (!empty($row[Metrics::INDEX_PAGE_NB_HITS])
|
||||
&& $row[Metrics::INDEX_PAGE_NB_HITS] > $actionRow->maxVisitsSummed
|
||||
) {
|
||||
$actionRow->setMetadata('url', $url);
|
||||
$actionRow->maxVisitsSummed = $row[Metrics::INDEX_PAGE_NB_HITS];
|
||||
}
|
||||
} else {
|
||||
$actionRow->setMetadata('url', $url);
|
||||
$actionRow->maxVisitsSummed = !empty($row[Metrics::INDEX_PAGE_NB_HITS]) ? $row[Metrics::INDEX_PAGE_NB_HITS] : 0;
|
||||
}
|
||||
}
|
||||
|
||||
if ($row['type'] != Action::TYPE_PAGE_URL
|
||||
&& $row['type'] != Action::TYPE_PAGE_TITLE
|
||||
) {
|
||||
// only keep performance metrics when they're used (i.e. for URLs and page titles)
|
||||
if (array_key_exists(Metrics::INDEX_PAGE_SUM_TIME_GENERATION, $row)) {
|
||||
unset($row[Metrics::INDEX_PAGE_SUM_TIME_GENERATION]);
|
||||
}
|
||||
if (array_key_exists(Metrics::INDEX_PAGE_NB_HITS_WITH_TIME_GENERATION, $row)) {
|
||||
unset($row[Metrics::INDEX_PAGE_NB_HITS_WITH_TIME_GENERATION]);
|
||||
}
|
||||
if (array_key_exists(Metrics::INDEX_PAGE_MIN_TIME_GENERATION, $row)) {
|
||||
unset($row[Metrics::INDEX_PAGE_MIN_TIME_GENERATION]);
|
||||
}
|
||||
if (array_key_exists(Metrics::INDEX_PAGE_MAX_TIME_GENERATION, $row)) {
|
||||
unset($row[Metrics::INDEX_PAGE_MAX_TIME_GENERATION]);
|
||||
}
|
||||
}
|
||||
|
||||
unset($row['name']);
|
||||
unset($row['type']);
|
||||
unset($row['idaction']);
|
||||
unset($row['url_prefix']);
|
||||
|
||||
foreach ($row as $name => $value) {
|
||||
// in some edge cases, we have twice the same action name with 2 different idaction
|
||||
// - this happens when 2 visitors visit the same new page at the same time, and 2 actions get recorded for the same name
|
||||
// - this could also happen when 2 URLs end up having the same label (eg. 2 subdomains get aggregated to the "/index" page name)
|
||||
if (($alreadyValue = $actionRow->getColumn($name)) !== false) {
|
||||
$newValue = self::getColumnValuesMerged($name, $alreadyValue, $value);
|
||||
$actionRow->setColumn($name, $newValue);
|
||||
} else {
|
||||
$actionRow->addColumn($name, $value);
|
||||
}
|
||||
}
|
||||
|
||||
// if the exit_action was not recorded properly in the log_link_visit_action
|
||||
// there would be an error message when getting the nb_hits column
|
||||
// we must fake the record and add the columns
|
||||
if ($actionRow->getColumn(Metrics::INDEX_PAGE_NB_HITS) === false) {
|
||||
// to test this code: delete the entries in log_link_action_visit for
|
||||
// a given exit_idaction_url
|
||||
foreach (self::getDefaultRow()->getColumns() as $name => $value) {
|
||||
$actionRow->addColumn($name, $value);
|
||||
}
|
||||
}
|
||||
$rowsProcessed++;
|
||||
}
|
||||
|
||||
// just to make sure php copies the last $actionRow in the $parentTable array
|
||||
$actionRow =& $actionsTablesByType;
|
||||
return $rowsProcessed;
|
||||
}
|
||||
|
||||
public static function removeEmptyColumns($dataTable)
|
||||
{
|
||||
// Delete all columns that have a value of zero
|
||||
$dataTable->filter('ColumnDelete', array(
|
||||
$columnsToRemove = array(Metrics::INDEX_PAGE_IS_FOLLOWING_SITE_SEARCH_NB_HITS),
|
||||
$columnsToKeep = array(),
|
||||
$deleteIfZeroOnly = true
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* For rows which have subtables (eg. directories with sub pages),
|
||||
* deletes columns which don't make sense when all values of sub pages are summed.
|
||||
*
|
||||
* @param $dataTable DataTable
|
||||
*/
|
||||
public static function deleteInvalidSummedColumnsFromDataTable($dataTable)
|
||||
{
|
||||
foreach ($dataTable->getRows() as $id => $row) {
|
||||
if (($idSubtable = $row->getIdSubDataTable()) !== null
|
||||
|| $id === DataTable::ID_SUMMARY_ROW
|
||||
) {
|
||||
if ($idSubtable !== null) {
|
||||
$subtable = Manager::getInstance()->getTable($idSubtable);
|
||||
self::deleteInvalidSummedColumnsFromDataTable($subtable);
|
||||
}
|
||||
|
||||
if ($row instanceof DataTableSummaryRow) {
|
||||
$row->recalculate();
|
||||
}
|
||||
|
||||
foreach (Archiver::$columnsToDeleteAfterAggregation as $name) {
|
||||
$row->deleteColumn($name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// And this as well
|
||||
ArchivingHelper::removeEmptyColumns($dataTable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the limit to use with RankingQuery for this plugin.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function getRankingQueryLimit()
|
||||
{
|
||||
$configGeneral = Config::getInstance()->General;
|
||||
$configLimit = $configGeneral['archiving_ranking_query_row_limit'];
|
||||
$limit = $configLimit == 0 ? 0 : max(
|
||||
$configLimit,
|
||||
$configGeneral['datatable_archiving_maximum_rows_actions'],
|
||||
$configGeneral['datatable_archiving_maximum_rows_subtable_actions']
|
||||
);
|
||||
|
||||
// FIXME: This is a quick fix for #3482. The actual cause of the bug is that
|
||||
// the site search & performance metrics additions to
|
||||
// ArchivingHelper::updateActionsTableWithRowQuery expect every
|
||||
// row to have 'type' data, but not all of the SQL queries that are run w/o
|
||||
// ranking query join on the log_action table and thus do not select the
|
||||
// log_action.type column.
|
||||
//
|
||||
// NOTES: Archiving logic can be generalized as follows:
|
||||
// 0) Do SQL query over log_link_visit_action & join on log_action to select
|
||||
// some metrics (like visits, hits, etc.)
|
||||
// 1) For each row, cache the action row & metrics. (This is done by
|
||||
// updateActionsTableWithRowQuery for result set rows that have
|
||||
// name & type columns.)
|
||||
// 2) Do other SQL queries for metrics we can't put in the first query (like
|
||||
// entry visits, exit vists, etc.) w/o joining log_action.
|
||||
// 3) For each row, find the cached row by idaction & add the new metrics to
|
||||
// it. (This is done by updateActionsTableWithRowQuery for result set rows
|
||||
// that DO NOT have name & type columns.)
|
||||
//
|
||||
// The site search & performance metrics additions expect a 'type' all the time
|
||||
// which breaks the original pre-rankingquery logic. Ranking query requires a
|
||||
// join, so the bug is only seen when ranking query is disabled.
|
||||
if ($limit === 0) {
|
||||
$limit = 100000;
|
||||
}
|
||||
return $limit;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $columnName
|
||||
* @param $alreadyValue
|
||||
* @param $value
|
||||
* @return mixed
|
||||
*/
|
||||
private static function getColumnValuesMerged($columnName, $alreadyValue, $value)
|
||||
{
|
||||
if ($columnName == Metrics::INDEX_PAGE_MIN_TIME_GENERATION) {
|
||||
if (empty($alreadyValue)) {
|
||||
$newValue = $value;
|
||||
} else if (empty($value)) {
|
||||
$newValue = $alreadyValue;
|
||||
} else {
|
||||
$newValue = min($alreadyValue, $value);
|
||||
}
|
||||
return $newValue;
|
||||
}
|
||||
if ($columnName == Metrics::INDEX_PAGE_MAX_TIME_GENERATION) {
|
||||
$newValue = max($alreadyValue, $value);
|
||||
return $newValue;
|
||||
}
|
||||
|
||||
$newValue = $alreadyValue + $value;
|
||||
return $newValue;
|
||||
}
|
||||
|
||||
static public $maximumRowsInDataTableLevelZero;
|
||||
static public $maximumRowsInSubDataTable;
|
||||
static public $columnToSortByBeforeTruncation;
|
||||
|
||||
static protected $actionUrlCategoryDelimiter = null;
|
||||
static protected $actionTitleCategoryDelimiter = null;
|
||||
static protected $defaultActionName = null;
|
||||
static protected $defaultActionNameWhenNotDefined = null;
|
||||
static protected $defaultActionUrlWhenNotDefined = null;
|
||||
|
||||
static public function reloadConfig()
|
||||
{
|
||||
// for BC, we read the old style delimiter first (see #1067)Row
|
||||
$actionDelimiter = @Config::getInstance()->General['action_category_delimiter'];
|
||||
if (empty($actionDelimiter)) {
|
||||
self::$actionUrlCategoryDelimiter = Config::getInstance()->General['action_url_category_delimiter'];
|
||||
self::$actionTitleCategoryDelimiter = Config::getInstance()->General['action_title_category_delimiter'];
|
||||
} else {
|
||||
self::$actionUrlCategoryDelimiter = self::$actionTitleCategoryDelimiter = $actionDelimiter;
|
||||
}
|
||||
|
||||
self::$defaultActionName = Config::getInstance()->General['action_default_name'];
|
||||
self::$columnToSortByBeforeTruncation = Metrics::INDEX_NB_VISITS;
|
||||
self::$maximumRowsInDataTableLevelZero = Config::getInstance()->General['datatable_archiving_maximum_rows_actions'];
|
||||
self::$maximumRowsInSubDataTable = Config::getInstance()->General['datatable_archiving_maximum_rows_subtable_actions'];
|
||||
|
||||
DataTable::setMaximumDepthLevelAllowedAtLeast(self::getSubCategoryLevelLimit() + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* The default row is used when archiving, if data is inconsistent in the DB,
|
||||
* there could be pages that have exit/entry hits, but don't yet
|
||||
* have a record in the table (or the record was truncated).
|
||||
*
|
||||
* @return Row
|
||||
*/
|
||||
static private function getDefaultRow()
|
||||
{
|
||||
static $row = false;
|
||||
if ($row === false) {
|
||||
// This row is used in the case where an action is know as an exit_action
|
||||
// but this action was not properly recorded when it was hit in the first place
|
||||
// so we add this fake row information to make sure there is a nb_hits, etc. column for every action
|
||||
$row = new Row(array(
|
||||
Row::COLUMNS => array(
|
||||
Metrics::INDEX_NB_VISITS => 1,
|
||||
Metrics::INDEX_NB_UNIQ_VISITORS => 1,
|
||||
Metrics::INDEX_PAGE_NB_HITS => 1,
|
||||
)));
|
||||
}
|
||||
return $row;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a page name and type, builds a recursive datatable where
|
||||
* each level of the tree is a category, based on the page name split by a delimiter (slash / by default)
|
||||
*
|
||||
* @param string $actionName
|
||||
* @param int $actionType
|
||||
* @param int $urlPrefix
|
||||
* @param array $actionsTablesByType
|
||||
* @return DataTable
|
||||
*/
|
||||
private static function getActionRow($actionName, $actionType, $urlPrefix = null, &$actionsTablesByType)
|
||||
{
|
||||
// we work on the root table of the given TYPE (either ACTION_URL or DOWNLOAD or OUTLINK etc.)
|
||||
/* @var DataTable $currentTable */
|
||||
$currentTable =& $actionsTablesByType[$actionType];
|
||||
|
||||
if(is_null($currentTable)) {
|
||||
throw new \Exception("Action table for type '$actionType' was not found during Actions archiving.");
|
||||
}
|
||||
|
||||
// check for ranking query cut-off
|
||||
if ($actionName == DataTable::LABEL_SUMMARY_ROW) {
|
||||
$summaryRow = $currentTable->getRowFromId(DataTable::ID_SUMMARY_ROW);
|
||||
if ($summaryRow === false) {
|
||||
$summaryRow = $currentTable->addSummaryRow(self::createSummaryRow());
|
||||
}
|
||||
return $summaryRow;
|
||||
}
|
||||
|
||||
// go to the level of the subcategory
|
||||
$actionExplodedNames = self::getActionExplodedNames($actionName, $actionType, $urlPrefix);
|
||||
list($row, $level) = $currentTable->walkPath(
|
||||
$actionExplodedNames, self::getDefaultRowColumns(), self::$maximumRowsInSubDataTable);
|
||||
|
||||
return $row;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configured sub-category level limit.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function getSubCategoryLevelLimit()
|
||||
{
|
||||
return Config::getInstance()->General['action_category_level_limit'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns default label for the action type
|
||||
*
|
||||
* @param $type
|
||||
* @return string
|
||||
*/
|
||||
static public function getUnknownActionName($type)
|
||||
{
|
||||
if (empty(self::$defaultActionNameWhenNotDefined)) {
|
||||
self::$defaultActionNameWhenNotDefined = Piwik::translate('General_NotDefined', Piwik::translate('Actions_ColumnPageName'));
|
||||
self::$defaultActionUrlWhenNotDefined = Piwik::translate('General_NotDefined', Piwik::translate('Actions_ColumnPageURL'));
|
||||
}
|
||||
if ($type == Action::TYPE_PAGE_TITLE) {
|
||||
return self::$defaultActionNameWhenNotDefined;
|
||||
}
|
||||
return self::$defaultActionUrlWhenNotDefined;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Explodes action name into an array of elements.
|
||||
*
|
||||
* NOTE: before calling this function make sure ArchivingHelper::reloadConfig(); is called
|
||||
*
|
||||
* for downloads:
|
||||
* we explode link http://piwik.org/some/path/piwik.zip into an array( 'piwik.org', '/some/path/piwik.zip' );
|
||||
*
|
||||
* for outlinks:
|
||||
* we explode link http://dev.piwik.org/some/path into an array( 'dev.piwik.org', '/some/path' );
|
||||
*
|
||||
* for action urls:
|
||||
* we explode link http://piwik.org/some/path into an array( 'some', 'path' );
|
||||
*
|
||||
* for action names:
|
||||
* we explode name 'Piwik / Category 1 / Category 2' into an array('Piwik', 'Category 1', 'Category 2');
|
||||
*
|
||||
* @param string $name action name
|
||||
* @param int $type action type
|
||||
* @param int $urlPrefix url prefix (only used for TYPE_PAGE_URL)
|
||||
* @return array of exploded elements from $name
|
||||
*/
|
||||
static public function getActionExplodedNames($name, $type, $urlPrefix = null)
|
||||
{
|
||||
// Site Search does not split Search keywords
|
||||
if ($type == Action::TYPE_SITE_SEARCH) {
|
||||
return array($name);
|
||||
}
|
||||
|
||||
$name = str_replace("\n", "", $name);
|
||||
|
||||
$name = self::parseNameFromPageUrl($name, $type, $urlPrefix);
|
||||
|
||||
// outlinks and downloads
|
||||
if(is_array($name)) {
|
||||
return $name;
|
||||
}
|
||||
$split = self::splitNameByDelimiter($name, $type);
|
||||
|
||||
if (empty($split)) {
|
||||
$defaultName = self::getUnknownActionName($type);
|
||||
return array(trim($defaultName));
|
||||
}
|
||||
|
||||
$lastPageName = end($split);
|
||||
// we are careful to prefix the page URL / name with some value
|
||||
// so that if a page has the same name as a category
|
||||
// we don't merge both entries
|
||||
if ($type != Action::TYPE_PAGE_TITLE) {
|
||||
$lastPageName = '/' . $lastPageName;
|
||||
} else {
|
||||
$lastPageName = ' ' . $lastPageName;
|
||||
}
|
||||
$split[count($split) - 1] = $lastPageName;
|
||||
return array_values($split);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the key for the cache of action rows from an action ID and type.
|
||||
*
|
||||
* @param int $idAction
|
||||
* @param int $actionType
|
||||
* @return string|int
|
||||
*/
|
||||
private static function getCachedActionRowKey($idAction, $actionType)
|
||||
{
|
||||
return $idAction == DataTable::LABEL_SUMMARY_ROW
|
||||
? $actionType . '_others'
|
||||
: $idAction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Static cache to store Rows during processing
|
||||
*/
|
||||
static protected $cacheParsedAction = array();
|
||||
|
||||
public static function clearActionsCache()
|
||||
{
|
||||
self::$cacheParsedAction = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cached action row by id & type. If $idAction is set to -1, the 'Others' row
|
||||
* for the specific action type will be returned.
|
||||
*
|
||||
* @param int $idAction
|
||||
* @param int $actionType
|
||||
* @return Row|false
|
||||
*/
|
||||
private static function getCachedActionRow($idAction, $actionType)
|
||||
{
|
||||
$cacheLabel = self::getCachedActionRowKey($idAction, $actionType);
|
||||
|
||||
if (!isset(self::$cacheParsedAction[$cacheLabel])) {
|
||||
// This can happen when
|
||||
// - We select an entry page ID that was only seen yesterday, so wasn't selected in the first query
|
||||
// - We count time spent on a page, when this page was only seen yesterday
|
||||
return false;
|
||||
}
|
||||
|
||||
return self::$cacheParsedAction[$cacheLabel];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set cached action row for an id & type.
|
||||
*
|
||||
* @param int $idAction
|
||||
* @param int $actionType
|
||||
* @param \DataTable\Row
|
||||
*/
|
||||
private static function setCachedActionRow($idAction, $actionType, $actionRow)
|
||||
{
|
||||
$cacheLabel = self::getCachedActionRowKey($idAction, $actionType);
|
||||
self::$cacheParsedAction[$cacheLabel] = $actionRow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default columns for a row in an Actions DataTable.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function getDefaultRowColumns()
|
||||
{
|
||||
return array(Metrics::INDEX_NB_VISITS => 0,
|
||||
Metrics::INDEX_NB_UNIQ_VISITORS => 0,
|
||||
Metrics::INDEX_PAGE_NB_HITS => 0,
|
||||
Metrics::INDEX_PAGE_SUM_TIME_SPENT => 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a summary row for an Actions DataTable.
|
||||
*
|
||||
* @return Row
|
||||
*/
|
||||
private static function createSummaryRow()
|
||||
{
|
||||
return new Row(array(
|
||||
Row::COLUMNS =>
|
||||
array('label' => DataTable::LABEL_SUMMARY_ROW) + self::getDefaultRowColumns()
|
||||
));
|
||||
}
|
||||
|
||||
private static function splitNameByDelimiter($name, $type)
|
||||
{
|
||||
if(is_array($name)) {
|
||||
return $name;
|
||||
}
|
||||
if ($type == Action::TYPE_PAGE_TITLE) {
|
||||
$categoryDelimiter = self::$actionTitleCategoryDelimiter;
|
||||
} else {
|
||||
$categoryDelimiter = self::$actionUrlCategoryDelimiter;
|
||||
}
|
||||
|
||||
if (empty($categoryDelimiter)) {
|
||||
return array(trim($name));
|
||||
}
|
||||
|
||||
$split = explode($categoryDelimiter, $name, self::getSubCategoryLevelLimit());
|
||||
|
||||
// trim every category and remove empty categories
|
||||
$split = array_map('trim', $split);
|
||||
$split = array_filter($split, 'strlen');
|
||||
|
||||
// forces array key to start at 0
|
||||
$split = array_values($split);
|
||||
|
||||
return $split;
|
||||
}
|
||||
|
||||
private static function parseNameFromPageUrl($name, $type, $urlPrefix)
|
||||
{
|
||||
$urlRegexAfterDomain = '([^/]+)[/]?([^#]*)[#]?(.*)';
|
||||
if ($urlPrefix === null) {
|
||||
// match url with protocol (used for outlinks / downloads)
|
||||
$urlRegex = '@^http[s]?://' . $urlRegexAfterDomain . '$@i';
|
||||
} else {
|
||||
// the name is a url that does not contain protocol and www anymore
|
||||
// we know that normalization has been done on db level because $urlPrefix is set
|
||||
$urlRegex = '@^' . $urlRegexAfterDomain . '$@i';
|
||||
}
|
||||
|
||||
$matches = array();
|
||||
preg_match($urlRegex, $name, $matches);
|
||||
if (!count($matches)) {
|
||||
return $name;
|
||||
}
|
||||
$urlHost = $matches[1];
|
||||
$urlPath = $matches[2];
|
||||
$urlFragment = $matches[3];
|
||||
|
||||
if (in_array($type, array(Action::TYPE_DOWNLOAD, Action::TYPE_OUTLINK))) {
|
||||
return array(trim($urlHost), '/' . trim($urlPath));
|
||||
}
|
||||
|
||||
$name = $urlPath;
|
||||
if ($name === '' || substr($name, -1) == '/') {
|
||||
$name .= self::$defaultActionName;
|
||||
}
|
||||
|
||||
$urlFragment = PageUrl::processUrlFragment($urlFragment);
|
||||
if (!empty($urlFragment)) {
|
||||
$name .= '#' . $urlFragment;
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
}
|
||||
151
www/analytics/plugins/Actions/Controller.php
Normal file
151
www/analytics/plugins/Actions/Controller.php
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\Actions;
|
||||
|
||||
use Piwik\Piwik;
|
||||
use Piwik\View;
|
||||
use Piwik\ViewDataTable\Factory;
|
||||
|
||||
/**
|
||||
* Actions controller
|
||||
*
|
||||
*/
|
||||
class Controller extends \Piwik\Plugin\Controller
|
||||
{
|
||||
//
|
||||
// Actions that render whole pages
|
||||
//
|
||||
|
||||
public function indexPageUrls()
|
||||
{
|
||||
return View::singleReport(
|
||||
Piwik::translate('General_Pages'),
|
||||
$this->getPageUrls(true));
|
||||
}
|
||||
|
||||
public function indexEntryPageUrls()
|
||||
{
|
||||
return View::singleReport(
|
||||
Piwik::translate('Actions_SubmenuPagesEntry'),
|
||||
$this->getEntryPageUrls(true));
|
||||
}
|
||||
|
||||
public function indexExitPageUrls()
|
||||
{
|
||||
return View::singleReport(
|
||||
Piwik::translate('Actions_SubmenuPagesExit'),
|
||||
$this->getExitPageUrls(true));
|
||||
}
|
||||
|
||||
public function indexSiteSearch()
|
||||
{
|
||||
$view = new View('@Actions/indexSiteSearch');
|
||||
|
||||
$view->keywords = $this->getSiteSearchKeywords(true);
|
||||
$view->noResultKeywords = $this->getSiteSearchNoResultKeywords(true);
|
||||
$view->pagesUrlsFollowingSiteSearch = $this->getPageUrlsFollowingSiteSearch(true);
|
||||
|
||||
$categoryTrackingEnabled = \Piwik\Plugin\Manager::getInstance()->isPluginActivated('CustomVariables');
|
||||
if ($categoryTrackingEnabled) {
|
||||
$view->categories = $this->getSiteSearchCategories(true);
|
||||
}
|
||||
|
||||
return $view->render();
|
||||
}
|
||||
|
||||
public function indexPageTitles()
|
||||
{
|
||||
return View::singleReport(
|
||||
Piwik::translate('Actions_SubmenuPageTitles'),
|
||||
$this->getPageTitles(true));
|
||||
}
|
||||
|
||||
public function indexDownloads()
|
||||
{
|
||||
return View::singleReport(
|
||||
Piwik::translate('General_Downloads'),
|
||||
$this->getDownloads(true));
|
||||
}
|
||||
|
||||
public function indexOutlinks()
|
||||
{
|
||||
return View::singleReport(
|
||||
Piwik::translate('General_Outlinks'),
|
||||
$this->getOutlinks(true));
|
||||
}
|
||||
|
||||
//
|
||||
// Actions that render individual reports
|
||||
//
|
||||
|
||||
public function getPageUrls()
|
||||
{
|
||||
return $this->renderReport(__FUNCTION__);
|
||||
}
|
||||
|
||||
public function getEntryPageUrls()
|
||||
{
|
||||
return $this->renderReport(__FUNCTION__);
|
||||
}
|
||||
|
||||
public function getExitPageUrls()
|
||||
{
|
||||
return $this->renderReport(__FUNCTION__);
|
||||
}
|
||||
|
||||
public function getSiteSearchKeywords()
|
||||
{
|
||||
return $this->renderReport(__FUNCTION__);
|
||||
}
|
||||
|
||||
public function getSiteSearchNoResultKeywords()
|
||||
{
|
||||
return $this->renderReport(__FUNCTION__);
|
||||
}
|
||||
|
||||
public function getSiteSearchCategories()
|
||||
{
|
||||
return $this->renderReport(__FUNCTION__);
|
||||
}
|
||||
|
||||
public function getPageUrlsFollowingSiteSearch()
|
||||
{
|
||||
return $this->renderReport(__FUNCTION__);
|
||||
}
|
||||
|
||||
public function getPageTitlesFollowingSiteSearch()
|
||||
{
|
||||
return $this->renderReport(__FUNCTION__);
|
||||
}
|
||||
|
||||
public function getPageTitles()
|
||||
{
|
||||
return $this->renderReport(__FUNCTION__);
|
||||
}
|
||||
|
||||
public function getEntryPageTitles()
|
||||
{
|
||||
return $this->renderReport(__FUNCTION__);
|
||||
}
|
||||
|
||||
public function getExitPageTitles()
|
||||
{
|
||||
return $this->renderReport(__FUNCTION__);
|
||||
}
|
||||
|
||||
public function getDownloads()
|
||||
{
|
||||
return $this->renderReport(__FUNCTION__);
|
||||
}
|
||||
|
||||
public function getOutlinks()
|
||||
{
|
||||
return $this->renderReport(__FUNCTION__);
|
||||
}
|
||||
}
|
||||
328
www/analytics/plugins/Actions/javascripts/actionsDataTable.js
Normal file
328
www/analytics/plugins/Actions/javascripts/actionsDataTable.js
Normal file
|
|
@ -0,0 +1,328 @@
|
|||
/*!
|
||||
* Piwik - Web Analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
(function ($, require) {
|
||||
|
||||
var exports = require('piwik/UI'),
|
||||
DataTable = exports.DataTable,
|
||||
dataTablePrototype = DataTable.prototype;
|
||||
|
||||
// helper function for ActionDataTable
|
||||
function getLevelFromClass(style) {
|
||||
if (!style || typeof style == "undefined") return 0;
|
||||
|
||||
var currentLevel = 0;
|
||||
|
||||
var currentLevelIndex = style.indexOf('level');
|
||||
if (currentLevelIndex >= 0) {
|
||||
currentLevel = Number(style.substr(currentLevelIndex + 5, 1));
|
||||
}
|
||||
return currentLevel;
|
||||
}
|
||||
|
||||
// helper function for ActionDataTable
|
||||
function setImageMinus(domElem) {
|
||||
$('img.plusMinus', domElem).attr('src', 'plugins/Zeitgeist/images/minus.png');
|
||||
}
|
||||
|
||||
// helper function for ActionDataTable
|
||||
function setImagePlus(domElem) {
|
||||
$('img.plusMinus', domElem).attr('src', 'plugins/Zeitgeist/images/plus.png');
|
||||
}
|
||||
|
||||
/**
|
||||
* UI control that handles extra functionality for Actions datatables.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
exports.ActionsDataTable = function (element) {
|
||||
this.parentAttributeParent = '';
|
||||
this.parentId = '';
|
||||
this.disabledRowDom = {}; // to handle double click on '+' row
|
||||
|
||||
DataTable.call(this, element);
|
||||
};
|
||||
|
||||
$.extend(exports.ActionsDataTable.prototype, dataTablePrototype, {
|
||||
|
||||
//see dataTable::bindEventsAndApplyStyle
|
||||
bindEventsAndApplyStyle: function (domElem, rows) {
|
||||
var self = this;
|
||||
|
||||
self.cleanParams();
|
||||
|
||||
if (!rows) {
|
||||
rows = $('tr', domElem);
|
||||
}
|
||||
|
||||
// we dont display the link on the row with subDataTable when we are already
|
||||
// printing all the subTables (case of recursive search when the content is
|
||||
// including recursively all the subtables
|
||||
if (!self.param.filter_pattern_recursive) {
|
||||
self.numberOfSubtables = rows.filter('.subDataTable').click(function () {
|
||||
self.onClickActionSubDataTable(this)
|
||||
}).size();
|
||||
}
|
||||
self.applyCosmetics(domElem, rows);
|
||||
self.handleColumnHighlighting(domElem);
|
||||
self.handleRowActions(domElem, rows);
|
||||
self.handleLimit(domElem);
|
||||
self.handleAnnotationsButton(domElem);
|
||||
self.handleExportBox(domElem);
|
||||
self.handleSort(domElem);
|
||||
self.handleOffsetInformation(domElem);
|
||||
if (self.workingDivId != undefined) {
|
||||
var dataTableLoadedProxy = function (response) {
|
||||
self.dataTableLoaded(response, self.workingDivId);
|
||||
};
|
||||
|
||||
self.handleSearchBox(domElem, dataTableLoadedProxy);
|
||||
self.handleConfigurationBox(domElem, dataTableLoadedProxy);
|
||||
}
|
||||
|
||||
self.handleColumnDocumentation(domElem);
|
||||
self.handleRelatedReports(domElem);
|
||||
self.handleTriggeredEvents(domElem);
|
||||
self.handleCellTooltips(domElem);
|
||||
self.handleExpandFooter(domElem);
|
||||
self.setFixWidthToMakeEllipsisWork(domElem);
|
||||
},
|
||||
|
||||
//see dataTable::applyCosmetics
|
||||
applyCosmetics: function (domElem, rows) {
|
||||
var self = this;
|
||||
var rowsWithSubtables = rows.filter('.subDataTable');
|
||||
|
||||
rowsWithSubtables.css('font-weight', 'bold');
|
||||
|
||||
$("th:first-child", domElem).addClass('label');
|
||||
var imagePlusMinusWidth = 12;
|
||||
var imagePlusMinusHeight = 12;
|
||||
$('td:first-child', rowsWithSubtables)
|
||||
.each(function () {
|
||||
$(this).prepend('<img width="' + imagePlusMinusWidth + '" height="' + imagePlusMinusHeight + '" class="plusMinus" src="" />');
|
||||
if (self.param.filter_pattern_recursive) {
|
||||
setImageMinus(this);
|
||||
}
|
||||
else {
|
||||
setImagePlus(this);
|
||||
}
|
||||
});
|
||||
|
||||
var rootRow = rows.first().prev();
|
||||
|
||||
// we look at the style of the row before the new rows to determine the rows'
|
||||
// level
|
||||
var level = rootRow.length ? getLevelFromClass(rootRow.attr('class')) + 1 : 0;
|
||||
|
||||
rows.each(function () {
|
||||
var currentStyle = $(this).attr('class') || '';
|
||||
|
||||
if (currentStyle.indexOf('level') == -1) {
|
||||
$(this).addClass('level' + level);
|
||||
}
|
||||
|
||||
// we add an attribute parent that contains the ID of all the parent categories
|
||||
// this ID is used when collapsing a parent row, it searches for all children rows
|
||||
// which 'parent' attribute's value contains the collapsed row ID
|
||||
$(this).prop('parent', function () {
|
||||
return self.parentAttributeParent + ' ' + self.parentId;
|
||||
});
|
||||
});
|
||||
|
||||
self.addOddAndEvenClasses(domElem);
|
||||
},
|
||||
|
||||
addOddAndEvenClasses: function(domElem) {
|
||||
// Add some styles on the cells even/odd
|
||||
// label (first column of a data row) or not
|
||||
$("tr:not(.hidden):odd td:first-child", domElem)
|
||||
.removeClass('labeleven').addClass('label labelodd');
|
||||
$("tr:not(.hidden):even td:first-child", domElem)
|
||||
.removeClass('labelodd').addClass('label labeleven');
|
||||
$("tr:not(.hidden):odd td", domElem).slice(1)
|
||||
.removeClass('columneven').addClass('column columnodd');
|
||||
$("tr:not(.hidden):even td", domElem).slice(1)
|
||||
.removeClass('columnodd').addClass('column columneven');
|
||||
},
|
||||
|
||||
handleRowActions: function (domElem, rows) {
|
||||
this.doHandleRowActions(rows);
|
||||
},
|
||||
|
||||
// Called when the user click on an actionDataTable row
|
||||
onClickActionSubDataTable: function (domElem) {
|
||||
var self = this;
|
||||
|
||||
// get the idSubTable
|
||||
var idSubTable = $(domElem).attr('id');
|
||||
|
||||
var divIdToReplaceWithSubTable = 'subDataTable_' + idSubTable;
|
||||
|
||||
var NextStyle = $(domElem).next().attr('class');
|
||||
var CurrentStyle = $(domElem).attr('class');
|
||||
|
||||
var currentRowLevel = getLevelFromClass(CurrentStyle);
|
||||
var nextRowLevel = getLevelFromClass(NextStyle);
|
||||
|
||||
// if the row has not been clicked
|
||||
// which is the same as saying that the next row level is equal or less than the current row
|
||||
// because when we click a row the level of the next rows is higher (level2 row gives level3 rows)
|
||||
if (currentRowLevel >= nextRowLevel) {
|
||||
//unbind click to avoid double click problem
|
||||
$(domElem).off('click');
|
||||
self.disabledRowDom = $(domElem);
|
||||
|
||||
var numberOfColumns = $(domElem).children().length;
|
||||
$(domElem).after('\
|
||||
<tr id="' + divIdToReplaceWithSubTable + '" class="cellSubDataTable">\
|
||||
<td colspan="' + numberOfColumns + '">\
|
||||
<span class="loadingPiwik" style="display:inline"><img src="plugins/Zeitgeist/images/loading-blue.gif" /> Loading...</span>\
|
||||
</td>\
|
||||
</tr>\
|
||||
');
|
||||
var savedActionVariable = self.param.action;
|
||||
|
||||
// reset all the filters from the Parent table
|
||||
var filtersToRestore = self.resetAllFilters();
|
||||
|
||||
// Do not reset the sorting filters that must be applied to sub tables
|
||||
this.param['filter_sort_column'] = filtersToRestore['filter_sort_column'];
|
||||
this.param['filter_sort_order'] = filtersToRestore['filter_sort_order'];
|
||||
this.param['enable_filter_excludelowpop'] = filtersToRestore['enable_filter_excludelowpop'];
|
||||
|
||||
self.param.idSubtable = idSubTable;
|
||||
self.param.action = self.props.subtable_controller_action;
|
||||
|
||||
self.reloadAjaxDataTable(false, function (resp) {
|
||||
self.actionsSubDataTableLoaded(resp, idSubTable);
|
||||
self.repositionRowActions($(domElem));
|
||||
});
|
||||
self.param.action = savedActionVariable;
|
||||
|
||||
self.restoreAllFilters(filtersToRestore);
|
||||
|
||||
delete self.param.idSubtable;
|
||||
}
|
||||
// else we toggle all these rows
|
||||
else {
|
||||
var plusDetected = $('td img.plusMinus', domElem).attr('src').indexOf('plus') >= 0;
|
||||
var stripingNeeded = false;
|
||||
|
||||
$(domElem).siblings().each(function () {
|
||||
var parents = $(this).prop('parent').split(' ');
|
||||
if (parents) {
|
||||
if (parents.indexOf(idSubTable) >= 0
|
||||
|| parents.indexOf('subDataTable_' + idSubTable) >= 0) {
|
||||
if (plusDetected) {
|
||||
$(this).css('display', '').removeClass('hidden');
|
||||
stripingNeeded = !stripingNeeded;
|
||||
|
||||
//unroll everything and display '-' sign
|
||||
//if the row is already opened
|
||||
var NextStyle = $(this).next().attr('class');
|
||||
var CurrentStyle = $(this).attr('class');
|
||||
|
||||
var currentRowLevel = getLevelFromClass(CurrentStyle);
|
||||
var nextRowLevel = getLevelFromClass(NextStyle);
|
||||
|
||||
if (currentRowLevel < nextRowLevel)
|
||||
setImageMinus(this);
|
||||
}
|
||||
else {
|
||||
$(this).css('display', 'none').addClass('hidden');
|
||||
stripingNeeded = !stripingNeeded;
|
||||
}
|
||||
self.repositionRowActions($(domElem));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var table = $(domElem);
|
||||
if (!table.hasClass('dataTable')) {
|
||||
table = table.closest('.dataTable');
|
||||
}
|
||||
if (stripingNeeded) {
|
||||
self.addOddAndEvenClasses(table);
|
||||
}
|
||||
|
||||
self.$element.trigger('piwik:actionsSubTableToggled');
|
||||
}
|
||||
|
||||
// toggle the +/- image
|
||||
var plusDetected = $('td img.plusMinus', domElem).attr('src').indexOf('plus') >= 0;
|
||||
if (plusDetected) {
|
||||
setImageMinus(domElem);
|
||||
}
|
||||
else {
|
||||
setImagePlus(domElem);
|
||||
}
|
||||
},
|
||||
|
||||
//called when the full table actions is loaded
|
||||
dataTableLoaded: function (response, workingDivId) {
|
||||
var content = $(response);
|
||||
var idToReplace = workingDivId || $(content).attr('id');
|
||||
|
||||
//reset parents id
|
||||
self.parentAttributeParent = '';
|
||||
self.parentId = '';
|
||||
|
||||
var dataTableSel = $('#' + idToReplace);
|
||||
|
||||
// keep the original list of related reports
|
||||
var oldReportsElem = $('.datatableRelatedReports', dataTableSel);
|
||||
$('.datatableRelatedReports', content).replaceWith(oldReportsElem);
|
||||
|
||||
dataTableSel.replaceWith(content);
|
||||
|
||||
content.trigger('piwik:dataTableLoaded');
|
||||
|
||||
piwikHelper.lazyScrollTo(content[0], 400);
|
||||
|
||||
return content;
|
||||
},
|
||||
|
||||
// Called when a set of rows for a category of actions is loaded
|
||||
actionsSubDataTableLoaded: function (response, idSubTable) {
|
||||
var self = this;
|
||||
var idToReplace = 'subDataTable_' + idSubTable;
|
||||
var root = $('#' + self.workingDivId);
|
||||
|
||||
var response = $(response);
|
||||
self.parentAttributeParent = $('tr#' + idToReplace).prev().prop('parent');
|
||||
self.parentId = idToReplace;
|
||||
|
||||
$('tr#' + idToReplace, root).after(response).remove();
|
||||
|
||||
var missingColumns = (response.prev().find('td').size() - response.find('td').size());
|
||||
for (var i = 0; i < missingColumns; i++) {
|
||||
// if the subtable has fewer columns than the parent table, add some columns.
|
||||
// this happens for example, when the parent table has performance metrics and the subtable doesn't.
|
||||
response.append('<td>-</td>');
|
||||
}
|
||||
|
||||
var re = /subDataTable_(\d+)/;
|
||||
var ok = re.exec(self.parentId);
|
||||
if (ok) {
|
||||
self.parentId = ok[1];
|
||||
}
|
||||
|
||||
// we execute the bindDataTableEvent function for the new DIV
|
||||
self.bindEventsAndApplyStyle($('#' + self.workingDivId), response);
|
||||
|
||||
self.$element.trigger('piwik:actionsSubDataTableLoaded');
|
||||
|
||||
//bind back the click event (disabled to avoid double-click problem)
|
||||
self.disabledRowDom.click(
|
||||
function () {
|
||||
self.onClickActionSubDataTable(this)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
})(jQuery, require);
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
.dataTableActions > .dataTableWrapper {
|
||||
width: 500px;
|
||||
min-height: 1px;
|
||||
}
|
||||
17
www/analytics/plugins/Actions/templates/indexSiteSearch.twig
Normal file
17
www/analytics/plugins/Actions/templates/indexSiteSearch.twig
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<div id='leftcolumn'>
|
||||
<h2 piwik-enriched-headline>{{ 'Actions_WidgetSearchKeywords'|translate }}</h2>
|
||||
{{ keywords|raw }}
|
||||
|
||||
<h2 piwik-enriched-headline>{{ 'Actions_WidgetSearchNoResultKeywords'|translate }}</h2>
|
||||
{{ noResultKeywords|raw }}
|
||||
|
||||
{% if categories is defined %}
|
||||
<h2 piwik-enriched-headline>{{ 'Actions_WidgetSearchCategories'|translate }}</h2>
|
||||
{{ categories|raw }}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div id='rightcolumn'>
|
||||
<h2 piwik-enriched-headline>{{ 'Actions_WidgetPageUrlsFollowingSearch'|translate }}</h2>
|
||||
{{ pagesUrlsFollowingSiteSearch|raw }}
|
||||
</div>
|
||||
Loading…
Add table
Add a link
Reference in a new issue