dao = $dao ?: new RawLogDao();
}
protected function configure()
{
$this->setName('usercountry:attribute');
$this->setDescription("Re-attribute existing log data (visits & conversions) with geolocated location data, using the specified or configured location provider.");
$this->addArgument(self::DATES_RANGE_ARGUMENT, InputArgument::REQUIRED, 'Attribute visits in this date range. Eg, 2012-01-01,2013-01-01');
$this->addOption(self::PERCENT_STEP_ARGUMENT, null, InputArgument::OPTIONAL,
'How often to display the command progress. A status update will be printed after N percent of visits are processed, '
. 'where N is the value of this option.', self::PERCENT_STEP_ARGUMENT_DEFAULT);
$this->addOption(self::PROVIDER_ARGUMENT, null, InputOption::VALUE_REQUIRED, 'Provider id which should be used to attribute visits. If empty then'
. ' Piwik will use the currently configured provider. If no provider is configured, the default provider is used.');
$this->addOption(self::SEGMENT_LIMIT_OPTION, null, InputOption::VALUE_OPTIONAL, 'Number of visits to process at a time.', self::SEGMENT_LIMIT_OPTION_DEFAULT);
$this->addOption(self::FORCE_OPTION, null, InputOption::VALUE_NONE, "Force geolocation, even if the requested provider does not appear to work. It is not "
. "recommended to use this option.");
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return void|int
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
list($from, $to) = $this->getDateRangeToAttribute($input);
$this->visitorGeolocator = $this->createGeolocator($output, $input);
$this->percentStep = $this->getPercentStep($input);
$this->amountOfVisits = $this->dao->countVisitsWithDatesLimit($from, $to);
$output->writeln(
sprintf('Re-attribution for date range: %s to %s. %d visits to process with provider "%s".',
$from, $to, $this->amountOfVisits, $this->visitorGeolocator->getProvider()->getId())
);
$this->timer = new Timer();
$this->processSpecifiedLogsInChunks($output, $from, $to, $input->getOption(self::SEGMENT_LIMIT_OPTION));
$output->writeln("Completed. " . $this->timer->__toString() . "");
return 0;
}
protected function processSpecifiedLogsInChunks(OutputInterface $output, $from, $to, $segmentLimit)
{
$self = $this;
$this->visitorGeolocator->reattributeVisitLogs($from, $to, $idSite = null, $segmentLimit, function () use ($output, $self) {
$self->onVisitProcessed($output);
});
}
/**
* @param InputInterface $input
* @return int
*/
protected function getPercentStep(InputInterface $input)
{
$percentStep = $input->getOption(self::PERCENT_STEP_ARGUMENT);
if (!is_numeric($percentStep)) {
return self::PERCENT_STEP_ARGUMENT_DEFAULT;
}
// Percent step should be between maximum percent value and minimum percent value (1-100)
if ($percentStep > 99 || $percentStep < 1) {
return 100;
}
return $percentStep;
}
/**
* Print information about progress.
* @param OutputInterface $output
*/
public function onVisitProcessed(OutputInterface $output)
{
++$this->processed;
$percent = ceil($this->processed / $this->amountOfVisits * 100);
if ($percent > $this->processedPercent
&& $percent % $this->percentStep === 0
) {
$output->writeln(sprintf('%d%% processed. %s', $percent, $this->timer->__toString()));
$this->processedPercent = $percent;
}
}
private function getDateRangeToAttribute(InputInterface $input)
{
$dateRangeString = $input->getArgument(self::DATES_RANGE_ARGUMENT);
$dates = explode(',', $dateRangeString);
$dates = array_map(array('Piwik\Date', 'factory'), $dates);
if (count($dates) != 2) {
throw new \InvalidArgumentException('Invalid date range supplied: ' . $dateRangeString);
}
return $dates;
}
private function createGeolocator(OutputInterface $output, InputInterface $input)
{
$providerId = $input->getOption(self::PROVIDER_ARGUMENT);
$geolocator = new VisitorGeolocator(LocationProvider::getProviderById($providerId) ?: null);
$usedProvider = $geolocator->getProvider();
if (!$usedProvider->isAvailable()) {
throw new \InvalidArgumentException("The provider '$providerId' is not currently available, please make sure it is configured correctly.");
}
$isWorkingOrErrorMessage = $usedProvider->isWorking();
if ($isWorkingOrErrorMessage !== true) {
$errorMessage = "The provider '$providerId' does not appear to be working correctly. Details: $isWorkingOrErrorMessage";
$forceGeolocation = $input->getOption(self::FORCE_OPTION);
if ($forceGeolocation) {
$output->writeln("$errorMessage");
$output->writeln("Ignoring location provider issue, geolocating anyway due to --force option.");
} else {
throw new \InvalidArgumentException($errorMessage);
}
}
return $geolocator;
}
}