PDF rausgenommen

This commit is contained in:
aschwarz
2023-01-23 11:03:31 +01:00
parent 82d562a322
commit a6523903eb
28078 changed files with 4247552 additions and 2 deletions

View File

@@ -0,0 +1,24 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome\Categories;
use Piwik\Category\Category;
use Piwik\Piwik;
class ActionsCategory extends Category
{
protected $id = 'General_Actions';
protected $order = 10;
protected $icon = 'icon-reporting-actions';
public function getDisplayName()
{
return Piwik::translate('Actions_Behaviour');
}
}

View File

@@ -0,0 +1,19 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome\Categories;
use Piwik\Category\Subcategory;
class DevicesSubcategory extends Subcategory
{
protected $categoryId = 'General_Visitors';
protected $id = 'DevicesDetection_Devices';
protected $order = 15;
}

View File

@@ -0,0 +1,19 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome\Categories;
use Piwik\Category\Subcategory;
class EngagementSubcategory extends Subcategory
{
protected $categoryId = 'General_Actions';
protected $id = 'VisitorInterest_Engagement';
protected $order = 46;
}

View File

@@ -0,0 +1,17 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome\Categories;
use Piwik\Category\Category;
class GenericCategory extends Category
{
protected $id = 'General_Generic';
protected $order = 70;
}

View File

@@ -0,0 +1,19 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome\Categories;
use Piwik\Category\Subcategory;
class SoftwareSubcategory extends Subcategory
{
protected $categoryId = 'General_Visitors';
protected $id = 'DevicesDetection_Software';
protected $order = 20;
}

View File

@@ -0,0 +1,18 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome\Categories;
use Piwik\Category\Category;
class VisitorsCategory extends Category
{
protected $id = 'General_Visitors';
protected $order = 5;
protected $icon = 'icon-reporting-visitors';
}

View File

@@ -0,0 +1,19 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome\Categories;
use Piwik\Category\Subcategory;
class VisitorsOverviewSubcategory extends Subcategory
{
protected $categoryId = 'General_Visitors';
protected $id = 'General_Overview';
protected $order = 2;
}

View File

@@ -0,0 +1,63 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome\Columns;
use Piwik\Metrics\Formatter;
use Piwik\Piwik;
use Piwik\Plugin\Dimension\VisitDimension;
use Piwik\Site;
use Piwik\Tracker\Action;
use Piwik\Tracker\Request;
use Piwik\Tracker\Visitor;
class IdSite extends VisitDimension
{
protected $columnName = 'idsite';
// we do not install or define column definition here as we need to create this column when installing as there is
// an index on it. Currently we do not define the index here... although we could overwrite the install() method
// and add column 'idsite' and add index. Problem is there is also an index
// INDEX(idsite, config_id, visit_last_action_time) and we maybe not be sure whether config_id already exists at
// installing point (we do not know whether visit_last_action_time or idsite column would be added first).
protected $nameSingular = 'General_Measurable';
protected $namePlural = 'General_Measurables';
protected $type = self::TYPE_TEXT;
/**
* @param Request $request
* @param Visitor $visitor
* @param Action|null $action
* @return mixed
*/
public function onNewVisit(Request $request, Visitor $visitor, $action)
{
return $request->getIdSite();
}
/**
* @param Request $request
* @param Visitor $visitor
* @param Action|null $action
* @return mixed
*/
public function onAnyGoalConversion(Request $request, Visitor $visitor, $action)
{
return $request->getIdSite();
}
public function formatValue($value, $idSite, Formatter $formatter)
{
try {
return Site::getNameFor($value);
} catch (\Exception $ex) {
$formatted = parent::formatValue($value, $idSite, $formatter);
return Piwik::translate('General_MeasurableId') . ': ' . $formatted;
}
}
}

View File

@@ -0,0 +1,33 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\CoreHome\Columns;
use Piwik\Columns\DimensionMetricFactory;
use Piwik\Columns\MetricsList;
use Piwik\Piwik;
use Piwik\Plugin\ArchivedMetric;
use Piwik\Plugin\Dimension\ActionDimension;
class LinkVisitActionId extends ActionDimension
{
protected $columnName = 'idlink_va';
protected $acceptValues = 'Any integer.';
protected $category = 'General_Actions';
protected $nameSingular = 'General_Actions';
protected $metricId = 'hits';
protected $type = self::TYPE_NUMBER;
public function configureMetrics(MetricsList $metricsList, DimensionMetricFactory $dimensionMetricFactory)
{
$metric = $dimensionMetricFactory->createMetric(ArchivedMetric::AGGREGATION_UNIQUE);
$metric->setTranslatedName(Piwik::translate('General_ColumnHits'));
$metric->setName('hits');
$metricsList->addMetric($metric);
}
}

View File

@@ -0,0 +1,44 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\CoreHome\Columns;
use Piwik\Columns\DimensionMetricFactory;
use Piwik\Columns\Discriminator;
use Piwik\Columns\MetricsList;
use Piwik\Piwik;
use Piwik\Plugin\ArchivedMetric;
use Piwik\Plugin\Dimension\ActionDimension;
use Piwik\Tracker\Action;
class LinkVisitActionIdPages extends ActionDimension
{
protected $columnName = 'idlink_va';
protected $category = 'General_Actions';
protected $nameSingular = 'General_Actions';
protected $type = self::TYPE_NUMBER;
public function configureSegments()
{
// empty so we don't auto-generate a segment
}
public function getDbDiscriminator()
{
return new Discriminator('log_action', 'type', Action::TYPE_PAGE_URL);
}
public function configureMetrics(MetricsList $metricsList, DimensionMetricFactory $dimensionMetricFactory)
{
$metric = $dimensionMetricFactory->createMetric(ArchivedMetric::AGGREGATION_UNIQUE);
$metric->setTranslatedName(Piwik::translate('General_ColumnPageviews'));
$metric->setDocumentation(Piwik::translate('General_ColumnPageviewsDocumentation'));
$metric->setName('pageviews');
$metricsList->addMetric($metric);
}
}

View File

@@ -0,0 +1,45 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\CoreHome\Columns\Metrics;
use Piwik\DataTable\Row;
use Piwik\Piwik;
use Piwik\Plugin\ProcessedMetric;
/**
* The average number of actions per visit. Calculated as:
*
* nb_actions / nb_visits
*
* nb_actions & nb_visits are calculated during archiving.
*/
class ActionsPerVisit extends ProcessedMetric
{
public function getName()
{
return 'nb_actions_per_visit';
}
public function compute(Row $row)
{
$actions = $this->getMetric($row, 'nb_actions');
$visits = $this->getMetric($row, 'nb_visits');
return Piwik::getQuotientSafe($actions, $visits, $precision = 1);
}
public function getTranslatedName()
{
return Piwik::translate('General_ColumnActionsPerVisit');
}
public function getDependentMetrics()
{
return array('nb_actions', 'nb_visits');
}
}

View File

@@ -0,0 +1,53 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\CoreHome\Columns\Metrics;
use Piwik\DataTable\Row;
use Piwik\Metrics\Formatter;
use Piwik\Piwik;
use Piwik\Plugin\ProcessedMetric;
/**
* The average number of seconds spent on the site per visit. Calculated as:
*
* sum_visit_length / nb_visits
*
* sum_visit_length & nb_visits are calculated during archiving.
*
* @api
*/
class AverageTimeOnSite extends ProcessedMetric
{
public function getName()
{
return 'avg_time_on_site';
}
public function compute(Row $row)
{
$sumVisitLength = $this->getMetric($row, 'sum_visit_length');
$nbVisits = $this->getMetric($row, 'nb_visits');
return Piwik::getQuotientSafe($sumVisitLength, $nbVisits, $precision = 0);
}
public function format($value, Formatter $formatter)
{
return $formatter->getPrettyTimeFromSeconds($value, true);
}
public function getTranslatedName()
{
return Piwik::translate('General_ColumnAvgTimeOnSite');
}
public function getDependentMetrics()
{
return array('sum_visit_length', 'nb_visits');
}
}

View File

@@ -0,0 +1,52 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\CoreHome\Columns\Metrics;
use Piwik\DataTable\Row;
use Piwik\Metrics\Formatter;
use Piwik\Piwik;
use Piwik\Plugin\ProcessedMetric;
/**
* The percentage of visits that leave the site without visiting another page. Calculated
* as:
*
* bounce_count / nb_visits
*
* bounce_count & nb_visits are calculated by an Archiver.
*/
class BounceRate extends ProcessedMetric
{
public function getName()
{
return 'bounce_rate';
}
public function getTranslatedName()
{
return Piwik::translate('General_ColumnBounceRate');
}
public function getDependentMetrics()
{
return array('bounce_count', 'nb_visits');
}
public function format($value, Formatter $formatter)
{
return $formatter->getPrettyPercentFromQuotient($value);
}
public function compute(Row $row)
{
$bounceCount = $this->getMetric($row, 'bounce_count');
$visits = $this->getMetric($row, 'nb_visits');
return Piwik::getQuotientSafe($bounceCount, $visits, $precision = 2);
}
}

View File

@@ -0,0 +1,47 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\CoreHome\Columns\Metrics;
use Piwik\DataTable\Row;
use Piwik\Plugin\ProcessedMetric;
class CallableProcessedMetric extends ProcessedMetric
{
private $name;
private $callback;
private $dependentMetrics;
public function __construct($name, $callback, $dependentMetrics = array())
{
$this->name = $name;
$this->callback = $callback;
$this->dependentMetrics = $dependentMetrics;
}
public function getName()
{
return $this->name;
}
public function compute(Row $row)
{
if ($this->callback) {
return call_user_func($this->callback, $row);
}
}
public function getTranslatedName()
{
return '';
}
public function getDependentMetrics()
{
return $this->dependentMetrics;
}
}

View File

@@ -0,0 +1,51 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\CoreHome\Columns\Metrics;
use Piwik\DataTable\Row;
use Piwik\Metrics\Formatter;
use Piwik\Piwik;
use Piwik\Plugin\ProcessedMetric;
/**
* The percent of visits that result in a conversion. Calculated as:
*
* nb_visits_converted / nb_visits
*
* nb_visits_converted & nb_visits are calculated by the archiving process.
*/
class ConversionRate extends ProcessedMetric
{
public function getName()
{
return 'conversion_rate';
}
public function getTranslatedName()
{
return Piwik::translate('General_ColumnConversionRate');
}
public function getDependentMetrics()
{
return array('nb_visits_converted', 'nb_visits');
}
public function format($value, Formatter $formatter)
{
return $formatter->getPrettyPercentFromQuotient($value);
}
public function compute(Row $row)
{
$nbVisitsConverted = $this->getMetric($row, 'nb_visits_converted');
$nbVisits = $this->getMetric($row, 'nb_visits');
return Piwik::getQuotientSafe($nbVisitsConverted, $nbVisits, $precision = 4);
}
}

View File

@@ -0,0 +1,173 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\CoreHome\Columns\Metrics;
use Piwik\DataTable;
use Piwik\DataTable\Row;
use Piwik\Metrics;
use Piwik\Metrics\Formatter;
use Piwik\Piwik;
use Piwik\Plugin\Metric;
use Piwik\Plugin\ProcessedMetric;
/**
* Calculates evolution values for any other metric. An evolution is the percent change from a
* point in the past to the present. They are computed as:
*
* (current value - value in past) / value in past
*
* @api
*/
class EvolutionMetric extends ProcessedMetric
{
/**
* @var Metric|string
*/
private $wrapped;
/**
* @var string
*/
private $evolutionMetricName;
/**
* @var int
*/
private $quotientPrecision;
/**
* @var DataTable
*/
private $pastData;
/**
* The list of labels leading to the current subtable being processed. Used to get the proper subtable in
* $pastData.
*
* @var string[]
*/
private $labelPath = [];
/**
* Constructor.
*
* @param string|Metric $wrapped The metric used to calculate the evolution.
* @param DataTable|null $pastData The data in the past to use when calculating evolutions.
* @param string|false $evolutionMetricName The name of the evolution processed metric. Defaults to
* $wrapped's name with `'_evolution'` appended.
* @param int $quotientPrecision The percent's quotient precision.
*/
public function __construct($wrapped, DataTable $pastData = null, $evolutionMetricName = false, $quotientPrecision = 0)
{
$this->wrapped = $wrapped;
$this->pastData = $pastData;
if (empty($evolutionMetricName)) {
$wrappedName = $this->getWrappedName();
$evolutionMetricName = $wrappedName . '_evolution';
}
$this->evolutionMetricName = $evolutionMetricName;
$this->quotientPrecision = $quotientPrecision;
}
public function getName()
{
return $this->evolutionMetricName;
}
public function getTranslatedName()
{
if ($this->wrapped instanceof Metric) {
$metricName = $this->wrapped->getTranslatedName();
} else {
$defaultMetricTranslations = Metrics::getDefaultMetricTranslations();
$metricName = isset($defaultMetricTranslations[$this->wrapped]) ? $defaultMetricTranslations[$this->wrapped] : $this->wrapped;
}
return Piwik::translate('CoreHome_EvolutionMetricName', [$metricName]);
}
public function compute(Row $row)
{
$columnName = $this->getWrappedName();
$pastRow = $this->getPastRowFromCurrent($row);
$currentValue = $this->getMetric($row, $columnName);
$pastValue = $pastRow ? $this->getMetric($pastRow, $columnName) : 0;
$dividend = $currentValue - $pastValue;
$divisor = $pastValue;
if ($dividend == 0) {
return 0;
} else if ($divisor == 0) {
return 1;
} else {
return Piwik::getQuotientSafe($dividend, $divisor, $this->quotientPrecision + 2);
}
}
public function format($value, Formatter $formatter)
{
return $formatter->getPrettyPercentFromQuotient($value);
}
public function getDependentMetrics()
{
return array($this->getWrappedName());
}
public function beforeComputeSubtable(Row $row)
{
$this->labelPath[] = $row->getColumn('label');
}
public function afterComputeSubtable(Row $row)
{
array_pop($this->labelPath);
}
protected function getWrappedName()
{
return $this->wrapped instanceof Metric ? $this->wrapped->getName() : $this->wrapped;
}
/**
* public for Insights use.
*/
public function getPastRowFromCurrent(Row $row)
{
$pastData = $this->getPastDataTable();
if (empty($pastData)) {
return null;
}
$label = $row->getColumn('label');
return $label ? $pastData->getRowFromLabel($label) : $pastData->getFirstRow();
}
private function getPastDataTable()
{
$result = $this->pastData;
foreach ($this->labelPath as $label) {
$row = $result->getRowFromLabel($label);
if (empty($row)) {
return null;
}
$subtable = $row->getSubtable();
if (empty($subtable)) {
return null;
}
$result = $subtable;
}
return $result;
}
}

View File

@@ -0,0 +1,77 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\CoreHome\Columns\Metrics;
use Piwik\DataTable;
use Piwik\DataTable\Row;
use Piwik\Metrics\Formatter;
use Piwik\Piwik;
use Piwik\Plugin\ProcessedMetric;
use Piwik\Plugin\Report;
/**
* Percent of visits in the whole table. Calculated as:
*
* nb_visits / sum(all nb_visits in table)
*
* nb_visits is calculated by core archiving process.
*/
class VisitsPercent extends ProcessedMetric
{
private $cachedTotalVisits = null;
private $forceTotalVisits = null;
/**
* Constructor.
*
* @param int|null $totalVisits The forced value of total visits to use.
*/
public function __construct($totalVisits = null)
{
$this->forceTotalVisits = $totalVisits;
}
public function getName()
{
return 'nb_visits_percentage';
}
public function getTranslatedName()
{
return Piwik::translate('General_ColumnPercentageVisits');
}
public function compute(Row $row)
{
$visits = $this->getMetric($row, 'nb_visits');
return Piwik::getQuotientSafe($visits, $this->cachedTotalVisits, $precision = 2);
}
public function format($value, Formatter $formatter)
{
return $formatter->getPrettyPercentFromQuotient($value);
}
public function getDependentMetrics()
{
return array('nb_visits');
}
public function beforeCompute($report, DataTable $table)
{
if ($this->forceTotalVisits === null) {
$this->cachedTotalVisits = array_sum($this->getMetricValues($table, 'nb_visits'));
} else {
$this->cachedTotalVisits = $this->forceTotalVisits;
}
return true; // always compute
}
}

View File

@@ -0,0 +1,35 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome\Columns;
use Piwik\Metrics\Formatter;
use Piwik\Plugin\Dimension\ActionDimension;
class ServerMinute extends ActionDimension
{
protected $columnName = 'server_time';
protected $segmentName = 'actionServerMinute';
protected $sqlSegment = 'MINUTE(log_link_visit_action.server_time)';
protected $nameSingular = 'VisitTime_ColumnServerMinute';
protected $type = self::TYPE_DATETIME;
protected $acceptValues = '0, 1, 2, 3, ..., 56, 57, 58, 59';
public function __construct()
{
$this->suggestedValuesCallback = function ($idSite, $maxValuesToReturn) {
return range(0, min(59, $maxValuesToReturn));
};
}
public function formatValue($value, $idSite, Formatter $formatter)
{
return $value;
}
}

View File

@@ -0,0 +1,55 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome\Columns;
use Piwik\Date;
use Piwik\Metrics\Formatter;
use Piwik\Plugin\Dimension\ActionDimension;
use Piwik\Tracker\Action;
use Piwik\Tracker\Request;
use Piwik\Tracker\Visitor;
require_once PIWIK_INCLUDE_PATH . '/plugins/VisitTime/functions.php';
class ServerTime extends ActionDimension
{
protected $columnName = 'server_time';
protected $columnType = 'DATETIME NOT NULL';
protected $segmentName = 'actionServerHour';
protected $sqlSegment = 'HOUR(log_link_visit_action.server_time)';
protected $nameSingular = 'VisitTime_ColumnServerHour';
protected $type = self::TYPE_DATETIME;
public function __construct()
{
$this->suggestedValuesCallback = function ($idSite, $maxValuesToReturn) {
return range(0, min(23, $maxValuesToReturn));
};
}
public function formatValue($value, $idSite, Formatter $formatter)
{
return \Piwik\Plugins\VisitTime\getTimeLabel($value);
}
public function install()
{
$changes = parent::install();
$changes['log_link_visit_action'][] = "ADD INDEX index_idsite_servertime ( idsite, server_time )";
return $changes;
}
public function onNewAction(Request $request, Visitor $visitor, Action $action)
{
$timestamp = $request->getCurrentTimestamp();
return Date::getDatetimeFromTimestamp($timestamp);
}
}

View File

@@ -0,0 +1,139 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome\Columns;
use Piwik\Cache;
use Piwik\DataTable;
use Piwik\DataTable\Map;
use Piwik\Metrics;
use Piwik\Plugin\Dimension\VisitDimension;
use Piwik\Plugins\VisitsSummary\API as VisitsSummaryApi;
use Piwik\Tracker\Request;
use Piwik\Tracker\Visitor;
use Piwik\Tracker\Action;
/**
* UserId dimension.
*/
class UserId extends VisitDimension
{
/**
* @var string
*/
protected $columnName = 'user_id';
protected $type = self::TYPE_TEXT;
protected $allowAnonymous = false;
protected $segmentName = 'userId';
protected $nameSingular = 'General_UserId';
protected $namePlural = 'General_UserIds';
protected $acceptValues = 'any non empty unique string identifying the user (such as an email address or a username).';
/**
* @var string
*/
protected $columnType = 'VARCHAR(200) NULL';
/**
* @param Request $request
* @param Visitor $visitor
* @param Action|null $action
* @return mixed|false
*/
public function onNewVisit(Request $request, Visitor $visitor, $action)
{
return $request->getForcedUserId();
}
/**
* @param Request $request
* @param Visitor $visitor
* @param Action|null $action
*
* @return mixed|false
*/
public function onExistingVisit(Request $request, Visitor $visitor, $action)
{
return $request->getForcedUserId();
}
public function isUsedInAtLeastOneSite($idSites, $period, $date)
{
if ($period === 'day' || $period === 'week') {
$period = 'month';
}
if ($period === 'range') {
$period = 'day';
}
if (!empty($idSites)) {
foreach ($idSites as $idSite) {
if ($this->isUsedInSiteCached($idSite, $period, $date)) {
return true;
}
}
}
return false;
}
private function isUsedInSiteCached($idSite, $period, $date)
{
$cache = Cache::getTransientCache();
$key = sprintf('%d.%s.%s', $idSite, $period, $date);
if (!$cache->contains($key)) {
$result = $this->isUsedInSite($idSite, $period, $date);
$cache->save($key, $result);
}
return $cache->fetch($key);
}
private function isUsedInSite($idSite, $period, $date)
{
$result = \Piwik\API\Request::processRequest('VisitsSummary.get', [
'columns' => 'nb_users',
'idSite' => $idSite,
'period' => $period,
'date' => $date,
'segment' => false,
], $default = []);
return $this->hasDataTableUsers($result);
}
public function hasDataTableUsers(DataTable\DataTableInterface $result)
{
if ($result instanceof Map) {
foreach ($result->getDataTables() as $table) {
if ($this->hasDataTableUsers($table)) {
return true;
}
}
}
if (!$result->getRowsCount()) {
return false;
}
$firstRow = $result->getFirstRow();
if ($firstRow instanceof DataTable\Row && $firstRow->hasColumn(Metrics::INDEX_NB_USERS)) {
$metric = Metrics::INDEX_NB_USERS;
} else {
$metric = 'nb_users';
}
$numUsers = $result->getColumn($metric);
$numUsers = array_sum($numUsers);
return !empty($numUsers);
}
}

View File

@@ -0,0 +1,45 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome\Columns;
use Piwik\Columns\DimensionMetricFactory;
use Piwik\Columns\MetricsList;
use Piwik\Plugin\Dimension\VisitDimension;
use Piwik\Metrics\Formatter;
require_once PIWIK_INCLUDE_PATH . '/plugins/VisitTime/functions.php';
class VisitFirstActionMinute extends VisitDimension
{
protected $columnName = 'visit_first_action_time';
protected $type = self::TYPE_DATETIME;
protected $sqlSegment = 'HOUR(log_visit.visit_first_action_time)';
protected $segmentName = 'visitStartServerMinute';
protected $acceptValues = '0, 1, 2, 3, ..., 56, 57, 58, 59';
protected $nameSingular = 'VisitTime_ColumnVisitStartServerMinute';
public function __construct()
{
$this->suggestedValuesCallback = function ($idSite, $maxValuesToReturn) {
return range(0, min(59, $maxValuesToReturn));
};
}
public function configureMetrics(MetricsList $metricsList, DimensionMetricFactory $dimensionMetricFactory)
{
// no metrics to be generated
}
public function formatValue($value, $idSite, Formatter $formatter)
{
return $value;
}
}

View File

@@ -0,0 +1,53 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome\Columns;
use Piwik\Date;
use Piwik\Plugin\Dimension\VisitDimension;
use Piwik\Tracker\Action;
use Piwik\Tracker\Request;
use Piwik\Tracker\Visitor;
use Piwik\Metrics\Formatter;
require_once PIWIK_INCLUDE_PATH . '/plugins/VisitTime/functions.php';
class VisitFirstActionTime extends VisitDimension
{
protected $columnName = 'visit_first_action_time';
protected $columnType = 'DATETIME NOT NULL';
protected $type = self::TYPE_DATETIME;
protected $sqlSegment = 'HOUR(log_visit.visit_first_action_time)';
protected $segmentName = 'visitStartServerHour';
protected $acceptValues = '0, 1, 2, 3, ..., 20, 21, 22, 23';
protected $nameSingular = 'VisitTime_ColumnVisitStartServerHour';
public function __construct()
{
$this->suggestedValuesCallback = function ($idSite, $maxValuesToReturn) {
return range(0, min(23, $maxValuesToReturn));
};
}
public function formatValue($value, $idSite, Formatter $formatter)
{
return \Piwik\Plugins\VisitTime\getTimeLabel($value);
}
/**
* @param Request $request
* @param Visitor $visitor
* @param Action|null $action
* @return mixed
*/
public function onNewVisit(Request $request, Visitor $visitor, $action)
{
return Date::getDatetimeFromTimestamp($request->getCurrentTimestamp());
}
}

View File

@@ -0,0 +1,143 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome\Columns;
use Piwik\Metrics\Formatter;
use Piwik\Piwik;
use Piwik\Plugin\Dimension\VisitDimension;
use Piwik\Plugins\CoreHome\Segment;
use Piwik\Tracker\Action;
use Piwik\Tracker\GoalManager;
use Piwik\Tracker\Request;
use Piwik\Tracker\Visitor;
class VisitGoalBuyer extends VisitDimension
{
// log_visit.visit_goal_buyer
const TYPE_BUYER_NONE = 0;
const TYPE_BUYER_ORDERED = 1;
const TYPE_BUYER_OPEN_CART = GoalManager::TYPE_BUYER_OPEN_CART;
const TYPE_BUYER_ORDERED_AND_OPEN_CART = GoalManager::TYPE_BUYER_ORDERED_AND_OPEN_CART;
protected static $visitEcommerceStatus = array(
self::TYPE_BUYER_NONE => 'none',
self::TYPE_BUYER_ORDERED => 'ordered',
self::TYPE_BUYER_OPEN_CART => 'abandonedCart',
self::TYPE_BUYER_ORDERED_AND_OPEN_CART => 'orderedThenAbandonedCart',
);
protected $columnName = 'visit_goal_buyer';
protected $columnType = 'TINYINT(1) NULL';
protected $segmentName = 'visitEcommerceStatus';
protected $nameSingular = 'General_EcommerceVisitStatusDesc';
protected $type = self::TYPE_ENUM;
public function __construct()
{
$example = Piwik::translate('General_EcommerceVisitStatusEg', '"&segment=visitEcommerceStatus==ordered,visitEcommerceStatus==orderedThenAbandonedCart"');
$this->acceptValues = implode(", ", self::$visitEcommerceStatus) . '. ' . $example;
}
public function formatValue($value, $idSite, Formatter $formatter)
{
switch ($value) {
case 'none';
case '0':
case self::TYPE_BUYER_NONE:
return Piwik::translate('UserCountryMap_None');
case 'ordered':
case '1':
case self::TYPE_BUYER_ORDERED:
return Piwik::translate('CoreHome_VisitStatusOrdered');
case 'abandonedCart':
case self::TYPE_BUYER_OPEN_CART:
return Piwik::translate('Goals_AbandonedCart');
case 'orderedThenAbandonedCart':
case self::TYPE_BUYER_ORDERED_AND_OPEN_CART:
return Piwik::translate('CoreHome_VisitStatusOrderedThenAbandoned');
}
return $value;
}
public function getEnumColumnValues()
{
return self::$visitEcommerceStatus;
}
/**
* @param Request $request
* @param Visitor $visitor
* @param Action|null $action
* @return mixed
*/
public function onNewVisit(Request $request, Visitor $visitor, $action)
{
return $this->getBuyerType($request);
}
/**
* @param Request $request
* @param Visitor $visitor
* @param Action|null $action
* @return int
*/
public function onExistingVisit(Request $request, Visitor $visitor, $action)
{
$goalBuyer = $visitor->getVisitorColumn($this->columnName);
// Ecommerce buyer status
$visitEcommerceStatus = $this->getBuyerType($request, $goalBuyer);
if ($visitEcommerceStatus != self::TYPE_BUYER_NONE
// only update if the value has changed (prevents overwriting the value in case a request has
// updated it in the meantime)
&& $visitEcommerceStatus != $goalBuyer) {
return $visitEcommerceStatus;
}
return false;
}
/**
* @ignore
*/
public static function getVisitEcommerceStatusFromId($id)
{
if (!isset(self::$visitEcommerceStatus[$id])) {
throw new \Exception("Unexpected ECommerce status value ");
}
return self::$visitEcommerceStatus[$id];
}
private function getBuyerType(Request $request, $existingType = self::TYPE_BUYER_NONE)
{
$isRequestEcommerce = $request->getMetadata('Ecommerce', 'isRequestEcommerce');
if (!$isRequestEcommerce) {
return $existingType;
}
$isGoalAnOrder = $request->getMetadata('Ecommerce', 'isGoalAnOrder');
if ($isGoalAnOrder) {
return self::TYPE_BUYER_ORDERED;
}
// request is Add to Cart
if ($existingType == self::TYPE_BUYER_ORDERED
|| $existingType == self::TYPE_BUYER_ORDERED_AND_OPEN_CART
) {
return self::TYPE_BUYER_ORDERED_AND_OPEN_CART;
}
return self::TYPE_BUYER_OPEN_CART;
}
}

View File

@@ -0,0 +1,62 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome\Columns;
use Piwik\Columns\DimensionMetricFactory;
use Piwik\Columns\MetricsList;
use Piwik\Piwik;
use Piwik\Plugin\ComputedMetric;
use Piwik\Plugin\Dimension\VisitDimension;
use Piwik\Tracker\Action;
use Piwik\Tracker\Request;
use Piwik\Tracker\Visitor;
class VisitGoalConverted extends VisitDimension
{
protected $columnName = 'visit_goal_converted';
protected $columnType = 'TINYINT(1) NULL';
protected $type = self::TYPE_BOOL;
protected $segmentName = 'visitConverted';
protected $nameSingular = 'General_VisitConvertedGoal';
protected $acceptValues = '0, 1';
public function configureMetrics(MetricsList $metricsList, DimensionMetricFactory $dimensionMetricFactory)
{
$metric1 = $dimensionMetricFactory->createCustomMetric('nb_visits_converted', Piwik::translate('General_ColumnVisitsWithConversions'), 'sum(case %s when 1 then 1 else 0 end)');
$metricsList->addMetric($metric1);
$metric = $dimensionMetricFactory->createComputedMetric($metric1->getName(), 'nb_visits', ComputedMetric::AGGREGATION_RATE);
$metric->setTranslatedName(Piwik::translate('General_ColumnConversionRate'));
$metric->setDocumentation(Piwik::translate('General_ColumnConversionRateDocumentation'));
$metric->setName('visits_conversion_rate');
$metricsList->addMetric($metric);
}
/**
* @param Request $request
* @param Visitor $visitor
* @param Action|null $action
* @return mixed
*/
public function onNewVisit(Request $request, Visitor $visitor, $action)
{
return 0;
}
/**
* @param Request $request
* @param Visitor $visitor
* @param Action|null $action
* @return mixed
*/
public function onConvertedVisit(Request $request, Visitor $visitor, $action)
{
return 1;
}
}

View File

@@ -0,0 +1,48 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\CoreHome\Columns;
use Piwik\Columns\DimensionMetricFactory;
use Piwik\Columns\MetricsList;
use Piwik\Piwik;
use Piwik\Plugin\ArchivedMetric;
use Piwik\Plugin\Dimension\VisitDimension;
use Piwik\Plugin\Segment;
/**
* Dimension for the log_visit.idvisit column. This column is added in the CREATE TABLE
* statement, so this dimension exists only to configure a segment.
*/
class VisitId extends VisitDimension
{
protected $columnName = 'idvisit';
protected $acceptValues = 'Any integer.';
protected $nameSingular = 'General_Visit';
protected $namePlural = 'General_ColumnNbVisits';
protected $segmentName = 'visitId';
protected $allowAnonymous = false;
protected $metricId = 'visits';
protected $type = self::TYPE_TEXT;
protected function configureSegments()
{
$segment = new Segment();
$segment->setName('General_VisitId');
$this->addSegment($segment);
}
public function configureMetrics(MetricsList $metricsList, DimensionMetricFactory $dimensionMetricFactory)
{
$metric = $dimensionMetricFactory->createMetric(ArchivedMetric::AGGREGATION_UNIQUE);
$metric->setTranslatedName(Piwik::translate('General_ColumnNbVisits'));
$metric->setDocumentation(Piwik::translate('General_ColumnNbVisitsDocumentation'));
$metric->setName('nb_visits');
$metricsList->addMetric($metric);
}
}

View File

@@ -0,0 +1,45 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\CoreHome\Columns;
use Piwik\Common;
use Piwik\Metrics\Formatter;
use Piwik\Network\IPUtils;
use Piwik\Plugin\Dimension\VisitDimension;
use Piwik\Plugin\Segment;
/**
* Dimension for the log_visit.location_ip column. This column is added in the CREATE TABLE
* statement, so this dimension exists only to configure a segment.
*/
class VisitIp extends VisitDimension
{
protected $columnName = 'location_ip';
protected $type = self::TYPE_BINARY;
protected $allowAnonymous = false;
protected $segmentName = 'visitIp';
protected $nameSingular = 'General_VisitorIP';
protected $namePlural = 'General_VisitorIPs';
protected $acceptValues = '13.54.122.1. </code>Select IP ranges with notation: <code>visitIp>13.54.122.0;visitIp<13.54.122.255';
protected $sqlFilterValue = array('Piwik\Network\IPUtils', 'stringToBinaryIP');
public function formatValue($value, $idSite, Formatter $formatter)
{
$value = Common::hex2bin($value);
$value = IPUtils::binaryToStringIP($value);
return $value;
}
protected function configureSegments()
{
$segment = new Segment();
$segment->setType(Segment::TYPE_METRIC); // we cannot remove this for now as it would assign dimension based on text type
$this->addSegment($segment);
}
}

View File

@@ -0,0 +1,52 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome\Columns;
use Piwik\Columns\DimensionMetricFactory;
use Piwik\Columns\MetricsList;
use Piwik\Date;
use Piwik\Plugin\Dimension\VisitDimension;
use Piwik\Metrics\Formatter;
class VisitLastActionDate extends VisitDimension
{
protected $columnName = 'visit_last_action_time';
protected $type = self::TYPE_DATETIME;
protected $segmentName = 'visitEndServerDate';
protected $nameSingular = 'VisitTime_ColumnVisitEndServerDate';
protected $sqlSegment = 'DATE(log_visit.visit_last_action_time)';
protected $acceptValues = '2018-12-31, 2018-03-20, ...';
public function __construct()
{
$this->suggestedValuesCallback = function ($idSite, $maxValuesToReturn) {
if (defined('PIWIK_TEST_MODE') && PIWIK_TEST_MODE) {
$date = Date::factory('2018-02-02');
} else {
$date = Date::now();
}
$return = array($date->toString());
for ($i = 0; $i < $maxValuesToReturn; $i++) {
$date = $date->subDay(1);
$return[] = $date->toString();
}
return $return;
};
}
public function configureMetrics(MetricsList $metricsList, DimensionMetricFactory $dimensionMetricFactory)
{
// no metrics for this dimension
}
public function formatValue($value, $idSite, Formatter $formatter)
{
return $value;
}
}

View File

@@ -0,0 +1,41 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome\Columns;
use Piwik\Columns\DimensionMetricFactory;
use Piwik\Columns\MetricsList;
use Piwik\Plugin\Dimension\VisitDimension;
use Piwik\Metrics\Formatter;
class VisitLastActionDayOfMonth extends VisitDimension
{
protected $columnName = 'visit_last_action_time';
protected $type = self::TYPE_DATETIME;
protected $segmentName = 'visitEndServerDayOfMonth';
protected $nameSingular = 'VisitTime_ColumnVisitEndServerDayOfMonth';
protected $sqlSegment = 'DAYOFMONTH(log_visit.visit_last_action_time)';
protected $acceptValues = '0, 1, 2, 3, ..., 29, 30, 31';
public function __construct()
{
$this->suggestedValuesCallback = function ($idSite, $maxValuesToReturn) {
return range(0, min(31, $maxValuesToReturn));
};
}
public function configureMetrics(MetricsList $metricsList, DimensionMetricFactory $dimensionMetricFactory)
{
// no metrics for this dimension
}
public function formatValue($value, $idSite, Formatter $formatter)
{
return $value;
}
}

View File

@@ -0,0 +1,43 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome\Columns;
use Piwik\Columns\DimensionMetricFactory;
use Piwik\Columns\MetricsList;
use Piwik\Plugin\Dimension\VisitDimension;
use Piwik\Metrics\Formatter;
require_once PIWIK_INCLUDE_PATH . '/plugins/VisitTime/functions.php';
class VisitLastActionDayOfWeek extends VisitDimension
{
protected $columnName = 'visit_last_action_time';
protected $type = self::TYPE_DATETIME;
protected $segmentName = 'visitEndServerDayOfWeek';
protected $nameSingular = 'VisitTime_ColumnVisitEndServerDayOfWeek';
protected $sqlSegment = 'DAYOFWEEK(log_visit.visit_last_action_time)';
protected $acceptValues = '1, 2, 3, 4, 5, 6, 7';
public function __construct()
{
$this->suggestedValuesCallback = function ($idSite, $maxValuesToReturn) {
return range(1, min(7, $maxValuesToReturn));
};
}
public function configureMetrics(MetricsList $metricsList, DimensionMetricFactory $dimensionMetricFactory)
{
// no metrics for this dimension
}
public function formatValue($value, $idSite, Formatter $formatter)
{
return \Piwik\Plugins\VisitTime\translateDayOfWeek($value);
}
}

View File

@@ -0,0 +1,41 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome\Columns;
use Piwik\Columns\DimensionMetricFactory;
use Piwik\Columns\MetricsList;
use Piwik\Plugin\Dimension\VisitDimension;
use Piwik\Metrics\Formatter;
class VisitLastActionDayOfYear extends VisitDimension
{
protected $columnName = 'visit_last_action_time';
protected $type = self::TYPE_DATETIME;
protected $segmentName = 'visitEndServerDayOfYear';
protected $nameSingular = 'VisitTime_ColumnVisitEndServerDayOfYear';
protected $sqlSegment = 'DAYOFYEAR(log_visit.visit_last_action_time)';
protected $acceptValues = '1, 2, 3, 4, ..., 365, 366';
public function __construct()
{
$this->suggestedValuesCallback = function ($idSite, $maxValuesToReturn) {
return range(1, min(366, $maxValuesToReturn));
};
}
public function configureMetrics(MetricsList $metricsList, DimensionMetricFactory $dimensionMetricFactory)
{
// no metrics for this dimension
}
public function formatValue($value, $idSite, Formatter $formatter)
{
return $value;
}
}

View File

@@ -0,0 +1,51 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome\Columns;
use Piwik\Columns\DimensionMetricFactory;
use Piwik\Columns\MetricsList;
use Piwik\Plugin\Dimension\VisitDimension;
use Piwik\Metrics\Formatter;
require_once PIWIK_INCLUDE_PATH . '/plugins/VisitTime/functions.php';
/**
* This dimension holds the best guess for a visit's end time. It is set the last action
* time for each visit. `ping=1` requests can be sent to update the dimension value so
* it can be a more accurate guess of the time the visitor spent on the site.
*
* Note: though it is named 'visit last action time' it actually refers to the visit's last action's
* end time.
*/
class VisitLastActionMinute extends VisitDimension
{
protected $columnName = 'visit_last_action_time';
protected $type = self::TYPE_DATETIME;
protected $segmentName = 'visitEndServerMinute';
protected $nameSingular = 'VisitTime_ColumnVisitEndServerMinute';
protected $sqlSegment = 'MINUTE(log_visit.visit_last_action_time)';
protected $acceptValues = '0, 1, 2, 3, ..., 56, 57, 58, 59';
public function __construct()
{
$this->suggestedValuesCallback = function ($idSite, $maxValuesToReturn) {
return range(0, min(59, $maxValuesToReturn));
};
}
public function configureMetrics(MetricsList $metricsList, DimensionMetricFactory $dimensionMetricFactory)
{
// no metrics for this dimension
}
public function formatValue($value, $idSite, Formatter $formatter)
{
return $value;
}
}

View File

@@ -0,0 +1,43 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome\Columns;
use Piwik\Columns\DimensionMetricFactory;
use Piwik\Columns\MetricsList;
use Piwik\Plugin\Dimension\VisitDimension;
use Piwik\Metrics\Formatter;
require_once PIWIK_INCLUDE_PATH . '/plugins/VisitTime/functions.php';
class VisitLastActionMonth extends VisitDimension
{
protected $columnName = 'visit_last_action_time';
protected $type = self::TYPE_DATETIME;
protected $segmentName = 'visitEndServerMonth';
protected $nameSingular = 'VisitTime_ColumnVisitEndServerMonth';
protected $sqlSegment = 'MONTH(log_visit.visit_last_action_time)';
protected $acceptValues = '1, 2, 3, ..., 11, 12';
public function __construct()
{
$this->suggestedValuesCallback = function ($idSite, $maxValuesToReturn) {
return range(1, min(12, $maxValuesToReturn));
};
}
public function configureMetrics(MetricsList $metricsList, DimensionMetricFactory $dimensionMetricFactory)
{
// no metrics for this dimension
}
public function formatValue($value, $idSite, Formatter $formatter)
{
return \Piwik\Plugins\VisitTime\translateMonth($value);
}
}

View File

@@ -0,0 +1,41 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome\Columns;
use Piwik\Columns\DimensionMetricFactory;
use Piwik\Columns\MetricsList;
use Piwik\Plugin\Dimension\VisitDimension;
use Piwik\Metrics\Formatter;
class VisitLastActionQuarter extends VisitDimension
{
protected $columnName = 'visit_last_action_time';
protected $type = self::TYPE_DATETIME;
protected $segmentName = 'visitEndServerQuarter';
protected $nameSingular = 'VisitTime_ColumnVisitEndServerQuarter';
protected $sqlSegment = 'QUARTER(log_visit.visit_last_action_time)';
protected $acceptValues = '1, 2, 3, 4';
public function __construct()
{
$this->suggestedValuesCallback = function ($idSite, $maxValuesToReturn) {
return range(1, min(4, $maxValuesToReturn));
};
}
public function configureMetrics(MetricsList $metricsList, DimensionMetricFactory $dimensionMetricFactory)
{
// no metrics for this dimension
}
public function formatValue($value, $idSite, Formatter $formatter)
{
return $value;
}
}

View File

@@ -0,0 +1,41 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome\Columns;
use Piwik\Columns\DimensionMetricFactory;
use Piwik\Columns\MetricsList;
use Piwik\Plugin\Dimension\VisitDimension;
use Piwik\Metrics\Formatter;
class VisitLastActionSecond extends VisitDimension
{
protected $columnName = 'visit_last_action_time';
protected $type = self::TYPE_DATETIME;
protected $segmentName = 'visitEndServerSecond';
protected $nameSingular = 'VisitTime_ColumnVisitEndServerSecond';
protected $sqlSegment = 'SECOND(log_visit.visit_last_action_time)';
protected $acceptValues = '0, 1, 2, 3, ..., 58, 59';
public function __construct()
{
$this->suggestedValuesCallback = function ($idSite, $maxValuesToReturn) {
return range(0, min(59, $maxValuesToReturn));
};
}
public function configureMetrics(MetricsList $metricsList, DimensionMetricFactory $dimensionMetricFactory)
{
// no metrics for this dimension
}
public function formatValue($value, $idSite, Formatter $formatter)
{
return $value;
}
}

View File

@@ -0,0 +1,73 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome\Columns;
use Piwik\Date;
use Piwik\Plugin\Dimension\VisitDimension;
use Piwik\Tracker\Action;
use Piwik\Tracker\Request;
use Piwik\Tracker\Visitor;
use Piwik\Metrics\Formatter;
require_once PIWIK_INCLUDE_PATH . '/plugins/VisitTime/functions.php';
/**
* This dimension holds the best guess for a visit's end time. It is set the last action
* time for each visit. `ping=1` requests can be sent to update the dimension value so
* it can be a more accurate guess of the time the visitor spent on the site.
*
* Note: though it is named 'visit last action time' it actually refers to the visit's last action's
* end time.
*/
class VisitLastActionTime extends VisitDimension
{
protected $columnName = 'visit_last_action_time';
protected $type = self::TYPE_DATETIME;
protected $nameSingular = 'VisitTime_ColumnVisitEndServerHour';
protected $sqlSegment = 'HOUR(log_visit.visit_last_action_time)';
protected $segmentName = 'visitServerHour';
protected $acceptValues = '0, 1, 2, 3, ..., 20, 21, 22, 23';
public function formatValue($value, $idSite, Formatter $formatter)
{
return \Piwik\Plugins\VisitTime\getTimeLabel($value);
}
// we do not install or define column definition here as we need to create this column when installing as there is
// an index on it. Currently we do not define the index here... although we could overwrite the install() method
// and add column 'visit_last_action_time' and add index. Problem is there is also an index
// INDEX(idsite, config_id, visit_last_action_time) and we maybe not be sure whether idsite already exists at
// installing point (we do not know whether idsite column will be added first).
/**
* @param Request $request
* @param Visitor $visitor
* @param Action|null $action
* @return mixed
*/
public function onNewVisit(Request $request, Visitor $visitor, $action)
{
return Date::getDatetimeFromTimestamp($request->getCurrentTimestamp());
}
/**
* @param Request $request
* @param Visitor $visitor
* @param Action|null $action
* @return mixed
*/
public function onExistingVisit(Request $request, Visitor $visitor, $action)
{
if ($request->getParam('ping') == 1) {
return false;
}
return $this->onNewVisit($request, $visitor, $action);
}
}

View File

@@ -0,0 +1,41 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome\Columns;
use Piwik\Columns\DimensionMetricFactory;
use Piwik\Columns\MetricsList;
use Piwik\Plugin\Dimension\VisitDimension;
use Piwik\Metrics\Formatter;
class VisitLastActionWeekOfYear extends VisitDimension
{
protected $columnName = 'visit_last_action_time';
protected $type = self::TYPE_DATETIME;
protected $segmentName = 'visitEndServerWeekOfYear';
protected $nameSingular = 'VisitTime_ColumnVisitEndServerWeekOfYear';
protected $sqlSegment = 'WEEKOFYEAR(log_visit.visit_last_action_time)';
protected $acceptValues = '1, 2, 3, 4, ..., 51, 52, 53';
public function __construct()
{
$this->suggestedValuesCallback = function ($idSite, $maxValuesToReturn) {
return range(1, min(53, $maxValuesToReturn));
};
}
public function configureMetrics(MetricsList $metricsList, DimensionMetricFactory $dimensionMetricFactory)
{
// no metrics for this dimension
}
public function formatValue($value, $idSite, Formatter $formatter)
{
return $value;
}
}

View File

@@ -0,0 +1,41 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome\Columns;
use Piwik\Columns\DimensionMetricFactory;
use Piwik\Columns\MetricsList;
use Piwik\Plugin\Dimension\VisitDimension;
use Piwik\Metrics\Formatter;
class VisitLastActionYear extends VisitDimension
{
protected $columnName = 'visit_last_action_time';
protected $type = self::TYPE_DATETIME;
protected $segmentName = 'visitEndServerYear';
protected $nameSingular = 'VisitTime_ColumnVisitEndServerYear';
protected $sqlSegment = 'YEAR(log_visit.visit_last_action_time)';
protected $acceptValues = '2016, 2017, 2018, ..., 9998, 9999';
public function __construct()
{
$this->suggestedValuesCallback = function ($idSite, $maxValuesToReturn) {
return range(2016, 2016 + $maxValuesToReturn);
};
}
public function configureMetrics(MetricsList $metricsList, DimensionMetricFactory $dimensionMetricFactory)
{
// no metrics for this dimension
}
public function formatValue($value, $idSite, Formatter $formatter)
{
return $value;
}
}

View File

@@ -0,0 +1,96 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome\Columns;
use Piwik\Config;
use Piwik\Plugin\Dimension\VisitDimension;
use Piwik\Plugins\CoreHome\Segment;
use Piwik\Tracker\Action;
use Piwik\Tracker\Request;
use Piwik\Tracker\Visitor;
class VisitTotalTime extends VisitDimension
{
protected $columnName = 'visit_total_time';
protected $columnType = 'INT(11) UNSIGNED NOT NULL';
protected $segmentName = 'visitDuration';
protected $nameSingular = 'General_ColumnVisitDuration';
protected $type = self::TYPE_DURATION_S;
/**
* @param Request $request
* @param Visitor $visitor
* @param Action|null $action
* @return mixed
*/
public function onNewVisit(Request $request, Visitor $visitor, $action)
{
$totalTime = Config::getInstance()->Tracker['default_time_one_page_visit'];
$totalTime = $this->cleanupVisitTotalTime($totalTime);
return $totalTime;
}
/**
* @param Request $request
* @param Visitor $visitor
* @param Action|null $action
* @return mixed
*/
public function onExistingVisit(Request $request, Visitor $visitor, $action)
{
$firstActionTime = $visitor->getVisitorColumn('visit_first_action_time');
$totalTime = 1 + $request->getCurrentTimestamp() - $firstActionTime;
$totalTime = $this->cleanupVisitTotalTime($totalTime);
return $totalTime;
}
/**
* @param Request $request
* @param Visitor $visitor
* @param Action|null $action
* @return int
*/
public function onConvertedVisit(Request $request, Visitor $visitor, $action)
{
if (!$visitor->isVisitorKnown()) {
return false;
}
$totalTime = $visitor->getVisitorColumn('visit_total_time');
// If a pageview and goal conversion in the same second, with previously a goal conversion recorded
// the request would not "update" the row since all values are the same as previous
// therefore the request below throws exception, instead we make sure the UPDATE will affect the row
$totalTime = $totalTime + $request->getParam('idgoal');
// +2 to offset idgoal=-1 and idgoal=0
$totalTime = $totalTime + 2;
return $this->cleanupVisitTotalTime($totalTime);
}
public function getRequiredVisitFields()
{
return array('visit_first_action_time');
}
private function cleanupVisitTotalTime($t)
{
$t = (int)$t;
if ($t < 0) {
$t = 0;
}
return $t;
}
}

View File

@@ -0,0 +1,50 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome\Columns;
use Piwik\Plugin\Dimension\VisitDimension;
use Piwik\Tracker\Action;
use Piwik\Tracker\Request;
use Piwik\Tracker\Visitor;
class VisitorDaysSinceFirst extends VisitDimension
{
protected $columnName = 'visitor_days_since_first';
protected $columnType = 'SMALLINT(5) UNSIGNED NULL';
protected $segmentName = 'daysSinceFirstVisit';
protected $nameSingular = 'General_DaysSinceFirstVisit';
protected $type = self::TYPE_NUMBER;
/**
* @param Request $request
* @param Visitor $visitor
* @param Action|null $action
* @return mixed
*/
public function onNewVisit(Request $request, Visitor $visitor, $action)
{
// if the visitor is new, force days since first to 0, to ignore any potential bad values for _idts
if (!$visitor->isVisitorKnown()) {
return 0;
}
return $request->getDaysSinceFirstVisit();
}
/**
* @param Request $request
* @param Visitor $visitor
* @param Action|null $action
* @return mixed
*/
public function onAnyGoalConversion(Request $request, Visitor $visitor, $action)
{
return $visitor->getVisitorColumn($this->columnName);
}
}

View File

@@ -0,0 +1,52 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome\Columns;
use Piwik\Plugin\Dimension\VisitDimension;
use Piwik\Tracker\Action;
use Piwik\Tracker\Request;
use Piwik\Tracker\Visitor;
class VisitorDaysSinceOrder extends VisitDimension
{
protected $columnName = 'visitor_days_since_order';
protected $columnType = 'SMALLINT(5) UNSIGNED NULL';
protected $segmentName = 'daysSinceLastEcommerceOrder';
protected $nameSingular = 'General_DaysSinceLastEcommerceOrder';
protected $category = 'General_Visitors'; // todo put into ecommerce category?
protected $type = self::TYPE_NUMBER;
/**
* @param Request $request
* @param Visitor $visitor
* @param Action|null $action
* @return mixed
*/
public function onNewVisit(Request $request, Visitor $visitor, $action)
{
$daysSinceLastOrder = $request->getDaysSinceLastOrder();
if ($daysSinceLastOrder === false) {
$daysSinceLastOrder = 0;
}
return $daysSinceLastOrder;
}
/**
* @param Request $request
* @param Visitor $visitor
* @param Action|null $action
* @return mixed
*/
public function onAnyGoalConversion(Request $request, Visitor $visitor, $action)
{
return $visitor->getVisitorColumn($this->columnName);
}
}

View File

@@ -0,0 +1,41 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\CoreHome\Columns;
use Piwik\Columns\DimensionMetricFactory;
use Piwik\Columns\MetricsList;
use Piwik\Metrics\Formatter;
use Piwik\Piwik;
use Piwik\Plugin\ArchivedMetric;
use Piwik\Plugin\Dimension\VisitDimension;
/**
* Dimension for the log_visit.idvisitor column. This column is added in the CREATE TABLE
* statement, so this dimension exists only to configure a segment.
*/
class VisitorId extends VisitDimension
{
protected $columnName = 'idvisitor';
protected $metricId = 'visitors';
protected $nameSingular = 'General_VisitorID';
protected $namePlural = 'General_Visitors';
protected $segmentName = 'visitorId';
protected $acceptValues = '34c31e04394bdc63 - any 16 Hexadecimal chars ID, which can be fetched using the Tracking API function getVisitorId()';
protected $allowAnonymous = false;
protected $sqlFilterValue = array('Piwik\Common', 'convertVisitorIdToBin');
protected $type = self::TYPE_BINARY;
public function configureMetrics(MetricsList $metricsList, DimensionMetricFactory $dimensionMetricFactory)
{
$metric = $dimensionMetricFactory->createMetric(ArchivedMetric::AGGREGATION_UNIQUE);
$metric->setTranslatedName(Piwik::translate('General_ColumnNbUniqVisitors'));
$metric->setName('nb_uniq_visitors');
$metricsList->addMetric($metric);
}
}

View File

@@ -0,0 +1,102 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome\Columns;
use Piwik\Metrics\Formatter;
use Piwik\Piwik;
use Piwik\Plugin\Dimension\VisitDimension;
use Piwik\Tracker\Action;
use Piwik\Tracker\Request;
use Piwik\Tracker\Visitor;
class VisitorReturning extends VisitDimension
{
const IS_RETURNING_CUSTOMER = 2;
const IS_RETURNING = 1;
const IS_NEW = 0;
protected $columnName = 'visitor_returning';
protected $columnType = 'TINYINT(1) NULL';
protected $segmentName = 'visitorType';
protected $nameSingular = 'General_VisitType';
protected $namePlural = 'General_VisitTypes';
protected $conversionField = true;
protected $type = self::TYPE_ENUM;
public function __construct()
{
$this->acceptValues = 'new, returning, returningCustomer. ';
$this->acceptValues .= Piwik::translate('General_VisitTypeExample', '"&segment=visitorType==returning,visitorType==returningCustomer"');
$this->sqlFilterValue = function ($type) {
if (is_numeric($type)) {
return $type;
}
return $type == "new" ? 0 : ($type == "returning" ? 1 : 2);
};
}
public function formatValue($value, $idSite, Formatter $formatter)
{
if ($value === 1 || $value === '1' || $value === 'returning') {
return Piwik::translate('CoreHome_VisitTypeReturning');
} elseif ($value === 2 || $value === '2' || $value === 'returningCustomer'){
return Piwik::translate('CoreHome_VisitTypeReturningCustomer');
} elseif ($value === 0 || $value === '0' || $value === 'new'){
return Piwik::translate('General_New');
}
return $value;
}
public function getEnumColumnValues()
{
return array(
self::IS_RETURNING_CUSTOMER => 'returningCustomer',
self::IS_RETURNING => 'returning',
self::IS_NEW => 'new',
);
}
/**
* @param Request $request
* @param Visitor $visitor
* @param Action|null $action
* @return mixed
*/
public function onNewVisit(Request $request, Visitor $visitor, $action)
{
$daysSinceLastOrder = $request->getDaysSinceLastOrder();
$isReturningCustomer = ($daysSinceLastOrder !== false);
if ($isReturningCustomer) {
return self::IS_RETURNING_CUSTOMER;
}
$visitCount = $request->getVisitCount();
$daysSinceFirstVisit = $request->getDaysSinceFirstVisit();
$daysSinceLastVisit = $request->getDaysSinceLastVisit();
if ($visitCount > 1 || $visitor->isVisitorKnown() || $daysSinceFirstVisit > 0 || $daysSinceLastVisit > 0) {
return self::IS_RETURNING;
}
return self::IS_NEW;
}
/**
* @param Request $request
* @param Visitor $visitor
* @param Action|null $action
* @return mixed
*/
public function onAnyGoalConversion(Request $request, Visitor $visitor, $action)
{
return $visitor->getVisitorColumn($this->columnName);
}
}

View File

@@ -0,0 +1,52 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome\Columns;
use Piwik\Columns\DimensionMetricFactory;
use Piwik\Columns\MetricsList;
use Piwik\Plugin\Dimension\VisitDimension;
use Piwik\Tracker\Action;
use Piwik\Tracker\Request;
use Piwik\Tracker\Visitor;
class VisitsCount extends VisitDimension
{
protected $columnName = 'visitor_count_visits';
protected $columnType = 'INT(11) UNSIGNED NOT NULL';
protected $segmentName = 'visitCount';
protected $nameSingular = 'General_NumberOfVisits';
protected $type = self::TYPE_NUMBER;
public function configureMetrics(MetricsList $metricsList, DimensionMetricFactory $dimensionMetricFactory)
{
// no metrics for this dimension, it would be rather confusing I think
}
/**
* @param Request $request
* @param Visitor $visitor
* @param Action|null $action
* @return mixed
*/
public function onNewVisit(Request $request, Visitor $visitor, $action)
{
return $request->getVisitCount();
}
/**
* @param Request $request
* @param Visitor $visitor
* @param Action|null $action
* @return mixed
*/
public function onAnyGoalConversion(Request $request, Visitor $visitor, $action)
{
return $visitor->getVisitorColumn($this->columnName);
}
}

View File

@@ -0,0 +1,312 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome;
use Exception;
use Piwik\API\Request;
use Piwik\Common;
use Piwik\Date;
use Piwik\FrontController;
use Piwik\Notification\Manager as NotificationManager;
use Piwik\Piwik;
use Piwik\Plugin\Report;
use Piwik\Widget\Widget;
use Piwik\Plugins\CoreHome\DataTableRowAction\MultiRowEvolution;
use Piwik\Plugins\CoreHome\DataTableRowAction\RowEvolution;
use Piwik\Plugins\Dashboard\DashboardManagerControl;
use Piwik\Plugins\UsersManager\API;
use Piwik\Site;
use Piwik\Translation\Translator;
use Piwik\UpdateCheck;
use Piwik\Url;
use Piwik\View;
use Piwik\ViewDataTable\Manager as ViewDataTableManager;
use Piwik\Widget\WidgetConfig;
class Controller extends \Piwik\Plugin\Controller
{
/**
* @var Translator
*/
private $translator;
public function __construct(Translator $translator)
{
$this->translator = $translator;
parent::__construct();
}
public function getDefaultAction()
{
return 'redirectToCoreHomeIndex';
}
public function renderReportWidget(Report $report)
{
Piwik::checkUserHasSomeViewAccess();
$this->checkSitePermission();
$report->checkIsEnabled();
return $report->render();
}
/**
* This is only used for exported widgets
* @return string
* @throws Exception
* @throws \Piwik\NoAccessException
*/
public function renderWidgetContainer()
{
Piwik::checkUserHasSomeViewAccess();
$this->checkSitePermission();
$view = new View('@CoreHome/widgetContainer');
$view->isWidgetized = (bool) Common::getRequestVar('widget', 0, 'int');
$view->containerId = Common::getRequestVar('containerId', null, 'string');
return $view->render();
}
/**
* @param Widget $widget
* @return mixed
* @throws Exception
*/
public function renderWidget($widget)
{
Piwik::checkUserHasSomeViewAccess();
$config = new WidgetConfig();
$widget::configure($config);
$content = $widget->render();
if ($config->getName() && Common::getRequestVar('showtitle', '', 'string') === '1') {
if (strpos($content, '<h2') !== false
|| strpos($content, ' content-title=') !== false
|| strpos($content, ' piwik-enriched-headline') !== false
|| strpos($content, '<h1') !== false ) {
// already includes title
return $content;
}
if (strpos($content, 'piwik-content-block') === false
&& strpos($content, 'class="card"') === false
&& strpos($content, "class='card'") === false
&& strpos($content, 'class="card-content"') === false
&& strpos($content, "class='card-content'") === false) {
$view = new View('@CoreHome/_singleWidget');
$view->title = $config->getName();
$view->content = $content;
return $view->render();
}
}
return $content;
}
function redirectToCoreHomeIndex()
{
$defaultReport = API::getInstance()->getUserPreference(Piwik::getCurrentUserLogin(), API::PREFERENCE_DEFAULT_REPORT);
$module = 'CoreHome';
$action = 'index';
// User preference: default report to load is the All Websites dashboard
if ($defaultReport == 'MultiSites'
&& \Piwik\Plugin\Manager::getInstance()->isPluginActivated('MultiSites')
) {
$module = 'MultiSites';
}
if ($defaultReport == Piwik::getLoginPluginName()) {
$module = Piwik::getLoginPluginName();
}
parent::redirectToIndex($module, $action, $this->idSite);
}
public function showInContext()
{
$controllerName = Common::getRequestVar('moduleToLoad');
$actionName = Common::getRequestVar('actionToLoad', 'index');
if($controllerName == 'API') {
throw new Exception("Showing API requests in context is not supported for security reasons. Please change query parameter 'moduleToLoad'.");
}
if ($actionName == 'showInContext') {
throw new Exception("Preventing infinite recursion...");
}
$view = $this->getDefaultIndexView();
$view->content = FrontController::getInstance()->fetchDispatch($controllerName, $actionName);
return $view->render();
}
public function markNotificationAsRead()
{
$notificationId = Common::getRequestVar('notificationId');
NotificationManager::cancel($notificationId);
}
protected function getDefaultIndexView()
{
$view = new View('@CoreHome/getDefaultIndexView');
$this->setGeneralVariablesView($view);
$view->showMenu = true;
$view->dashboardSettingsControl = new DashboardManagerControl();
$view->content = '';
return $view;
}
protected function setDateTodayIfWebsiteCreatedToday()
{
$date = Common::getRequestVar('date', false);
if ($date == 'today'
|| Common::getRequestVar('period', false) == 'range'
) {
return;
}
if ($this->site) {
$datetimeCreationDate = $this->site->getCreationDate()->getDatetime();
$creationDateLocalTimezone = Date::factory($datetimeCreationDate, $this->site->getTimezone())->toString('Y-m-d');
$todayLocalTimezone = Date::factory('now', $this->site->getTimezone())->toString('Y-m-d');
if ($creationDateLocalTimezone == $todayLocalTimezone) {
Piwik::redirectToModule('CoreHome', 'index',
array('date' => 'today',
'idSite' => $this->idSite,
'period' => Common::getRequestVar('period'))
);
}
}
}
public function index()
{
$this->setDateTodayIfWebsiteCreatedToday();
$view = $this->getDefaultIndexView();
return $view->render();
}
// --------------------------------------------------------
// ROW EVOLUTION
// The following methods render the popover that shows the
// evolution of a singe or multiple rows in a data table
// --------------------------------------------------------
/** Render the entire row evolution popover for a single row */
public function getRowEvolutionPopover()
{
$rowEvolution = $this->makeRowEvolution($isMulti = false);
$view = new View('@CoreHome/getRowEvolutionPopover');
return $rowEvolution->renderPopover($this, $view);
}
/** Render the entire row evolution popover for multiple rows */
public function getMultiRowEvolutionPopover()
{
$rowEvolution = $this->makeRowEvolution($isMulti = true);
$view = new View('@CoreHome/getMultiRowEvolutionPopover');
return $rowEvolution->renderPopover($this, $view);
}
/** Generic method to get an evolution graph or a sparkline for the row evolution popover */
public function getRowEvolutionGraph($fetch = false, $rowEvolution = null)
{
if (empty($rowEvolution)) {
$label = Common::getRequestVar('label', '', 'string');
$isMultiRowEvolution = strpos($label, ',') !== false;
$rowEvolution = $this->makeRowEvolution($isMultiRowEvolution, $graphType = 'graphEvolution');
$rowEvolution->useAvailableMetrics();
}
$view = $rowEvolution->getRowEvolutionGraph();
return $this->renderView($view);
}
/** Utility function. Creates a RowEvolution instance. */
private function makeRowEvolution($isMultiRowEvolution, $graphType = null)
{
if ($isMultiRowEvolution) {
return new MultiRowEvolution($this->idSite, $this->date, $graphType);
} else {
return new RowEvolution($this->idSite, $this->date, $graphType);
}
}
/**
* Forces a check for updates and re-renders the header message.
*
* This will check piwik.org at most once per 10s.
*/
public function checkForUpdates()
{
Piwik::checkUserHasSomeAdminAccess();
$this->checkTokenInUrl();
// perform check (but only once every 10s)
UpdateCheck::check($force = false, UpdateCheck::UI_CLICK_CHECK_INTERVAL);
$view = new View('@CoreHome/checkForUpdates');
$this->setGeneralVariablesView($view);
return $view->render();
}
/**
* Redirects the user to a paypal so they can donate to Piwik.
*/
public function redirectToPaypal()
{
$parameters = Request::getRequestArrayFromString($request = null);
foreach ($parameters as $name => $param) {
if ($name == 'idSite'
|| $name == 'module'
|| $name == 'action'
) {
unset($parameters[$name]);
}
}
$paypalParameters = [
"cmd" => "_s-xclick"
];
if (empty($parameters["onetime"]) || $parameters["onetime"] != "true") {
$paypalParameters["hosted_button_id"] = "DVKLY73RS7JTE";
$paypalParameters["currency_code"] = "USD";
$paypalParameters["on0"] = "Piwik Supporter";
if (!empty($parameters["os0"])) {
$paypalParameters["os0"] = $parameters["os0"];
}
} else {
$paypalParameters["hosted_button_id"] = "RPL23NJURMTFA";
}
$url = "https://www.paypal.com/cgi-bin/webscr?" . Url::getQueryStringFromParameters($paypalParameters);
Url::redirectToUrl($url);
exit;
}
public function saveViewDataTableParameters()
{
Piwik::checkUserIsNotAnonymous();
$this->checkTokenInUrl();
$reportId = Common::getRequestVar('report_id', null, 'string');
$parameters = (array) Common::getRequestVar('parameters', null, 'json');
$login = Piwik::getCurrentUserLogin();
$containerId = Common::getRequestVar('containerId', '', 'string');
ViewDataTableManager::saveViewDataTableParameters($login, $reportId, $parameters, $containerId);
}
}

View File

@@ -0,0 +1,463 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome;
use Piwik\Columns\ComputedMetricFactory;
use Piwik\Columns\MetricsList;
use Piwik\IP;
use Piwik\Piwik;
use Piwik\Plugin\ArchivedMetric;
use Piwik\Plugin\ComputedMetric;
use Piwik\Plugin\ThemeStyles;
use Piwik\SettingsServer;
/**
*
*/
class CoreHome extends \Piwik\Plugin
{
/**
* Defines a widget container layout that will display all widgets within a container inside a "tab" menu
* where on the left side a link is shown for each widget and on the right side the selected widget.
* @api
*/
const WIDGET_CONTAINER_LAYOUT_BY_DIMENSION = 'ByDimension';
/**
* @see \Piwik\Plugin::registerEvents
*/
public function registerEvents()
{
return array(
'AssetManager.getStylesheetFiles' => 'getStylesheetFiles',
'AssetManager.getJavaScriptFiles' => 'getJsFiles',
'AssetManager.filterMergedJavaScripts' => 'filterMergedJavaScripts',
'Translate.getClientSideTranslationKeys' => 'getClientSideTranslationKeys',
'Metric.addComputedMetrics' => 'addComputedMetrics',
'Request.initAuthenticationObject' => 'initAuthenticationObject',
'AssetManager.addStylesheets' => 'addStylesheets',
'Request.dispatchCoreAndPluginUpdatesScreen' => 'initAuthenticationObject',
);
}
public function addStylesheets(&$mergedContent)
{
$themeStyles = ThemeStyles::get();
$mergedContent = $themeStyles->toLessCode() . "\n" . $mergedContent;
}
public function initAuthenticationObject()
{
$isApi = Piwik::getModule() === 'API' && (Piwik::getAction() == '' || Piwik::getAction() == 'index');
if (!SettingsServer::isTrackerApiRequest() && $isApi) {
// will be checked in API itself to make sure we return an API response in the proper format.
return;
}
$whitelist = new LoginWhitelist();
if ($whitelist->shouldCheckWhitelist()) {
$ip = IP::getIpFromHeader();
$whitelist->checkIsWhitelisted($ip);
}
}
public function addComputedMetrics(MetricsList $list, ComputedMetricFactory $computedMetricFactory)
{
$metrics = $list->getMetrics();
foreach ($metrics as $metric) {
if ($metric instanceof ArchivedMetric && $metric->getDimension()) {
$metricName = $metric->getName();
if ($metric->getDbTableName() === 'log_visit'
&& $metricName !== 'nb_uniq_visitors'
&& $metricName !== 'nb_visits'
&& strpos($metricName, ArchivedMetric::AGGREGATION_SUM_PREFIX) === 0) {
$metric = $computedMetricFactory->createComputedMetric($metric->getName(), 'nb_visits', ComputedMetric::AGGREGATION_AVG);
$list->addMetric($metric);
}
}
}
}
public function filterMergedJavaScripts(&$mergedContent)
{
$mergedContent = preg_replace('/(sourceMappingURL=(.*?).map)/', '', $mergedContent);
}
public function getStylesheetFiles(&$stylesheets)
{
$stylesheets[] = "libs/jquery/themes/base/jquery-ui.min.css";
$stylesheets[] = "libs/bower_components/materialize/dist/css/materialize.min.css";
$stylesheets[] = "libs/jquery/stylesheets/jquery.jscrollpane.css";
$stylesheets[] = "libs/jquery/stylesheets/scroll.less";
$stylesheets[] = "libs/bower_components/ngDialog/css/ngDialog.min.css";
$stylesheets[] = "libs/bower_components/ngDialog/css/ngDialog-theme-default.min.css";
$stylesheets[] = "plugins/Morpheus/stylesheets/base/bootstrap.css";
$stylesheets[] = "plugins/Morpheus/stylesheets/base/icons.css";
$stylesheets[] = "plugins/Morpheus/stylesheets/base.less";
$stylesheets[] = "plugins/Morpheus/stylesheets/main.less";
$stylesheets[] = "plugins/CoreHome/stylesheets/coreHome.less";
$stylesheets[] = "plugins/CoreHome/stylesheets/dataTable.less";
$stylesheets[] = "plugins/CoreHome/stylesheets/cloud.less";
$stylesheets[] = "plugins/CoreHome/stylesheets/jquery.ui.autocomplete.css";
$stylesheets[] = "plugins/CoreHome/stylesheets/jqplotColors.less";
$stylesheets[] = "plugins/CoreHome/stylesheets/sparklineColors.less";
$stylesheets[] = "plugins/CoreHome/stylesheets/promo.less";
$stylesheets[] = "plugins/CoreHome/stylesheets/color_manager.css";
$stylesheets[] = "plugins/CoreHome/stylesheets/sparklineColors.less";
$stylesheets[] = "plugins/CoreHome/stylesheets/notification.less";
$stylesheets[] = "plugins/CoreHome/stylesheets/zen-mode.less";
$stylesheets[] = "plugins/CoreHome/stylesheets/layout.less";
$stylesheets[] = "plugins/CoreHome/angularjs/enrichedheadline/enrichedheadline.directive.less";
$stylesheets[] = "plugins/CoreHome/angularjs/dialogtoggler/ngdialog.less";
$stylesheets[] = "plugins/CoreHome/angularjs/notification/notification.directive.less";
$stylesheets[] = "plugins/CoreHome/angularjs/quick-access/quick-access.directive.less";
$stylesheets[] = "plugins/CoreHome/angularjs/selector/selector.directive.less";
$stylesheets[] = "plugins/CoreHome/angularjs/reporting-page/reportingpage.directive.less";
$stylesheets[] = "plugins/CoreHome/angularjs/report-export/reportexport.popover.less";
$stylesheets[] = "plugins/CoreHome/angularjs/widget-bydimension-container/widget-bydimension-container.directive.less";
$stylesheets[] = "plugins/CoreHome/angularjs/progressbar/progressbar.directive.less";
$stylesheets[] = "plugins/CoreHome/angularjs/date-range-picker/date-range-picker.component.less";
$stylesheets[] = "plugins/CoreHome/angularjs/period-date-picker/period-date-picker.component.less";
$stylesheets[] = "plugins/CoreHome/angularjs/period-selector/period-selector.directive.less";
$stylesheets[] = "plugins/CoreHome/angularjs/multipairfield/multipairfield.directive.less";
$stylesheets[] = "plugins/CoreHome/angularjs/dropdown-menu/dropdown-menu.directive.less";
$stylesheets[] = "plugins/CoreHome/angularjs/sparkline/sparkline.component.less";
$stylesheets[] = "plugins/CoreHome/angularjs/field-array/field-array.directive.less";
}
public function getJsFiles(&$jsFiles)
{
$jsFiles[] = "libs/bower_components/jquery/dist/jquery.min.js";
$jsFiles[] = "libs/bower_components/jquery-ui/ui/minified/jquery-ui.min.js";
$jsFiles[] = "libs/bower_components/materialize/dist/js/materialize.min.js";
$jsFiles[] = "libs/jquery/jquery.browser.js";
$jsFiles[] = "libs/jquery/jquery.truncate.js";
$jsFiles[] = "libs/bower_components/jquery.scrollTo/jquery.scrollTo.min.js";
$jsFiles[] = "libs/bower_components/jScrollPane/script/jquery.jscrollpane.min.js";
$jsFiles[] = "libs/bower_components/jquery-mousewheel/jquery.mousewheel.min.js";
$jsFiles[] = "libs/jquery/mwheelIntent.js";
$jsFiles[] = "libs/bower_components/sprintf/dist/sprintf.min.js";
$jsFiles[] = "libs/bower_components/mousetrap/mousetrap.min.js";
$jsFiles[] = "libs/bower_components/angular/angular.min.js";
$jsFiles[] = "libs/bower_components/angular-sanitize/angular-sanitize.js";
$jsFiles[] = "libs/bower_components/angular-animate/angular-animate.js";
$jsFiles[] = "libs/bower_components/angular-cookies/angular-cookies.js";
$jsFiles[] = "libs/bower_components/ngDialog/js/ngDialog.min.js";
$jsFiles[] = "plugins/Morpheus/javascripts/piwikHelper.js";
$jsFiles[] = "plugins/Morpheus/javascripts/ajaxHelper.js";
$jsFiles[] = "plugins/Morpheus/javascripts/layout.js";
$jsFiles[] = "plugins/CoreHome/javascripts/require.js";
$jsFiles[] = "plugins/CoreHome/javascripts/uiControl.js";
$jsFiles[] = "plugins/CoreHome/javascripts/dataTable.js";
$jsFiles[] = "plugins/CoreHome/javascripts/dataTable_rowactions.js";
$jsFiles[] = "plugins/CoreHome/javascripts/popover.js";
$jsFiles[] = "plugins/CoreHome/javascripts/broadcast.js";
$jsFiles[] = "plugins/CoreHome/javascripts/calendar.js";
$jsFiles[] = "plugins/CoreHome/javascripts/sparkline.js";
$jsFiles[] = "plugins/CoreHome/javascripts/corehome.js";
$jsFiles[] = "plugins/CoreHome/javascripts/top_controls.js";
$jsFiles[] = "plugins/CoreHome/javascripts/donate.js";
$jsFiles[] = "libs/jqplot/jqplot-custom.min.js";
$jsFiles[] = "plugins/CoreHome/javascripts/color_manager.js";
$jsFiles[] = "plugins/CoreHome/javascripts/notification.js";
$jsFiles[] = "plugins/CoreHome/javascripts/numberFormatter.js";
$jsFiles[] = "plugins/CoreHome/javascripts/zen-mode.js";
$jsFiles[] = "plugins/CoreHome/javascripts/noreferrer.js";
$jsFiles[] = "plugins/CoreHome/angularjs/piwikApp.config.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/services/service.module.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/services/global-ajax-queue.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/services/piwik.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/services/piwik-api.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/services/piwik-url.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/services/report-metadata-model.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/services/reporting-pages-model.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/services/periods.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/filters/filter.module.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/filters/translate.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/filters/startfrom.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/filters/evolution.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/filters/length.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/filters/trim.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/filters/pretty-url.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/filters/escape.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/filters/htmldecode.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/filters/ucfirst.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/directives/directive.module.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/directives/attributes.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/directives/field-condition.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/directives/show-sensitive-data.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/directives/autocomplete-matched.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/directives/focus-anywhere-but-here.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/directives/ignore-click.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/directives/onenter.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/directives/focusif.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/directives/dialog.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/directives/translate.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/directives/dropdown-button.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/directives/select-on-focus.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/directives/side-nav.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/directives/string-to-number.js";
$jsFiles[] = "plugins/CoreHome/angularjs/piwikApp.js";
$jsFiles[] = "plugins/CoreHome/angularjs/anchorLinkFix.js";
$jsFiles[] = "plugins/CoreHome/angularjs/http404check.js";
$jsFiles[] = "plugins/CoreHome/angularjs/history/history.service.js";
$jsFiles[] = "plugins/CoreHome/angularjs/activity-indicator/activityindicator.directive.js";
$jsFiles[] = "plugins/CoreHome/angularjs/progressbar/progressbar.directive.js";
$jsFiles[] = "plugins/CoreHome/angularjs/alert/alert.directive.js";
$jsFiles[] = "plugins/CoreHome/angularjs/sparkline/sparkline.component.js";
$jsFiles[] = "plugins/CoreHome/angularjs/siteselector/siteselector-model.service.js";
$jsFiles[] = "plugins/CoreHome/angularjs/siteselector/siteselector.controller.js";
$jsFiles[] = "plugins/CoreHome/angularjs/siteselector/siteselector.directive.js";
$jsFiles[] = "plugins/CoreHome/angularjs/menudropdown/menudropdown.directive.js";
$jsFiles[] = "plugins/CoreHome/angularjs/enrichedheadline/enrichedheadline.directive.js";
$jsFiles[] = "plugins/CoreHome/angularjs/content-intro/content-intro.directive.js";
$jsFiles[] = "plugins/CoreHome/angularjs/content-block/content-block.directive.js";
$jsFiles[] = "plugins/CoreHome/angularjs/dialogtoggler/dialogtoggler.directive.js";
$jsFiles[] = "plugins/CoreHome/angularjs/dialogtoggler/dialogtoggler.controller.js";
$jsFiles[] = "plugins/CoreHome/angularjs/dialogtoggler/dialogtoggler-urllistener.service.js";
$jsFiles[] = "plugins/CoreHome/angularjs/notification/notification.controller.js";
$jsFiles[] = "plugins/CoreHome/angularjs/notification/notification.directive.js";
$jsFiles[] = "plugins/CoreHome/angularjs/notification/notification.service.js";
$jsFiles[] = "plugins/CoreHome/angularjs/ajax-form/ajax-form.controller.js";
$jsFiles[] = "plugins/CoreHome/angularjs/ajax-form/ajax-form.directive.js";
$jsFiles[] = "plugins/CoreHome/angularjs/widget-loader/widgetloader.directive.js";
$jsFiles[] = "plugins/CoreHome/angularjs/widget-bydimension-container/widget-bydimension-container.directive.js";
$jsFiles[] = "plugins/CoreHome/angularjs/widget-container/widgetcontainer.directive.js";
$jsFiles[] = "plugins/CoreHome/angularjs/widget/widget.directive.js";
$jsFiles[] = "plugins/CoreHome/angularjs/popover-handler/popover-handler.directive.js";
$jsFiles[] = "plugins/CoreHome/angularjs/report-export/reportexport.directive.js";
$jsFiles[] = "plugins/CoreHome/angularjs/reporting-page/reportingpage.controller.js";
$jsFiles[] = "plugins/CoreHome/angularjs/reporting-page/reportingpage-model.js";
$jsFiles[] = "plugins/CoreHome/angularjs/reporting-page/reportingpage.directive.js";
$jsFiles[] = "plugins/CoreHome/angularjs/reporting-menu/reportingmenu.controller.js";
$jsFiles[] = "plugins/CoreHome/angularjs/reporting-menu/reportingmenu-model.js";
$jsFiles[] = "plugins/CoreHome/angularjs/reporting-menu/reportingmenu.directive.js";
$jsFiles[] = "plugins/CoreHome/angularjs/quick-access/quick-access.controller.js";
$jsFiles[] = "plugins/CoreHome/angularjs/quick-access/quick-access.directive.js";
$jsFiles[] = "plugins/CoreHome/angularjs/selector/selector.directive.js";
$jsFiles[] = "plugins/CoreHome/angularjs/content-table/content-table.directive.js";
$jsFiles[] = "plugins/CoreHome/angularjs/date-picker/date-picker.directive.js";
$jsFiles[] = "plugins/CoreHome/angularjs/date-range-picker/date-range-picker.component.js";
$jsFiles[] = "plugins/CoreHome/angularjs/period-date-picker/period-date-picker.component.js";
$jsFiles[] = "plugins/CoreHome/angularjs/period-selector/period-selector.directive.js";
$jsFiles[] = "plugins/CoreHome/angularjs/period-selector/period-selector.controller.js";
$jsFiles[] = "plugins/CoreHome/angularjs/multipairfield/multipairfield.directive.js";
$jsFiles[] = "plugins/CoreHome/angularjs/multipairfield/multipairfield.controller.js";
$jsFiles[] = "plugins/CoreHome/angularjs/dropdown-menu/dropdown-menu.directive.js";
$jsFiles[] = "plugins/CoreHome/angularjs/field-array/field-array.directive.js";
$jsFiles[] = "plugins/CoreHome/angularjs/field-array/field-array.controller.js";
// we have to load these CoreAdminHome files here. If we loaded them in CoreAdminHome,
// there would be JS errors as CoreAdminHome is loaded first. Meaning it is loaded before
// any angular JS file is loaded etc.
$jsFiles[] = "plugins/CoreAdminHome/angularjs/smtp/mail-smtp.controller.js";
$jsFiles[] = "plugins/CoreAdminHome/angularjs/branding/branding.controller.js";
$jsFiles[] = "plugins/CoreAdminHome/angularjs/trackingcode/jstrackingcode.controller.js";
$jsFiles[] = "plugins/CoreAdminHome/angularjs/trackingcode/imagetrackingcode.controller.js";
$jsFiles[] = "plugins/CoreAdminHome/angularjs/archiving/archiving.controller.js";
$jsFiles[] = "plugins/CoreAdminHome/angularjs/trackingfailures/trackingfailures.controller.js";
$jsFiles[] = "plugins/CoreAdminHome/angularjs/trackingfailures/trackingfailures.directive.js";
// we have to load these CorePluginsAdmin files here. If we loaded them in CorePluginsAdmin,
// there would be JS errors as CorePluginsAdmin is loaded first. Meaning it is loaded before
// any angular JS file is loaded etc.
$jsFiles[] = "plugins/CorePluginsAdmin/angularjs/plugin-settings/plugin-settings.controller.js";
$jsFiles[] = "plugins/CorePluginsAdmin/angularjs/plugin-settings/plugin-settings.directive.js";
$jsFiles[] = "plugins/CorePluginsAdmin/angularjs/form/form.directive.js";
$jsFiles[] = "plugins/CorePluginsAdmin/angularjs/form-field/form-field.directive.js";
$jsFiles[] = "plugins/CorePluginsAdmin/angularjs/field/field.directive.js";
$jsFiles[] = "plugins/CorePluginsAdmin/angularjs/save-button/save-button.directive.js";
$jsFiles[] = "plugins/CorePluginsAdmin/angularjs/plugins/plugin-filter.directive.js";
$jsFiles[] = "plugins/CorePluginsAdmin/angularjs/plugins/plugin-management.directive.js";
$jsFiles[] = "plugins/CorePluginsAdmin/angularjs/plugins/plugin-upload.directive.js";
$jsFiles[] = "plugins/CoreHome/javascripts/iframeResizer.min.js";
}
public function getClientSideTranslationKeys(&$translationKeys)
{
$translationKeys[] = 'General_Export';
$translationKeys[] = 'General_InvalidDateRange';
$translationKeys[] = 'General_Loading';
$translationKeys[] = 'General_Show';
$translationKeys[] = 'General_Remove';
$translationKeys[] = 'General_Hide';
$translationKeys[] = 'General_Save';
$translationKeys[] = 'General_Website';
$translationKeys[] = 'General_Pagination';
$translationKeys[] = 'General_RowsToDisplay';
$translationKeys[] = 'Intl_Year_Short';
$translationKeys[] = 'General_MultiSitesSummary';
$translationKeys[] = 'General_SearchNoResults';
$translationKeys[] = 'CoreHome_ChooseX';
$translationKeys[] = 'CoreHome_ClickToSeeFullInformation';
$translationKeys[] = 'CoreHome_YouAreUsingTheLatestVersion';
$translationKeys[] = 'CoreHome_IncludeRowsWithLowPopulation';
$translationKeys[] = 'CoreHome_ExcludeRowsWithLowPopulation';
$translationKeys[] = 'CoreHome_DataTableIncludeAggregateRows';
$translationKeys[] = 'CoreHome_DataTableExcludeAggregateRows';
$translationKeys[] = 'CoreHome_Default';
$translationKeys[] = 'CoreHome_FormatMetrics';
$translationKeys[] = 'CoreHome_ShowExportUrl';
$translationKeys[] = 'CoreHome_HideExportUrl';
$translationKeys[] = 'CoreHome_FlattenDataTable';
$translationKeys[] = 'CoreHome_UnFlattenDataTable';
$translationKeys[] = 'CoreHome_ExternalHelp';
$translationKeys[] = 'CoreHome_ClickToEditX';
$translationKeys[] = 'CoreHome_Menu';
$translationKeys[] = 'CoreHome_AddTotalsRowDataTable';
$translationKeys[] = 'CoreHome_RemoveTotalsRowDataTable';
$translationKeys[] = 'SitesManager_NotFound';
$translationKeys[] = 'Annotations_ViewAndAddAnnotations';
$translationKeys[] = 'General_RowEvolutionRowActionTooltipTitle';
$translationKeys[] = 'General_RowEvolutionRowActionTooltip';
$translationKeys[] = 'Annotations_IconDesc';
$translationKeys[] = 'Annotations_IconDescHideNotes';
$translationKeys[] = 'Annotations_HideAnnotationsFor';
$translationKeys[] = 'General_LoadingPopover';
$translationKeys[] = 'General_LoadingPopoverFor';
$translationKeys[] = 'Intl_Month_Short_StandAlone_1';
$translationKeys[] = 'Intl_Month_Short_StandAlone_2';
$translationKeys[] = 'Intl_Month_Short_StandAlone_3';
$translationKeys[] = 'Intl_Month_Short_StandAlone_4';
$translationKeys[] = 'Intl_Month_Short_StandAlone_5';
$translationKeys[] = 'Intl_Month_Short_StandAlone_6';
$translationKeys[] = 'Intl_Month_Short_StandAlone_7';
$translationKeys[] = 'Intl_Month_Short_StandAlone_8';
$translationKeys[] = 'Intl_Month_Short_StandAlone_9';
$translationKeys[] = 'Intl_Month_Short_StandAlone_10';
$translationKeys[] = 'Intl_Month_Short_StandAlone_11';
$translationKeys[] = 'Intl_Month_Short_StandAlone_12';
$translationKeys[] = 'Intl_Month_Long_StandAlone_1';
$translationKeys[] = 'Intl_Month_Long_StandAlone_2';
$translationKeys[] = 'Intl_Month_Long_StandAlone_3';
$translationKeys[] = 'Intl_Month_Long_StandAlone_4';
$translationKeys[] = 'Intl_Month_Long_StandAlone_5';
$translationKeys[] = 'Intl_Month_Long_StandAlone_6';
$translationKeys[] = 'Intl_Month_Long_StandAlone_7';
$translationKeys[] = 'Intl_Month_Long_StandAlone_8';
$translationKeys[] = 'Intl_Month_Long_StandAlone_9';
$translationKeys[] = 'Intl_Month_Long_StandAlone_10';
$translationKeys[] = 'Intl_Month_Long_StandAlone_11';
$translationKeys[] = 'Intl_Month_Long_StandAlone_12';
$translationKeys[] = 'Intl_Day_Short_StandAlone_1';
$translationKeys[] = 'Intl_Day_Short_StandAlone_2';
$translationKeys[] = 'Intl_Day_Short_StandAlone_3';
$translationKeys[] = 'Intl_Day_Short_StandAlone_4';
$translationKeys[] = 'Intl_Day_Short_StandAlone_5';
$translationKeys[] = 'Intl_Day_Short_StandAlone_6';
$translationKeys[] = 'Intl_Day_Short_StandAlone_7';
$translationKeys[] = 'Intl_Day_Long_StandAlone_1';
$translationKeys[] = 'Intl_Day_Long_StandAlone_2';
$translationKeys[] = 'Intl_Day_Long_StandAlone_3';
$translationKeys[] = 'Intl_Day_Long_StandAlone_4';
$translationKeys[] = 'Intl_Day_Long_StandAlone_5';
$translationKeys[] = 'Intl_Day_Long_StandAlone_6';
$translationKeys[] = 'Intl_Day_Long_StandAlone_7';
$translationKeys[] = 'Intl_Day_Min_StandAlone_1';
$translationKeys[] = 'Intl_Day_Min_StandAlone_2';
$translationKeys[] = 'Intl_Day_Min_StandAlone_3';
$translationKeys[] = 'Intl_Day_Min_StandAlone_4';
$translationKeys[] = 'Intl_Day_Min_StandAlone_5';
$translationKeys[] = 'Intl_Day_Min_StandAlone_6';
$translationKeys[] = 'Intl_Day_Min_StandAlone_7';
$translationKeys[] = 'Intl_PeriodDay';
$translationKeys[] = 'Intl_PeriodWeek';
$translationKeys[] = 'Intl_PeriodMonth';
$translationKeys[] = 'Intl_PeriodYear';
$translationKeys[] = 'General_DateRangeInPeriodList';
$translationKeys[] = 'General_And';
$translationKeys[] = 'General_All';
$translationKeys[] = 'General_Search';
$translationKeys[] = 'General_Clear';
$translationKeys[] = 'General_MoreDetails';
$translationKeys[] = 'General_Help';
$translationKeys[] = 'General_MoreDetails';
$translationKeys[] = 'General_Help';
$translationKeys[] = 'General_Id';
$translationKeys[] = 'General_Name';
$translationKeys[] = 'General_JsTrackingTag';
$translationKeys[] = 'General_Yes';
$translationKeys[] = 'General_No';
$translationKeys[] = 'General_Edit';
$translationKeys[] = 'General_Delete';
$translationKeys[] = 'General_Default';
$translationKeys[] = 'General_LoadingData';
$translationKeys[] = 'General_Error';
$translationKeys[] = 'General_ErrorRequest';
$translationKeys[] = 'General_YourChangesHaveBeenSaved';
$translationKeys[] = 'General_LearnMore';
$translationKeys[] = 'General_ChooseDate';
$translationKeys[] = 'General_ReadThisToLearnMore';
$translationKeys[] = 'CoreHome_UndoPivotBySubtable';
$translationKeys[] = 'CoreHome_PivotBySubtable';
$translationKeys[] = 'General_LearnMore';
$translationKeys[] = 'CoreHome_NoSuchPage';
$translationKeys[] = 'CoreHome_QuickAccessTitle';
$translationKeys[] = 'CoreHome_Segments';
$translationKeys[] = 'CoreHome_MenuEntries';
$translationKeys[] = 'SitesManager_Sites';
$translationKeys[] = 'CoreHome_MainNavigation';
$translationKeys[] = 'CoreHome_ChangeCurrentWebsite';
$translationKeys[] = 'General_CreatedByUser';
$translationKeys[] = 'General_DateRangeFromTo';
$translationKeys[] = 'General_DateRangeFrom';
$translationKeys[] = 'General_DateRangeTo';
$translationKeys[] = 'General_DoubleClickToChangePeriod';
$translationKeys[] = 'General_Apply';
$translationKeys[] = 'General_Period';
$translationKeys[] = 'CoreHome_EnterZenMode';
$translationKeys[] = 'CoreHome_ExitZenMode';
$translationKeys[] = 'CoreHome_ShortcutZenMode';
$translationKeys[] = 'CoreHome_ShortcutSegmentSelector';
$translationKeys[] = 'CoreHome_ShortcutWebsiteSelector';
$translationKeys[] = 'CoreHome_ShortcutCalendar';
$translationKeys[] = 'CoreHome_ShortcutSearch';
$translationKeys[] = 'CoreHome_ShortcutHelp';
$translationKeys[] = 'CoreHome_StandardReport';
$translationKeys[] = 'CoreHome_ReportWithMetadata';
$translationKeys[] = 'CoreHome_ReportType';
$translationKeys[] = 'CoreHome_RowLimit';
$translationKeys[] = 'CoreHome_ExportFormat';
$translationKeys[] = 'CoreHome_FlattenReport';
$translationKeys[] = 'CoreHome_CustomLimit';
$translationKeys[] = 'CoreHome_ExpandSubtables';
$translationKeys[] = 'CoreHome_HomeShortcut';
$translationKeys[] = 'CoreHome_PageUpShortcutDescription';
$translationKeys[] = 'CoreHome_EndShortcut';
$translationKeys[] = 'CoreHome_PageDownShortcutDescription';
$translationKeys[] = 'CoreHome_MacPageUp';
$translationKeys[] = 'CoreHome_MacPageDown';
}
}

View File

@@ -0,0 +1,71 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome\DataTableRowAction;
use Piwik\Common;
use Piwik\Piwik;
/**
* MULTI ROW EVOLUTION
* The class handles the popover that shows the evolution of a multiple rows in a data table
*/
class MultiRowEvolution extends RowEvolution
{
/** The requested metric */
protected $metric;
/** Show all metrics in the evolution graph when the popover opens */
protected $initiallyShowAllMetrics = true;
/** The metrics available in the metrics select */
protected $metricsForSelect;
/**
* The constructor
* @param int $idSite
* @param \Piwik\Date $date ($this->date from controller)
*/
public function __construct($idSite, $date)
{
$this->metric = Common::getRequestVar('column', '', 'string');
parent::__construct($idSite, $date);
}
protected function loadEvolutionReport($column = false)
{
// set the "column" parameter for the API.getRowEvolution call
parent::loadEvolutionReport($this->metric);
}
protected function extractEvolutionReport($report)
{
$this->metric = $report['column'];
$this->dataTable = $report['reportData'];
$this->availableMetrics = $report['metadata']['metrics'];
$this->metricsForSelect = $report['metadata']['columns'];
$this->dimension = $report['metadata']['dimension'];
}
/**
* Render the popover
* @param \Piwik\Plugins\CoreHome\Controller $controller
* @param View (the popover_rowevolution template)
*/
public function renderPopover($controller, $view)
{
// add data for metric select box
$view->availableMetrics = $this->metricsForSelect;
$view->selectedMetric = $this->metric;
$view->availableRecordsText = $this->dimension . ': '
. Piwik::translate('RowEvolution_ComparingRecords', array(count($this->availableMetrics)));
return parent::renderPopover($controller, $view);
}
}

View File

@@ -0,0 +1,377 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome\DataTableRowAction;
use Exception;
use Piwik\API\DataTablePostProcessor;
use Piwik\API\Request;
use Piwik\Common;
use Piwik\Date;
use Piwik\Metrics;
use Piwik\NumberFormatter;
use Piwik\Period\Factory as PeriodFactory;
use Piwik\Piwik;
use Piwik\Plugins\CoreVisualizations\Visualizations\JqplotGraph\Evolution as EvolutionViz;
use Piwik\Url;
use Piwik\ViewDataTable\Factory;
/**
* ROW EVOLUTION
* The class handles the popover that shows the evolution of a singe row in a data table
*/
class RowEvolution
{
/** The current site id */
protected $idSite;
/** The api method to get the data. Format: Plugin.apiAction */
protected $apiMethod;
/** The label of the requested row */
protected $label;
/** The requested period */
protected $period;
/** The requested date */
protected $date;
/** The request segment */
protected $segment;
/** The metrics that are available for the requested report and period */
protected $availableMetrics;
/** The name of the dimension of the current report */
protected $dimension;
/**
* The data
* @var \Piwik\DataTable
*/
protected $dataTable;
/** The label of the current record */
protected $rowLabel;
/** The icon of the current record */
protected $rowIcon;
/** The type of graph that has been requested last */
protected $graphType;
/** The metrics for the graph that has been requested last */
protected $graphMetrics;
/** Whether or not to show all metrics in the evolution graph when to popover opens */
protected $initiallyShowAllMetrics = false;
/**
* The constructor
* Initialize some local variables from the request
* @param int $idSite
* @param Date $date ($this->date from controller)
* @param null|string $graphType
* @throws Exception
*/
public function __construct($idSite, $date, $graphType = 'graphEvolution')
{
$this->apiMethod = Common::getRequestVar('apiMethod', '', 'string');
if (empty($this->apiMethod)) {
throw new Exception("Parameter apiMethod not set.");
}
$this->label = DataTablePostProcessor::getLabelFromRequest($_GET);
if (!is_array($this->label)) {
throw new Exception("Expected label to be an array, got instead: " . $this->label);
}
$this->label = Common::unsanitizeInputValue($this->label[0]);
if ($this->label === '') throw new Exception("Parameter label not set.");
$this->period = Common::getRequestVar('period', '', 'string');
PeriodFactory::checkPeriodIsEnabled($this->period);
if ($this->period != 'range' && !($date instanceof Date)) {
throw new Exception("Expected date to be an instance of \\Piwik\\Date");
}
$this->idSite = $idSite;
$this->graphType = $graphType;
if ($this->period != 'range') {
// handle day, week, month and year: display last X periods
$end = $date->toString();
list($this->date, $lastN) = EvolutionViz::getDateRangeAndLastN($this->period, $end);
}
$this->segment = \Piwik\API\Request::getRawSegmentFromRequest();
$this->loadEvolutionReport();
}
/**
* Render the popover
* @param \Piwik\Plugins\CoreHome\Controller $controller
* @param View (the popover_rowevolution template)
*/
public function renderPopover($controller, $view)
{
// render main evolution graph
$this->graphType = 'graphEvolution';
$this->graphMetrics = $this->availableMetrics;
$view->graph = $controller->getRowEvolutionGraph($fetch = true, $rowEvolution = $this);
// render metrics overview
$view->metrics = $this->getMetricsToggles();
// available metrics text
$metricsText = Piwik::translate('RowEvolution_AvailableMetrics');
$popoverTitle = '';
if ($this->rowLabel) {
$icon = $this->rowIcon ? '<img height="16px" src="' . $this->rowIcon . '" alt="">' : '';
$metricsText = sprintf(Piwik::translate('RowEvolution_MetricsFor'), $this->dimension . ': ' . $icon . ' ' . $this->rowLabel);
$popoverTitle = $icon . ' ' . $this->rowLabel;
}
$view->availableMetricsText = $metricsText;
$view->popoverTitle = $popoverTitle;
return $view->render();
}
protected function loadEvolutionReport($column = false)
{
list($apiModule, $apiAction) = explode('.', $this->apiMethod);
// getQueryStringFromParameters expects sanitised query parameter values
$parameters = array(
'method' => 'API.getRowEvolution',
'label' => $this->label,
'apiModule' => $apiModule,
'apiAction' => $apiAction,
'idSite' => $this->idSite,
'period' => $this->period,
'date' => $this->date,
'format' => 'original',
'serialize' => '0'
);
if (!empty($this->segment)) {
$parameters['segment'] = $this->segment;
}
if ($column !== false) {
$parameters['column'] = $column;
}
$url = Url::getQueryStringFromParameters($parameters);
$request = new Request($url);
$report = $request->process();
$this->extractEvolutionReport($report);
}
protected function extractEvolutionReport($report)
{
$this->dataTable = $report['reportData'];
$this->rowLabel = $this->extractPrettyLabel($report);
$this->rowIcon = !empty($report['logo']) ? $report['logo'] : false;
$this->availableMetrics = $report['metadata']['metrics'];
$this->dimension = $report['metadata']['dimension'];
}
/**
* Generic method to get an evolution graph or a sparkline for the row evolution popover.
* Do as much as possible from outside the controller.
* @param string|bool $graphType
* @param array|bool $metrics
* @return Factory
*/
public function getRowEvolutionGraph($graphType = false, $metrics = false)
{
// set up the view data table
$view = Factory::build($graphType ? : $this->graphType, $this->apiMethod,
$controllerAction = 'CoreHome.getRowEvolutionGraph', $forceDefault = true);
$view->setDataTable($this->dataTable);
if (!empty($this->graphMetrics)) { // In row Evolution popover, this is empty
$view->config->columns_to_display = array_keys($metrics ? : $this->graphMetrics);
}
$view->requestConfig->request_parameters_to_modify['label'] = '';
$view->config->show_goals = false;
$view->config->show_search = false;
$view->config->show_all_views_icons = false;
$view->config->show_related_reports = false;
$view->config->show_series_picker = false;
$view->config->show_footer_message = false;
foreach ($this->availableMetrics as $metric => $metadata) {
$view->config->translations[$metric] = $metadata['name'];
}
$view->config->external_series_toggle = 'RowEvolutionSeriesToggle';
$view->config->external_series_toggle_show_all = $this->initiallyShowAllMetrics;
return $view;
}
/**
* Prepare metrics toggles with spark lines
* @return array
*/
protected function getMetricsToggles()
{
$i = 0;
$metrics = array();
foreach ($this->availableMetrics as $metric => $metricData) {
$unit = Metrics::getUnit($metric, $this->idSite);
$change = isset($metricData['change']) ? $metricData['change'] : false;
list($first, $last) = $this->getFirstAndLastDataPointsForMetric($metric);
$fractionDigits = max($this->getFractionDigits($first), $this->getFractionDigits($last));
$details = Piwik::translate('RowEvolution_MetricBetweenText', array(
NumberFormatter::getInstance()->format($first, $fractionDigits, $fractionDigits) . $unit,
NumberFormatter::getInstance()->format($last, $fractionDigits, $fractionDigits) . $unit,
));
if ($change !== false) {
$lowerIsBetter = Metrics::isLowerValueBetter($metric);
if (substr($change, 0, 1) == '+') {
$changeClass = $lowerIsBetter ? 'bad' : 'good';
$changeImage = $lowerIsBetter ? 'arrow_up_red' : 'arrow_up';
} else if (substr($change, 0, 1) == '-') {
$changeClass = $lowerIsBetter ? 'good' : 'bad';
$changeImage = $lowerIsBetter ? 'arrow_down_green' : 'arrow_down';
} else {
$changeClass = 'neutral';
$changeImage = false;
}
$change = '<span class="' . $changeClass . '">'
. ($changeImage ? '<img src="plugins/MultiSites/images/' . $changeImage . '.png" /> ' : '')
. $change . '</span>';
$details .= ', ' . Piwik::translate('RowEvolution_MetricChangeText', $change);
}
// set metric min/max text (used as tooltip for details)
$max = isset($metricData['max']) ? $metricData['max'] : 0;
$min = isset($metricData['min']) ? $metricData['min'] : 0;
$minmax = Piwik::translate('RowEvolution_MetricMinMax', array(
$metricData['name'],
NumberFormatter::getInstance()->formatNumber($min, $fractionDigits, $fractionDigits) . $unit,
NumberFormatter::getInstance()->formatNumber($max, $fractionDigits, $fractionDigits) . $unit,
));
$newMetric = array(
'label' => $metricData['name'],
'details' => $details,
'minmax' => $minmax,
'sparkline' => $this->getSparkline($metric),
);
// Multi Rows, each metric can be for a particular row and display an icon
if (!empty($metricData['logo'])) {
$newMetric['logo'] = $metricData['logo'];
}
// TODO: this check should be determined by metric metadata, not hardcoded here
if ($metric == 'nb_users'
&& $first == 0
&& $last == 0
) {
$newMetric['hide'] = true;
}
$metrics[] = $newMetric;
$i++;
}
return $metrics;
}
/** Get the img tag for a sparkline showing a single metric */
protected function getSparkline($metric)
{
// sparkline is always echoed, so we need to buffer the output
$view = $this->getRowEvolutionGraph($graphType = 'sparkline', $metrics = array($metric => $metric));
ob_start();
$view->render();
$spark = ob_get_contents();
ob_end_clean();
// undo header change by sparkline renderer
Common::sendHeader('Content-type: text/html');
// base64 encode the image and put it in an img tag
$spark = base64_encode($spark);
return '<img width="100" height="25" src="data:image/png;base64,' . $spark . '" />';
}
/** Use the available metrics for the metrics of the last requested graph. */
public function useAvailableMetrics()
{
$this->graphMetrics = $this->availableMetrics;
}
private function getFirstAndLastDataPointsForMetric($metric)
{
$first = 0;
$firstTable = $this->dataTable->getFirstRow();
if (!empty($firstTable)) {
$row = $firstTable->getFirstRow();
if (!empty($row)) {
$first = floatval($row->getColumn($metric));
}
}
$last = 0;
$lastTable = $this->dataTable->getLastRow();
if (!empty($lastTable)) {
$row = $lastTable->getFirstRow();
if (!empty($row)) {
$last = floatval($row->getColumn($metric));
}
}
return array($first, $last);
}
/**
* @param $report
* @return string
*/
protected function extractPrettyLabel($report)
{
// By default, use the specified label
$rowLabel = Common::sanitizeInputValue($report['label']);
$rowLabel = str_replace('/', '<wbr>/', str_replace('&', '<wbr>&', $rowLabel ));
// If the dataTable specifies a label_html, use this instead
/** @var $dataTableMap \Piwik\DataTable\Map */
$dataTableMap = $report['reportData'];
$labelPretty = $dataTableMap->getColumn('label_html');
$labelPretty = array_filter($labelPretty, 'strlen');
$labelPretty = current($labelPretty);
if (!empty($labelPretty)) {
return $labelPretty;
}
return $rowLabel;
}
private function getFractionDigits($value)
{
$value = (string) $value;
$fraction = substr(strrchr($value, "."), 1);
return strlen($fraction);
}
}

View File

@@ -0,0 +1,91 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome;
use Piwik\Common;
use Piwik\Config;
use Piwik\Container\StaticContainer;
use Piwik\Network\IP as NetworkIp;
use Piwik\NoAccessException;
use Piwik\Piwik;
use Piwik\SettingsServer;
/**
* This class is in CoreHome since some alternative Login plugins disable the Login plugin and we want to ensure the
* feature works for all login plugins.
*/
class LoginWhitelist
{
public function shouldWhitelistApplyToAPI()
{
$general = $this->getGeneralConfig();
return !empty($general['login_whitelist_apply_to_reporting_api_requests']);
}
public function shouldCheckWhitelist()
{
if (Common::isPhpCliMode()) {
return false;
}
// ignore whitelist checks for opt out iframe
if (!SettingsServer::isTrackerApiRequest() && 'CoreAdminHome' === Piwik::getModule() && 'optOut' === Piwik::getAction()) {
return false;
}
$ips = $this->getWhitelistedLoginIps();
return !empty($ips);
}
public function checkIsWhitelisted($ipString)
{
if (!$this->isIpWhitelisted($ipString)) {
throw new NoAccessException(Piwik::translate('CoreHome_ExceptionNotWhitelistedIP', $ipString));
}
}
public function isIpWhitelisted($userIpString)
{
$userIp = NetworkIp::fromStringIP($userIpString);
$ipsWhitelisted = $this->getWhitelistedLoginIps();
if (empty($ipsWhitelisted)) {
return false;
}
return $userIp->isInRanges($ipsWhitelisted);
}
/**
* @return array
*/
protected function getWhitelistedLoginIps()
{
$ips = StaticContainer::get('login.whitelist.ips');
if (!empty($ips) && is_array($ips)) {
$ips = array_map(function ($ip) {
return trim($ip);
}, $ips);
$ips = array_filter($ips, function ($ip) {
return !empty($ip);
});
return array_unique(array_values($ips));
}
return array();
}
private function getGeneralConfig()
{
$config = Config::getInstance();
$general = $config->General;
return $general;
}
}

View File

@@ -0,0 +1,35 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome;
use Piwik\Db;
use Piwik\Menu\MenuTop;
use Piwik\Piwik;
use Piwik\Plugin;
class Menu extends \Piwik\Plugin\Menu
{
public function configureTopMenu(MenuTop $menu)
{
$module = $this->getLoginModule();
if (Piwik::isUserIsAnonymous()) {
$menu->registerMenuIcon('Login_LogIn', 'icon-sign-in');
$menu->addItem('Login_LogIn', null, array('module' => $module, 'action' => false), 1000, Piwik::translate('Login_LogIn'));
} else {
$menu->registerMenuIcon('General_Logout', 'icon-sign-out');
$menu->addItem('General_Logout', null, array('module' => $module, 'action' => 'logout', 'idSite' => null), 1000, Piwik::translate('General_Logout'));
}
}
private function getLoginModule()
{
return Piwik::getLoginPluginName();
}
}

View File

@@ -0,0 +1,21 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome;
/**
* CoreHome segment base class
*/
class Segment extends \Piwik\Plugin\Segment
{
protected function init()
{
$this->setCategory('General_Visit');
}
}

View File

@@ -0,0 +1,94 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome\SystemSummary;
/**
* This class can be used to add a new entry / item to the system summary widget.
*
* @api
*/
class Item
{
private $key;
private $label;
private $value;
private $urlParams;
private $icon;
private $order;
/**
* Item constructor.
* @param string $key The key or ID for this item. The entry in the widget will have this class so it is possible
* to style it individually and other plugins can use this key to for example remove this item
* from the list of system summary items.
* @param string $label The label that will be displayed for this item. The label may already include the value such as "5 segments"
* @param string|null $value Optional label. If given, the value will be displayed after the label separated by a colon, eg: "Segments: 5"
* @param array|null $urlParams Optional URL to make the item clickable. Accepts an array of URL parameters that need to be modfified.
* @param string $icon Optional icon css class, eg "icon-user".
* @param int $order Optional sort order. The lower the value, the higher up the entry will be shown
*/
public function __construct($key, $label, $value = null, $urlParams = null, $icon = '', $order = 99)
{
$this->key = $key;
$this->label = $label;
$this->value = $value;
$this->urlParams = $urlParams;
$this->icon = $icon;
$this->order = $order;
}
/**
* @return string
*/
public function getKey()
{
return $this->key;
}
/**
* @return string
*/
public function getLabel()
{
return $this->label;
}
/**
* @return mixed
*/
public function getValue()
{
return $this->value;
}
/**
* @return array|null
*/
public function getUrlParams()
{
return $this->urlParams;
}
/**
* @return string
*/
public function getIcon()
{
return $this->icon;
}
/**
* @return int
*/
public function getOrder()
{
return $this->order;
}
}

View File

@@ -0,0 +1,39 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\CoreHome\Tracker\LogTable;
use Piwik\Tracker\LogTable;
class Action extends LogTable
{
public function getName()
{
return 'log_action';
}
public function getIdColumn()
{
return 'idaction';
}
public function getColumnToJoinOnIdAction()
{
return 'idaction';
}
public function getLinkTableToBeAbleToJoinOnVisit()
{
return 'log_link_visit_action';
}
public function getPrimaryKey()
{
return array('idaction');
}
}

View File

@@ -0,0 +1,34 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\CoreHome\Tracker\LogTable;
use Piwik\Tracker\LogTable;
class Conversion extends LogTable
{
public function getName()
{
return 'log_conversion';
}
public function getIdColumn()
{
return 'idvisit';
}
public function getColumnToJoinOnIdVisit()
{
return 'idvisit';
}
public function getPrimaryKey()
{
return array('idvisit', 'idgoal', 'buster');
}
}

View File

@@ -0,0 +1,35 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\CoreHome\Tracker\LogTable;
use Piwik\Tracker\LogTable;
class ConversionItem extends LogTable
{
public function getName()
{
return 'log_conversion_item';
}
public function getIdColumn()
{
return 'idvisit';
}
public function getColumnToJoinOnIdVisit()
{
return 'idvisit';
}
public function getPrimaryKey()
{
return array('idvisit', 'idorder', 'idaction_sku');
}
}

View File

@@ -0,0 +1,39 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\CoreHome\Tracker\LogTable;
use Piwik\Tracker\LogTable;
class LinkVisitAction extends LogTable
{
public function getName()
{
return 'log_link_visit_action';
}
public function getIdColumn()
{
return 'idlink_va';
}
public function getColumnToJoinOnIdAction()
{
return 'idaction_url';
}
public function getColumnToJoinOnIdVisit()
{
return 'idvisit';
}
public function getPrimaryKey()
{
return array('idlink_va');
}
}

View File

@@ -0,0 +1,39 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\CoreHome\Tracker\LogTable;
use Piwik\Tracker\LogTable;
class Visit extends LogTable
{
public function getName()
{
return 'log_visit';
}
public function getIdColumn()
{
return 'idvisit';
}
public function getColumnToJoinOnIdVisit()
{
return 'idvisit';
}
public function shouldJoinWithSubSelect()
{
return true;
}
public function getPrimaryKey()
{
return array('idvisit');
}
}

View File

@@ -0,0 +1,229 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\CoreHome\Tracker;
use Piwik\Common;
use Piwik\Date;
use Piwik\Config;
use Piwik\EventDispatcher;
use Piwik\Exception\UnexpectedWebsiteFoundException;
use Piwik\Tracker\Cache;
use Piwik\Tracker\Request;
use Piwik\Tracker\RequestProcessor;
use Piwik\Tracker\Settings;
use Piwik\Tracker\Visit\VisitProperties;
use Piwik\Tracker\VisitExcluded;
use Piwik\Tracker\VisitorRecognizer;
use Piwik\Plugins\PrivacyManager\Config as PrivacyManagerConfig;
/**
* Encapsulates core tracking logic related to visits.
*
* ## Request Metadata
*
* This RequestProcessor exposes the following metadata for the **CoreHome** plugin:
*
* * **visitorId**: A hash that identifies the current visitor being tracked. This value is
* calculated using the Piwik\Tracker\Settings;:getConfigId() method.
*
* Set in `processRequestParams()`.
*
* * **isVisitorKnown**: True if the current visitor has visited the site before. False if
* otherwise.
*
* Set in `processRequestParams()`.
*
* * **isNewVisit**: True if the current action is the start of a new visit, false if it
* is part of an ongoing visit.
*
* Set in `processRequestParams()`. Other RequestProcessors can override
* this value to force a new visit or stop a new visit.
*
* * **visitorNotFoundInDb**: True if the current visit could not be updated.
*
* Set by the Visit object.
*/
class VisitRequestProcessor extends RequestProcessor
{
// TODO: much of the logic in this class should be moved to new service class
/**
* @var EventDispatcher
*/
private $eventDispatcher;
/**
* @var VisitorRecognizer
*/
private $visitorRecognizer;
/**
* @var Settings
*/
private $userSettings;
/**
* @var int
*/
private $visitStandardLength;
public function __construct(EventDispatcher $eventDispatcher, VisitorRecognizer $visitorRecognizer, Settings $userSettings,
$visitStandardLength)
{
$this->eventDispatcher = $eventDispatcher;
$this->visitorRecognizer = $visitorRecognizer;
$this->userSettings = $userSettings;
$this->visitStandardLength = $visitStandardLength;
}
public function processRequestParams(VisitProperties $visitProperties, Request $request)
{
// the IP is needed by isExcluded() and GoalManager->recordGoals()
$visitProperties->setProperty('location_ip', $request->getIp());
$excluded = new VisitExcluded($request);
$isExcluded = $excluded->isExcluded();
$request->setMetadata('CoreHome', 'isVisitExcluded', $isExcluded);
if ($isExcluded) {
return true;
}
$privacyConfig = new PrivacyManagerConfig();
$ip = $request->getIpString();
if ($privacyConfig->useAnonymizedIpForVisitEnrichment) {
$ip = $visitProperties->getProperty('location_ip');
}
// visitor recognition
$visitorId = $this->userSettings->getConfigId($request, $ip);
$request->setMetadata('CoreHome', 'visitorId', $visitorId);
$isKnown = $this->visitorRecognizer->findKnownVisitor($visitorId, $visitProperties, $request);
$request->setMetadata('CoreHome', 'isVisitorKnown', $isKnown);
$isNewVisit = $this->isVisitNew($visitProperties, $request);
$request->setMetadata('CoreHome', 'isNewVisit', $isNewVisit);
if (!$isNewVisit) { // only copy over known visitor's information, if this is for an ongoing visit
$this->visitorRecognizer->updateVisitPropertiesFromLastVisitRow($visitProperties);
}
return false;
}
public function afterRequestProcessed(VisitProperties $visitProperties, Request $request)
{
$ip = $visitProperties->getProperty('location_ip');
/**
* Triggered after visits are tested for exclusion so plugins can modify the IP address
* persisted with a visit.
*
* This event is primarily used by the **PrivacyManager** plugin to anonymize IP addresses.
*
* @param string &$ip The visitor's IP address.
*/
$this->eventDispatcher->postEvent('Tracker.setVisitorIp', array(&$ip));
$visitProperties->setProperty('location_ip', $ip);
}
/**
* Determines if the tracker if the current action should be treated as the start of a new visit or
* an action in an existing visit.
*
* Note: public only for tests.
*
* @param VisitProperties $visitProperties The current visit/visitor information.
* @param Request $request
* @return bool
*/
public function isVisitNew(VisitProperties $visitProperties, Request $request)
{
$isKnown = $request->getMetadata('CoreHome', 'isVisitorKnown');
if (!$isKnown) {
return true;
}
$isLastActionInTheSameVisit = $this->isLastActionInTheSameVisit($visitProperties, $request);
if (!$isLastActionInTheSameVisit) {
Common::printDebug("Visitor detected, but last action was more than 30 minutes ago...");
return true;
}
$wasLastActionYesterday = $this->wasLastActionNotToday($visitProperties, $request);
$forceNewVisitAtMidnight = (bool) Config::getInstance()->Tracker['create_new_visit_after_midnight'];
if ($wasLastActionYesterday && $forceNewVisitAtMidnight) {
Common::printDebug("Visitor detected, but last action was yesterday...");
return true;
}
return false;
}
/**
* Returns true if the last action was done during the last 30 minutes
* @return bool
*/
protected function isLastActionInTheSameVisit(VisitProperties $visitProperties, Request $request)
{
$lastActionTime = $visitProperties->getProperty('visit_last_action_time');
return isset($lastActionTime)
&& false !== $lastActionTime
&& ($lastActionTime > ($request->getCurrentTimestamp() - $this->visitStandardLength));
}
/**
* Returns true if the last action was not today.
* @param VisitProperties $visitor
* @return bool
*/
private function wasLastActionNotToday(VisitProperties $visitProperties, Request $request)
{
$lastActionTime = $visitProperties->getProperty('visit_last_action_time');
if (empty($lastActionTime)) {
return false;
}
$idSite = $request->getIdSite();
$timezone = $this->getTimezoneForSite($idSite);
if (empty($timezone)) {
throw new UnexpectedWebsiteFoundException('An unexpected website was found, check idSite in the request');
}
$date = Date::factory((int)$lastActionTime, $timezone);
$now = $request->getCurrentTimestamp();
$now = Date::factory((int)$now, $timezone);
return $date->toString() !== $now->toString();
}
private function getTimezoneForSite($idSite) // TODO: duplicate function in Visit
{
try {
$site = Cache::getCacheWebsiteAttributes($idSite);
} catch (UnexpectedWebsiteFoundException $e) {
return null;
}
if (!empty($site['timezone'])) {
return $site['timezone'];
}
return null;
}
}

View File

@@ -0,0 +1,119 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome;
use Piwik\Metrics\Formatter;
use Piwik\Plugins\CoreHome\Columns\VisitGoalBuyer;
use Piwik\Plugins\Live\VisitorDetailsAbstract;
class VisitorDetails extends VisitorDetailsAbstract
{
public function extendVisitorDetails(&$visitor)
{
$visitor['userId'] = $this->getUserId();
$visitor['visitorType'] = $this->getVisitorReturning();
$visitor['visitorTypeIcon'] = $this->getVisitorReturningIcon();
$visitor['visitConverted'] = $this->isVisitorGoalConverted();
$visitor['visitConvertedIcon'] = $this->getVisitorGoalConvertedIcon();
$visitor['visitCount'] = $this->getVisitCount();
$visitor['visitEcommerceStatus'] = $this->getVisitEcommerceStatus();
$visitor['visitEcommerceStatusIcon'] = $this->getVisitEcommerceStatusIcon();
$visitor['daysSinceFirstVisit'] = $this->getDaysSinceFirstVisit();
$visitor['daysSinceLastEcommerceOrder'] = $this->getDaysSinceLastEcommerceOrder();
$visitor['visitDuration'] = $this->getVisitLength();
$visitor['visitDurationPretty'] = $this->getVisitLengthPretty();
}
protected function getVisitEcommerceStatusIcon()
{
$status = $this->getVisitEcommerceStatus();
if (in_array($status, array('ordered', 'orderedThenAbandonedCart'))) {
return "plugins/Morpheus/images/ecommerceOrder.svg";
} elseif ($status == 'abandonedCart') {
return "plugins/Morpheus/images/ecommerceAbandonedCart.svg";
}
// Note: it is important that there is no icon when there was no ecommerce conversion
return null;
}
protected function getVisitEcommerceStatus()
{
return VisitGoalBuyer::getVisitEcommerceStatusFromId($this->details['visit_goal_buyer']);
}
protected function isVisitorGoalConverted()
{
return $this->details['visit_goal_converted'];
}
protected function getVisitorGoalConvertedIcon()
{
return $this->isVisitorGoalConverted()
? "plugins/Morpheus/images/goal.svg"
: null;
}
protected function getDaysSinceFirstVisit()
{
return $this->details['visitor_days_since_first'];
}
protected function getDaysSinceLastEcommerceOrder()
{
return $this->details['visitor_days_since_order'];
}
protected function getVisitorReturning()
{
$type = $this->details['visitor_returning'];
return $type == 2
? 'returningCustomer'
: ($type == 1
? 'returning'
: 'new');
}
protected function getVisitorReturningIcon()
{
$type = $this->getVisitorReturning();
if ($type == 'returning'
|| $type == 'returningCustomer'
) {
return "plugins/Live/images/returningVisitor.png";
}
return null;
}
protected function getVisitCount()
{
return $this->details['visitor_count_visits'];
}
protected function getVisitLength()
{
return $this->details['visit_total_time'];
}
protected function getVisitLengthPretty()
{
$formatter = new Formatter();
return $formatter->getPrettyTimeFromSeconds($this->details['visit_total_time'], true);
}
protected function getUserId()
{
if (isset($this->details['user_id'])
&& strlen($this->details['user_id']) > 0
) {
return $this->details['user_id'];
}
return null;
}
}

View File

@@ -0,0 +1,49 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome\Widgets;
use Piwik\Common;
use Piwik\Piwik;
use Piwik\Widget\Widget;
use Piwik\Widget\WidgetConfig;
use Piwik\Translation\Translator;
use Piwik\View;
class GetDonateForm extends Widget
{
/**
* @var Translator
*/
private $translator;
public function __construct(Translator $translator)
{
$this->translator = $translator;
}
public static function configure(WidgetConfig $config)
{
$config->setCategoryId('About Matomo');
$config->setName('CoreHome_SupportPiwik');
$config->setOrder(5);
}
public function render()
{
$footerMessage = null;
if (Common::getRequestVar('widget', false)
&& Piwik::hasUserSuperUserAccess()) {
$footerMessage = $this->translator->translate('CoreHome_OnlyForSuperUserAccess');
}
return $this->renderTemplate('getDonateForm', array(
'footerMessage' => $footerMessage
));
}
}

View File

@@ -0,0 +1,44 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome\Widgets;
use Piwik\Widget\Widget;
use Piwik\Widget\WidgetConfig;
use Piwik\Translation\Translator;
use Piwik\View;
class GetPromoVideo extends Widget
{
/**
* @var Translator
*/
private $translator;
public function __construct(Translator $translator)
{
$this->translator = $translator;
}
public static function configure(WidgetConfig $config)
{
$config->setCategoryId('About Matomo');
$config->setName('Installation_Welcome');
$config->setOrder(10);
}
public function render()
{
$view = new View('@CoreHome/getPromoVideo');
$view->shareText = $this->translator->translate('CoreHome_SharePiwikShort');
$view->shareTextLong = $this->translator->translate('CoreHome_SharePiwikLong');
$view->promoVideoUrl = 'https://www.youtube.com/watch?v=OslfF_EH81g';
return $view->render();
}
}

View File

@@ -0,0 +1,115 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome\Widgets;
use Piwik\API\Request;
use Piwik\Db;
use Piwik\Piwik;
use Piwik\Plugin;
use Piwik\Plugins\CoreHome\SystemSummary\Item;
use Piwik\Plugins\SegmentEditor\Services\StoredSegmentService;
use Piwik\Version;
use Piwik\Widget\Widget;
use Piwik\Widget\WidgetConfig;
class GetSystemSummary extends Widget
{
/**
* @var StoredSegmentService
*/
private $storedSegmentService;
/**
* @var Plugin\Manager
*/
private $pluginManager;
public function __construct(StoredSegmentService $storedSegmentService, Plugin\Manager $pluginManager)
{
$this->storedSegmentService = $storedSegmentService;
$this->pluginManager = $pluginManager;
}
public static function configure(WidgetConfig $config)
{
$config->setCategoryId('About Matomo');
$config->setName('CoreHome_SystemSummaryWidget');
$config->setOrder(15);
$config->setIsEnabled(Piwik::hasUserSuperUserAccess());
}
public function render()
{
$mysqlVersion = $this->getMySqlVersion();
$systemSummary = array();
/**
* Triggered to add system summary items that are shown in the System Summary widget.
*
* **Example**
*
* public function addSystemSummaryItem(&$systemSummary)
* {
* $numUsers = 5;
* $systemSummary[] = new SystemSummary\Item($key = 'users', Piwik::translate('General_NUsers', $numUsers), $value = null, array('module' => 'UsersManager', 'action' => 'index'), $icon = 'icon-user');
* }
*
* @param Item[] &$systemSummary An array containing system summary items.
*/
Piwik::postEvent('System.addSystemSummaryItems', array(&$systemSummary));
$systemSummary[] = new Item($key = 'piwik-version', Piwik::translate('CoreHome_SystemSummaryPiwikVersion'), Version::VERSION, $url = null, $icon = '', $order = 21);
$systemSummary[] = new Item($key = 'mysql-version', Piwik::translate('CoreHome_SystemSummaryMysqlVersion'), $mysqlVersion, $url = null, $icon = '', $order = 22);
$systemSummary[] = new Item($key = 'php-version', Piwik::translate('CoreHome_SystemSummaryPhpVersion'), phpversion(), $url = null, $icon = '', $order = 23);
$systemSummary = array_filter($systemSummary);
usort($systemSummary, function ($itemA, $itemB) {
if ($itemA->getOrder() == $itemB->getOrder()) {
return 0;
}
if ($itemA->getOrder() > $itemB->getOrder()) {
return 1;
}
return -1;
});
/**
* Triggered to filter system summary items that are shown in the System Summary widget. A plugin might also
* sort the system summary items differently.
*
* **Example**
*
* public function filterSystemSummaryItems(&$systemSummary)
* {
* foreach ($systemSummaryItems as $index => $item) {
* if ($item && $item->getKey() === 'users') {
* $systemSummaryItems[$index] = null;
* }
* }
* }
*
* @param Item[] &$systemSummary An array containing system summary items.
*/
Piwik::postEvent('System.filterSystemSummaryItems', array(&$systemSummary));
$systemSummary = array_filter($systemSummary);
return $this->renderTemplate('getSystemSummary', array(
'items' => $systemSummary
));
}
private function getMySqlVersion()
{
$db = Db::get();
return $db->getServerVersion();
}
}

View File

@@ -0,0 +1,42 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* Shows a general loading message while [loading] is set to true.
*
* @param {Boolean} loading If true, the activity indicator is shown, otherwise the indicator is hidden.
*
* Example:
* <div piwik-activity-indicator loading-message="'My custom message'" loading="true|false"></div>
*/
(function () {
angular.module('piwikApp').directive('piwikActivityIndicator', piwikActivityIndicator);
piwikActivityIndicator.$inject = ['piwik'];
function piwikActivityIndicator(piwik){
return {
restrict: 'A',
transclude: true,
scope: {
loading: '=',
loadingMessage: '=?'
},
templateUrl: 'plugins/CoreHome/angularjs/activity-indicator/activityindicator.html?cb=' + piwik.cacheBuster,
compile: function (element, attrs) {
return function (scope, element, attrs) {
if (!scope.loadingMessage) {
scope.loadingMessage = _pk_translate('General_LoadingData');
}
};
}
};
}
})();

View File

@@ -0,0 +1,3 @@
<div ng-show="loading" class="loadingPiwik">
<img src="plugins/Morpheus/images/loading-blue.gif" alt=""/> <span>{{ loadingMessage }}</span>
</div>

View File

@@ -0,0 +1,85 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
(function () {
angular.module('piwikApp').controller('AjaxFormController', AjaxFormController);
AjaxFormController.$inject = ['piwikApi', '$filter'];
function AjaxFormController(piwikApi, $filter) {
var vm = this;
/**
* Set to non-null when a form submit request returns successfully. When successful, it will
* be the entire JSON parsed response of the request.
*
* @type {null|string}
*/
vm.successfulPostResponse = null;
/**
* Set to non-null when a form submit request results in an error. When an error occurs,
* it will be set to the string error message.
*
* @type {null|string}
*/
vm.errorPostResponse = null;
/**
* true if currently submitting a POST request, false if otherwise.
*
* @type {bool}
*/
vm.isSubmitting = false;
vm.submitForm = submitForm;
/**
* Sends a POST to the configured API method.
*/
function submitForm() {
var postParams;
vm.successfulPostResponse = null;
vm.errorPostResponse = null;
if (vm.sendJsonPayload) {
postParams = {data: JSON.stringify(vm.data)};
} else {
postParams = vm.data;
}
vm.isSubmitting = true;
piwikApi.post(
{ // GET params
module: 'API',
method: vm.submitApiMethod
},
postParams,
{ // request options
createErrorNotification: !vm.noErrorNotification
}
).then(function (response) {
vm.successResponse = response;
if (!vm.noSuccessNotification) {
var UI = require('piwik/UI');
var notification = new UI.Notification();
notification.show($filter('translate')('General_YourChangesHaveBeenSaved'), {
context: 'success',
type: 'toast',
id: 'ajaxHelper'
});
notification.scrollToNotification();
}
}).catch(function (errorMessage) {
vm.errorPostResponse = errorMessage;
}).finally(function () {
vm.isSubmitting = false;
});
}
}
})();

View File

@@ -0,0 +1,144 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* AngularJS directive that manages an AJAX form.
*
* This directive will detect inputs & selects defined within an element and when a
* submit button is clicked, will post data from the inputs & selects to a Piwik API method.
*
* When the POST request is finished the result will, by default, be displayed as a
* notification.
*
* This directive accepts the following attributes:
*
* - **submit-api-method**: **required** The Piwik API method that handles the POST request.
* - **send-json-payload**: Whether to send the data as a form encoded URL or to send it as JSON.
* If sending as JSON, the payload will still be a form encoded value,
* but will contain a JSON object like `{data: {...form data...}}`.
*
* This is for forms with lots of fields where having the same number
* of parameters in an API method would not be desired.
* - **no-error-notification**: If true, does not display an error notification if the AJAX post
* fails.
* - **no-success-notification**: If true, does not display an error notification if the AJAX
* results in success.
*
* **Custom Success/Error Handling**
*
* On success/failure, the response will be stored in controller scope. Child elements of a
* piwik-ajax-form element can access this data, and thus, can customize what happens when
* a form submit succeeds/fails.
*
* See the ajax-form.controller.js file for more info.
*
* Usage:
*
* <div piwik-ajax-form
* submit-api-method="'MyPlugin.myFormSaveMethod'"
* send-json-payload="true"
* ng-model="myFormData">
*
* <h2>My Form</h2>
* <input name="myOption" value="myDefaultValue" type="text" />
* <input name="myOtherOption" type="checkbox" checked="checked" />
* <input type="submit" value="Submit" ng-disabled="ajaxForm.isSubmitting" />
*
* <div piwik-notification context='error' ng-show="errorPostResponse">ERROR!</div>
* </div>
*/
(function () {
angular.module('piwikApp').directive('piwikAjaxForm', piwikAjaxForm);
piwikAjaxForm.$inject = ['$parse'];
function piwikAjaxForm($parse) {
return {
restrict: 'A',
scope: {
submitApiMethod: '=',
sendJsonPayload: '=',
noErrorNotification: '=',
noSuccessNotification: '=',
useCustomDataBinding: '='
},
require: '?ngModel',
controller: 'AjaxFormController',
controllerAs: 'ajaxForm',
transclude: true,
compile: function (element, attrs) {
attrs.noErrorNotification = !! attrs.noErrorNotification;
return function (scope, element, attrs, ngModel, transclude) {
if (!scope.submitApiMethod) {
throw new Error("submitApiMethod is required");
}
scope.ajaxForm.submitApiMethod = scope.submitApiMethod;
scope.ajaxForm.sendJsonPayload = scope.sendJsonPayload;
scope.ajaxForm.noErrorNotification = scope.noErrorNotification;
scope.ajaxForm.noSuccessNotification = scope.noSuccessNotification;
scope.ajaxForm.data = {};
// if a model is supplied, initiate form data w/ model value
if (ngModel) {
var ngModelGetter = $parse(attrs.ngModel); // probably redundant, but I cannot find another way to
// get the ng model value here
scope.ajaxForm.data = ngModelGetter(scope.$parent);
}
// on change of any input, change appropriate value in model, but only if requested
if (!scope.useCustomDataBinding) {
element.on('change', 'input,select', function () {
setFormValueFromInput(this);
});
}
// on submit call controller submit method
element.on('click', 'input[type=submit]', function () {
scope.ajaxForm.submitForm();
});
// make sure child elements can access this directive's scope
transclude(scope, function(clone, scope) {
if (!scope.useCustomDataBinding) {
var $inputs = clone.find('input,select').not('[type=submit]');
// initialize form data to input values (include <select>s
$inputs.each(function () {
setFormValueFromInput(this, true);
});
}
element.append(clone);
});
function setFormValueFromInput(inputElement, skipScopeApply) {
var $ = angular.element,
name = $(inputElement).attr('name'),
val;
if ($(inputElement).attr('type') == 'checkbox') {
val = $(inputElement).is(':checked');
} else {
val = $(inputElement).val();
}
scope.ajaxForm.data[name] = val;
if (!skipScopeApply) {
setTimeout(function () {
scope.$apply();
}, 0);
}
}
};
}
};
}
})();

View File

@@ -0,0 +1,2 @@
<div class="alert alert-{{severity}}" ng-transclude>
</div>

View File

@@ -0,0 +1,26 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* Usage:
* <div piwik-alert>
*/
(function () {
angular.module('piwikApp').directive('piwikAlert', piwikAlert);
piwikAlert.$inject = ['piwik'];
function piwikAlert(piwik){
return {
restrict: 'A',
transclude: true,
scope: {severity: '@piwikAlert'},
templateUrl: 'plugins/CoreHome/angularjs/alert/alert.directive.html?cb=' + piwik.cacheBuster
};
}
})();

View File

@@ -0,0 +1,124 @@
.alert-icon-center-vertically(@font-size) {
@half-height: @font-size / 2;
// IE8 doesn't support calc(): it's OK, the icon will just be aligned to the top
top: calc(~'50% - @{half-height}');
// phantomjs only supports -webkit-calc()
top: -webkit-calc(~'50% - @{half-height}');
}
.alert {
padding: 20px 20px 20px 60px;
margin-bottom: 20px;
border: 1px solid transparent;
border-radius: 2px;
font-size: 14px;
position: relative;
&:before {
font-family: "matomo";
content: "\e625";
position: absolute;
left: 20px;
line-height: 100%; // line-height = font-size
font-size: 24px;
}
a {
color: inherit;
text-decoration: underline;
}
}
body #content .alert-success p {
color: @color-green-piwik;
}
.alert-success {
color: @color-green-piwik;
border-color: @color-green-piwik;
&:before {
content: "\e63d";
color: @color-green-piwik;
}
p {
color: @color-green-piwik;
}
a {
color: @color-green-piwik;
text-decoration: underline;
&:hover {
text-decoration: underline;
}
}
}
body #content .alert-info p {
color: #838383;
}
.alert-info {
color: #838383;
background-color: #F5F5F5;
font-size: 14px;
padding-top: 15px;
padding-bottom: 15px;
p {
color: #838383;
}
&:before {
color: #afafaf;
font-size: 20px;
}
a {
color: #838383;
text-decoration: underline;
&:hover {
text-decoration: underline;
}
}
}
body #content .alert-warning p {
color: #fbf7f1;
}
.alert-warning {
color: #f57c00;
border-color: #f57c00;
&:before {
content: "\e621";
color: #f57c00;
}
p {
color: #f57c00;
}
a {
color: #f57c00;
text-decoration: underline;
&:hover {
text-decoration: underline;
}
}
}
body #content .alert-danger p {
color: @color-red-piwik;
}
.alert-danger {
color: @color-red-piwik;
border-color: @color-red-piwik;
&:before {
content: "\e616";
color: @color-red-piwik;
}
p {
color: @color-red-piwik;
}
a {
color: @color-red-piwik;
text-decoration: underline;
&:hover {
text-decoration: underline;
}
}
}

View File

@@ -0,0 +1,115 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* See https://github.com/piwik/piwik/issues/4795 "linking to #hash tag does not work after merging AngularJS"
*/
(function () {
function scrollToAnchorNode($node)
{
$.scrollTo($node, 20);
}
function preventDefaultIfEventExists(event)
{
if (event) {
event.preventDefault();
}
}
function scrollToAnchorIfPossible(hash, event)
{
if (!hash) {
return;
}
if (-1 !== hash.indexOf('&')) {
return;
}
try {
var $node = $('#' + hash);
} catch (err) {
// on jquery syntax error, ignore so nothing is logged to the console
return;
}
if ($node && $node.length) {
scrollToAnchorNode($node);
preventDefaultIfEventExists(event);
return;
}
$node = $('a[name='+ hash + ']');
if ($node && $node.length) {
scrollToAnchorNode($node);
preventDefaultIfEventExists(event);
}
}
function isLinkWithinSamePage(location, newUrl)
{
if (location && location.origin && -1 === newUrl.indexOf(location.origin)) {
// link to different domain
return false;
}
if (location && location.pathname && -1 === newUrl.indexOf(location.pathname)) {
// link to different path
return false;
}
if (location && location.search && -1 === newUrl.indexOf(location.search)) {
// link with different search
return false;
}
return true;
}
function handleScrollToAnchorIfPresentOnPageLoad()
{
if (location.hash.substr(0, 2) == '#/') {
var hash = location.hash.substr(2);
scrollToAnchorIfPossible(hash, null);
}
}
function handleScrollToAnchorAfterPageLoad()
{
angular.module('piwikApp').run(['$rootScope', function ($rootScope) {
$rootScope.$on('$locationChangeStart', onLocationChangeStart);
function onLocationChangeStart (event, newUrl, oldUrl, $location) {
if (!newUrl) {
return;
}
var hashPos = newUrl.indexOf('#/');
if (-1 === hashPos) {
return;
}
if (!isLinkWithinSamePage(this.location, newUrl)) {
return;
}
var hash = newUrl.substr(hashPos + 2);
scrollToAnchorIfPossible(hash, event);
}
}]);
}
handleScrollToAnchorAfterPageLoad();
$(handleScrollToAnchorIfPresentOnPageLoad);
})();

View File

@@ -0,0 +1,63 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* If the given text or resolved expression matches any text within the element, the matching text will be wrapped
* with a class.
*
* Example:
* <div piwik-autocomplete-matched="'text'">My text</div> ==> <div>My <span class="autocompleteMatched">text</span></div>
*
* <div piwik-autocomplete-matched="searchTerm">{{ name }}</div>
* <input type="text" ng-model="searchTerm">
*/
(function () {
angular.module('piwikApp.directive').directive('piwikAttributes', piwikAttributes);
piwikAttributes.$inject = ['$sanitize'];
function piwikAttributes(piwik, $sanitize) {
return {
link: function (scope, element, attrs) {
if (!attrs.piwikAttributes || !angular.isString(attrs.piwikAttributes)) {
return;
}
function applyAttributes(attributes)
{
if (angular.isObject(attributes)) {
angular.forEach(attributes, function (value, key) {
if (angular.isObject(value)) {
value = JSON.stringify(value);
}
// replace line breaks in placeholder with big amount of spaces for safari,
// as line breaks are not support there
if (key === 'placeholder' && /^((?!chrome|android).)*safari/i.test(navigator.userAgent)) {
value = value.replace(/(?:\r\n|\r|\n)/g, (new Array(200)).join(' '));
}
if (key === 'disabled') {
element.prop(key, value);
} else {
element.attr(key, value);
}
});
}
}
applyAttributes(JSON.parse(attrs.piwikAttributes));
attrs.$observe('piwikAttributes', function (newVal) {
applyAttributes(JSON.parse(newVal));
});
}
};
}
})();

View File

@@ -0,0 +1,53 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* If the given text or resolved expression matches any text within the element, the matching text will be wrapped
* with a class.
*
* Example:
* <div piwik-autocomplete-matched="'text'">My text</div> ==> <div>My <span class="autocompleteMatched">text</span></div>
*
* <div piwik-autocomplete-matched="searchTerm">{{ name }}</div>
* <input type="text" ng-model="searchTerm">
*/
(function () {
angular.module('piwikApp.directive').directive('piwikAutocompleteMatched', piwikAutocompleteMatched);
piwikAutocompleteMatched.$inject = ['piwik', '$sanitize'];
function piwikAutocompleteMatched(piwik, $sanitize) {
return {
priority: 10, // makes sure to render after other directives, otherwise the content might be overwritten again see https://github.com/piwik/piwik/pull/8467
link: function (scope, element, attrs) {
var searchTerm;
scope.$watch(attrs.piwikAutocompleteMatched, function (value) {
searchTerm = value;
updateText();
});
function updateText() {
if (!searchTerm || !element) {
return;
}
var content = piwik.helper.htmlEntities(element.text());
var startTerm = content.toLowerCase().indexOf(searchTerm.toLowerCase());
if (-1 !== startTerm) {
var word = content.substr(startTerm, searchTerm.length);
var escapedword = $sanitize(piwik.helper.htmlEntities(word));
content = content.replace(word, '<span class="autocompleteMatched">' + escapedword + '</span>');
element.html(content);
}
}
}
};
}
})();

View File

@@ -0,0 +1,50 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* Usage:
* <div piwik-dialog="showDialog">...</div>
* Will show dialog once showDialog evaluates to true.
*
* <div piwik-dialog="showDialog" yes="executeMyFunction();">
* ... <input type="button" role="yes" value="button">
* </div>
* Will execute the "executeMyFunction" function in the current scope once the yes button is pressed.
*/
(function () {
angular.module('piwikApp.directive').directive('piwikDialog', piwikDialog);
piwikDialog.$inject = ['piwik', '$parse'];
function piwikDialog(piwik, $parse) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
element.css('display', 'none');
scope.$watch(attrs.piwikDialog, function(newValue, oldValue) {
if (newValue) {
piwik.helper.modalConfirm(element, {yes: function() {
if (attrs.yes) {
scope.$eval(attrs.yes);
setTimeout(function () { scope.$apply(); }, 0);
}
}}, {
complete: function () {
setTimeout(function () {
scope.$apply($parse(attrs.piwikDialog).assign(scope, false));
}, 0);
}
});
}
});
}
};
}
})();

View File

@@ -0,0 +1,9 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
(function () {
angular.module('piwikApp.directive', []);
})();

View File

@@ -0,0 +1,37 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* Usage:
* <div piwik-dropdown-button>
*/
(function () {
angular.module('piwikApp.directive').directive('dropdownButton', piwikDropdownButton);
piwikDropdownButton.$inject = ['piwik'];
function piwikDropdownButton(piwik){
return {
restrict: 'C',
compile: function (element, attrs) {
$(element).dropdown({
inDuration: 300,
outDuration: 225,
constrain_width: false, // Does not change width of dropdown to that of the activator
// hover: true, // Activate on hover
belowOrigin: true // Displays dropdown below the button
});
return function (scope, element, attrs) {
};
}
};
}
})();

View File

@@ -0,0 +1,88 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* Usage:
* <div piwik-form-condition>
*/
(function () {
angular.module('piwikApp.directive').directive('fieldCondition', piwikFieldCondition);
piwikFieldCondition.$inject = ['piwik', '$timeout'];
function piwikFieldCondition(piwik, $timeout){
function evaluate(scope, condition, element)
{
if (scope.$eval(condition, scope.allValues)) {
element.show();
} else {
element.hide();
}
}
function getValueFromElement(element)
{
if (element.attr('type') === 'checkbox') {
return element.is(':checked');
} else if (element.attr('type') === 'radio') {
return $('.form-group [name=' + element.attr('name') + ']:checked').val();
} else if (element.prop('tagName').toLowerCase() === 'select') {
var name = element.val();
if (name.indexOf('string:') === 0) {
return name.substr('string:'.length);
}
return name;
}
return element.val();
}
function evaluateConditionalExpression(scope, condition, element)
{
var fieldParts = condition.replace('!', '');
fieldParts = fieldParts.split(' ');
var fieldNames = [];
fieldParts.forEach(function (name) {
name = $.trim(name);
if (name && name.length > 3) {
fieldNames.push(name);
}
});
scope.allValues = {};
angular.forEach(fieldNames, function (name) {
var actualField = $('.form-group [name=' + name + ']').first();
if (actualField.length) {
scope.allValues[name] = getValueFromElement(actualField);
actualField.on('change', function () {
scope.allValues[name] = getValueFromElement($(this));
evaluate(scope, condition, element);
});
}
});
evaluate(scope, condition, element);
}
return {
priority: 10, // makes sure to render after other directives, otherwise the content might be overwritten again see https://github.com/piwik/piwik/pull/8467
restrict: 'A',
link: function(scope, element, attrs) {
var condition = attrs.fieldCondition;
if (condition) {
$timeout(function (){
evaluateConditionalExpression(scope, condition, element);
});
}
},
};
}
})();

View File

@@ -0,0 +1,76 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* The given expression will be executed when the user presses either escape or presses something outside
* of this element
*
* Example:
* <div piwik-focus-anywhere-but-here="closeDialog()">my dialog</div>
*/
(function () {
angular.module('piwikApp.directive').directive('piwikFocusAnywhereButHere', piwikFocusAnywhereButHere);
piwikFocusAnywhereButHere.$inject = ['$document'];
function piwikFocusAnywhereButHere($document){
return {
restrict: 'A',
link: function(scope, element, attr, ctrl) {
var isMouseDown = false;
var hasScrolled = false;
function onClickOutsideElement (event) {
var hadUsedScrollbar = isMouseDown && hasScrolled;
isMouseDown = false;
hasScrolled = false;
if (hadUsedScrollbar) {
return;
}
if (element.has(event.target).length === 0) {
setTimeout(function () {
scope.$apply(attr.piwikFocusAnywhereButHere);
}, 0);
}
}
function onScroll (event) {
hasScrolled = true;
}
function onMouseDown (event) {
isMouseDown = true;
hasScrolled = false;
}
function onEscapeHandler (event) {
if (event.which === 27) {
setTimeout(function () {
isMouseDown = false;
hasScrolled = false;
scope.$apply(attr.piwikFocusAnywhereButHere);
}, 0);
}
}
$document.on('keyup', onEscapeHandler);
$document.on('mousedown', onMouseDown);
$document.on('mouseup', onClickOutsideElement);
$document.on('scroll', onScroll);
scope.$on('$destroy', function() {
$document.off('keyup', onEscapeHandler);
$document.off('mousedown', onMouseDown);
$document.off('mouseup', onClickOutsideElement);
$document.off('scroll', onScroll);
});
}
};
}
})();

View File

@@ -0,0 +1,33 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* If the given expression evaluates to true the element will be focussed
*
* Example:
* <input type="text" piwik-focus-if="view.editName">
*/
(function () {
angular.module('piwikApp.directive').directive('piwikFocusIf', piwikFocusIf);
piwikFocusIf.$inject = ['$timeout'];
function piwikFocusIf($timeout) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
scope.$watch(attrs.piwikFocusIf, function(newValue, oldValue) {
if (newValue) {
$timeout(function () {
element[0].focus();
}, 5);
}
});
}
};
}
})();

View File

@@ -0,0 +1,25 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* Prevents the default behavior of the click. For instance useful if a link should only work in case the user
* does a "right click open in new window".
*
* Example
* <a piwik-ignore-click ng-click="doSomething()" href="/">my link</a>
*/
(function () {
angular.module('piwikApp.directive').directive('piwikIgnoreClick', piwikIgnoreClick);
function piwikIgnoreClick() {
return function(scope, element, attrs) {
$(element).click(function(event) {
event.preventDefault();
});
};
}
})();

View File

@@ -0,0 +1,31 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* Allows you to define any expression to be executed in case the user presses enter
*
* Example
* <div piwik-onenter="save()">
* <div piwik-onenter="showList=false">
*/
(function () {
angular.module('piwikApp.directive').directive('piwikOnenter', piwikOnenter);
function piwikOnenter() {
return function(scope, element, attrs) {
element.bind("keydown keypress", function(event) {
if(event.which === 13) {
scope.$apply(function(){
scope.$eval(attrs.piwikOnenter, {'event': event});
});
event.preventDefault();
}
});
};
}
})();

View File

@@ -0,0 +1,69 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* On focus (click, tab) selects the text within the current element
*
* Example:
* <div piwik-select-on-focus>my dialog</div>
*/
(function () {
angular.module('piwikApp.directive').directive('piwikSelectOnFocus', piwikSelectOnFocus);
function piwikSelectOnFocus(){
return {
restrict: 'A',
link: function(scope, element, attr, ctrl) {
var focusedElement = null;
var tagName = (element.prop('tagName') + '').toLowerCase();
var elementSupportsSelect = tagName === 'textarea';
function onFocusHandler(event) {
if (focusedElement !== this) {
focusedElement = this;
angular.element(this).select();
}
}
function onClickHandler(event) {
// .select() + focus and blur seems to not work on pre elements
var range = document.createRange();
range.selectNode(this);
var selection = window.getSelection();
if (selection && selection.rangeCount > 0) {
selection.removeAllRanges();
}
if (selection) {
selection.addRange(range);
}
}
function onBlurHandler(event) {
focusedElement = null;
}
if (elementSupportsSelect) {
element.on('focus', onFocusHandler);
element.on('blur', onBlurHandler);
} else {
element.on('click', onClickHandler);
}
scope.$on('$destroy', function() {
if (elementSupportsSelect) {
element.off('focus', onFocusHandler);
element.off('blur', onBlurHandler);
} else {
element.off('click', onClickHandler);
}
});
}
};
}
})();

View File

@@ -0,0 +1,59 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* Handles visibility of sensitive data. By default data will be shown replaced with stars (*)
* On click on the element the full data will be shown
*
* Configuration attributes:
* data-show-characters number of characters to show in clear text (defaults to 6)
* data-click-element-selector selector for element that will show the full data on click (defaults to element)
*
* Example:
* <div piwik-show-sensitive-date="some text"></div>
*/
(function () {
angular.module('piwikApp.directive').directive('piwikShowSensitiveData', piwikShowSensitiveData);
function piwikShowSensitiveData(){
return {
restrict: 'A',
link: function(scope, element, attr) {
var sensitiveData = attr.piwikShowSensitiveData || attr.text();
var showCharacters = attr.showCharacters || 6;
var clickElement = attr.clickElementSelector || element;
var protectedData = '';
if (showCharacters > 0) {
protectedData += sensitiveData.substr(0, showCharacters);
}
protectedData += sensitiveData.substr(showCharacters).replace(/./g, '*');
element.html(protectedData);
function onClickHandler(event) {
element.html(sensitiveData);
$(clickElement).css({
cursor: ''
});
$(clickElement).tooltip("destroy");
}
$(clickElement).tooltip({
content: _pk_translate('CoreHome_ClickToSeeFullInformation'),
items: '*',
track: true
});
$(clickElement).one('click', onClickHandler);
$(clickElement).css({
cursor: 'pointer'
});
}
};
}
})();

View File

@@ -0,0 +1,49 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* Will activate the materialize side nav feature once rendered. We use this directive as it makes sure
* the actual left menu is rendered at the time we init the side nav.
*
* Has to be set on a collaapsible element
*
* Example:
* <div class="collapsible" piwik-side-nav="nav .activateLeftMenu">...</div>
*/
(function () {
angular.module('piwikApp.directive').directive('piwikSideNav', piwikSideNav);
piwikSideNav.$inject = ['$timeout'];
var initialized = false;
function piwikSideNav($timeout){
return {
restrict: 'A',
priority: 10,
link: function(scope, element, attr, ctrl) {
if (attr.piwikSideNav) {
$timeout(function () {
if (!initialized) {
initialized = true;
var sideNavActivator = $(attr.piwikSideNav).show();
sideNavActivator.sideNav({
closeOnClick: true
});
}
if (element.hasClass('collapsible')) {
element.collapsible();
}
});
}
}
};
}
})();

View File

@@ -0,0 +1,28 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* Converts ngModel value to a number
*
* Example:
* <input type="number" string-to-number>...</div>
*/
(function () {
angular.module('piwikApp.directive').directive('stringToNumber', function() {
return {
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
ngModel.$parsers.push(function(value) {
return '' + value;
});
ngModel.$formatters.push(function(value) {
return parseFloat(value);
});
}
};
});
})();

View File

@@ -0,0 +1,36 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* Directive for easy & safe complex internationalization. This directive allows
* users to embed the sprintf arguments used in internationalization inside an HTML
* element. Since the HTML will eventually be sanitized by AngularJS, HTML can be used
* within the sprintf args. Using the filter, this is not possible w/o manually sanitizing
* and creating trusted HTML, which is not as safe.
*
* Note: nesting this directive is not supported.
*
* Usage:
* <span piwik-translate="Plugin_TranslationToken">
* first arg::<strong>second arg</strong>::{{ unsafeDataThatWillBeSanitized }}
* </span>
*/
(function () {
angular.module('piwikApp.directive').directive('piwikTranslate', piwikTranslate);
function piwikTranslate() {
return {
priority: 1,
restrict: 'A',
compile: function(element, attrs) {
var parts = element.html().split('::'),
translated = _pk_translate(attrs.piwikTranslate, parts);
element.html(translated);
}
};
}
})();

View File

@@ -0,0 +1,16 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
(function () {
angular.module('piwikApp.filter').filter('escape', escape);
function escape() {
return function(value) {
return piwikHelper.escape(piwikHelper.htmlEntities(value));
};
}
})();

View File

@@ -0,0 +1,49 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
(function () {
angular.module('piwikApp.filter').filter('evolution', evolutionFilter);
function evolutionFilter() {
function calculateEvolution(currentValue, pastValue)
{
pastValue = parseInt(pastValue, 10);
currentValue = parseInt(currentValue, 10) - pastValue;
var evolution;
if (currentValue === 0 || isNaN(currentValue)) {
evolution = 0;
} else if (pastValue === 0 || isNaN(pastValue)) {
evolution = 100;
} else {
evolution = (currentValue / pastValue) * 100;
}
return evolution;
}
function formatEvolution(evolution)
{
evolution = Math.round(evolution);
if (evolution > 0) {
evolution = '+' + evolution;
}
evolution += '%';
return evolution;
}
return function(currentValue, pastValue) {
var evolution = calculateEvolution(currentValue, pastValue);
return formatEvolution(evolution);
};
}
})();

View File

@@ -0,0 +1,9 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
(function () {
angular.module('piwikApp.filter', []);
})();

View File

@@ -0,0 +1,26 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
(function () {
angular.module('piwikApp.filter').filter('htmldecode', htmldecode);
htmldecode.$inject = ['piwik'];
/**
* Be aware that this filter can cause XSS so only use it when you're sure it is safe.
* Eg it should be safe when it is afterwards escaped by angular sanitize again.
*/
function htmldecode(piwik) {
return function(text) {
if (text && text.length) {
return piwik.helper.htmlDecode(text);
}
return text;
};
}
})();

View File

@@ -0,0 +1,21 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
(function () {
angular.module('piwikApp.filter').filter('length', length);
function length() {
return function(stringOrArray) {
if (stringOrArray && stringOrArray.length) {
return stringOrArray.length;
}
return 0;
};
}
})();

View File

@@ -0,0 +1,16 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
(function () {
angular.module('piwikApp.filter').filter('prettyUrl', prettyUrl);
function prettyUrl() {
return function(input) {
return input.trim().replace('http://', '');
};
}
})();

View File

@@ -0,0 +1,16 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
(function () {
angular.module('piwikApp.filter').filter('startFrom', startFrom);
function startFrom() {
return function(input, start) {
start = +start; //parse to int
return input.slice(start);
};
}
})();

View File

@@ -0,0 +1,41 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
(function () {
describe('startFromFilter', function() {
var startFrom;
beforeEach(module('piwikApp.filter'));
beforeEach(inject(function($injector) {
var $filter = $injector.get('$filter');
startFrom = $filter('startFrom');
}));
describe('#startFrom()', function() {
it('should return all entries if index is zero', function() {
var result = startFrom([1,2,3], 0);
expect(result).to.eql([1,2,3]);
});
it('should return only partial entries if filter is higher than zero', function() {
var result = startFrom([1,2,3], 2);
expect(result).to.eql([3]);
});
it('should return no entries if start is higher than input length', function() {
var result = startFrom([1,2,3], 11);
expect(result).to.eql([]);
});
});
});
})();

View File

@@ -0,0 +1,22 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
(function () {
angular.module('piwikApp.filter').filter('translate', translate);
function translate() {
return function(key, value1, value2, value3) {
var values = [];
if (arguments && arguments.length > 1) {
for (var index = 1; index < arguments.length; index++) {
values.push(arguments[index]);
}
}
return _pk_translate(key, values);
};
}
})();

View File

@@ -0,0 +1,20 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
(function () {
angular.module('piwikApp.filter').filter('trim', trim);
function trim() {
return function(string) {
if (string) {
return $.trim('' + string);
}
return string;
};
}
})();

View File

@@ -0,0 +1,21 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
(function () {
angular.module('piwikApp.filter').filter('ucfirst', ucfirst);
function ucfirst() {
return function(value) {
if (!value) {
return value;
}
var firstLetter = (value + '').charAt(0).toUpperCase();
return firstLetter + value.substr(1);
};
}
})();

View File

@@ -0,0 +1,14 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
(function () {
angular.module('piwikApp.service').service('globalAjaxQueue', ajaxQueue);
function ajaxQueue() {
return globalAjaxQueue;
}
})();

View File

@@ -0,0 +1,358 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* Piwik period management service for the frontend.
*
* Usage:
*
* var DayPeriod = piwikPeriods.get('day');
* var day = new DayPeriod(new Date());
*
* or
*
* var day = piwikPeriods.parse('day', '2013-04-05');
*
* Adding custom periods:
*
* To add your own period to the frontend, create a period class for it
* w/ the following methods:
*
* - **getPrettyString()**: returns a human readable display string for the period.
* - **getDateRange()**: returns an array w/ two elements, the first being the start
* Date of the period, the second being the end Date. The dates
* must be Date objects, not strings, and are inclusive.
* - (_static_) **parse(strDate)**: creates a new instance of this period from the
* value of the 'date' query parameter.
* - (_static_) **getDisplayText**: returns translated text for the period, eg, 'month',
* 'week', etc.
*
* Then call piwik.addCustomPeriod w/ your period class:
*
* piwik.addCustomPeriod('mycustomperiod', MyCustomPeriod);
*
* NOTE: currently only single date periods like day, week, month year can
* be extended. Other types of periods that require a special UI to
* view/edit aren't, since there is currently no way to use a
* custom UI for a custom period.
*/
(function () {
angular.module('piwikApp.service').factory('piwikPeriods', piwikPeriods);
var periods = {}, periodOrder = [];
piwik.addCustomPeriod = addCustomPeriod;
// day period
function DayPeriod(date) {
this.dateInPeriod = date;
}
DayPeriod.parse = singleDatePeriodFactory(DayPeriod);
DayPeriod.getDisplayText = function () {
return _pk_translate('Intl_PeriodDay');
};
DayPeriod.prototype = {
getPrettyString: function () {
return $.datepicker.formatDate('yy-mm-dd', this.dateInPeriod);
},
getDateRange: function () {
return [this.dateInPeriod, this.dateInPeriod];
}
};
addCustomPeriod('day', DayPeriod);
// week period
function WeekPeriod(date) {
this.dateInPeriod = date;
}
WeekPeriod.parse = singleDatePeriodFactory(WeekPeriod);
WeekPeriod.getDisplayText = function () {
return _pk_translate('Intl_PeriodWeek');
};
WeekPeriod.prototype = {
getPrettyString: function () {
var weekDates = this.getDateRange(this.dateInPeriod);
var startWeek = $.datepicker.formatDate('yy-mm-dd', weekDates[0]);
var endWeek = $.datepicker.formatDate('yy-mm-dd', weekDates[1]);
return _pk_translate('General_DateRangeFromTo', [startWeek, endWeek]);
},
getDateRange: function () {
var daysToMonday = (this.dateInPeriod.getDay() + 6) % 7;
var startWeek = new Date(this.dateInPeriod.getTime());
startWeek.setDate(this.dateInPeriod.getDate() - daysToMonday);
var endWeek = new Date(startWeek.getTime());
endWeek.setDate(startWeek.getDate() + 6);
return [startWeek, endWeek];
}
};
addCustomPeriod('week', WeekPeriod);
// month period
function MonthPeriod(date) {
this.dateInPeriod = date;
}
MonthPeriod.parse = singleDatePeriodFactory(MonthPeriod);
MonthPeriod.getDisplayText = function () {
return _pk_translate('Intl_PeriodMonth');
};
MonthPeriod.prototype = {
getPrettyString: function () {
return _pk_translate('Intl_Month_Long_StandAlone_' + (this.dateInPeriod.getMonth() + 1)) + ' ' +
this.dateInPeriod.getFullYear();
},
getDateRange: function () {
var startMonth = new Date(this.dateInPeriod.getTime());
startMonth.setDate(1);
var endMonth = new Date(this.dateInPeriod.getTime());
endMonth.setMonth(endMonth.getMonth() + 1);
endMonth.setDate(0);
return [startMonth, endMonth];
}
};
addCustomPeriod('month', MonthPeriod);
// year period
function YearPeriod(date) {
this.dateInPeriod = date;
}
YearPeriod.parse = singleDatePeriodFactory(YearPeriod);
YearPeriod.getDisplayText = function () {
return _pk_translate('Intl_PeriodYear');
};
YearPeriod.prototype = {
getPrettyString: function () {
return this.dateInPeriod.getFullYear();
},
getDateRange: function () {
var startYear = new Date(this.dateInPeriod.getTime());
startYear.setMonth(0);
startYear.setDate(1);
var endYear = new Date(this.dateInPeriod.getTime());
endYear.setMonth(12);
endYear.setDate(0);
return [startYear, endYear];
}
};
addCustomPeriod('year', YearPeriod);
// range period
function RangePeriod(startDate, endDate, childPeriodType) {
this.startDate = startDate;
this.endDate = endDate;
this.childPeriodType = childPeriodType;
}
RangePeriod.parse = function parseRangePeriod(strDate, childPeriodType) {
childPeriodType = childPeriodType || 'day';
if (/^previous/.test(strDate)) {
var endDate = RangePeriod.getLastNRange(childPeriodType, 2).startDate;
return RangePeriod.getLastNRange(childPeriodType, strDate.substring(8), endDate);
} else if (/^last/.test(strDate)) {
return RangePeriod.getLastNRange(childPeriodType, strDate.substring(4));
} else {
var parts = strDate.split(',');
return new RangePeriod(parseDate(parts[0]), parseDate(parts[1]), childPeriodType)
}
};
/**
* Returns a range representing the last N childPeriodType periods, including the current one.
*
* @param childPeriodType
* @param strAmount
* @param endDate
* @returns {RangePeriod}
*/
RangePeriod.getLastNRange = function (childPeriodType, strAmount, endDate) {
var nAmount = Math.max(parseInt(strAmount) - 1, 0);
if (isNaN(nAmount)) {
throw new Error('Invalid range date: ' + strDate);
}
endDate = endDate ? parseDate(endDate) : getToday();
var startDate = new Date(endDate.getTime());
if (childPeriodType === 'day') {
startDate.setDate(startDate.getDate() - nAmount);
} else if (childPeriodType === 'week') {
startDate.setDate(startDate.getDate() - (nAmount * 7));
} else if (childPeriodType === 'month') {
startDate.setMonth(startDate.getMonth() - nAmount);
} else if (childPeriodType === 'year') {
startDate.setFullYear(startDate.getFullYear() - nAmount);
} else {
throw new Error("Unknown period type '" + childPeriodType + "'.");
}
if (childPeriodType !== 'day') {
var startPeriod = periods[childPeriodType].parse(startDate);
var endPeriod = periods[childPeriodType].parse(endDate);
startDate = startPeriod.getDateRange()[0];
endDate = endPeriod.getDateRange()[1];
}
var firstWebsiteDate = new Date(1991, 7, 6);
if (startDate - firstWebsiteDate < 0) {
switch (childPeriodType) {
case 'year':
startDate = new Date(1992, 0, 1);
break;
case 'month':
startDate = new Date(1991, 8, 1);
break;
case 'week':
startDate = new Date(1991, 8, 12);
break;
case 'day':
default:
startDate = firstWebsiteDate;
break;
}
}
return new RangePeriod(startDate, endDate, childPeriodType);
};
RangePeriod.getDisplayText = function () {
return _pk_translate('General_DateRangeInPeriodList');
};
RangePeriod.prototype = {
getPrettyString: function () {
var start = $.datepicker.formatDate('yy-mm-dd', this.startDate);
var end = $.datepicker.formatDate('yy-mm-dd', this.endDate);
return _pk_translate('General_DateRangeFromTo', [start, end]);
},
getDateRange: function () {
return [this.startDate, this.endDate];
}
};
addCustomPeriod('range', RangePeriod);
// piwikPeriods service
function piwikPeriods() {
return {
getAllLabels: getAllLabels,
isRecognizedPeriod: isRecognizedPeriod,
get: get,
parse: parse,
parseDate: parseDate
};
function getAllLabels() {
return [].concat(periodOrder);
}
function get(strPeriod) {
var periodClass = periods[strPeriod];
if (!periodClass) {
throw new Error('Invalid period label: ' + strPeriod);
}
return periodClass;
}
function parse(strPeriod, strDate) {
return get(strPeriod).parse(strDate);
}
function isRecognizedPeriod(strPeriod) {
return !! periods[strPeriod];
}
}
function addCustomPeriod(name, periodClass) {
if (periods[name]) {
throw new Error('The "' + name + '" period already exists! It cannot be overridden.');
}
periods[name] = periodClass;
periodOrder.push(name);
}
function singleDatePeriodFactory(periodClass) {
return function (strDate) {
return new periodClass(parseDate(strDate));
};
}
function parseDate(strDate) {
if (strDate instanceof Date) {
return strDate;
}
if (strDate === 'today'
|| strDate === 'now'
) {
return getToday();
}
if (strDate === 'yesterday'
// note: ignoring the 'same time' part since the frontend doesn't care about the time
|| strDate === 'yesterdaySameTime'
) {
var yesterday = getToday();
yesterday.setDate(yesterday.getDate() - 1);
return yesterday;
}
try {
return $.datepicker.parseDate('yy-mm-dd', strDate);
} catch (err) {
// angular swallows this error, so manual console log here
console.error(err.message || err);
throw err;
}
}
function getToday() {
var date = new Date(Date.now());
// undo browser timezone
date.setTime(date.getTime() + date.getTimezoneOffset() * 60 * 1000);
// apply piwik site timezone (if it exists)
date.setHours(date.getHours() + ((piwik.timezoneOffset || 0) / 3600));
// get rid of hours/minutes/seconds/etc.
date.setHours(0);
date.setMinutes(0);
date.setSeconds(0);
date.setMilliseconds(0);
return date;
}
})();

View File

@@ -0,0 +1,342 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
// see https://github.com/piwik/piwik/issues/5094 used to detect an ad blocker
var hasBlockedContent = false;
(function () {
angular.module('piwikApp.service').factory('piwikApi', piwikApiService);
piwikApiService.$inject = ['$http', '$q', '$rootScope', 'piwik', '$window'];
function piwikApiService ($http, $q, $rootScope, piwik, $window) {
var url = 'index.php';
var format = 'json';
var getParams = {};
var postParams = {};
var allRequests = [];
/**
* Adds params to the request.
* If params are given more then once, the latest given value is used for the request
*
* @param {object} params
* @return {void}
*/
function addParams (params) {
if (typeof params == 'string') {
params = piwik.broadcast.getValuesFromUrl(params);
}
for (var key in params) {
getParams[key] = params[key];
}
}
function withTokenInUrl()
{
postParams['token_auth'] = piwik.token_auth;
}
function isRequestToApiMethod() {
return getParams && getParams['module'] === 'API' && getParams['method'];
}
function reset () {
getParams = {};
postParams = {};
}
function isErrorResponse(response) {
return response && angular.isObject(response) && response.result == 'error';
}
function createResponseErrorNotification(response, options) {
if (response.message
&& options.createErrorNotification
) {
var UI = require('piwik/UI');
var notification = new UI.Notification();
notification.show(response.message, {
context: 'error',
type: 'toast',
id: 'ajaxHelper',
placeat: options.placeat
});
setTimeout(function () {
// give some time for angular to render it
notification.scrollToNotification();
}, 100);
}
}
/**
* Send the request
* @return $promise
*/
function send (options) {
if (!options) {
options = {};
}
if (options.createErrorNotification === undefined) {
options.createErrorNotification = true;
}
function onSuccess(response)
{
var headers = response.headers;
response = response.data;
if (!angular.isDefined(response) || response === null) {
return $q.reject(null);
} else if (isErrorResponse(response)) {
createResponseErrorNotification(response, options);
return $q.reject(response.message || null);
} else {
return options.includeHeaders ? { headers: headers, response: response } : response;
}
}
function onError(response)
{
var message = 'Something went wrong';
if (response && (response.status === 0 || response.status === -1)) {
message = 'Request was possibly aborted';
}
return $q.reject(message);
}
var deferred = $q.defer(),
requestPromise = deferred.promise;
var headers = {
'Content-Type': 'application/x-www-form-urlencoded',
// ie 8,9,10 caches ajax requests, prevent this
'cache-control': 'no-cache'
};
var requestFormat = format;
if (getParams.format && getParams.format.toLowerCase() !== 'json' && getParams.format.toLowerCase() !== 'json2') {
requestFormat = getParams.format;
}
var ajaxCall = {
method: 'POST',
url: url,
responseType: requestFormat,
params: mixinDefaultGetParams(getParams),
data: $.param(getPostParams(postParams)),
timeout: requestPromise,
headers: headers
};
var promise = $http(ajaxCall).then(onSuccess, onError);
// we can't modify requestPromise directly and add an abort method since for some reason it gets
// removed after then/finally/catch is called.
var addAbortMethod = function (to, deferred) {
return {
then: function () {
return addAbortMethod(to.then.apply(to, arguments), deferred);
},
'finally': function () {
return addAbortMethod(to.finally.apply(to, arguments), deferred);
},
'catch': function () {
return addAbortMethod(to.catch.apply(to, arguments), deferred);
},
abort: function () {
deferred.resolve();
return this;
}
};
};
var request = addAbortMethod(promise, deferred);
allRequests.push(request);
return request.finally(function() {
var index = allRequests.indexOf(request);
if (index !== -1) {
allRequests.splice(index, 1);
}
});
}
/**
* Get the parameters to send as POST
*
* @param {object} params parameter object
* @return {object}
* @private
*/
function getPostParams (params) {
if (isRequestToApiMethod() || piwik.shouldPropagateTokenAuth) {
params.token_auth = piwik.token_auth;
}
return params;
}
/**
* Mixin the default parameters to send as GET
*
* @param {object} getParamsToMixin parameter object
* @return {object}
* @private
*/
function mixinDefaultGetParams (getParamsToMixin) {
var segment = piwik.broadcast.getValueFromHash('segment', $window.location.href.split('#')[1]);
// we have to decode the value manually because broadcast will not decode anything itself. if we don't,
// angular will encode it again before sending the value in an HTTP request.
segment = decodeURIComponent(segment);
var defaultParams = {
idSite: piwik.idSite || piwik.broadcast.getValueFromUrl('idSite'),
period: piwik.period || piwik.broadcast.getValueFromUrl('period'),
segment: segment
};
// never append token_auth to url
if (getParamsToMixin.token_auth) {
getParamsToMixin.token_auth = null;
delete getParamsToMixin.token_auth;
}
for (var key in defaultParams) {
if (!(key in getParamsToMixin) && !(key in postParams) && defaultParams[key]) {
getParamsToMixin[key] = defaultParams[key];
}
}
// handle default date & period if not already set
if (!getParamsToMixin.date && !postParams.date) {
getParamsToMixin.date = piwik.currentDateString;
}
return getParamsToMixin;
}
function abortAll() {
reset();
allRequests.forEach(function (request) {
request.abort();
});
allRequests = [];
}
function abort () {
abortAll();
}
/**
* Perform a reading API request.
* @param getParams
*/
function fetch (getParams, options) {
getParams.module = getParams.module || 'API';
if (!getParams.format) {
getParams.format = 'JSON2';
}
addParams(getParams);
var promise = send(options);
reset();
return promise;
}
function post(getParams, _postParams_, options) {
if (_postParams_) {
if (postParams && postParams.token_auth && !_postParams_.token_auth) {
_postParams_.token_auth = postParams.token_auth;
}
postParams = _postParams_;
}
return fetch(getParams, options);
}
function addPostParams(_postParams_) {
if (_postParams_) {
angular.merge(postParams, _postParams_);
}
}
/**
* Convenience method that will perform a bulk request using Piwik's API.getBulkRequest method.
* Bulk requests allow you to execute multiple Piwik requests with one HTTP request.
*
* @param {object[]} requests
* @param {object} options
* @return {HttpPromise} a promise that is resolved when the request finishes. The argument passed
* to the .then(...) callback will be an array with one element per request
* made.
*/
function bulkFetch(requests, options) {
var bulkApiRequestParams = {
urls: requests.map(function (requestObj) { return '?' + $.param(requestObj); })
};
var deferred = $q.defer(),
requestPromise = post({method: "API.getBulkRequest"}, bulkApiRequestParams, options).then(function (response) {
if (!(response instanceof Array)) {
response = [response];
}
// check for errors
for (var i = 0; i != response.length; ++i) {
var specificResponse = response[i];
if (isErrorResponse(specificResponse)) {
deferred.reject(specificResponse.message || null);
createResponseErrorNotification(specificResponse, options || {});
return;
}
}
deferred.resolve(response);
}).catch(function () {
deferred.reject.apply(deferred, arguments);
});
return deferred.promise;
}
return {
withTokenInUrl: withTokenInUrl,
bulkFetch: bulkFetch,
post: post,
fetch: fetch,
addPostParams: addPostParams,
/**
* @deprecated
*/
abort: abort,
abortAll: abortAll,
mixinDefaultGetParams: mixinDefaultGetParams
};
}
})();

View File

@@ -0,0 +1,273 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
(function () {
describe('piwikApiClient', function () {
var piwikApi,
$httpBackend;
if (!window.piwik) window.piwik = {};
if (!window.piwik.UI) window.piwik.UI = {};
if (!window.piwik.UI.Notification) {
window.piwik.UI.Notification = function () {
this.show = function () {};
this.scrollToNotification = function () {};
return this;
};
}
beforeEach(module('piwikApp.service'));
beforeEach(inject(function($injector) {
piwikApi = $injector.get('piwikApi');
$httpBackend = $injector.get('$httpBackend');
$httpBackend.when('POST', /.*getBulkRequest.*/, /.*errorAction.*/).respond(function (method, url, data, headers) {
url = url.replace(/date=[^&]+/, "date=");
var errorResponse = {result: 'error', message: "error message"},
successResponse= "Response #2: " + url + " - " + data;
return [200, [errorResponse, successResponse]];
});
$httpBackend.when('POST', /.*getBulkRequest.*/).respond(function (method, url, data, headers) {
url = url.replace(/date=[^&]+/, "date=");
var responses = [
"Response #1: " + url + " - " + data,
"Response #2: " + url + " - " + data
];
return [200, JSON.stringify(responses)];
});
$httpBackend.when('POST', /.*/).respond(function (method, url, data, headers) {
url = url.replace(/date=[^&]+/, "date=");
return [200, "Request url: " + url];
});
}));
it("should successfully send a request to Piwik when fetch is called", function (done) {
piwikApi.fetch({
method: "SomePlugin.action"
}).then(function (response) {
expect(response).to.equal("Request url: index.php?date=&format=JSON2&idSite=1&method=SomePlugin.action&module=API&period=day");
done();
}).catch(function (ex) {
done(ex);
});
$httpBackend.flush();
});
it("should chain multiple then callbacks correctly when a fetch succeeds", function (done) {
var firstThenDone = false;
piwikApi.fetch({
method: "SomePlugin.action"
}).then(function (response) {
firstThenDone = true;
return "newval";
}).then(function (response) {
expect(firstThenDone).to.equal(true);
expect(response).to.equal("newval");
done();
}).catch(function (ex) {
done(ex);
});
$httpBackend.flush();
});
it("should fail when multiple aborts are issued", function (done) {
var request = piwikApi.fetch({
method: "SomePlugin.action"
}).then(function (response) {
done(new Error("Aborted request succeeded but should fail!"));
}).catch(function (ex) {
done();
});
request.abort();
request.abort();
$httpBackend.flush();
request.abort();
});
it("should send multiple requests concurrently when fetch is called more than once", function (done) {
var request1Done, request2Done;
function finishIfBothDone() {
if (request1Done && request2Done) {
done();
}
}
piwikApi.fetch({
method: "SomePlugin.action"
}).then(function (response) {
expect(response).to.equal("Request url: index.php?date=&format=JSON2&idSite=1&method=SomePlugin.action&module=API&period=day");
request1Done = true;
finishIfBothDone();
}).catch(function (ex) {
done(ex);
});
piwikApi.fetch({
method: "SomeOtherPlugin.action"
}).then(function (response) {
expect(response).to.equal("Request url: index.php?date=&format=JSON2&idSite=1&method=SomeOtherPlugin.action&module=API&period=day");
request2Done = true;
finishIfBothDone();
}).catch(function (ex) {
done(ex);
});
$httpBackend.flush();
});
it("should abort individual requests when abort() is called on a promise", function (done) {
var request1Done, request2Done;
function finishIfBothDone() {
if (request1Done && request2Done) {
done();
}
}
var request = piwikApi.fetch({
method: "SomePlugin.waitAction"
}).then(function (response) {
done(new Error("Aborted request finished!"));
}).catch(function (ex) {
request1Done = true;
finishIfBothDone();
});
piwikApi.fetch({
method: "SomeOtherPlugin.action"
}).then(function (response) {
expect(response).to.equal("Request url: index.php?date=&format=JSON2&idSite=1&method=SomeOtherPlugin.action&module=API&period=day");
request2Done = true;
finishIfBothDone();
}).catch(function (ex) {
done(ex);
});
request.abort();
$httpBackend.flush();
});
it("should abort all requests when abortAll() is called on the piwikApi", function (done) {
var request1Done, request2Done;
function finishIfBothDone() {
if (request1Done && request2Done) {
done();
}
}
piwikApi.fetch({
method: "SomePlugin.waitAction"
}).then(function (response) {
done(new Error("Aborted request finished (request 1)!"));
}).catch(function (ex) {
request1Done = true;
finishIfBothDone();
});
piwikApi.fetch({
method: "SomePlugin.waitAction"
}).then(function (response) {
done(new Error("Aborted request finished (request 2)!"));
}).catch(function (ex) {
request2Done = true;
finishIfBothDone();
});
piwikApi.abortAll();
$httpBackend.flush();
});
it("should perform a bulk request correctly when bulkFetch is called on the piwikApi", function (done) {
piwikApi.bulkFetch([
{
method: "SomePlugin.action",
param: "value"
},
{
method: "SomeOtherPlugin.action"
}
]).then(function (response) {
var restOfExpected = "index.php?date=&format=JSON2&idSite=1&method=API.getBulkRequest&" +
"module=API&period=day - urls%5B%5D=%3Fmethod%3DSomePlugin.action%26param%3D" +
"value&urls%5B%5D=%3Fmethod%3DSomeOtherPlugin.action&token_auth=100bf5eeeed1468f3f9d93750044d3dd";
expect(response.length).to.equal(2);
expect(response[0]).to.equal("Response #1: " + restOfExpected);
expect(response[1]).to.equal("Response #2: " + restOfExpected);
done();
}).catch(function (ex) {
done(ex);
});
$httpBackend.flush();
});
it("should correctly handle errors in a bulk request response", function (done) {
piwikApi.bulkFetch([
{
method: "SomePlugin.errorAction"
},
{
method: "SomeOtherPlugin.whatever"
}
]).then(function (response) {
done(new Error("promise resolved after bulkFetch request returned an error (response = " + JSON.stringify(response) + ")"));
}).catch(function (error) {
expect(error).to.equal("error message");
done();
});
$httpBackend.flush();
});
it("shuld correctly handle errors in a bulk request response, regardless of error location", function (done) {
piwikApi.bulkFetch([
{
method: "SomeOtherPlugin.whatever"
},
{
method: "SomePlugin.errorAction"
}
]).then(function (response) {
done(new Error("promise resolved after bulkFetch request returned an error (response = " + JSON.stringify(response) + ")"));
}).catch(function (error) {
expect(error).to.equal("error message");
done();
});
$httpBackend.flush();
});
});
})();

View File

@@ -0,0 +1,40 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
(function () {
describe('piwikHelper', function() {
var piwikHelper;
beforeEach(module('piwikApp.service'));
beforeEach(inject(function ($injector) {
piwikHelper = $injector.get('piwik').helper;
}));
beforeEach(function () {
delete window._dosomething;
});
describe('#htmlDecode', function () {
it('should correctly decode html entities', function (done) {
var called = false;
window._dosomething = function () {
called = true;
};
var encoded = 'str <img src=\'x/\' onerror=\'_dosomething()\'/>';
var decoded = piwikHelper.htmlDecode(encoded);
setTimeout(function () {
expect(called).to.be.false;
expect(decoded).to.equal('str <img src=\'x/\' onerror=\'_dosomething()\'/>');
done();
}, 500);
});
});
});
})();

Some files were not shown because too many files have changed in this diff Show More