update Piwik to version 2.16 (fixes #91)
This commit is contained in:
parent
9abe039dc8
commit
47263617c5
79 changed files with 418860 additions and 226988 deletions
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -9,78 +9,144 @@
|
|||
namespace Piwik\Plugins\CoreAdminHome;
|
||||
|
||||
use Exception;
|
||||
use Piwik\Config;
|
||||
use Piwik\DataAccess\ArchiveTableCreator;
|
||||
use Monolog\Handler\StreamHandler;
|
||||
use Monolog\Logger;
|
||||
use Piwik\Container\StaticContainer;
|
||||
use Piwik\Archive\ArchiveInvalidator;
|
||||
use Piwik\CronArchive;
|
||||
use Piwik\Date;
|
||||
use Piwik\Db;
|
||||
use Piwik\Option;
|
||||
use Piwik\Period;
|
||||
use Piwik\Period\Week;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Plugins\PrivacyManager\PrivacyManager;
|
||||
use Piwik\Plugins\SitesManager\SitesManager;
|
||||
use Piwik\SettingsPiwik;
|
||||
use Piwik\Segment;
|
||||
use Piwik\Scheduler\Scheduler;
|
||||
use Piwik\Site;
|
||||
use Piwik\TaskScheduler;
|
||||
|
||||
/**
|
||||
* @method static \Piwik\Plugins\CoreAdminHome\API getInstance()
|
||||
*/
|
||||
class API extends \Piwik\Plugin\API
|
||||
{
|
||||
/**
|
||||
* @var Scheduler
|
||||
*/
|
||||
private $scheduler;
|
||||
|
||||
/**
|
||||
* @var ArchiveInvalidator
|
||||
*/
|
||||
private $invalidator;
|
||||
|
||||
public function __construct(Scheduler $scheduler, ArchiveInvalidator $invalidator)
|
||||
{
|
||||
$this->scheduler = $scheduler;
|
||||
$this->invalidator = $invalidator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will run all scheduled tasks due to run at this time.
|
||||
*
|
||||
* @return array
|
||||
* @hideExceptForSuperUser
|
||||
*/
|
||||
public function runScheduledTasks()
|
||||
{
|
||||
Piwik::checkUserHasSuperUserAccess();
|
||||
return TaskScheduler::runTasks();
|
||||
|
||||
return $this->scheduler->run();
|
||||
}
|
||||
|
||||
/*
|
||||
* stores the list of websites IDs to re-reprocess in archive.php
|
||||
*/
|
||||
const OPTION_INVALIDATED_IDSITES = 'InvalidatedOldReports_WebsiteIds';
|
||||
|
||||
/**
|
||||
* When tracking data in the past (using Tracking API), this function
|
||||
* can be used to invalidate reports for the idSites and dates where new data
|
||||
* was added.
|
||||
* DEV: If you call this API, the UI should display the data correctly, but will process
|
||||
* in real time, which could be very slow after large data imports.
|
||||
* After calling this function via REST, you can manually force all data
|
||||
* to be reprocessed by visiting the script as the Super User:
|
||||
* http://example.net/piwik/misc/cron/archive.php?token_auth=$SUPER_USER_TOKEN_AUTH_HERE
|
||||
* REQUIREMENTS: On large piwik setups, you will need in PHP configuration: max_execution_time = 0
|
||||
* We recommend to use an hourly schedule of the script at misc/cron/archive.php
|
||||
* More information: http://piwik.org/setup-auto-archiving/
|
||||
* Invalidates report data, forcing it to be recomputed during the next archiving run.
|
||||
*
|
||||
* @param string $idSites Comma separated list of idSite that have had data imported for the specified dates
|
||||
* @param string $dates Comma separated list of dates to invalidate for all these websites
|
||||
* Note: This is done automatically when tracking or importing visits in the past.
|
||||
*
|
||||
* @param string $idSites Comma separated list of site IDs to invalidate reports for.
|
||||
* @param string $dates Comma separated list of dates of periods to invalidate reports for.
|
||||
* @param string|bool $period The type of period to invalidate: either 'day', 'week', 'month', 'year', 'range'.
|
||||
* The command will automatically cascade up, invalidating reports for parent periods as
|
||||
* well. So invalidating a day will invalidate the week it's in, the month it's in and the
|
||||
* year it's in, since those periods will need to be recomputed too.
|
||||
* @param string|bool $segment Optional. The segment to invalidate reports for.
|
||||
* @param bool $cascadeDown If true, child periods will be invalidated as well. So if it is requested to invalidate
|
||||
* a month, then all the weeks and days within that month will also be invalidated. But only
|
||||
* if this parameter is set.
|
||||
* @throws Exception
|
||||
* @return array
|
||||
* @hideExceptForSuperUser
|
||||
*/
|
||||
public function invalidateArchivedReports($idSites, $dates)
|
||||
public function invalidateArchivedReports($idSites, $dates, $period = false, $segment = false, $cascadeDown = false)
|
||||
{
|
||||
$idSites = Site::getIdSitesFromIdSitesString($idSites);
|
||||
if (empty($idSites)) {
|
||||
throw new Exception("Specify a value for &idSites= as a comma separated list of website IDs, for which your token_auth has 'admin' permission");
|
||||
}
|
||||
|
||||
Piwik::checkUserHasAdminAccess($idSites);
|
||||
|
||||
// Ensure the specified dates are valid
|
||||
$toInvalidate = $invalidDates = array();
|
||||
$dates = explode(',', $dates);
|
||||
if (!empty($segment)) {
|
||||
$segment = new Segment($segment, $idSites);
|
||||
} else {
|
||||
$segment = null;
|
||||
}
|
||||
|
||||
list($dateObjects, $invalidDates) = $this->getDatesToInvalidateFromString($dates);
|
||||
|
||||
$invalidationResult = $this->invalidator->markArchivesAsInvalidated($idSites, $dateObjects, $period, $segment, (bool)$cascadeDown);
|
||||
|
||||
$output = $invalidationResult->makeOutputLogs();
|
||||
if ($invalidDates) {
|
||||
$output[] = 'Warning: some of the Dates to invalidate were invalid: ' .
|
||||
implode(", ", $invalidDates) . ". Piwik simply ignored those and proceeded with the others.";
|
||||
}
|
||||
|
||||
Site::clearCache(); // TODO: is this needed? it shouldn't be needed...
|
||||
|
||||
return $invalidationResult->makeOutputLogs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates cron archiving via web request.
|
||||
*
|
||||
* @hideExceptForSuperUser
|
||||
*/
|
||||
public function runCronArchiving()
|
||||
{
|
||||
Piwik::checkUserHasSuperUserAccess();
|
||||
|
||||
// HTTP request: logs needs to be dumped in the HTTP response (on top of existing log destinations)
|
||||
/** @var \Monolog\Logger $logger */
|
||||
$logger = StaticContainer::get('Psr\Log\LoggerInterface');
|
||||
$handler = new StreamHandler('php://output', Logger::INFO);
|
||||
$handler->setFormatter(StaticContainer::get('Piwik\Plugins\Monolog\Formatter\LineMessageFormatter'));
|
||||
$logger->pushHandler($handler);
|
||||
|
||||
$archiver = new CronArchive();
|
||||
$archiver->main();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the specified dates are valid.
|
||||
* Store invalid date so we can log them
|
||||
* @param array $dates
|
||||
* @return Date[]
|
||||
*/
|
||||
private function getDatesToInvalidateFromString($dates)
|
||||
{
|
||||
$toInvalidate = array();
|
||||
$invalidDates = array();
|
||||
|
||||
$dates = explode(',', trim($dates));
|
||||
$dates = array_unique($dates);
|
||||
|
||||
foreach ($dates as $theDate) {
|
||||
$theDate = trim($theDate);
|
||||
try {
|
||||
$date = Date::factory($theDate);
|
||||
} catch (Exception $e) {
|
||||
} catch (\Exception $e) {
|
||||
$invalidDates[] = $theDate;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($date->toString() == $theDate) {
|
||||
$toInvalidate[] = $date;
|
||||
} else {
|
||||
|
|
@ -88,122 +154,6 @@ class API extends \Piwik\Plugin\API
|
|||
}
|
||||
}
|
||||
|
||||
// If using the feature "Delete logs older than N days"...
|
||||
$purgeDataSettings = PrivacyManager::getPurgeDataSettings();
|
||||
$logsAreDeletedBeforeThisDate = $purgeDataSettings['delete_logs_schedule_lowest_interval'];
|
||||
$logsDeleteEnabled = $purgeDataSettings['delete_logs_enable'];
|
||||
$minimumDateWithLogs = false;
|
||||
if ($logsDeleteEnabled
|
||||
&& $logsAreDeletedBeforeThisDate
|
||||
) {
|
||||
$minimumDateWithLogs = Date::factory('today')->subDay($logsAreDeletedBeforeThisDate);
|
||||
}
|
||||
|
||||
// Given the list of dates, process which tables they should be deleted from
|
||||
$minDate = false;
|
||||
$warningDates = $processedDates = array();
|
||||
/* @var $date Date */
|
||||
foreach ($toInvalidate as $date) {
|
||||
// we should only delete reports for dates that are more recent than N days
|
||||
if ($minimumDateWithLogs
|
||||
&& $date->isEarlier($minimumDateWithLogs)
|
||||
) {
|
||||
$warningDates[] = $date->toString();
|
||||
} else {
|
||||
$processedDates[] = $date->toString();
|
||||
}
|
||||
|
||||
$month = $date->toString('Y_m');
|
||||
// For a given date, we must invalidate in the monthly archive table
|
||||
$datesByMonth[$month][] = $date->toString();
|
||||
|
||||
// But also the year stored in January
|
||||
$year = $date->toString('Y_01');
|
||||
$datesByMonth[$year][] = $date->toString();
|
||||
|
||||
// but also weeks overlapping several months stored in the month where the week is starting
|
||||
/* @var $week Week */
|
||||
$week = Period::factory('week', $date);
|
||||
$weekAsString = $week->getDateStart()->toString('Y_m');
|
||||
$datesByMonth[$weekAsString][] = $date->toString();
|
||||
|
||||
// Keep track of the minimum date for each website
|
||||
if ($minDate === false
|
||||
|| $date->isEarlier($minDate)
|
||||
) {
|
||||
$minDate = $date;
|
||||
}
|
||||
}
|
||||
|
||||
// In each table, invalidate day/week/month/year containing this date
|
||||
$archiveTables = ArchiveTableCreator::getTablesArchivesInstalled();
|
||||
foreach ($archiveTables as $table) {
|
||||
// Extract Y_m from table name
|
||||
$suffix = ArchiveTableCreator::getDateFromTableName($table);
|
||||
if (!isset($datesByMonth[$suffix])) {
|
||||
continue;
|
||||
}
|
||||
// Dates which are to be deleted from this table
|
||||
$datesToDeleteInTable = $datesByMonth[$suffix];
|
||||
|
||||
// Build one statement to delete all dates from the given table
|
||||
$sql = $bind = array();
|
||||
$datesToDeleteInTable = array_unique($datesToDeleteInTable);
|
||||
foreach ($datesToDeleteInTable as $dateToDelete) {
|
||||
$sql[] = '(date1 <= ? AND ? <= date2)';
|
||||
$bind[] = $dateToDelete;
|
||||
$bind[] = $dateToDelete;
|
||||
}
|
||||
$sql = implode(" OR ", $sql);
|
||||
|
||||
$query = "DELETE FROM $table " .
|
||||
" WHERE ( $sql ) " .
|
||||
" AND idsite IN (" . implode(",", $idSites) . ")";
|
||||
Db::query($query, $bind);
|
||||
}
|
||||
\Piwik\Plugins\SitesManager\API::getInstance()->updateSiteCreatedTime($idSites, $minDate);
|
||||
|
||||
// Force to re-process data for these websites in the next archive.php cron run
|
||||
$invalidatedIdSites = self::getWebsiteIdsToInvalidate();
|
||||
$invalidatedIdSites = array_merge($invalidatedIdSites, $idSites);
|
||||
$invalidatedIdSites = array_unique($invalidatedIdSites);
|
||||
$invalidatedIdSites = array_values($invalidatedIdSites);
|
||||
Option::set(self::OPTION_INVALIDATED_IDSITES, serialize($invalidatedIdSites));
|
||||
|
||||
Site::clearCache();
|
||||
|
||||
$output = array();
|
||||
// output logs
|
||||
if ($warningDates) {
|
||||
$output[] = 'Warning: the following Dates have not been invalidated, because they are earlier than your Log Deletion limit: ' .
|
||||
implode(", ", $warningDates) .
|
||||
"\n The last day with logs is " . $minimumDateWithLogs . ". " .
|
||||
"\n Please disable 'Delete old Logs' or set it to a higher deletion threshold (eg. 180 days or 365 years).'.";
|
||||
}
|
||||
$output[] = "Success. The following dates were invalidated successfully: " .
|
||||
implode(", ", $processedDates);
|
||||
return $output;
|
||||
return array($toInvalidate, $invalidDates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns array of idSites to force re-process next time archive.php runs
|
||||
*
|
||||
* @ignore
|
||||
* @return mixed
|
||||
*/
|
||||
static public function getWebsiteIdsToInvalidate()
|
||||
{
|
||||
Piwik::checkUserHasSomeAdminAccess();
|
||||
|
||||
Option::clearCachedOption(self::OPTION_INVALIDATED_IDSITES);
|
||||
$invalidatedIdSites = Option::get(self::OPTION_INVALIDATED_IDSITES);
|
||||
if ($invalidatedIdSites
|
||||
&& ($invalidatedIdSites = unserialize($invalidatedIdSites))
|
||||
&& count($invalidatedIdSites)
|
||||
) {
|
||||
return $invalidatedIdSites;
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
201
www/analytics/plugins/CoreAdminHome/Commands/DeleteLogsData.php
Normal file
201
www/analytics/plugins/CoreAdminHome/Commands/DeleteLogsData.php
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
<?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\CoreAdminHome\Commands;
|
||||
|
||||
use Piwik\Common;
|
||||
use Piwik\Container\StaticContainer;
|
||||
use Piwik\DataAccess\RawLogDao;
|
||||
use Piwik\Date;
|
||||
use Piwik\Db;
|
||||
use Piwik\LogDeleter;
|
||||
use Piwik\Plugin\ConsoleCommand;
|
||||
use Piwik\Site;
|
||||
use Piwik\Timer;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
|
||||
/**
|
||||
* Command to selectively delete visits.
|
||||
*/
|
||||
class DeleteLogsData extends ConsoleCommand
|
||||
{
|
||||
private static $logTables = array(
|
||||
'log_visit',
|
||||
'log_link_visit_action',
|
||||
'log_conversion',
|
||||
'log_conversion_item',
|
||||
'log_action'
|
||||
);
|
||||
|
||||
/**
|
||||
* @var RawLogDao
|
||||
*/
|
||||
private $rawLogDao;
|
||||
|
||||
/**
|
||||
* @var LogDeleter
|
||||
*/
|
||||
private $logDeleter;
|
||||
|
||||
public function __construct(LogDeleter $logDeleter = null, RawLogDao $rawLogDao = null)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->logDeleter = $logDeleter ?: StaticContainer::get('Piwik\LogDeleter');
|
||||
$this->rawLogDao = $rawLogDao ?: StaticContainer::get('Piwik\DataAccess\RawLogDao');
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('core:delete-logs-data');
|
||||
$this->setDescription('Delete data from the user log tables: ' . implode(', ', self::$logTables) . '.');
|
||||
$this->addOption('dates', null, InputOption::VALUE_REQUIRED, 'Delete log data with a date within this date range. Eg, 2012-01-01,2013-01-01');
|
||||
$this->addOption('idsite', null, InputOption::VALUE_OPTIONAL,
|
||||
'Delete log data belonging to the site with this ID. Comma separated list of website id. Eg, 1, 2, 3, etc. By default log data from all sites is purged.');
|
||||
$this->addOption('limit', null, InputOption::VALUE_REQUIRED, "The number of rows to delete at a time. The larger the number, "
|
||||
. "the more time is spent deleting logs, and the less progress will be printed to the screen.", 1000);
|
||||
$this->addOption('optimize-tables', null, InputOption::VALUE_NONE,
|
||||
"If supplied, the command will optimize log tables after deleting logs. Note: this can take a very long time.");
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
list($from, $to) = $this->getDateRangeToDeleteFrom($input);
|
||||
$idSite = $this->getSiteToDeleteFrom($input);
|
||||
$step = $this->getRowIterationStep($input);
|
||||
|
||||
$output->writeln( sprintf(
|
||||
"<info>Preparing to delete all visits belonging to %s between $from and $to.</info>",
|
||||
$idSite ? "website $idSite" : "ALL websites"
|
||||
));
|
||||
|
||||
$confirm = $this->askForDeleteConfirmation($input, $output);
|
||||
if (!$confirm) {
|
||||
return;
|
||||
}
|
||||
|
||||
$timer = new Timer();
|
||||
|
||||
try {
|
||||
$logsDeleted = $this->logDeleter->deleteVisitsFor($from, $to, $idSite, $step, function () use ($output) {
|
||||
$output->write('.');
|
||||
});
|
||||
} catch (\Exception $ex) {
|
||||
$output->writeln("");
|
||||
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
$this->writeSuccessMessage($output, array(
|
||||
"Successfully deleted $logsDeleted visits. <comment>" . $timer . "</comment>"));
|
||||
|
||||
if ($input->getOption('optimize-tables')) {
|
||||
$this->optimizeTables($output);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InputInterface $input
|
||||
* @return Date[]
|
||||
*/
|
||||
private function getDateRangeToDeleteFrom(InputInterface $input)
|
||||
{
|
||||
$dates = $input->getOption('dates');
|
||||
if (empty($dates)) {
|
||||
throw new \InvalidArgumentException("No date range supplied in --dates option. Deleting all logs by default is not allowed, you must specify a date range.");
|
||||
}
|
||||
|
||||
$parts = explode(',', $dates);
|
||||
$parts = array_map('trim', $parts);
|
||||
|
||||
if (count($parts) !== 2) {
|
||||
throw new \InvalidArgumentException("Invalid date range supplied: $dates");
|
||||
}
|
||||
|
||||
list($start, $end) = $parts;
|
||||
|
||||
try {
|
||||
/** @var Date[] $dateObjects */
|
||||
$dateObjects = array(Date::factory($start), Date::factory($end));
|
||||
} catch (\Exception $ex) {
|
||||
throw new \InvalidArgumentException("Invalid date range supplied: $dates (" . $ex->getMessage() . ")", $code = 0, $ex);
|
||||
}
|
||||
|
||||
if ($dateObjects[0]->getTimestamp() > $dateObjects[1]->getTimestamp()) {
|
||||
throw new \InvalidArgumentException("Invalid date range supplied: $dates (first date is older than the last date)");
|
||||
}
|
||||
|
||||
$dateObjects = array($dateObjects[0]->getDatetime(), $dateObjects[1]->getDatetime());
|
||||
|
||||
return $dateObjects;
|
||||
}
|
||||
|
||||
private function getSiteToDeleteFrom(InputInterface $input)
|
||||
{
|
||||
$idSite = $input->getOption('idsite');
|
||||
|
||||
if(is_null($idSite)) {
|
||||
return $idSite;
|
||||
}
|
||||
// validate the site ID
|
||||
try {
|
||||
new Site($idSite);
|
||||
} catch (\Exception $ex) {
|
||||
throw new \InvalidArgumentException("Invalid site ID: $idSite", $code = 0, $ex);
|
||||
}
|
||||
|
||||
return $idSite;
|
||||
}
|
||||
|
||||
private function getRowIterationStep(InputInterface $input)
|
||||
{
|
||||
$step = (int) $input->getOption('limit');
|
||||
|
||||
if ($step <= 0) {
|
||||
throw new \InvalidArgumentException("Invalid row limit supplied: $step. Must be a number greater than 0.");
|
||||
}
|
||||
|
||||
return $step;
|
||||
}
|
||||
|
||||
private function askForDeleteConfirmation(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
if ($input->getOption('no-interaction')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$helper = $this->getHelper('question');
|
||||
$question = new ConfirmationQuestion('<comment>You are about to delete log data. This action cannot be undone, are you sure you want to continue? (Y/N)</comment> ', false);
|
||||
|
||||
return $helper->ask($input, $output, $question);
|
||||
}
|
||||
|
||||
private function optimizeTables(OutputInterface $output)
|
||||
{
|
||||
foreach (self::$logTables as $table) {
|
||||
$output->write("Optimizing table $table... ");
|
||||
|
||||
$timer = new Timer();
|
||||
|
||||
$prefixedTable = Common::prefixTable($table);
|
||||
|
||||
$done = Db::optimizeTables($prefixedTable);
|
||||
|
||||
if($done) {
|
||||
$output->writeln("done. <comment>" . $timer . "</comment>");
|
||||
} else {
|
||||
$output->writeln("skipped! <comment>" . $timer . "</comment>");
|
||||
}
|
||||
}
|
||||
|
||||
$this->writeSuccessMessage($output, array("Table optimization finished."));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,201 @@
|
|||
<?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\CoreAdminHome\Commands;
|
||||
|
||||
use Piwik\Common;
|
||||
use Piwik\Container\StaticContainer;
|
||||
use Piwik\DataAccess\Actions;
|
||||
use Piwik\Archive\ArchiveInvalidator;
|
||||
use Piwik\Date;
|
||||
use Piwik\Plugin\ConsoleCommand;
|
||||
use Piwik\Plugins\CoreAdminHome\Model\DuplicateActionRemover;
|
||||
use Piwik\Timer;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Finds duplicate actions rows in log_action and removes them. Fixes references to duplicate
|
||||
* actions in the log_link_visit_action table, log_conversion table, and log_conversion_item
|
||||
* table.
|
||||
*
|
||||
* Prior to version 2.11, there was a race condition in the tracker where it was possible for
|
||||
* two or more actions with the same name and type to be inserted simultaneously. This resulted
|
||||
* in inaccurate data. A Piwik database with this problem can be fixed using this class.
|
||||
*
|
||||
* With version 2.11 and above, it is still possible for duplicate actions to be inserted, but
|
||||
* ONLY if the tracker's PHP process fails suddenly right after inserting an action. This is
|
||||
* very rare, and even if it does happen, report data will not be affected, but the extra
|
||||
* actions can be deleted w/ this class.
|
||||
*/
|
||||
class FixDuplicateLogActions extends ConsoleCommand
|
||||
{
|
||||
/**
|
||||
* Used to invalidate archives. Only used if $shouldInvalidateArchives is true.
|
||||
*
|
||||
* @var ArchiveInvalidator
|
||||
*/
|
||||
private $archiveInvalidator;
|
||||
|
||||
/**
|
||||
* DAO used to find duplicate actions in log_action and fix references to them in other tables.
|
||||
*
|
||||
* @var DuplicateActionRemover
|
||||
*/
|
||||
private $duplicateActionRemover;
|
||||
|
||||
/**
|
||||
* DAO used to remove actions from the log_action table.
|
||||
*
|
||||
* @var Actions
|
||||
*/
|
||||
private $actionsAccess;
|
||||
|
||||
/**
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param ArchiveInvalidator $invalidator
|
||||
* @param DuplicateActionRemover $duplicateActionRemover
|
||||
* @param Actions $actionsAccess
|
||||
* @param LoggerInterface $logger
|
||||
*/
|
||||
public function __construct(ArchiveInvalidator $invalidator = null, DuplicateActionRemover $duplicateActionRemover = null,
|
||||
Actions $actionsAccess = null, LoggerInterface $logger = null)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->archiveInvalidator = $invalidator ?: StaticContainer::get('Piwik\Archive\ArchiveInvalidator');
|
||||
$this->duplicateActionRemover = $duplicateActionRemover ?: new DuplicateActionRemover();
|
||||
$this->actionsAccess = $actionsAccess ?: new Actions();
|
||||
$this->logger = $logger ?: StaticContainer::get('Psr\Log\LoggerInterface');
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('core:fix-duplicate-log-actions');
|
||||
$this->addOption('invalidate-archives', null, InputOption::VALUE_NONE, "If supplied, archives for logs that use duplicate actions will be invalidated."
|
||||
. " On the next cron archive run, the reports for those dates will be re-processed.");
|
||||
$this->setDescription('Removes duplicates in the log action table and fixes references to the duplicates in '
|
||||
. 'related tables. NOTE: This action can take a long time to run!');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$invalidateArchives = $input->getOption('invalidate-archives');
|
||||
|
||||
$timer = new Timer();
|
||||
|
||||
$duplicateActions = $this->duplicateActionRemover->getDuplicateIdActions();
|
||||
if (empty($duplicateActions)) {
|
||||
$output->writeln("Found no duplicate actions.");
|
||||
return;
|
||||
}
|
||||
|
||||
$output->writeln("<info>Found " . count($duplicateActions) . " actions with duplicates.</info>");
|
||||
|
||||
list($numberRemoved, $allArchivesAffected) = $this->fixDuplicateActionReferences($duplicateActions, $output);
|
||||
|
||||
$this->deleteDuplicatesFromLogAction($output, $duplicateActions);
|
||||
|
||||
if ($invalidateArchives) {
|
||||
$this->invalidateArchivesUsingActionDuplicates($allArchivesAffected, $output);
|
||||
} else {
|
||||
$this->printAffectedArchives($allArchivesAffected, $output);
|
||||
}
|
||||
|
||||
$logActionTable = Common::prefixTable('log_action');
|
||||
$this->writeSuccessMessage($output, array(
|
||||
"Found and deleted $numberRemoved duplicate action entries in the $logActionTable table.",
|
||||
"References in log_link_visit_action, log_conversion and log_conversion_item were corrected.",
|
||||
$timer->__toString()
|
||||
));
|
||||
}
|
||||
|
||||
private function invalidateArchivesUsingActionDuplicates($archivesAffected, OutputInterface $output)
|
||||
{
|
||||
$output->write("Invalidating archives affected by duplicates fixed...");
|
||||
foreach ($archivesAffected as $archiveInfo) {
|
||||
$dates = array(Date::factory($archiveInfo['server_time']));
|
||||
$this->archiveInvalidator->markArchivesAsInvalidated(array($archiveInfo['idsite']), $dates, $period = false);
|
||||
}
|
||||
$output->writeln("Done.");
|
||||
}
|
||||
|
||||
private function printAffectedArchives($allArchivesAffected, OutputInterface $output)
|
||||
{
|
||||
$output->writeln("The following archives used duplicate actions and should be invalidated if you want correct reports:");
|
||||
foreach ($allArchivesAffected as $archiveInfo) {
|
||||
$output->writeln("\t[ idSite = {$archiveInfo['idsite']}, date = {$archiveInfo['server_time']} ]");
|
||||
}
|
||||
}
|
||||
|
||||
private function fixDuplicateActionReferences($duplicateActions, OutputInterface $output)
|
||||
{
|
||||
$dupeCount = count($duplicateActions);
|
||||
|
||||
$numberRemoved = 0;
|
||||
$allArchivesAffected = array();
|
||||
|
||||
foreach ($duplicateActions as $index => $dupeInfo) {
|
||||
$name = $dupeInfo['name'];
|
||||
$toIdAction = $dupeInfo['idaction'];
|
||||
$fromIdActions = $dupeInfo['duplicateIdActions'];
|
||||
|
||||
$numberRemoved += count($fromIdActions);
|
||||
|
||||
$output->writeln("<info>[$index / $dupeCount]</info> Fixing duplicates for '$name'");
|
||||
|
||||
$this->logger->debug(" idaction = {idaction}, duplicate idactions = {duplicateIdActions}", array(
|
||||
'idaction' => $toIdAction,
|
||||
'duplicateIdActions' => $fromIdActions
|
||||
));
|
||||
|
||||
foreach (DuplicateActionRemover::$tablesWithIdActionColumns as $table) {
|
||||
$archivesAffected = $this->fixDuplicateActionsInTable($output, $table, $toIdAction, $fromIdActions);
|
||||
$allArchivesAffected = array_merge($allArchivesAffected, $archivesAffected);
|
||||
}
|
||||
}
|
||||
|
||||
$allArchivesAffected = array_values(array_unique($allArchivesAffected, SORT_REGULAR));
|
||||
|
||||
return array($numberRemoved, $allArchivesAffected);
|
||||
}
|
||||
|
||||
private function fixDuplicateActionsInTable(OutputInterface $output, $table, $toIdAction, $fromIdActions)
|
||||
{
|
||||
$timer = new Timer();
|
||||
|
||||
$archivesAffected = $this->duplicateActionRemover->getSitesAndDatesOfRowsUsingDuplicates($table, $fromIdActions);
|
||||
|
||||
$this->duplicateActionRemover->fixDuplicateActionsInTable($table, $toIdAction, $fromIdActions);
|
||||
|
||||
$output->writeln("\tFixed duplicates in " . Common::prefixTable($table) . ". <comment>" . $timer->__toString() . "</comment>.");
|
||||
|
||||
return $archivesAffected;
|
||||
}
|
||||
|
||||
private function deleteDuplicatesFromLogAction(OutputInterface $output, $duplicateActions)
|
||||
{
|
||||
$logActionTable = Common::prefixTable('log_action');
|
||||
$output->writeln("<info>Deleting duplicate actions from $logActionTable...</info>");
|
||||
|
||||
$idActions = array();
|
||||
foreach ($duplicateActions as $dupeInfo) {
|
||||
$idActions = array_merge($idActions, $dupeInfo['duplicateIdActions']);
|
||||
}
|
||||
|
||||
$this->actionsAccess->delete($idActions);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,200 @@
|
|||
<?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\CoreAdminHome\Commands;
|
||||
|
||||
use Piwik\Container\StaticContainer;
|
||||
use Piwik\Date;
|
||||
use Piwik\Period;
|
||||
use Piwik\Period\Range;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Segment;
|
||||
use Piwik\Plugin\ConsoleCommand;
|
||||
use Piwik\Plugins\SitesManager\API as SitesManagerAPI;
|
||||
use Piwik\Site;
|
||||
use Piwik\Period\Factory as PeriodFactory;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Provides a simple interface for invalidating report data by date ranges, site IDs and periods.
|
||||
*/
|
||||
class InvalidateReportData extends ConsoleCommand
|
||||
{
|
||||
const ALL_OPTION_VALUE = 'all';
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('core:invalidate-report-data');
|
||||
$this->setDescription('Invalidate archived report data by date range, site and period.');
|
||||
$this->addOption('dates', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED,
|
||||
'List of dates or date ranges to invalidate report data for, eg, 2015-01-03 or 2015-01-05,2015-02-12.');
|
||||
$this->addOption('sites', null, InputOption::VALUE_REQUIRED,
|
||||
'List of site IDs to invalidate report data for, eg, "1,2,3,4" or "all" for all sites.',
|
||||
self::ALL_OPTION_VALUE);
|
||||
$this->addOption('periods', null, InputOption::VALUE_REQUIRED,
|
||||
'List of period types to invalidate report data for. Can be one or more of the following values: day, '
|
||||
. 'week, month, year or "all" for all of them.',
|
||||
self::ALL_OPTION_VALUE);
|
||||
$this->addOption('segment', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED,
|
||||
'List of segments to invalidate report data for.');
|
||||
$this->addOption('cascade', null, InputOption::VALUE_NONE,
|
||||
'If supplied, invalidation will cascade, invalidating child period types even if they aren\'t specified in'
|
||||
. ' --periods. For example, if --periods=week, --cascade will cause the days within those weeks to be '
|
||||
. 'invalidated as well. If --periods=month, then weeks and days will be invalidated. Note: if a period '
|
||||
. 'falls partly outside of a date range, then --cascade will also invalidate data for child periods '
|
||||
. 'outside the date range. For example, if --dates=2015-09-14,2015-09-15 & --periods=week, --cascade will'
|
||||
. ' also invalidate all days within 2015-09-13,2015-09-19, even those outside the date range.');
|
||||
$this->addOption('dry-run', null, InputOption::VALUE_NONE, 'For tests. Runs the command w/o actually '
|
||||
. 'invalidating anything.');
|
||||
$this->setHelp('Invalidate archived report data by date range, site and period. Invalidated archive data will '
|
||||
. 'be re-archived during the next core:archive run. If your log data has changed for some reason, this '
|
||||
. 'command can be used to make sure reports are generated using the new, changed log data.');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$invalidator = StaticContainer::get('Piwik\Archive\ArchiveInvalidator');
|
||||
|
||||
$cascade = $input->getOption('cascade');
|
||||
$dryRun = $input->getOption('dry-run');
|
||||
|
||||
$sites = $this->getSitesToInvalidateFor($input);
|
||||
$periodTypes = $this->getPeriodTypesToInvalidateFor($input);
|
||||
$dateRanges = $this->getDateRangesToInvalidateFor($input);
|
||||
$segments = $this->getSegmentsToInvalidateFor($input, $sites);
|
||||
|
||||
foreach ($periodTypes as $periodType) {
|
||||
foreach ($dateRanges as $dateRange) {
|
||||
foreach ($segments as $segment) {
|
||||
$segmentStr = $segment ? $segment->getString() : '';
|
||||
|
||||
$output->writeln("Invalidating $periodType periods in $dateRange [segment = $segmentStr]...");
|
||||
|
||||
$dates = $this->getPeriodDates($periodType, $dateRange);
|
||||
|
||||
if ($dryRun) {
|
||||
$output->writeln("[Dry-run] invalidating archives for site = [ " . implode(', ', $sites)
|
||||
. " ], dates = [ " . implode(', ', $dates) . " ], period = [ $periodType ], segment = [ "
|
||||
. "$segmentStr ], cascade = [ " . (int)$cascade . " ]");
|
||||
} else {
|
||||
$invalidationResult = $invalidator->markArchivesAsInvalidated($sites, $dates, $periodType, $segment, $cascade);
|
||||
|
||||
if ($output->getVerbosity() > OutputInterface::VERBOSITY_NORMAL) {
|
||||
$output->writeln($invalidationResult->makeOutputLogs());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getSitesToInvalidateFor(InputInterface $input)
|
||||
{
|
||||
$sites = $input->getOption('sites');
|
||||
|
||||
$siteIds = Site::getIdSitesFromIdSitesString($sites);
|
||||
if (empty($siteIds)) {
|
||||
throw new \InvalidArgumentException("Invalid --sites value: '$sites'.");
|
||||
}
|
||||
|
||||
$allSiteIds = SitesManagerAPI::getInstance()->getAllSitesId();
|
||||
foreach ($siteIds as $idSite) {
|
||||
if (!in_array($idSite, $allSiteIds)) {
|
||||
throw new \InvalidArgumentException("Invalid --sites value: '$sites', there are no sites with IDs = $idSite");
|
||||
}
|
||||
}
|
||||
|
||||
return $siteIds;
|
||||
}
|
||||
|
||||
private function getPeriodTypesToInvalidateFor(InputInterface $input)
|
||||
{
|
||||
$periods = $input->getOption('periods');
|
||||
if (empty($periods)) {
|
||||
throw new \InvalidArgumentException("The --periods argument is required.");
|
||||
}
|
||||
|
||||
if ($periods == self::ALL_OPTION_VALUE) {
|
||||
$result = array_keys(Piwik::$idPeriods);
|
||||
unset($result[4]); // remove 'range' period
|
||||
return $result;
|
||||
}
|
||||
|
||||
$periods = explode(',', $periods);
|
||||
$periods = array_map('trim', $periods);
|
||||
|
||||
foreach ($periods as $periodIdentifier) {
|
||||
if ($periodIdentifier == 'range') {
|
||||
throw new \InvalidArgumentException(
|
||||
"Invalid period type: invalidating range periods is not currently supported.");
|
||||
}
|
||||
|
||||
if (!isset(Piwik::$idPeriods[$periodIdentifier])) {
|
||||
throw new \InvalidArgumentException("Invalid period type '$periodIdentifier' supplied in --periods.");
|
||||
}
|
||||
}
|
||||
|
||||
return $periods;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InputInterface $input
|
||||
* @return Date[][]
|
||||
*/
|
||||
private function getDateRangesToInvalidateFor(InputInterface $input)
|
||||
{
|
||||
$dateRanges = $input->getOption('dates');
|
||||
if (empty($dateRanges)) {
|
||||
throw new \InvalidArgumentException("The --dates option is required.");
|
||||
}
|
||||
|
||||
return $dateRanges;
|
||||
}
|
||||
|
||||
private function getPeriodDates($periodType, $dateRange)
|
||||
{
|
||||
if (!isset(Piwik::$idPeriods[$periodType])) {
|
||||
throw new \InvalidArgumentException("Invalid period type '$periodType'.");
|
||||
}
|
||||
|
||||
try {
|
||||
$period = PeriodFactory::build($periodType, $dateRange);
|
||||
} catch (\Exception $ex) {
|
||||
throw new \InvalidArgumentException("Invalid date or date range specifier '$dateRange'", $code = 0, $ex);
|
||||
}
|
||||
|
||||
$result = array();
|
||||
if ($period instanceof Range) {
|
||||
foreach ($period->getSubperiods() as $subperiod) {
|
||||
$result[] = $subperiod->getDateStart();
|
||||
}
|
||||
} else {
|
||||
$result[] = $period->getDateStart();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function getSegmentsToInvalidateFor(InputInterface $input, $idSites)
|
||||
{
|
||||
$segments = $input->getOption('segment');
|
||||
$segments = array_map('trim', $segments);
|
||||
$segments = array_unique($segments);
|
||||
|
||||
if (empty($segments)) {
|
||||
return array(null);
|
||||
}
|
||||
|
||||
$result = array();
|
||||
foreach ($segments as $segmentString) {
|
||||
$result[] = new Segment($segmentString, $idSites);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
<?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\CoreAdminHome\Commands;
|
||||
|
||||
use Piwik\Common;
|
||||
use Piwik\DataAccess\ArchiveTableCreator;
|
||||
use Piwik\Date;
|
||||
use Piwik\Db;
|
||||
use Piwik\Plugin\ConsoleCommand;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Administration command that optimizes archive tables (even if they use InnoDB).
|
||||
*/
|
||||
class OptimizeArchiveTables extends ConsoleCommand
|
||||
{
|
||||
const ALL_TABLES_STRING = 'all';
|
||||
const CURRENT_MONTH_STRING = 'now';
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('database:optimize-archive-tables');
|
||||
$this->setDescription("Runs an OPTIMIZE TABLE query on the specified archive tables.");
|
||||
$this->addArgument("dates", InputArgument::IS_ARRAY | InputArgument::REQUIRED,
|
||||
"The months of the archive tables to optimize. Use '" . self::ALL_TABLES_STRING. "' for all dates or '" .
|
||||
self::CURRENT_MONTH_STRING . "' to optimize the current month only.");
|
||||
$this->addOption('dry-run', null, InputOption::VALUE_NONE, 'For testing purposes.');
|
||||
$this->setHelp("This command can be used to ease or automate maintenance. Instead of manually running "
|
||||
. "OPTIMIZE TABLE queries, the command can be used.\n\nYou should run the command if you find your "
|
||||
. "archive tables grow and do not shrink after purging. Optimizing them will reclaim some space.");
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$dryRun = $input->getOption('dry-run');
|
||||
|
||||
$tableMonths = $this->getTableMonthsToOptimize($input);
|
||||
|
||||
foreach ($tableMonths as $month) {
|
||||
$this->optimizeTable($output, $dryRun, 'archive_numeric_' . $month);
|
||||
$this->optimizeTable($output, $dryRun, 'archive_blob_' . $month);
|
||||
}
|
||||
}
|
||||
|
||||
private function optimizeTable(OutputInterface $output, $dryRun, $table)
|
||||
{
|
||||
$output->write("Optimizing table '$table'...");
|
||||
|
||||
if ($dryRun) {
|
||||
$output->write("[dry-run, not optimising table]");
|
||||
} else {
|
||||
Db::optimizeTables(Common::prefixTable($table), $force = true);
|
||||
}
|
||||
|
||||
$output->writeln("Done.");
|
||||
}
|
||||
|
||||
private function getTableMonthsToOptimize(InputInterface $input)
|
||||
{
|
||||
$dateSpecifiers = $input->getArgument('dates');
|
||||
if (count($dateSpecifiers) === 1) {
|
||||
$dateSpecifier = reset($dateSpecifiers);
|
||||
|
||||
if ($dateSpecifier == self::ALL_TABLES_STRING) {
|
||||
return $this->getAllArchiveTableMonths();
|
||||
} else if ($dateSpecifier == self::CURRENT_MONTH_STRING) {
|
||||
$now = Date::factory('now');
|
||||
return array(ArchiveTableCreator::getTableMonthFromDate($now));
|
||||
} else if (strpos($dateSpecifier, 'last') === 0) {
|
||||
$lastN = substr($dateSpecifier, 4);
|
||||
if (!ctype_digit($lastN)) {
|
||||
throw new \Exception("Invalid lastN specifier '$lastN'. The end must be an integer, eg, last1 or last2.");
|
||||
}
|
||||
|
||||
if ($lastN <= 0) {
|
||||
throw new \Exception("Invalid lastN value '$lastN'.");
|
||||
}
|
||||
|
||||
return $this->getLastNTableMonths((int)$lastN);
|
||||
}
|
||||
}
|
||||
|
||||
$tableMonths = array();
|
||||
foreach ($dateSpecifiers as $date) {
|
||||
$date = Date::factory($date);
|
||||
$tableMonths[] = ArchiveTableCreator::getTableMonthFromDate($date);
|
||||
}
|
||||
return $tableMonths;
|
||||
}
|
||||
|
||||
private function getAllArchiveTableMonths()
|
||||
{
|
||||
$tableMonths = array();
|
||||
foreach (ArchiveTableCreator::getTablesArchivesInstalled() as $table) {
|
||||
$tableMonths[] = ArchiveTableCreator::getDateFromTableName($table);
|
||||
}
|
||||
return $tableMonths;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $lastN
|
||||
* @return string[]
|
||||
*/
|
||||
private function getLastNTableMonths($lastN)
|
||||
{
|
||||
$now = Date::factory('now');
|
||||
|
||||
$result = array();
|
||||
for ($i = 0; $i < $lastN; ++$i) {
|
||||
$date = $now->subMonth($i + 1);
|
||||
$result[] = ArchiveTableCreator::getTableMonthFromDate($date);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,207 @@
|
|||
<?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\CoreAdminHome\Commands;
|
||||
|
||||
use Piwik\Archive;
|
||||
use Piwik\Archive\ArchivePurger;
|
||||
use Piwik\DataAccess\ArchiveTableCreator;
|
||||
use Piwik\Date;
|
||||
use Piwik\Db;
|
||||
use Piwik\Plugin\ConsoleCommand;
|
||||
use Piwik\Timer;
|
||||
use Psr\Log\NullLogger;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Command that allows users to force purge old or invalid archive data. In the event of a failure
|
||||
* in the archive purging scheduled task, this command can be used to manually delete old/invalid archives.
|
||||
*/
|
||||
class PurgeOldArchiveData extends ConsoleCommand
|
||||
{
|
||||
const ALL_DATES_STRING = 'all';
|
||||
|
||||
/**
|
||||
* For tests.
|
||||
*
|
||||
* @var Date
|
||||
*/
|
||||
public static $todayOverride = null;
|
||||
|
||||
/**
|
||||
* @var ArchivePurger
|
||||
*/
|
||||
private $archivePurger;
|
||||
|
||||
public function __construct(ArchivePurger $archivePurger = null)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->archivePurger = $archivePurger;
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('core:purge-old-archive-data');
|
||||
$this->setDescription('Purges out of date and invalid archive data from archive tables.');
|
||||
$this->addArgument("dates", InputArgument::IS_ARRAY | InputArgument::OPTIONAL,
|
||||
"The months of the archive tables to purge data from. By default, only deletes from the current month. Use '" . self::ALL_DATES_STRING. "' for all dates.",
|
||||
array(self::getToday()->toString()));
|
||||
$this->addOption('exclude-outdated', null, InputOption::VALUE_NONE, "Do not purge outdated archive data.");
|
||||
$this->addOption('exclude-invalidated', null, InputOption::VALUE_NONE, "Do not purge invalidated archive data.");
|
||||
$this->addOption('exclude-ranges', null, InputOption::VALUE_NONE, "Do not purge custom ranges.");
|
||||
$this->addOption('skip-optimize-tables', null, InputOption::VALUE_NONE, "Do not run OPTIMIZE TABLES query on affected archive tables.");
|
||||
$this->addOption('include-year-archives', null, InputOption::VALUE_NONE, "If supplied, the command will purge archive tables that contain year archives for every supplied date.");
|
||||
$this->addOption('force-optimize-tables', null, InputOption::VALUE_NONE, "If supplied, forces optimize table SQL to be run, even on InnoDB tables.");
|
||||
$this->setHelp("By default old and invalidated archives are purged. Custom ranges are also purged with outdated archives.\n\n"
|
||||
. "Note: archive purging is done during scheduled task execution, so under normal circumstances, you should not need to "
|
||||
. "run this command manually.");
|
||||
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
// during normal command execution, we don't want the INFO level logs logged by the ArchivePurger service
|
||||
// to display in the console, so we use a NullLogger for the service
|
||||
$logger = null;
|
||||
if ($output->getVerbosity() <= OutputInterface::VERBOSITY_NORMAL) {
|
||||
$logger = new NullLogger();
|
||||
}
|
||||
|
||||
$archivePurger = $this->archivePurger ?: new ArchivePurger($model = null, $purgeDatesOlderThan = null, $logger);
|
||||
|
||||
$dates = $this->getDatesToPurgeFor($input);
|
||||
|
||||
$excludeOutdated = $input->getOption('exclude-outdated');
|
||||
if ($excludeOutdated) {
|
||||
$output->writeln("Skipping purge outdated archive data.");
|
||||
} else {
|
||||
foreach ($dates as $date) {
|
||||
$message = sprintf("Purging outdated archives for %s...", $date->toString('Y_m'));
|
||||
$this->performTimedPurging($output, $message, function () use ($date, $archivePurger) {
|
||||
$archivePurger->purgeOutdatedArchives($date);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$excludeInvalidated = $input->getOption('exclude-invalidated');
|
||||
if ($excludeInvalidated) {
|
||||
$output->writeln("Skipping purge invalidated archive data.");
|
||||
} else {
|
||||
foreach ($dates as $date) {
|
||||
$message = sprintf("Purging invalidated archives for %s...", $date->toString('Y_m'));
|
||||
$this->performTimedPurging($output, $message, function () use ($archivePurger, $date) {
|
||||
$archivePurger->purgeInvalidatedArchivesFrom($date);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$excludeCustomRanges = $input->getOption('exclude-ranges');
|
||||
if ($excludeCustomRanges) {
|
||||
$output->writeln("Skipping purge custom range archives.");
|
||||
} else {
|
||||
foreach ($dates as $date) {
|
||||
$message = sprintf("Purging custom range archives for %s...", $date->toString('Y_m'));
|
||||
$this->performTimedPurging($output, $message, function () use ($date, $archivePurger) {
|
||||
$archivePurger->purgeArchivesWithPeriodRange($date);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$skipOptimizeTables = $input->getOption('skip-optimize-tables');
|
||||
if ($skipOptimizeTables) {
|
||||
$output->writeln("Skipping OPTIMIZE TABLES.");
|
||||
} else {
|
||||
$this->optimizeArchiveTables($output, $dates, $input->getOption('force-optimize-tables'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InputInterface $input
|
||||
* @return Date[]
|
||||
*/
|
||||
private function getDatesToPurgeFor(InputInterface $input)
|
||||
{
|
||||
$dates = array();
|
||||
|
||||
$dateSpecifier = $input->getArgument('dates');
|
||||
if (count($dateSpecifier) === 1
|
||||
&& reset($dateSpecifier) == self::ALL_DATES_STRING
|
||||
) {
|
||||
foreach (ArchiveTableCreator::getTablesArchivesInstalled() as $table) {
|
||||
$tableDate = ArchiveTableCreator::getDateFromTableName($table);
|
||||
|
||||
list($year, $month) = explode('_', $tableDate);
|
||||
|
||||
$dates[] = Date::factory($year . '-' . $month . '-' . '01');
|
||||
}
|
||||
} else {
|
||||
$includeYearArchives = $input->getOption('include-year-archives');
|
||||
|
||||
foreach ($dateSpecifier as $date) {
|
||||
$dateObj = Date::factory($date);
|
||||
$yearMonth = $dateObj->toString('Y-m');
|
||||
$dates[$yearMonth] = $dateObj;
|
||||
|
||||
// if --include-year-archives is supplied, add a date for the january table for this date's year
|
||||
// so year archives will be purged
|
||||
if ($includeYearArchives) {
|
||||
$janYearMonth = $dateObj->toString('Y') . '-01';
|
||||
if (empty($dates[$janYearMonth])) {
|
||||
$dates[$janYearMonth] = Date::factory($janYearMonth . '-01');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$dates = array_values($dates);
|
||||
}
|
||||
|
||||
return $dates;
|
||||
}
|
||||
|
||||
private function performTimedPurging(OutputInterface $output, $startMessage, $callback)
|
||||
{
|
||||
$timer = new Timer();
|
||||
|
||||
$output->write($startMessage);
|
||||
|
||||
$callback();
|
||||
|
||||
$output->writeln("Done. <comment>[" . $timer->__toString() . "]</comment>");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param OutputInterface $output
|
||||
* @param Date[] $dates
|
||||
* @param bool $forceOptimzation
|
||||
*/
|
||||
private function optimizeArchiveTables(OutputInterface $output, $dates, $forceOptimzation = false)
|
||||
{
|
||||
$output->writeln("Optimizing archive tables...");
|
||||
|
||||
foreach ($dates as $date) {
|
||||
$numericTable = ArchiveTableCreator::getNumericTable($date);
|
||||
$this->performTimedPurging($output, "Optimizing table $numericTable...", function () use ($numericTable, $forceOptimzation) {
|
||||
Db::optimizeTables($numericTable, $forceOptimzation);
|
||||
});
|
||||
|
||||
$blobTable = ArchiveTableCreator::getBlobTable($date);
|
||||
$this->performTimedPurging($output, "Optimizing table $blobTable...", function () use ($blobTable, $forceOptimzation) {
|
||||
Db::optimizeTables($blobTable, $forceOptimzation);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static function getToday()
|
||||
{
|
||||
return self::$todayOverride ?: Date::today();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
<?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\CoreAdminHome\Commands;
|
||||
|
||||
use Piwik\Container\StaticContainer;
|
||||
use Piwik\FrontController;
|
||||
use Piwik\Plugin\ConsoleCommand;
|
||||
use Piwik\Scheduler\Scheduler;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class RunScheduledTasks extends ConsoleCommand
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('scheduled-tasks:run');
|
||||
$this->setAliases(array('core:run-scheduled-tasks'));
|
||||
$this->setDescription('Will run all scheduled tasks due to run at this time.');
|
||||
$this->addArgument('task', InputArgument::OPTIONAL, 'Optionally pass the name of a task to run (will run even if not scheduled to run now)');
|
||||
$this->addOption('force', null, InputOption::VALUE_NONE, 'If set, it will execute all tasks even the ones not due to run at this time.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute command like: ./console core:run-scheduled-tasks
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$this->forceRunAllTasksIfRequested($input);
|
||||
|
||||
FrontController::getInstance()->init();
|
||||
|
||||
// TODO use dependency injection
|
||||
/** @var Scheduler $scheduler */
|
||||
$scheduler = StaticContainer::get('Piwik\Scheduler\Scheduler');
|
||||
|
||||
$task = $input->getArgument('task');
|
||||
|
||||
if ($task) {
|
||||
$this->runSingleTask($scheduler, $task, $output);
|
||||
} else {
|
||||
$scheduler->run();
|
||||
}
|
||||
|
||||
$this->writeSuccessMessage($output, array('Scheduled Tasks executed'));
|
||||
}
|
||||
|
||||
private function forceRunAllTasksIfRequested(InputInterface $input)
|
||||
{
|
||||
$force = $input->getOption('force');
|
||||
|
||||
if ($force && !defined('DEBUG_FORCE_SCHEDULED_TASKS')) {
|
||||
define('DEBUG_FORCE_SCHEDULED_TASKS', true);
|
||||
}
|
||||
}
|
||||
|
||||
private function runSingleTask(Scheduler $scheduler, $task, OutputInterface $output)
|
||||
{
|
||||
try {
|
||||
$message = $scheduler->runTaskNow($task);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
$message = $e->getMessage() . PHP_EOL
|
||||
. 'Available tasks:' . PHP_EOL
|
||||
. implode(PHP_EOL, $scheduler->getTaskList());
|
||||
|
||||
throw new \Exception($message);
|
||||
}
|
||||
|
||||
$output->writeln($message);
|
||||
}
|
||||
}
|
||||
97
www/analytics/plugins/CoreAdminHome/Commands/SetConfig.php
Normal file
97
www/analytics/plugins/CoreAdminHome/Commands/SetConfig.php
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
<?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\CoreAdminHome\Commands;
|
||||
|
||||
use Piwik\Config;
|
||||
use Piwik\Plugin\ConsoleCommand;
|
||||
use Piwik\Plugins\CoreAdminHome\Commands\SetConfig\ConfigSettingManipulation;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class SetConfig extends ConsoleCommand
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('config:set');
|
||||
$this->setDescription('Set one or more config settings in the file config/config.ini.php');
|
||||
$this->addArgument('assignment', InputArgument::OPTIONAL | InputArgument::IS_ARRAY,
|
||||
"List of config setting assignments, eg, Section.key=1 or Section.array_key[]=value");
|
||||
$this->addOption('section', null, InputOption::VALUE_REQUIRED, 'The section the INI config setting belongs to.');
|
||||
$this->addOption('key', null, InputOption::VALUE_REQUIRED, 'The name of the INI config setting.');
|
||||
$this->addOption('value', null, InputOption::VALUE_REQUIRED, 'The value of the setting. (Not JSON encoded)');
|
||||
$this->setHelp("This command can be used to set INI config settings on a Piwik instance.
|
||||
|
||||
You can set config values two ways, via --section, --key, --value or by command arguments.
|
||||
|
||||
To use --section, --key, --value, simply supply those options. You can only set one setting this way, and you cannot
|
||||
append to arrays.
|
||||
|
||||
To use arguments, supply one or more arguments in the following format: Section.config_setting_name=\"value\"
|
||||
'Section' is the name of the section, 'config_setting_name' the name of the setting and 'value' is the value.
|
||||
NOTE: 'value' must be JSON encoded, so Section.config_setting_name=\"value\" would work but
|
||||
Section.config_setting_name=value would not.
|
||||
|
||||
To append to an array setting, supply an argument like this: Section.config_setting_name[]=\"value to append\"
|
||||
|
||||
To reset an array setting, supply an argument like this: Section.config_setting_name=[]
|
||||
Resetting an array will not work if the array has default values in global.ini.php (such as, [log] log_writers).
|
||||
In this case the values in global.ini.php will be used, since there is no way to explicitly set an
|
||||
array setting to empty in INI config.
|
||||
|
||||
Use the --piwik-domain option to specify which instance to modify.
|
||||
|
||||
");
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$section = $input->getOption('section');
|
||||
$key = $input->getOption('key');
|
||||
$value = $input->getOption('value');
|
||||
|
||||
$manipulations = $this->getAssignments($input);
|
||||
|
||||
$isSingleAssignment = !empty($section) && !empty($key) && $value !== false;
|
||||
if ($isSingleAssignment) {
|
||||
$manipulations[] = new ConfigSettingManipulation($section, $key, $value);
|
||||
}
|
||||
|
||||
if (empty($manipulations)) {
|
||||
throw new \InvalidArgumentException("Nothing to assign. Add assignments as arguments or use the "
|
||||
. "--section, --key and --value options.");
|
||||
}
|
||||
|
||||
$config = Config::getInstance();
|
||||
foreach ($manipulations as $manipulation) {
|
||||
$manipulation->manipulate($config);
|
||||
|
||||
$output->writeln("<info>Setting [{$manipulation->getSectionName()}] {$manipulation->getName()} = {$manipulation->getValueString()}</info>");
|
||||
}
|
||||
|
||||
$config->forceSave();
|
||||
|
||||
$this->writeSuccessMessage($output, array("Done."));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ConfigSettingManipulation[]
|
||||
*/
|
||||
private function getAssignments(InputInterface $input)
|
||||
{
|
||||
$assignments = $input->getArgument('assignment');
|
||||
|
||||
$result = array();
|
||||
foreach ($assignments as $assignment) {
|
||||
$result[] = ConfigSettingManipulation::make($assignment);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,176 @@
|
|||
<?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\CoreAdminHome\Commands\SetConfig;
|
||||
|
||||
use Piwik\Config;
|
||||
|
||||
/**
|
||||
* Representation of a INI config manipulation operation. Only supports two types
|
||||
* of manipulations: appending to a config array and assigning a config value.
|
||||
*/
|
||||
class ConfigSettingManipulation
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sectionName;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $name;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $value;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $isArrayAppend;
|
||||
|
||||
/**
|
||||
* @param string $sectionName
|
||||
* @param string $name
|
||||
* @param string $value
|
||||
* @param bool $isArrayAppend
|
||||
*/
|
||||
public function __construct($sectionName, $name, $value, $isArrayAppend = false)
|
||||
{
|
||||
$this->sectionName = $sectionName;
|
||||
$this->name = $name;
|
||||
$this->value = $value;
|
||||
$this->isArrayAppend = $isArrayAppend;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the INI config manipulation.
|
||||
*
|
||||
* @param Config $config
|
||||
* @throws \Exception if trying to append to a non-array setting value or if trying to set an
|
||||
* array value to a non-array setting
|
||||
*/
|
||||
public function manipulate(Config $config)
|
||||
{
|
||||
if ($this->isArrayAppend) {
|
||||
$this->appendToArraySetting($config);
|
||||
} else {
|
||||
$this->setSingleConfigValue($config);
|
||||
}
|
||||
}
|
||||
|
||||
private function setSingleConfigValue(Config $config)
|
||||
{
|
||||
$sectionName = $this->sectionName;
|
||||
$section = $config->$sectionName;
|
||||
|
||||
if (isset($section[$this->name])
|
||||
&& is_array($section[$this->name])
|
||||
&& !is_array($this->value)
|
||||
) {
|
||||
throw new \Exception("Trying to set non-array value to array setting " . $this->getSettingString() . ".");
|
||||
}
|
||||
|
||||
$section[$this->name] = $this->value;
|
||||
$config->$sectionName = $section;
|
||||
}
|
||||
|
||||
private function appendToArraySetting(Config $config)
|
||||
{
|
||||
$sectionName = $this->sectionName;
|
||||
$section = $config->$sectionName;
|
||||
|
||||
if (isset($section[$this->name])
|
||||
&& !is_array($section[$this->name])
|
||||
) {
|
||||
throw new \Exception("Trying to append to non-array setting value " . $this->getSettingString() . ".");
|
||||
}
|
||||
|
||||
$section[$this->name][] = $this->value;
|
||||
$config->$sectionName = $section;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a ConfigSettingManipulation instance from a string like:
|
||||
*
|
||||
* `SectionName.setting_name=value`
|
||||
*
|
||||
* or
|
||||
*
|
||||
* `SectionName.setting_name[]=value`
|
||||
*
|
||||
* The value must be JSON so `="string"` will work but `=string` will not.
|
||||
*
|
||||
* @param string $assignment
|
||||
* @return self
|
||||
*/
|
||||
public static function make($assignment)
|
||||
{
|
||||
if (!preg_match('/^([a-zA-Z0-9_]+)\.([a-zA-Z0-9_]+)(\[\])?=(.*)/', $assignment, $matches)) {
|
||||
throw new \InvalidArgumentException("Invalid assignment string '$assignment': expected section.name=value or section.name[]=value");
|
||||
}
|
||||
|
||||
$section = $matches[1];
|
||||
$name = $matches[2];
|
||||
$isAppend = !empty($matches[3]);
|
||||
|
||||
$value = json_decode($matches[4], $isAssoc = true);
|
||||
if ($value === null) {
|
||||
throw new \InvalidArgumentException("Invalid assignment string '$assignment': could not parse value as JSON");
|
||||
}
|
||||
|
||||
return new self($section, $name, $value, $isAppend);
|
||||
}
|
||||
|
||||
private function getSettingString()
|
||||
{
|
||||
return "[{$this->sectionName}] {$this->name}";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getSectionName()
|
||||
{
|
||||
return $this->sectionName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function isArrayAppend()
|
||||
{
|
||||
return $this->isArrayAppend;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getValueString()
|
||||
{
|
||||
return json_encode($this->value);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -13,29 +13,47 @@ use Piwik\API\ResponseBuilder;
|
|||
use Piwik\ArchiveProcessor\Rules;
|
||||
use Piwik\Common;
|
||||
use Piwik\Config;
|
||||
use Piwik\Container\StaticContainer;
|
||||
use Piwik\DataTable\Renderer\Json;
|
||||
use Piwik\Menu\MenuTop;
|
||||
use Piwik\Menu\MenuUser;
|
||||
use Piwik\Nonce;
|
||||
use Piwik\Option;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Plugin\ControllerAdmin;
|
||||
use Piwik\Plugins\CorePluginsAdmin\UpdateCommunication;
|
||||
use Piwik\Plugins\CustomVariables\CustomVariables;
|
||||
use Piwik\Plugins\LanguagesManager\API as APILanguagesManager;
|
||||
use Piwik\Plugins\LanguagesManager\LanguagesManager;
|
||||
use Piwik\Plugins\PrivacyManager\DoNotTrackHeaderChecker;
|
||||
use Piwik\Plugins\SitesManager\API as APISitesManager;
|
||||
use Piwik\Settings\Manager as SettingsManager;
|
||||
use Piwik\Settings\SystemSetting;
|
||||
use Piwik\Settings\UserSetting;
|
||||
use Piwik\Site;
|
||||
use Piwik\Tracker\IgnoreCookie;
|
||||
use Piwik\Translation\Translator;
|
||||
use Piwik\UpdateCheck;
|
||||
use Piwik\Url;
|
||||
use Piwik\View;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class Controller extends \Piwik\Plugin\ControllerAdmin
|
||||
class Controller extends ControllerAdmin
|
||||
{
|
||||
const SET_PLUGIN_SETTINGS_NONCE = 'CoreAdminHome.setPluginSettings';
|
||||
|
||||
/**
|
||||
* @var Translator
|
||||
*/
|
||||
private $translator;
|
||||
|
||||
/** @var OptOutManager */
|
||||
private $optOutManager;
|
||||
|
||||
public function __construct(Translator $translator, OptOutManager $optOutManager)
|
||||
{
|
||||
$this->translator = $translator;
|
||||
$this->optOutManager = $optOutManager;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
$this->redirectToIndex('UsersManager', 'userSettings');
|
||||
|
|
@ -44,42 +62,87 @@ class Controller extends \Piwik\Plugin\ControllerAdmin
|
|||
|
||||
public function generalSettings()
|
||||
{
|
||||
Piwik::checkUserHasSomeAdminAccess();
|
||||
Piwik::checkUserHasSuperUserAccess();
|
||||
|
||||
$view = new View('@CoreAdminHome/generalSettings');
|
||||
$this->handleGeneralSettingsAdmin($view);
|
||||
|
||||
if (Piwik::hasUserSuperUserAccess()) {
|
||||
$this->handleGeneralSettingsAdmin($view);
|
||||
|
||||
$view->trustedHosts = Url::getTrustedHostsFromConfig();
|
||||
|
||||
$logo = new CustomLogo();
|
||||
$view->branding = array('use_custom_logo' => $logo->isEnabled());
|
||||
$view->logosWriteable = $logo->isCustomLogoWritable();
|
||||
$view->pathUserLogo = CustomLogo::getPathUserLogo();
|
||||
$view->pathUserLogoSmall = CustomLogo::getPathUserLogoSmall();
|
||||
$view->pathUserLogoSVG = CustomLogo::getPathUserSvgLogo();
|
||||
$view->pathUserLogoDirectory = realpath(dirname($view->pathUserLogo) . '/');
|
||||
}
|
||||
$view->trustedHosts = Url::getTrustedHostsFromConfig();
|
||||
$logo = new CustomLogo();
|
||||
$view->branding = array('use_custom_logo' => $logo->isEnabled());
|
||||
$view->fileUploadEnabled = $logo->isFileUploadEnabled();
|
||||
$view->logosWriteable = $logo->isCustomLogoWritable();
|
||||
$view->pathUserLogo = CustomLogo::getPathUserLogo();
|
||||
$view->pathUserFavicon = CustomLogo::getPathUserFavicon();
|
||||
$view->pathUserLogoSmall = CustomLogo::getPathUserLogoSmall();
|
||||
$view->pathUserLogoSVG = CustomLogo::getPathUserSvgLogo();
|
||||
$view->pathUserLogoDirectory = realpath(dirname($view->pathUserLogo) . '/');
|
||||
|
||||
$view->language = LanguagesManager::getLanguageCodeForCurrentUser();
|
||||
$this->setBasicVariablesView($view);
|
||||
return $view->render();
|
||||
}
|
||||
|
||||
public function pluginSettings()
|
||||
public function adminPluginSettings()
|
||||
{
|
||||
Piwik::checkUserHasSuperUserAccess();
|
||||
|
||||
$settings = $this->getPluginSettings();
|
||||
|
||||
$vars = array(
|
||||
'nonce' => Nonce::getNonce(static::SET_PLUGIN_SETTINGS_NONCE),
|
||||
'pluginsSettings' => $this->getSettingsByType($settings, 'admin'),
|
||||
'firstSuperUserSettingNames' => $this->getFirstSuperUserSettingNames($settings),
|
||||
'mode' => 'admin'
|
||||
);
|
||||
|
||||
return $this->renderTemplate('pluginSettings', $vars);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Piwik\Plugin\Settings[] $pluginsSettings
|
||||
* @return array array([pluginName] => [])
|
||||
*/
|
||||
private function getSettingsByType($pluginsSettings, $mode)
|
||||
{
|
||||
$byType = array();
|
||||
|
||||
foreach ($pluginsSettings as $pluginName => $pluginSettings) {
|
||||
$settings = array();
|
||||
|
||||
foreach ($pluginSettings->getSettingsForCurrentUser() as $setting) {
|
||||
if ('admin' === $mode && $setting instanceof SystemSetting) {
|
||||
$settings[] = $setting;
|
||||
} elseif ('user' === $mode && $setting instanceof UserSetting) {
|
||||
$settings[] = $setting;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($settings)) {
|
||||
$byType[$pluginName] = array(
|
||||
'introduction' => $pluginSettings->getIntroduction(),
|
||||
'settings' => $settings
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $byType;
|
||||
}
|
||||
|
||||
public function userPluginSettings()
|
||||
{
|
||||
Piwik::checkUserIsNotAnonymous();
|
||||
|
||||
$settings = $this->getPluginSettings();
|
||||
|
||||
$view = new View('@CoreAdminHome/pluginSettings');
|
||||
$view->nonce = Nonce::getNonce(static::SET_PLUGIN_SETTINGS_NONCE);
|
||||
$view->pluginSettings = $settings;
|
||||
$view->firstSuperUserSettingNames = $this->getFirstSuperUserSettingNames($settings);
|
||||
$vars = array(
|
||||
'nonce' => Nonce::getNonce(static::SET_PLUGIN_SETTINGS_NONCE),
|
||||
'pluginsSettings' => $this->getSettingsByType($settings, 'user'),
|
||||
'firstSuperUserSettingNames' => $this->getFirstSuperUserSettingNames($settings),
|
||||
'mode' => 'user'
|
||||
);
|
||||
|
||||
$this->setBasicVariablesView($view);
|
||||
|
||||
return $view->render();
|
||||
return $this->renderTemplate('pluginSettings', $vars);
|
||||
}
|
||||
|
||||
private function getPluginSettings()
|
||||
|
|
@ -121,7 +184,7 @@ class Controller extends \Piwik\Plugin\ControllerAdmin
|
|||
if (!Nonce::verifyNonce(static::SET_PLUGIN_SETTINGS_NONCE, $nonce)) {
|
||||
return json_encode(array(
|
||||
'result' => 'error',
|
||||
'message' => Piwik::translate('General_ExceptionNonceMismatch')
|
||||
'message' => $this->translator->translate('General_ExceptionNonceMismatch')
|
||||
));
|
||||
}
|
||||
|
||||
|
|
@ -140,15 +203,28 @@ class Controller extends \Piwik\Plugin\ControllerAdmin
|
|||
}
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
$message = $e->getMessage();
|
||||
|
||||
if (!empty($setting)) {
|
||||
$message = $setting->title . ': ' . $message;
|
||||
}
|
||||
|
||||
$message = html_entity_decode($message, ENT_QUOTES, 'UTF-8');
|
||||
return json_encode(array('result' => 'error', 'message' => $message));
|
||||
}
|
||||
|
||||
try {
|
||||
foreach ($pluginsSettings as $pluginSetting) {
|
||||
$pluginSetting->save();
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
$message = html_entity_decode($e->getMessage(), ENT_QUOTES, 'UTF-8');
|
||||
return json_encode(array('result' => 'error', 'message' => $message));
|
||||
return json_encode(array(
|
||||
'result' => 'error',
|
||||
'message' => $this->translator->translate('CoreAdminHome_PluginSettingsSaveFailed'))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Nonce::discardNonce(static::SET_PLUGIN_SETTINGS_NONCE);
|
||||
return json_encode(array('result' => 'success'));
|
||||
}
|
||||
|
|
@ -165,7 +241,13 @@ class Controller extends \Piwik\Plugin\ControllerAdmin
|
|||
|
||||
foreach ($settings as $setting) {
|
||||
if ($setting['name'] == $settingKey) {
|
||||
return $setting['value'];
|
||||
$value = $setting['value'];
|
||||
|
||||
if (is_string($value)) {
|
||||
return Common::unsanitizeInputValue($value);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -200,9 +282,12 @@ class Controller extends \Piwik\Plugin\ControllerAdmin
|
|||
*/
|
||||
public function trackingCodeGenerator()
|
||||
{
|
||||
Piwik::checkUserHasSomeViewAccess();
|
||||
|
||||
$view = new View('@CoreAdminHome/trackingCodeGenerator');
|
||||
$this->setBasicVariablesView($view);
|
||||
$view->topMenu = MenuTop::getInstance()->getMenu();
|
||||
$view->topMenu = MenuTop::getInstance()->getMenu();
|
||||
$view->userMenu = MenuUser::getInstance()->getMenu();
|
||||
|
||||
$viewableIdSites = APISitesManager::getInstance()->getSitesIdWithAtLeastViewAccess();
|
||||
|
||||
|
|
@ -210,8 +295,8 @@ class Controller extends \Piwik\Plugin\ControllerAdmin
|
|||
$view->idSite = Common::getRequestVar('idSite', $defaultIdSite, 'int');
|
||||
|
||||
$view->defaultReportSiteName = Site::getNameFor($view->idSite);
|
||||
$view->defaultSiteRevenue = \Piwik\MetricsFormatter::getCurrencySymbol($view->idSite);
|
||||
$view->maxCustomVariables = CustomVariables::getMaxCustomVariables();
|
||||
$view->defaultSiteRevenue = Site::getCurrencySymbolFor($view->idSite);
|
||||
$view->maxCustomVariables = CustomVariables::getNumUsableCustomVariables();
|
||||
|
||||
$allUrls = APISitesManager::getInstance()->getSiteUrlsFromId($view->idSite);
|
||||
if (isset($allUrls[1])) {
|
||||
|
|
@ -227,7 +312,8 @@ class Controller extends \Piwik\Plugin\ControllerAdmin
|
|||
// get currencies for each viewable site
|
||||
$view->currencySymbols = APISitesManager::getInstance()->getCurrencySymbols();
|
||||
|
||||
$view->serverSideDoNotTrackEnabled = \Piwik\Plugins\PrivacyManager\DoNotTrackHeaderChecker::isActive();
|
||||
$dntChecker = new DoNotTrackHeaderChecker();
|
||||
$view->serverSideDoNotTrackEnabled = $dntChecker->isActive();
|
||||
|
||||
return $view->render();
|
||||
}
|
||||
|
|
@ -237,46 +323,37 @@ class Controller extends \Piwik\Plugin\ControllerAdmin
|
|||
*/
|
||||
public function optOut()
|
||||
{
|
||||
$trackVisits = !IgnoreCookie::isIgnoreCookieFound();
|
||||
|
||||
$nonce = Common::getRequestVar('nonce', false);
|
||||
$language = Common::getRequestVar('language', '');
|
||||
if ($nonce !== false && Nonce::verifyNonce('Piwik_OptOut', $nonce)) {
|
||||
Nonce::discardNonce('Piwik_OptOut');
|
||||
IgnoreCookie::setIgnoreCookie();
|
||||
$trackVisits = !$trackVisits;
|
||||
}
|
||||
|
||||
$view = new View('@CoreAdminHome/optOut');
|
||||
$view->trackVisits = $trackVisits;
|
||||
$view->nonce = Nonce::getNonce('Piwik_OptOut', 3600);
|
||||
$view->language = APILanguagesManager::getInstance()->isLanguageAvailable($language)
|
||||
? $language
|
||||
: LanguagesManager::getLanguageCodeForCurrentUser();
|
||||
return $view->render();
|
||||
return $this->optOutManager->getOptOutView()->render();
|
||||
}
|
||||
|
||||
public function uploadCustomLogo()
|
||||
{
|
||||
Piwik::checkUserHasSuperUserAccess();
|
||||
$this->checkTokenInUrl();
|
||||
|
||||
$logo = new CustomLogo();
|
||||
$success = $logo->copyUploadedLogoToFilesystem();
|
||||
$successLogo = $logo->copyUploadedLogoToFilesystem();
|
||||
$successFavicon = $logo->copyUploadedFaviconToFilesystem();
|
||||
|
||||
if($success) {
|
||||
if ($successLogo || $successFavicon) {
|
||||
return '1';
|
||||
}
|
||||
return '0';
|
||||
}
|
||||
|
||||
static public function isGeneralSettingsAdminEnabled()
|
||||
public static function isGeneralSettingsAdminEnabled()
|
||||
{
|
||||
return (bool) Config::getInstance()->General['enable_general_settings_admin'];
|
||||
}
|
||||
|
||||
private function makeReleaseChannels()
|
||||
{
|
||||
return StaticContainer::get('Piwik\Plugin\ReleaseChannels');
|
||||
}
|
||||
|
||||
private function saveGeneralSettings()
|
||||
{
|
||||
if(!self::isGeneralSettingsAdminEnabled()) {
|
||||
if (!self::isGeneralSettingsAdminEnabled()) {
|
||||
// General settings + Beta channel + SMTP settings is disabled
|
||||
return;
|
||||
}
|
||||
|
|
@ -287,10 +364,14 @@ class Controller extends \Piwik\Plugin\ControllerAdmin
|
|||
Rules::setBrowserTriggerArchiving((bool)$enableBrowserTriggerArchiving);
|
||||
Rules::setTodayArchiveTimeToLive($todayArchiveTimeToLive);
|
||||
|
||||
$releaseChannels = $this->makeReleaseChannels();
|
||||
|
||||
// update beta channel setting
|
||||
$debug = Config::getInstance()->Debug;
|
||||
$debug['allow_upgrades_to_beta'] = Common::getRequestVar('enableBetaReleaseCheck', '0', 'int');
|
||||
Config::getInstance()->Debug = $debug;
|
||||
$releaseChannel = Common::getRequestVar('releaseChannel', '', 'string');
|
||||
if (!$releaseChannels->isValidReleaseChannelId($releaseChannel)) {
|
||||
$releaseChannel = '';
|
||||
}
|
||||
$releaseChannels->setActiveReleaseChannelId($releaseChannel);
|
||||
|
||||
// Update email settings
|
||||
$mail = array();
|
||||
|
|
@ -309,6 +390,7 @@ class Controller extends \Piwik\Plugin\ControllerAdmin
|
|||
if ($trustedHosts !== false) {
|
||||
Url::saveTrustedHostnameInConfig($trustedHosts);
|
||||
}
|
||||
|
||||
Config::getInstance()->forceSave();
|
||||
|
||||
$pluginUpdateCommunication = new UpdateCommunication();
|
||||
|
|
@ -323,7 +405,7 @@ class Controller extends \Piwik\Plugin\ControllerAdmin
|
|||
{
|
||||
// Whether to display or not the general settings (cron, beta, smtp)
|
||||
$view->isGeneralSettingsAdminEnabled = self::isGeneralSettingsAdminEnabled();
|
||||
if($view->isGeneralSettingsAdminEnabled) {
|
||||
if ($view->isGeneralSettingsAdminEnabled) {
|
||||
$this->displayWarningIfConfigFileNotWritable();
|
||||
}
|
||||
|
||||
|
|
@ -337,9 +419,22 @@ class Controller extends \Piwik\Plugin\ControllerAdmin
|
|||
}
|
||||
$view->showWarningCron = $showWarningCron;
|
||||
$view->todayArchiveTimeToLive = $todayArchiveTimeToLive;
|
||||
$view->todayArchiveTimeToLiveDefault = Rules::getTodayArchiveTimeToLiveDefault();
|
||||
$view->enableBrowserTriggerArchiving = $enableBrowserTriggerArchiving;
|
||||
|
||||
$view->enableBetaReleaseCheck = Config::getInstance()->Debug['allow_upgrades_to_beta'];
|
||||
$releaseChannels = $this->makeReleaseChannels();
|
||||
$activeChannelId = $releaseChannels->getActiveReleaseChannel()->getId();
|
||||
$allChannels = array();
|
||||
foreach ($releaseChannels->getAllReleaseChannels() as $channel) {
|
||||
$allChannels[] = array(
|
||||
'id' => $channel->getId(),
|
||||
'name' => $channel->getName(),
|
||||
'description' => $channel->getDescription(),
|
||||
'active' => $channel->getId() === $activeChannelId
|
||||
);
|
||||
}
|
||||
|
||||
$view->releaseChannels = $allChannels;
|
||||
$view->mail = Config::getInstance()->mail;
|
||||
|
||||
$pluginUpdateCommunication = new UpdateCommunication();
|
||||
|
|
@ -347,5 +442,4 @@ class Controller extends \Piwik\Plugin\ControllerAdmin
|
|||
$view->enableSendPluginUpdateCommunication = $pluginUpdateCommunication->isEnabled();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -8,15 +8,9 @@
|
|||
*/
|
||||
namespace Piwik\Plugins\CoreAdminHome;
|
||||
|
||||
use Piwik\DataAccess\ArchiveSelector;
|
||||
use Piwik\DataAccess\ArchiveTableCreator;
|
||||
use Piwik\Date;
|
||||
use Piwik\Db;
|
||||
use Piwik\Menu\MenuAdmin;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\ScheduledTask;
|
||||
use Piwik\ScheduledTime;
|
||||
use Piwik\Settings\Manager as SettingsManager;
|
||||
use Piwik\ProxyHttp;
|
||||
use Piwik\Settings\UserSetting;
|
||||
|
||||
/**
|
||||
|
|
@ -25,16 +19,17 @@ use Piwik\Settings\UserSetting;
|
|||
class CoreAdminHome extends \Piwik\Plugin
|
||||
{
|
||||
/**
|
||||
* @see Piwik\Plugin::getListHooksRegistered
|
||||
* @see Piwik\Plugin::registerEvents
|
||||
*/
|
||||
public function getListHooksRegistered()
|
||||
public function registerEvents()
|
||||
{
|
||||
return array(
|
||||
'AssetManager.getStylesheetFiles' => 'getStylesheetFiles',
|
||||
'AssetManager.getJavaScriptFiles' => 'getJsFiles',
|
||||
'Menu.Admin.addItems' => 'addMenu',
|
||||
'TaskScheduler.getScheduledTasks' => 'getScheduledTasks',
|
||||
'UsersManager.deleteUser' => 'cleanupUser'
|
||||
'UsersManager.deleteUser' => 'cleanupUser',
|
||||
'API.DocumentationGenerator.@hideExceptForSuperUser' => 'displayOnlyForSuperUser',
|
||||
'Template.jsGlobalVariables' => 'addJsGlobalVariables',
|
||||
'Translate.getClientSideTranslationKeys' => 'getClientSideTranslationKeys'
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -43,87 +38,50 @@ class CoreAdminHome extends \Piwik\Plugin
|
|||
UserSetting::removeAllUserSettingsForUser($userLogin);
|
||||
}
|
||||
|
||||
public function getScheduledTasks(&$tasks)
|
||||
{
|
||||
// general data purge on older archive tables, executed daily
|
||||
$purgeArchiveTablesTask = new ScheduledTask ($this,
|
||||
'purgeOutdatedArchives',
|
||||
null,
|
||||
ScheduledTime::factory('daily'),
|
||||
ScheduledTask::HIGH_PRIORITY);
|
||||
$tasks[] = $purgeArchiveTablesTask;
|
||||
|
||||
// lowest priority since tables should be optimized after they are modified
|
||||
$optimizeArchiveTableTask = new ScheduledTask ($this,
|
||||
'optimizeArchiveTable',
|
||||
null,
|
||||
ScheduledTime::factory('daily'),
|
||||
ScheduledTask::LOWEST_PRIORITY);
|
||||
$tasks[] = $optimizeArchiveTableTask;
|
||||
}
|
||||
|
||||
public function getStylesheetFiles(&$stylesheets)
|
||||
{
|
||||
$stylesheets[] = "libs/jquery/themes/base/jquery-ui.css";
|
||||
$stylesheets[] = "plugins/CoreAdminHome/stylesheets/menu.less";
|
||||
$stylesheets[] = "plugins/Zeitgeist/stylesheets/base.less";
|
||||
$stylesheets[] = "libs/jquery/themes/base/jquery-ui.min.css";
|
||||
$stylesheets[] = "plugins/Morpheus/stylesheets/base.less";
|
||||
$stylesheets[] = "plugins/Morpheus/stylesheets/main.less";
|
||||
$stylesheets[] = "plugins/CoreAdminHome/stylesheets/generalSettings.less";
|
||||
$stylesheets[] = "plugins/CoreAdminHome/stylesheets/pluginSettings.less";
|
||||
}
|
||||
|
||||
public function getJsFiles(&$jsFiles)
|
||||
{
|
||||
$jsFiles[] = "libs/jquery/jquery.js";
|
||||
$jsFiles[] = "libs/jquery/jquery-ui.js";
|
||||
$jsFiles[] = "libs/bower_components/jquery/dist/jquery.min.js";
|
||||
$jsFiles[] = "libs/bower_components/jquery-ui/ui/minified/jquery-ui.min.js";
|
||||
$jsFiles[] = "libs/jquery/jquery.browser.js";
|
||||
$jsFiles[] = "libs/javascript/sprintf.js";
|
||||
$jsFiles[] = "plugins/Zeitgeist/javascripts/piwikHelper.js";
|
||||
$jsFiles[] = "plugins/Zeitgeist/javascripts/ajaxHelper.js";
|
||||
$jsFiles[] = "libs/jquery/jquery.history.js";
|
||||
$jsFiles[] = "libs/bower_components/sprintf/dist/sprintf.min.js";
|
||||
$jsFiles[] = "plugins/Morpheus/javascripts/piwikHelper.js";
|
||||
$jsFiles[] = "plugins/Morpheus/javascripts/ajaxHelper.js";
|
||||
$jsFiles[] = "plugins/Morpheus/javascripts/jquery.icheck.min.js";
|
||||
$jsFiles[] = "plugins/Morpheus/javascripts/morpheus.js";
|
||||
$jsFiles[] = "plugins/CoreHome/javascripts/broadcast.js";
|
||||
$jsFiles[] = "plugins/CoreAdminHome/javascripts/generalSettings.js";
|
||||
$jsFiles[] = "plugins/CoreHome/javascripts/donate.js";
|
||||
$jsFiles[] = "plugins/CoreAdminHome/javascripts/pluginSettings.js";
|
||||
$jsFiles[] = "plugins/CoreAdminHome/javascripts/protocolCheck.js";
|
||||
}
|
||||
|
||||
function addMenu()
|
||||
public function displayOnlyForSuperUser(&$hide)
|
||||
{
|
||||
MenuAdmin::getInstance()->add('CoreAdminHome_MenuManage', null, "", Piwik::isUserHasSomeAdminAccess(), $order = 1);
|
||||
MenuAdmin::getInstance()->add('CoreAdminHome_MenuDiagnostic', null, "", Piwik::isUserHasSomeAdminAccess(), $order = 10);
|
||||
MenuAdmin::getInstance()->add('General_Settings', null, "", Piwik::isUserHasSomeAdminAccess(), $order = 5);
|
||||
MenuAdmin::getInstance()->add('General_Settings', 'CoreAdminHome_MenuGeneralSettings',
|
||||
array('module' => 'CoreAdminHome', 'action' => 'generalSettings'),
|
||||
Piwik::isUserHasSomeAdminAccess(),
|
||||
$order = 6);
|
||||
MenuAdmin::getInstance()->add('CoreAdminHome_MenuManage', 'CoreAdminHome_TrackingCode',
|
||||
array('module' => 'CoreAdminHome', 'action' => 'trackingCodeGenerator'),
|
||||
Piwik::isUserHasSomeAdminAccess(),
|
||||
$order = 4);
|
||||
|
||||
MenuAdmin::getInstance()->add('General_Settings', 'CoreAdminHome_PluginSettings',
|
||||
array('module' => 'CoreAdminHome', 'action' => 'pluginSettings'),
|
||||
SettingsManager::hasPluginsSettingsForCurrentUser(),
|
||||
$order = 7);
|
||||
|
||||
$hide = !Piwik::hasUserSuperUserAccess();
|
||||
}
|
||||
|
||||
function purgeOutdatedArchives()
|
||||
public function addJsGlobalVariables(&$out)
|
||||
{
|
||||
$archiveTables = ArchiveTableCreator::getTablesArchivesInstalled();
|
||||
foreach ($archiveTables as $table) {
|
||||
$date = ArchiveTableCreator::getDateFromTableName($table);
|
||||
list($year, $month) = explode('_', $date);
|
||||
|
||||
// Somehow we may have archive tables created with older dates, prevent exception from being thrown
|
||||
if($year > 1990) {
|
||||
ArchiveSelector::purgeOutdatedArchives(Date::factory("$year-$month-15"));
|
||||
}
|
||||
if (ProxyHttp::isHttps()) {
|
||||
$isHttps = 'true';
|
||||
} else {
|
||||
$isHttps = 'false';
|
||||
}
|
||||
|
||||
$out .= "piwik.hasServerDetectedHttps = $isHttps;\n";
|
||||
}
|
||||
|
||||
function optimizeArchiveTable()
|
||||
public function getClientSideTranslationKeys(&$translationKeys)
|
||||
{
|
||||
$archiveTables = ArchiveTableCreator::getTablesArchivesInstalled();
|
||||
Db::optimizeTables($archiveTables);
|
||||
$translationKeys[] = 'CoreAdminHome_ProtocolNotDetectedCorrectly';
|
||||
$translationKeys[] = 'CoreAdminHome_ProtocolNotDetectedCorrectlySolution';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -11,16 +11,18 @@ namespace Piwik\Plugins\CoreAdminHome;
|
|||
use Piwik\Config;
|
||||
use Piwik\Filesystem;
|
||||
use Piwik\Option;
|
||||
use Piwik\Plugin\Manager;
|
||||
use Piwik\SettingsPiwik;
|
||||
|
||||
class CustomLogo
|
||||
{
|
||||
const LOGO_HEIGHT = 300;
|
||||
const LOGO_SMALL_HEIGHT = 100;
|
||||
const FAVICON_HEIGHT = 32;
|
||||
|
||||
public function getLogoUrl($pathOnly = false)
|
||||
{
|
||||
$defaultLogo = 'plugins/Zeitgeist/images/logo.png';
|
||||
$defaultLogo = 'plugins/Morpheus/images/logo.png';
|
||||
$themeLogo = 'plugins/%s/images/logo.png';
|
||||
$userLogo = CustomLogo::getPathUserLogo();
|
||||
return $this->getPathToLogo($pathOnly, $defaultLogo, $themeLogo, $userLogo);
|
||||
|
|
@ -28,7 +30,7 @@ class CustomLogo
|
|||
|
||||
public function getHeaderLogoUrl($pathOnly = false)
|
||||
{
|
||||
$defaultLogo = 'plugins/Zeitgeist/images/logo-header.png';
|
||||
$defaultLogo = 'plugins/Morpheus/images/logo-header.png';
|
||||
$themeLogo = 'plugins/%s/images/logo-header.png';
|
||||
$customLogo = CustomLogo::getPathUserLogoSmall();
|
||||
return $this->getPathToLogo($pathOnly, $defaultLogo, $themeLogo, $customLogo);
|
||||
|
|
@ -36,7 +38,7 @@ class CustomLogo
|
|||
|
||||
public function getSVGLogoUrl($pathOnly = false)
|
||||
{
|
||||
$defaultLogo = 'plugins/Zeitgeist/images/logo.svg';
|
||||
$defaultLogo = 'plugins/Morpheus/images/logo.svg';
|
||||
$themeLogo = 'plugins/%s/images/logo.svg';
|
||||
$customLogo = CustomLogo::getPathUserSvgLogo();
|
||||
$svg = $this->getPathToLogo($pathOnly, $defaultLogo, $themeLogo, $customLogo);
|
||||
|
|
@ -74,12 +76,20 @@ class CustomLogo
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isFileUploadEnabled()
|
||||
{
|
||||
return ini_get('file_uploads') == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isCustomLogoWritable()
|
||||
{
|
||||
if(Config::getInstance()->General['enable_custom_logo_check'] == 0) {
|
||||
if (Config::getInstance()->General['enable_custom_logo_check'] == 0) {
|
||||
return true;
|
||||
}
|
||||
$pathUserLogo = $this->getPathUserLogo();
|
||||
|
|
@ -87,15 +97,14 @@ class CustomLogo
|
|||
$directoryWritingTo = PIWIK_DOCUMENT_ROOT . '/' . dirname($pathUserLogo);
|
||||
|
||||
// Create directory if not already created
|
||||
Filesystem::mkdir($directoryWritingTo, $denyAccess = false);
|
||||
Filesystem::mkdir($directoryWritingTo);
|
||||
|
||||
$directoryWritable = is_writable($directoryWritingTo);
|
||||
$logoFilesWriteable = is_writeable(PIWIK_DOCUMENT_ROOT . '/' . $pathUserLogo)
|
||||
&& is_writeable(PIWIK_DOCUMENT_ROOT . '/' . $this->getPathUserSvgLogo())
|
||||
&& is_writeable(PIWIK_DOCUMENT_ROOT . '/' . $this->getPathUserLogoSmall());;
|
||||
|
||||
$serverUploadEnabled = ini_get('file_uploads') == 1;
|
||||
$isCustomLogoWritable = ($logoFilesWriteable || $directoryWritable) && $serverUploadEnabled;
|
||||
$isCustomLogoWritable = ($logoFilesWriteable || $directoryWritable) && $this->isFileUploadEnabled();
|
||||
|
||||
return $isCustomLogoWritable;
|
||||
}
|
||||
|
|
@ -106,7 +115,12 @@ class CustomLogo
|
|||
|
||||
$logo = $defaultLogo;
|
||||
|
||||
$themeName = \Piwik\Plugin\Manager::getInstance()->getThemeEnabled()->getPluginName();
|
||||
$theme = \Piwik\Plugin\Manager::getInstance()->getThemeEnabled();
|
||||
if(!$theme) {
|
||||
$themeName = Manager::DEFAULT_THEME;
|
||||
} else {
|
||||
$themeName = $theme->getPluginName();
|
||||
}
|
||||
$themeLogo = sprintf($themeLogo, $themeName);
|
||||
|
||||
if (file_exists($pathToPiwikRoot . '/' . $themeLogo)) {
|
||||
|
|
@ -129,6 +143,11 @@ class CustomLogo
|
|||
return self::rewritePath('misc/user/logo.png');
|
||||
}
|
||||
|
||||
public static function getPathUserFavicon()
|
||||
{
|
||||
return self::rewritePath('misc/user/favicon.png');
|
||||
}
|
||||
|
||||
public static function getPathUserSvgLogo()
|
||||
{
|
||||
return self::rewritePath('misc/user/logo.svg');
|
||||
|
|
@ -141,62 +160,72 @@ class CustomLogo
|
|||
|
||||
protected static function rewritePath($path)
|
||||
{
|
||||
return SettingsPiwik::rewriteMiscUserPathWithHostname($path);
|
||||
return SettingsPiwik::rewriteMiscUserPathWithInstanceId($path);
|
||||
}
|
||||
|
||||
public function copyUploadedLogoToFilesystem()
|
||||
{
|
||||
$uploadFieldName = 'customLogo';
|
||||
|
||||
if (empty($_FILES['customLogo'])
|
||||
|| !empty($_FILES['customLogo']['error'])
|
||||
$success = $this->uploadImage($uploadFieldName, self::LOGO_SMALL_HEIGHT, $this->getPathUserLogoSmall());
|
||||
$success = $success && $this->uploadImage($uploadFieldName, self::LOGO_HEIGHT, $this->getPathUserLogo());
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
public function copyUploadedFaviconToFilesystem()
|
||||
{
|
||||
$uploadFieldName = 'customFavicon';
|
||||
|
||||
return $this->uploadImage($uploadFieldName, self::FAVICON_HEIGHT, $this->getPathUserFavicon());
|
||||
}
|
||||
|
||||
private function uploadImage($uploadFieldName, $targetHeight, $userPath)
|
||||
{
|
||||
if (empty($_FILES[$uploadFieldName])
|
||||
|| !empty($_FILES[$uploadFieldName]['error'])
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$file = $_FILES['customLogo']['tmp_name'];
|
||||
$file = $_FILES[$uploadFieldName]['tmp_name'];
|
||||
if (!file_exists($file)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
list($width, $height) = getimagesize($file);
|
||||
switch ($_FILES['customLogo']['type']) {
|
||||
switch ($_FILES[$uploadFieldName]['type']) {
|
||||
case 'image/jpeg':
|
||||
$image = imagecreatefromjpeg($file);
|
||||
$image = @imagecreatefromjpeg($file);
|
||||
break;
|
||||
case 'image/png':
|
||||
$image = imagecreatefrompng($file);
|
||||
$image = @imagecreatefrompng($file);
|
||||
break;
|
||||
case 'image/gif':
|
||||
$image = imagecreatefromgif($file);
|
||||
$image = @imagecreatefromgif ($file);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
$widthExpected = round($width * self::LOGO_HEIGHT / $height);
|
||||
$smallWidthExpected = round($width * self::LOGO_SMALL_HEIGHT / $height);
|
||||
|
||||
$logo = imagecreatetruecolor($widthExpected, self::LOGO_HEIGHT);
|
||||
$logoSmall = imagecreatetruecolor($smallWidthExpected, self::LOGO_SMALL_HEIGHT);
|
||||
|
||||
// Handle transparency
|
||||
$background = imagecolorallocate($logo, 0, 0, 0);
|
||||
$backgroundSmall = imagecolorallocate($logoSmall, 0, 0, 0);
|
||||
imagecolortransparent($logo, $background);
|
||||
imagecolortransparent($logoSmall, $backgroundSmall);
|
||||
|
||||
if ($_FILES['customLogo']['type'] == 'image/png') {
|
||||
imagealphablending($logo, false);
|
||||
imagealphablending($logoSmall, false);
|
||||
imagesavealpha($logo, true);
|
||||
imagesavealpha($logoSmall, true);
|
||||
if (!is_resource($image)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
imagecopyresized($logo, $image, 0, 0, 0, 0, $widthExpected, self::LOGO_HEIGHT, $width, $height);
|
||||
imagecopyresized($logoSmall, $image, 0, 0, 0, 0, $smallWidthExpected, self::LOGO_SMALL_HEIGHT, $width, $height);
|
||||
$targetWidth = round($width * $targetHeight / $height);
|
||||
|
||||
imagepng($logo, PIWIK_DOCUMENT_ROOT . '/' . $this->getPathUserLogo(), 3);
|
||||
imagepng($logoSmall, PIWIK_DOCUMENT_ROOT . '/' . $this->getPathUserLogoSmall(), 3);
|
||||
$newImage = imagecreatetruecolor($targetWidth, $targetHeight);
|
||||
|
||||
if ($_FILES[$uploadFieldName]['type'] == 'image/png') {
|
||||
imagealphablending($newImage, false);
|
||||
imagesavealpha($newImage, true);
|
||||
}
|
||||
|
||||
$backgroundColor = imagecolorallocate($newImage, 0, 0, 0);
|
||||
imagecolortransparent($newImage, $backgroundColor);
|
||||
|
||||
imagecopyresampled($newImage, $image, 0, 0, 0, 0, $targetWidth, $targetHeight, $width, $height);
|
||||
imagepng($newImage, PIWIK_DOCUMENT_ROOT . '/' . $userPath, 3);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
74
www/analytics/plugins/CoreAdminHome/Menu.php
Normal file
74
www/analytics/plugins/CoreAdminHome/Menu.php
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
<?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\CoreAdminHome;
|
||||
|
||||
use Piwik\Db;
|
||||
use Piwik\Menu\MenuAdmin;
|
||||
use Piwik\Menu\MenuTop;
|
||||
use Piwik\Menu\MenuUser;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Settings\Manager as SettingsManager;
|
||||
|
||||
class Menu extends \Piwik\Plugin\Menu
|
||||
{
|
||||
|
||||
public function configureAdminMenu(MenuAdmin $menu)
|
||||
{
|
||||
$hasAdminAccess = Piwik::isUserHasSomeAdminAccess();
|
||||
|
||||
if ($hasAdminAccess) {
|
||||
$menu->addManageItem(null, array(), $order = 1);
|
||||
$menu->addSettingsItem(null, array(), $order = 5);
|
||||
$menu->addDiagnosticItem(null, array(), $order = 10);
|
||||
$menu->addDevelopmentItem(null, array(), $order = 15);
|
||||
|
||||
if (Piwik::hasUserSuperUserAccess()) {
|
||||
$menu->addSettingsItem('General_General',
|
||||
$this->urlForAction('generalSettings'),
|
||||
$order = 6);
|
||||
}
|
||||
}
|
||||
|
||||
if (Piwik::hasUserSuperUserAccess() && SettingsManager::hasSystemPluginsSettingsForCurrentUser()) {
|
||||
$menu->addSettingsItem('CoreAdminHome_PluginSettings',
|
||||
$this->urlForAction('adminPluginSettings'),
|
||||
$order = 7);
|
||||
}
|
||||
}
|
||||
|
||||
public function configureTopMenu(MenuTop $menu)
|
||||
{
|
||||
if (Piwik::isUserHasSomeAdminAccess()) {
|
||||
$url = $this->urlForModuleAction('SitesManager', 'index');
|
||||
|
||||
if (Piwik::hasUserSuperUserAccess()) {
|
||||
$url = $this->urlForAction('generalSettings');
|
||||
}
|
||||
|
||||
$menu->registerMenuIcon('CoreAdminHome_Administration', 'icon-configure');
|
||||
$menu->addItem('CoreAdminHome_Administration', null, $url, 980, Piwik::translate('CoreAdminHome_Administration'));
|
||||
}
|
||||
}
|
||||
|
||||
public function configureUserMenu(MenuUser $menu)
|
||||
{
|
||||
if (!Piwik::isUserIsAnonymous()) {
|
||||
$menu->addManageItem('CoreAdminHome_TrackingCode',
|
||||
$this->urlForAction('trackingCodeGenerator'),
|
||||
$order = 20);
|
||||
|
||||
if (SettingsManager::hasUserPluginsSettingsForCurrentUser()) {
|
||||
$menu->addPersonalItem('CoreAdminHome_PluginSettings',
|
||||
$this->urlForAction('userPluginSettings'),
|
||||
$order = 15);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,188 @@
|
|||
<?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\CoreAdminHome\Model;
|
||||
|
||||
use Piwik\Common;
|
||||
use Piwik\Container\StaticContainer;
|
||||
use Piwik\DataAccess\TableMetadata;
|
||||
use Piwik\Db;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Provides methods to find duplicate actions and fix duplicate action references in tables
|
||||
* that reference log_action rows.
|
||||
*/
|
||||
class DuplicateActionRemover
|
||||
{
|
||||
/**
|
||||
* The tables that contain idaction reference columns.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
public static $tablesWithIdActionColumns = array(
|
||||
'log_link_visit_action',
|
||||
'log_conversion',
|
||||
'log_conversion_item'
|
||||
);
|
||||
|
||||
/**
|
||||
* DAO used to get idaction column names in tables that reference log_action rows.
|
||||
*
|
||||
* @var TableMetadata
|
||||
*/
|
||||
private $tableMetadataAccess;
|
||||
|
||||
/**
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* List of idaction columns in each table in $tablesWithIdActionColumns. idaction
|
||||
* columns are table columns with the string `"idaction"` in them.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $idactionColumns = null;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param TableMetadata $tableMetadataAccess
|
||||
* @param LoggerInterface $logger
|
||||
*/
|
||||
public function __construct(TableMetadata $tableMetadataAccess = null, LoggerInterface $logger = null)
|
||||
{
|
||||
$this->tableMetadataAccess = $tableMetadataAccess ?: new TableMetadata();
|
||||
$this->logger = $logger ?: StaticContainer::get('Psr\Log\LoggerInterface');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of all duplicate actions in the log_action table by name and the lowest action ID.
|
||||
* The duplicate actions are returned with each action.
|
||||
*
|
||||
* @return array Contains the following elements:
|
||||
*
|
||||
* * **name**: The action's name.
|
||||
* * **idaction**: The action's ID.
|
||||
* * **duplicateIdActions**: An array of duplicate action IDs.
|
||||
*/
|
||||
public function getDuplicateIdActions()
|
||||
{
|
||||
$sql = "SELECT name, COUNT(*) AS count, GROUP_CONCAT(idaction ORDER BY idaction ASC SEPARATOR ',') as idactions
|
||||
FROM " . Common::prefixTable('log_action') . "
|
||||
GROUP BY name, hash, type HAVING count > 1";
|
||||
|
||||
$result = array();
|
||||
foreach (Db::fetchAll($sql) as $row) {
|
||||
$dupeInfo = array('name' => $row['name']);
|
||||
|
||||
$idActions = explode(",", $row['idactions']);
|
||||
$dupeInfo['idaction'] = array_shift($idActions);
|
||||
$dupeInfo['duplicateIdActions'] = $idActions;
|
||||
|
||||
$result[] = $dupeInfo;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes one SQL statement that sets all idaction columns in a table to a single value, if the
|
||||
* values of those columns are in the specified set (`$duplicateIdActions`).
|
||||
*
|
||||
* Notes:
|
||||
*
|
||||
* The SQL will look like:
|
||||
*
|
||||
* UPDATE $table SET
|
||||
* col1 = IF((col1 IN ($duplicateIdActions)), $realIdAction, col1),
|
||||
* col2 = IF((col2 IN ($duplicateIdActions)), $realIdAction, col2),
|
||||
* ...
|
||||
* WHERE col1 IN ($duplicateIdActions) OR col2 IN ($duplicateIdActions) OR ...
|
||||
*
|
||||
* @param string $table
|
||||
* @param int $realIdAction The idaction to set column values to.
|
||||
* @param int[] $duplicateIdActions The idaction values that should be changed.
|
||||
*/
|
||||
public function fixDuplicateActionsInTable($table, $realIdAction, $duplicateIdActions)
|
||||
{
|
||||
$idactionColumns = $this->getIdActionTableColumnsFromMetadata();
|
||||
$idactionColumns = array_values($idactionColumns[$table]);
|
||||
$table = Common::prefixTable($table);
|
||||
|
||||
$inFromIdsExpression = $this->getInFromIdsExpression($duplicateIdActions);
|
||||
$setExpression = "%1\$s = IF(($inFromIdsExpression), $realIdAction, %1\$s)";
|
||||
|
||||
$sql = "UPDATE $table SET\n";
|
||||
foreach ($idactionColumns as $index => $column) {
|
||||
if ($index != 0) {
|
||||
$sql .= ",\n";
|
||||
}
|
||||
$sql .= sprintf($setExpression, $column);
|
||||
}
|
||||
$sql .= $this->getWhereToGetRowsUsingDuplicateActions($idactionColumns, $duplicateIdActions);
|
||||
|
||||
Db::query($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the server time and idsite of rows in a log table that reference at least one action
|
||||
* in a set.
|
||||
*
|
||||
* @param string $table
|
||||
* @param int[] $duplicateIdActions
|
||||
* @return array with two elements **idsite** and **server_time**. idsite is the site ID and server_time
|
||||
* is the date of the log.
|
||||
*/
|
||||
public function getSitesAndDatesOfRowsUsingDuplicates($table, $duplicateIdActions)
|
||||
{
|
||||
$idactionColumns = $this->getIdActionTableColumnsFromMetadata();
|
||||
$idactionColumns = array_values($idactionColumns[$table]);
|
||||
$table = Common::prefixTable($table);
|
||||
|
||||
$sql = "SELECT idsite, DATE(server_time) as server_time FROM $table ";
|
||||
$sql .= $this->getWhereToGetRowsUsingDuplicateActions($idactionColumns, $duplicateIdActions);
|
||||
return Db::fetchAll($sql);
|
||||
}
|
||||
|
||||
private function getIdActionTableColumnsFromMetadata()
|
||||
{
|
||||
if ($this->idactionColumns === null) {
|
||||
$this->idactionColumns = array();
|
||||
foreach (self::$tablesWithIdActionColumns as $table) {
|
||||
$columns = $this->tableMetadataAccess->getIdActionColumnNames(Common::prefixTable($table));
|
||||
|
||||
$this->logger->debug("Found following idactions in {table}: {columns}", array(
|
||||
'table' => $table,
|
||||
'columns' => implode(',', $columns)
|
||||
));
|
||||
|
||||
$this->idactionColumns[$table] = $columns;
|
||||
}
|
||||
}
|
||||
return $this->idactionColumns;
|
||||
}
|
||||
|
||||
private function getWhereToGetRowsUsingDuplicateActions($idactionColumns, $fromIdActions)
|
||||
{
|
||||
$sql = "WHERE ";
|
||||
foreach ($idactionColumns as $index => $column) {
|
||||
if ($index != 0) {
|
||||
$sql .= "OR ";
|
||||
}
|
||||
|
||||
$sql .= sprintf($this->getInFromIdsExpression($fromIdActions), $column) . " ";
|
||||
}
|
||||
return $sql;
|
||||
}
|
||||
|
||||
private function getInFromIdsExpression($fromIdActions)
|
||||
{
|
||||
return "%1\$s IN (" . implode(',', $fromIdActions) . ")";
|
||||
}
|
||||
}
|
||||
225
www/analytics/plugins/CoreAdminHome/OptOutManager.php
Normal file
225
www/analytics/plugins/CoreAdminHome/OptOutManager.php
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
<?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\CoreAdminHome;
|
||||
|
||||
use Piwik\Common;
|
||||
use Piwik\Nonce;
|
||||
use Piwik\Plugins\LanguagesManager\API as APILanguagesManager;
|
||||
use Piwik\Plugins\LanguagesManager\LanguagesManager;
|
||||
use Piwik\Plugins\PrivacyManager\DoNotTrackHeaderChecker;
|
||||
use Piwik\Tracker\IgnoreCookie;
|
||||
use Piwik\Url;
|
||||
use Piwik\View;
|
||||
|
||||
class OptOutManager
|
||||
{
|
||||
/** @var DoNotTrackHeaderChecker */
|
||||
private $doNotTrackHeaderChecker;
|
||||
|
||||
/** @var array */
|
||||
private $javascripts;
|
||||
|
||||
/** @var array */
|
||||
private $stylesheets;
|
||||
|
||||
/** @var string */
|
||||
private $title;
|
||||
|
||||
/** @var View|null */
|
||||
private $view;
|
||||
|
||||
/** @var array */
|
||||
private $queryParameters = array();
|
||||
|
||||
/**
|
||||
* @param DoNotTrackHeaderChecker $doNotTrackHeaderChecker
|
||||
*/
|
||||
public function __construct(DoNotTrackHeaderChecker $doNotTrackHeaderChecker = null)
|
||||
{
|
||||
$this->doNotTrackHeaderChecker = $doNotTrackHeaderChecker ?: new DoNotTrackHeaderChecker();
|
||||
|
||||
$this->javascripts = array(
|
||||
'inline' => array(),
|
||||
'external' => array(),
|
||||
);
|
||||
|
||||
$this->stylesheets = array(
|
||||
'inline' => array(),
|
||||
'external' => array(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a javascript file|code into the OptOut View
|
||||
* Note: This method will not escape the inline javascript code!
|
||||
*
|
||||
* @param string $javascript
|
||||
* @param bool $inline
|
||||
*/
|
||||
public function addJavascript($javascript, $inline = true)
|
||||
{
|
||||
$type = $inline ? 'inline' : 'external';
|
||||
$this->javascripts[$type][] = $javascript;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getJavascripts()
|
||||
{
|
||||
return $this->javascripts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a stylesheet file|code into the OptOut View
|
||||
* Note: This method will not escape the inline css code!
|
||||
*
|
||||
* @param string $stylesheet Escaped stylesheet
|
||||
* @param bool $inline
|
||||
*/
|
||||
public function addStylesheet($stylesheet, $inline = true)
|
||||
{
|
||||
$type = $inline ? 'inline' : 'external';
|
||||
$this->stylesheets[$type][] = $stylesheet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getStylesheets()
|
||||
{
|
||||
return $this->stylesheets;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle()
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $title
|
||||
*/
|
||||
public function setTitle($title)
|
||||
{
|
||||
$this->title = $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param string $value
|
||||
* @param bool $override
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function addQueryParameter($key, $value, $override = true)
|
||||
{
|
||||
if (!isset($this->queryParameters[$key]) || true === $override) {
|
||||
$this->queryParameters[$key] = $value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $items
|
||||
* @param bool|true $override
|
||||
*/
|
||||
public function addQueryParameters(array $items, $override = true)
|
||||
{
|
||||
foreach ($items as $key => $value) {
|
||||
$this->addQueryParameter($key, $value, $override);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $key
|
||||
*/
|
||||
public function removeQueryParameter($key)
|
||||
{
|
||||
unset($this->queryParameters[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getQueryParameters()
|
||||
{
|
||||
return $this->queryParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return View
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getOptOutView()
|
||||
{
|
||||
if ($this->view) {
|
||||
return $this->view;
|
||||
}
|
||||
|
||||
$trackVisits = !IgnoreCookie::isIgnoreCookieFound();
|
||||
$dntFound = $this->getDoNotTrackHeaderChecker()->isDoNotTrackFound();
|
||||
|
||||
$setCookieInNewWindow = Common::getRequestVar('setCookieInNewWindow', false, 'int');
|
||||
if ($setCookieInNewWindow) {
|
||||
$reloadUrl = Url::getCurrentQueryStringWithParametersModified(array(
|
||||
'showConfirmOnly' => 1,
|
||||
'setCookieInNewWindow' => 0,
|
||||
));
|
||||
} else {
|
||||
$reloadUrl = false;
|
||||
|
||||
$nonce = Common::getRequestVar('nonce', false);
|
||||
if ($nonce !== false && Nonce::verifyNonce('Piwik_OptOut', $nonce)) {
|
||||
Nonce::discardNonce('Piwik_OptOut');
|
||||
IgnoreCookie::setIgnoreCookie();
|
||||
$trackVisits = !$trackVisits;
|
||||
}
|
||||
}
|
||||
|
||||
$language = Common::getRequestVar('language', '');
|
||||
$lang = APILanguagesManager::getInstance()->isLanguageAvailable($language)
|
||||
? $language
|
||||
: LanguagesManager::getLanguageCodeForCurrentUser();
|
||||
|
||||
$this->addQueryParameters(array(
|
||||
'module' => 'CoreAdminHome',
|
||||
'action' => 'optOut',
|
||||
'language' => $lang,
|
||||
'setCookieInNewWindow' => 1
|
||||
), false);
|
||||
|
||||
$this->view = new View("@CoreAdminHome/optOut");
|
||||
$this->view->setXFrameOptions('allow');
|
||||
$this->view->dntFound = $dntFound;
|
||||
$this->view->trackVisits = $trackVisits;
|
||||
$this->view->nonce = Nonce::getNonce('Piwik_OptOut', 3600);
|
||||
$this->view->language = $lang;
|
||||
$this->view->showConfirmOnly = Common::getRequestVar('showConfirmOnly', false, 'int');
|
||||
$this->view->reloadUrl = $reloadUrl;
|
||||
$this->view->javascripts = $this->getJavascripts();
|
||||
$this->view->stylesheets = $this->getStylesheets();
|
||||
$this->view->title = $this->getTitle();
|
||||
$this->view->queryParameters = $this->getQueryParameters();
|
||||
|
||||
return $this->view;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DoNotTrackHeaderChecker
|
||||
*/
|
||||
protected function getDoNotTrackHeaderChecker()
|
||||
{
|
||||
return $this->doNotTrackHeaderChecker;
|
||||
}
|
||||
}
|
||||
146
www/analytics/plugins/CoreAdminHome/Tasks.php
Normal file
146
www/analytics/plugins/CoreAdminHome/Tasks.php
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
<?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\CoreAdminHome;
|
||||
|
||||
use Piwik\ArchiveProcessor\Rules;
|
||||
use Piwik\Archive\ArchivePurger;
|
||||
use Piwik\DataAccess\ArchiveTableCreator;
|
||||
use Piwik\Date;
|
||||
use Piwik\Db;
|
||||
use Piwik\Http;
|
||||
use Piwik\Option;
|
||||
use Piwik\Plugins\CoreAdminHome\Tasks\ArchivesToPurgeDistributedList;
|
||||
use Piwik\Tracker\Visit\ReferrerSpamFilter;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class Tasks extends \Piwik\Plugin\Tasks
|
||||
{
|
||||
/**
|
||||
* @var ArchivePurger
|
||||
*/
|
||||
private $archivePurger;
|
||||
|
||||
/**
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
public function __construct(ArchivePurger $archivePurger, LoggerInterface $logger)
|
||||
{
|
||||
$this->archivePurger = $archivePurger;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
public function schedule()
|
||||
{
|
||||
// general data purge on older archive tables, executed daily
|
||||
$this->daily('purgeOutdatedArchives', null, self::HIGH_PRIORITY);
|
||||
|
||||
// general data purge on invalidated archive records, executed daily
|
||||
$this->daily('purgeInvalidatedArchives', null, self::LOW_PRIORITY);
|
||||
|
||||
// lowest priority since tables should be optimized after they are modified
|
||||
$this->daily('optimizeArchiveTable', null, self::LOWEST_PRIORITY);
|
||||
|
||||
$this->weekly('updateSpammerBlacklist');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool `true` if the purge was executed, `false` if it was skipped.
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function purgeOutdatedArchives()
|
||||
{
|
||||
if ($this->willPurgingCausePotentialProblemInUI()) {
|
||||
$this->logger->info("Purging temporary archives: skipped (browser triggered archiving not enabled & not running after core:archive)");
|
||||
return false;
|
||||
}
|
||||
|
||||
$archiveTables = ArchiveTableCreator::getTablesArchivesInstalled();
|
||||
|
||||
$this->logger->info("Purging archives in {tableCount} archive tables.", array('tableCount' => count($archiveTables)));
|
||||
|
||||
// keep track of dates we purge for, since getTablesArchivesInstalled() will return numeric & blob
|
||||
// tables (so dates will appear two times, and we should only purge once per date)
|
||||
$datesPurged = array();
|
||||
|
||||
foreach ($archiveTables as $table) {
|
||||
$date = ArchiveTableCreator::getDateFromTableName($table);
|
||||
list($year, $month) = explode('_', $date);
|
||||
|
||||
// Somehow we may have archive tables created with older dates, prevent exception from being thrown
|
||||
if ($year > 1990) {
|
||||
if (empty($datesPurged[$date])) {
|
||||
$dateObj = Date::factory("$year-$month-15");
|
||||
|
||||
$this->archivePurger->purgeOutdatedArchives($dateObj);
|
||||
$this->archivePurger->purgeArchivesWithPeriodRange($dateObj);
|
||||
|
||||
$datesPurged[$date] = true;
|
||||
} else {
|
||||
$this->logger->debug("Date {date} already purged.", array('date' => $date));
|
||||
}
|
||||
} else {
|
||||
$this->logger->info("Skipping purging of archive tables *_{year}_{month}, year <= 1990.", array('year' => $year, 'month' => $month));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function purgeInvalidatedArchives()
|
||||
{
|
||||
$archivesToPurge = new ArchivesToPurgeDistributedList();
|
||||
foreach ($archivesToPurge->getAllAsDates() as $date) {
|
||||
$this->archivePurger->purgeInvalidatedArchivesFrom($date);
|
||||
|
||||
$archivesToPurge->removeDate($date);
|
||||
}
|
||||
}
|
||||
|
||||
public function optimizeArchiveTable()
|
||||
{
|
||||
$archiveTables = ArchiveTableCreator::getTablesArchivesInstalled();
|
||||
Db::optimizeTables($archiveTables);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the referrer spam blacklist
|
||||
*
|
||||
* @see https://github.com/piwik/referrer-spam-blacklist
|
||||
*/
|
||||
public function updateSpammerBlacklist()
|
||||
{
|
||||
$url = 'https://raw.githubusercontent.com/piwik/referrer-spam-blacklist/master/spammers.txt';
|
||||
$list = Http::sendHttpRequest($url, 30);
|
||||
$list = preg_split("/\r\n|\n|\r/", $list);
|
||||
if (count($list) < 10) {
|
||||
throw new \Exception(sprintf(
|
||||
'The spammers list downloaded from %s contains less than 10 entries, considering it a fail',
|
||||
$url
|
||||
));
|
||||
}
|
||||
|
||||
Option::set(ReferrerSpamFilter::OPTION_STORAGE_NAME, serialize($list));
|
||||
}
|
||||
|
||||
/**
|
||||
* we should only purge outdated & custom range archives if we know cron archiving has just run,
|
||||
* or if browser triggered archiving is enabled. if cron archiving has run, then we know the latest
|
||||
* archives are in the database, and we can remove temporary ones. if browser triggered archiving is
|
||||
* enabled, then we know any archives that are wrongly purged, can be re-archived on demand.
|
||||
* this prevents some situations where "no data" is displayed for reports that should have data.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function willPurgingCausePotentialProblemInUI()
|
||||
{
|
||||
return !Rules::isRequestAuthorizedToArchive();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
<?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\CoreAdminHome\Tasks;
|
||||
|
||||
use Piwik\Concurrency\DistributedList;
|
||||
use Piwik\Date;
|
||||
|
||||
/**
|
||||
* Distributed list that holds a list of year-month archive table identifiers (eg, 2015_01 or 2014_11). Each item in the
|
||||
* list is expected to identify a pair of archive tables that contain invalidated archives.
|
||||
*
|
||||
* The archiving purging scheduled task will read items in this list when executing the daily purge.
|
||||
*
|
||||
* This class is necessary in order to keep the archive purging scheduled task fast. W/o a way to keep track of
|
||||
* tables w/ invalid data, the task would have to iterate over every table, which is not desired for a task that
|
||||
* is executed daily.
|
||||
*
|
||||
* If users find other tables contain invalidated archives, they can use the core:purge-old-archive-data command
|
||||
* to manually purge them.
|
||||
*/
|
||||
class ArchivesToPurgeDistributedList extends DistributedList
|
||||
{
|
||||
const OPTION_INVALIDATED_DATES_SITES_TO_PURGE = 'InvalidatedOldReports_DatesWebsiteIds';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(self::OPTION_INVALIDATED_DATES_SITES_TO_PURGE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function setAll($yearMonths)
|
||||
{
|
||||
$yearMonths = array_unique($yearMonths, SORT_REGULAR);
|
||||
parent::setAll($yearMonths);
|
||||
}
|
||||
|
||||
protected function getListOptionValue()
|
||||
{
|
||||
$result = parent::getListOptionValue();
|
||||
$this->convertOldDistributedList($result);
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getAllAsDates()
|
||||
{
|
||||
$dates = array();
|
||||
foreach ($this->getAll() as $yearMonth) {
|
||||
try {
|
||||
$date = Date::factory(str_replace('_', '-', $yearMonth) . '-01');
|
||||
} catch (\Exception $ex) {
|
||||
continue; // invalid year month in distributed list
|
||||
}
|
||||
|
||||
$dates[] = $date;
|
||||
}
|
||||
return $dates;
|
||||
}
|
||||
|
||||
public function removeDate(Date $date)
|
||||
{
|
||||
$yearMonth = $date->toString('Y_m');
|
||||
$this->remove($yearMonth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Before 2.12.0 Piwik stored this list as an array mapping year months to arrays of site IDs. If this is
|
||||
* found in the DB, we convert the array to an array of year months to avoid errors and to make sure
|
||||
* the correct tables are still purged.
|
||||
*/
|
||||
private function convertOldDistributedList(&$yearMonths)
|
||||
{
|
||||
foreach ($yearMonths as $key => $value) {
|
||||
if (preg_match("/^[0-9]{4}_[0-9]{2}$/", $key)) {
|
||||
unset($yearMonths[$key]);
|
||||
|
||||
$yearMonths[] = $key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*!
|
||||
* Piwik - Web Analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
function sendGeneralSettingsAJAX() {
|
||||
var enableBrowserTriggerArchiving = $('input[name=enableBrowserTriggerArchiving]:checked').val();
|
||||
var enablePluginUpdateCommunication = $('input[name=enablePluginUpdateCommunication]:checked').val();
|
||||
var enableBetaReleaseCheck = $('input[name=enableBetaReleaseCheck]:checked').val();
|
||||
var releaseChannel = $('input[name=releaseChannel]:checked').val();
|
||||
var todayArchiveTimeToLive = $('#todayArchiveTimeToLive').val();
|
||||
|
||||
var trustedHosts = [];
|
||||
|
|
@ -22,7 +22,7 @@ function sendGeneralSettingsAJAX() {
|
|||
format: 'json',
|
||||
enableBrowserTriggerArchiving: enableBrowserTriggerArchiving,
|
||||
enablePluginUpdateCommunication: enablePluginUpdateCommunication,
|
||||
enableBetaReleaseCheck: enableBetaReleaseCheck,
|
||||
releaseChannel: releaseChannel,
|
||||
todayArchiveTimeToLive: todayArchiveTimeToLive,
|
||||
mailUseSmtp: isSmtpEnabled(),
|
||||
mailPort: $('#mailPort').val(),
|
||||
|
|
@ -55,10 +55,14 @@ function isCustomLogoEnabled() {
|
|||
}
|
||||
|
||||
function refreshCustomLogo() {
|
||||
var imageDiv = $("#currentLogo");
|
||||
if (imageDiv && imageDiv.attr("src")) {
|
||||
var logoUrl = imageDiv.attr("src").split("?")[0];
|
||||
imageDiv.attr("src", logoUrl + "?" + (new Date()).getTime());
|
||||
var selectors = ['#currentLogo', '#currentFavicon'];
|
||||
var index;
|
||||
for (index = 0; index < selectors.length; index++) {
|
||||
var imageDiv = $(selectors[index]);
|
||||
if (imageDiv && imageDiv.attr("src")) {
|
||||
var logoUrl = imageDiv.attr("src").split("?")[0];
|
||||
imageDiv.attr("src", logoUrl + "?" + (new Date()).getTime());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -67,7 +71,7 @@ $(document).ready(function () {
|
|||
|
||||
showSmtpSettings(isSmtpEnabled());
|
||||
showCustomLogoSettings(isCustomLogoEnabled());
|
||||
$('#generalSettingsSubmit').click(function () {
|
||||
$('.generalSettingsSubmit').click(function () {
|
||||
var doSubmit = function () {
|
||||
sendGeneralSettingsAJAX();
|
||||
};
|
||||
|
|
@ -102,27 +106,41 @@ $(document).ready(function () {
|
|||
$('input').keypress(function (e) {
|
||||
var key = e.keyCode || e.which;
|
||||
if (key == 13) {
|
||||
$('#generalSettingsSubmit').click();
|
||||
$('.generalSettingsSubmit').click();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
$("#logoUploadForm").submit(function (data) {
|
||||
var submittingForm = $(this);
|
||||
$('.uploaderror').fadeOut();
|
||||
var frameName = "upload" + (new Date()).getTime();
|
||||
var uploadFrame = $("<iframe name=\"" + frameName + "\" />");
|
||||
uploadFrame.css("display", "none");
|
||||
uploadFrame.load(function (data) {
|
||||
setTimeout(function () {
|
||||
refreshCustomLogo();
|
||||
uploadFrame.remove();
|
||||
|
||||
var frameContent = $(uploadFrame.contents()).find('body').html();
|
||||
frameContent = $.trim(frameContent);
|
||||
|
||||
if ('0' === frameContent) {
|
||||
$('.uploaderror').show();
|
||||
}
|
||||
|
||||
if ('1' === frameContent || '0' === frameContent) {
|
||||
uploadFrame.remove();
|
||||
}
|
||||
}, 1000);
|
||||
});
|
||||
$("body:first").append(uploadFrame);
|
||||
submittingForm.attr("target", frameName);
|
||||
});
|
||||
|
||||
$('#customLogo').change(function () {$("#logoUploadForm").submit()});
|
||||
$('#customLogo,#customFavicon').change(function () {
|
||||
$("#logoUploadForm").submit();
|
||||
$(this).val('');
|
||||
});
|
||||
|
||||
// trusted hosts event handling
|
||||
var trustedHostSettings = $('#trustedHostSettings');
|
||||
|
|
@ -136,7 +154,8 @@ $(document).ready(function () {
|
|||
|
||||
// append new row to the table
|
||||
trustedHostSettings.find('ul').append(trustedHostSettings.find('li:last').clone());
|
||||
trustedHostSettings.find('li:last input').val('');
|
||||
trustedHostSettings.find('li:last input').val('').focus();
|
||||
return false;
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*!
|
||||
* Piwik - Web Analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
/**
|
||||
* This class is deprecated. Use server-side events instead.
|
||||
*
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
var TrackingCodeGenerator = function () {
|
||||
|
|
@ -150,7 +150,7 @@
|
|||
|
||||
// function that generates JS code
|
||||
var generateJsCodeAjax = null,
|
||||
generateJsCode = function () {
|
||||
generateJsCode = function (trackingCodeChangedManually) {
|
||||
// get params used to generate JS code
|
||||
var params = {
|
||||
piwikUrl: piwikHost + piwikPath,
|
||||
|
|
@ -162,6 +162,7 @@
|
|||
customCampaignNameQueryParam: null,
|
||||
customCampaignKeywordParam: null,
|
||||
doNotTrack: $('#javascript-tracking-do-not-track').is(':checked') ? 1 : 0,
|
||||
disableCookies: $('#javascript-tracking-disable-cookies').is(':checked') ? 1 : 0
|
||||
};
|
||||
|
||||
if ($('#custom-campaign-query-params-check').is(':checked')) {
|
||||
|
|
@ -184,24 +185,30 @@
|
|||
generateJsCodeAjax.setCallback(function (response) {
|
||||
generateJsCodeAjax = null;
|
||||
|
||||
$('#javascript-text').find('textarea').val(response.value);
|
||||
var jsCodeTextarea = $('#javascript-text').find('textarea');
|
||||
jsCodeTextarea.val(response.value);
|
||||
|
||||
if(trackingCodeChangedManually) {
|
||||
jsCodeTextarea.effect("highlight", {}, 1500);
|
||||
}
|
||||
|
||||
});
|
||||
generateJsCodeAjax.send();
|
||||
};
|
||||
|
||||
// function that generates image tracker link
|
||||
var generateImageTrackingAjax = null,
|
||||
generateImageTrackerLink = function () {
|
||||
generateImageTrackerLink = function (trackingCodeChangedManually) {
|
||||
// get data used to generate the link
|
||||
var generateDataParams = {
|
||||
piwikUrl: piwikHost + piwikPath,
|
||||
actionName: $('#image-tracker-action-name').val(),
|
||||
actionName: $('#image-tracker-action-name').val()
|
||||
};
|
||||
|
||||
if ($('#image-tracking-goal-check').is(':checked')) {
|
||||
generateDataParams.idGoal = $('#image-tracker-goal').val();
|
||||
if (generateDataParams.idGoal) {
|
||||
generateDataParams.revenue = $('#image-tracker-advanced-options').find('.revenue').val();
|
||||
generateDataParams.revenue = $('#image-goal-picker-extra').find('.revenue').val();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -220,7 +227,12 @@
|
|||
generateImageTrackingAjax.setCallback(function (response) {
|
||||
generateImageTrackingAjax = null;
|
||||
|
||||
$('#image-tracking-text').find('textarea').val(response.value);
|
||||
var jsCodeTextarea = $('#image-tracking-text').find('textarea');
|
||||
jsCodeTextarea.val(response.value);
|
||||
|
||||
if(trackingCodeChangedManually) {
|
||||
jsCodeTextarea.effect("highlight", {}, 1500);
|
||||
}
|
||||
});
|
||||
generateImageTrackingAjax.send();
|
||||
};
|
||||
|
|
@ -229,7 +241,7 @@
|
|||
$('#image-tracker-website').bind('change', function (e, site) {
|
||||
getSiteData(site.id, '#image-tracking-code-options', function () {
|
||||
resetGoalSelectItems(site.id, 'image-tracker-goal');
|
||||
generateImageTrackerLink();
|
||||
generateImageTrackerLink(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -249,7 +261,7 @@
|
|||
$('.current-site-alias').text(siteUrls[site.id][1] || defaultAliasUrl);
|
||||
|
||||
resetGoalSelectItems(site.id, 'js-tracker-goal');
|
||||
generateJsCode();
|
||||
generateJsCode(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -259,9 +271,7 @@
|
|||
e.preventDefault();
|
||||
|
||||
var newRow = '<tr>\
|
||||
<td> </td>\
|
||||
<td><input type="textbox" class="custom-variable-name"/></td>\
|
||||
<td> </td>\
|
||||
<td><input type="textbox" class="custom-variable-value"/></td>\
|
||||
</tr>',
|
||||
row = $(this).closest('tr');
|
||||
|
|
@ -279,20 +289,20 @@
|
|||
|
||||
// when any input in the JS tracking options section changes, regenerate JS code
|
||||
$('#optional-js-tracking-options').on('change', 'input', function () {
|
||||
generateJsCode();
|
||||
generateJsCode(true);
|
||||
});
|
||||
|
||||
// when any input/select in the image tracking options section changes, regenerate
|
||||
// image tracker link
|
||||
$('#image-tracking-section').on('change', 'input,select', function () {
|
||||
generateImageTrackerLink();
|
||||
generateImageTrackerLink(true);
|
||||
});
|
||||
|
||||
// on click generated code textareas, select the text so it can be easily copied
|
||||
$('#javascript-text>textarea,#image-tracking-text>textarea').click(function () {
|
||||
$(this).select();
|
||||
});
|
||||
|
||||
|
||||
// initial generation
|
||||
getSiteData(
|
||||
$('#js-tracker-website').attr('siteid'),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*!
|
||||
* Piwik - Web Analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -17,6 +17,8 @@ $(document).ready(function () {
|
|||
|
||||
function updatePluginSettings()
|
||||
{
|
||||
$submit.prop('disabled', true);
|
||||
|
||||
var $nonce = $('[name="setpluginsettingsnonce"]');
|
||||
var nonceValue = '';
|
||||
|
||||
|
|
@ -34,7 +36,10 @@ $(document).ready(function () {
|
|||
ajaxHandler.redirectOnSuccess();
|
||||
ajaxHandler.setLoadingElement(getLoadingElement());
|
||||
ajaxHandler.setErrorElement(getErrorElement());
|
||||
ajaxHandler.send(true);
|
||||
ajaxHandler.setCompleteCallback(function () {
|
||||
$submit.prop('disabled', false);
|
||||
});
|
||||
ajaxHandler.send();
|
||||
}
|
||||
|
||||
function getSettings()
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue