displayHelp(); $this->initPiwikHost(); $this->initLog(); $this->initCore(); $this->initTokenAuth(); $this->initCheckCli(); $this->initStateFromParameters(); Piwik::setUserHasSuperUserAccess(true); $this->logInitInfo(); $this->checkPiwikUrlIsValid(); $this->logArchiveTimeoutInfo(); $this->segments = $this->initSegmentsToArchive(); $this->allWebsites = APISitesManager::getInstance()->getAllSitesId(); $websitesIds = $this->initWebsiteIds(); $this->filterWebsiteIds($websitesIds); $this->websites = $websitesIds; if($this->shouldStartProfiler) { \Piwik\Profiler::setupProfilerXHProf($mainRun = true); $this->log("XHProf profiling is enabled."); } } public function runScheduledTasksInTrackerMode() { $this->initPiwikHost(); $this->initLog(); $this->initCore(); $this->initTokenAuth(); $this->logInitInfo(); $this->checkPiwikUrlIsValid(); $this->runScheduledTasks(); } /** * Main function, runs archiving on all websites with new activity */ public function run() { $websitesWithVisitsSinceLastRun = $skippedPeriodsArchivesWebsite = $skippedDayArchivesWebsites = $skipped = $processed = $archivedPeriodsArchivesWebsite = 0; $timer = new Timer; $this->logSection("START"); foreach ($this->websites as $idsite) { flush(); $requestsBefore = $this->requests; if ($idsite <= 0) { continue; } $skipWebsiteForced = in_array($idsite, $this->shouldSkipSpecifiedSites); if($skipWebsiteForced) { $this->log("Skipped website id $idsite, found in --skip-idsites "); $skipped++; continue; } $timerWebsite = new Timer; $lastTimestampWebsiteProcessedPeriods = $lastTimestampWebsiteProcessedDay = false; if ($this->archiveAndRespectTTL) { $lastTimestampWebsiteProcessedPeriods = Option::get($this->lastRunKey($idsite, "periods")); $lastTimestampWebsiteProcessedDay = Option::get($this->lastRunKey($idsite, "day")); } // For period other than days, we only re-process the reports at most // 1) every $processPeriodsMaximumEverySeconds $secondsSinceLastExecution = time() - $lastTimestampWebsiteProcessedPeriods; // if timeout is more than 10 min, we account for a 5 min processing time, and allow trigger 1 min earlier if ($this->processPeriodsMaximumEverySeconds > 10 * 60) { $secondsSinceLastExecution += 5 * 60; } $shouldArchivePeriods = $secondsSinceLastExecution > $this->processPeriodsMaximumEverySeconds; if (empty($lastTimestampWebsiteProcessedPeriods)) { // 2) OR always if script never executed for this website before $shouldArchivePeriods = true; } // (*) If the website is archived because it is a new day in its timezone // We make sure all periods are archived, even if there is 0 visit today $dayHasEndedMustReprocess = in_array($idsite, $this->websiteDayHasFinishedSinceLastRun); if ($dayHasEndedMustReprocess) { $shouldArchivePeriods = true; } // (*) If there was some old reports invalidated for this website // we make sure all these old reports are triggered at least once $websiteIsOldDataInvalidate = in_array($idsite, $this->idSitesInvalidatedOldReports); if ($websiteIsOldDataInvalidate) { $shouldArchivePeriods = true; } $websiteIdIsForced = in_array($idsite, $this->shouldArchiveSpecifiedSites); if($websiteIdIsForced) { $shouldArchivePeriods = true; } // Test if we should process this website at all $elapsedSinceLastArchiving = time() - $lastTimestampWebsiteProcessedDay; // Skip this day archive if last archive was older than TTL $existingArchiveIsValid = ($elapsedSinceLastArchiving < $this->todayArchiveTimeToLive); $skipDayArchive = $existingArchiveIsValid; // Invalidate old website forces the archiving for this site $skipDayArchive = $skipDayArchive && !$websiteIsOldDataInvalidate; // Also reprocess when day has ended since last run if($dayHasEndedMustReprocess && !$existingArchiveIsValid) { $skipDayArchive = false; } if($websiteIdIsForced) { $skipDayArchive = false; } if ($skipDayArchive) { $this->log("Skipped website id $idsite, already processed today's report in recent run, " . \Piwik\MetricsFormatter::getPrettyTimeFromSeconds($elapsedSinceLastArchiving, true, $isHtml = false) . " ago, " . $timerWebsite->__toString()); $skippedDayArchivesWebsites++; $skipped++; continue; } // Fake that the request is already done, so that other archive.php // running do not grab the same website from the queue Option::set($this->lastRunKey($idsite, "day"), time()); // when some data was purged from this website // we make sure we query all previous days/weeks/months $processDaysSince = $lastTimestampWebsiteProcessedDay; if($websiteIsOldDataInvalidate // when --force-all-websites option, // also forces to archive last52 days to be safe || $this->shouldArchiveAllSites) { $processDaysSince = false; } $url = $this->getVisitsRequestUrl($idsite, "day", $processDaysSince); $content = $this->request($url); $response = @unserialize($content); if (empty($content) || !is_array($response) || count($response) == 0 ) { // cancel the succesful run flag Option::set($this->lastRunKey($idsite, "day"), 0); $this->log("WARNING: Empty or invalid response '$content' for website id $idsite, " . $timerWebsite->__toString() . ", skipping"); $skipped++; continue; } $visitsToday = end($response); if(empty($visitsToday)) { $visitsToday = 0; } $this->requests++; $processed++; // If there is no visit today and we don't need to process this website, we can skip remaining archives if ($visitsToday == 0 && !$shouldArchivePeriods ) { $this->log("Skipped website id $idsite, no visit today, " . $timerWebsite->__toString()); $skipped++; continue; } $visitsAllDays = array_sum($response); if ($visitsAllDays == 0 && !$shouldArchivePeriods && $this->shouldArchiveAllSites ) { $this->log("Skipped website id $idsite, no visits in the last " . count($response) . " days, " . $timerWebsite->__toString()); $skipped++; continue; } $this->visits += $visitsToday; $websitesWithVisitsSinceLastRun++; $this->archiveVisitsAndSegments($idsite, "day", $lastTimestampWebsiteProcessedDay, $timerWebsite); if (!$shouldArchivePeriods) { $this->log("Skipped website id $idsite, already processed period reports in recent run, " . \Piwik\MetricsFormatter::getPrettyTimeFromSeconds($elapsedSinceLastArchiving, true, $isHtml = false) . " ago, " . $timerWebsite->__toString()); $skippedDayArchivesWebsites++; $skipped++; continue; } $success = true; foreach (array('week', 'month', 'year') as $period) { $success = $this->archiveVisitsAndSegments($idsite, $period, $lastTimestampWebsiteProcessedPeriods) && $success; } // Record succesful run of this website's periods archiving if ($success) { Option::set($this->lastRunKey($idsite, "periods"), time()); // Remove this website from the list of websites to be invalidated // since it's now just been re-processing the reports, job is done! if ($websiteIsOldDataInvalidate) { $this->setSiteIsArchived($idsite); } } $archivedPeriodsArchivesWebsite++; $requestsWebsite = $this->requests - $requestsBefore; $debug = $this->shouldArchiveAllSites ? ", last days = $visitsAllDays visits" : ""; Log::info("Archived website id = $idsite, today = $visitsToday visits" . $debug . ", $requestsWebsite API requests, " . $timerWebsite->__toString() . " [" . ($websitesWithVisitsSinceLastRun + $skipped) . "/" . count($this->websites) . " done]"); } $this->log("Starting Piwik reports archiving..."); $this->log("Done archiving!"); $this->logSection("SUMMARY"); $this->log("Total daily visits archived: " . $this->visits); $totalWebsites = count($this->allWebsites); $skipped = $totalWebsites - $websitesWithVisitsSinceLastRun; $this->log("Archived today's reports for $websitesWithVisitsSinceLastRun websites"); $this->log("Archived week/month/year for $archivedPeriodsArchivesWebsite websites"); $this->log("Skipped $skipped websites: no new visit since the last script execution"); $this->log("Skipped $skippedDayArchivesWebsites websites day archiving: existing daily reports are less than {$this->todayArchiveTimeToLive} seconds old"); $this->log("Skipped $skippedPeriodsArchivesWebsite websites week/month/year archiving: existing periods reports are less than {$this->processPeriodsMaximumEverySeconds} seconds old"); $this->log("Total API requests: $this->requests"); //DONE: done/total, visits, wtoday, wperiods, reqs, time, errors[count]: first eg. $percent = count($this->websites) == 0 ? "" : " " . round($processed * 100 / count($this->websites), 0) . "%"; $this->log("done: " . $processed . "/" . count($this->websites) . "" . $percent . ", " . $this->visits . " v, $websitesWithVisitsSinceLastRun wtoday, $archivedPeriodsArchivesWebsite wperiods, " . $this->requests . " req, " . round($timer->getTimeMs()) . " ms, " . (empty($this->errors) ? "no error" : (count($this->errors) . " errors. eg. '" . reset($this->errors) . "'")) ); $this->log($timer->__toString()); } /** * End of the script */ public function end() { // How to test the error handling code? // - Generate some hits since last archive.php run // - Start the script, in the middle, shutdown apache, then restore // Some errors should be logged and script should successfully finish and then report the errors and trigger a PHP error if (!empty($this->errors)) { $this->logSection("SUMMARY OF ERRORS"); foreach ($this->errors as $error) { $this->log("Error: " . $error); } $summary = count($this->errors) . " total errors during this script execution, please investigate and try and fix these errors"; $this->log($summary); $summary .= '. First error was: ' . reset($this->errors); $this->logFatalError($summary); } else { // No error -> Logs the successful script execution until completion Option::set(self::OPTION_ARCHIVING_FINISHED_TS, time()); } } public function logFatalError($m, $backtrace = true) { $this->logError($m); $fe = fopen('php://stderr', 'w'); fwrite($fe, "Error in the last Piwik archive.php run: \n" . $m . "\n" . ($backtrace ? "\n\n Here is the full errors output:\n\n" . $this->output : '') ); exit(1); } public function runScheduledTasks() { $this->logSection("SCHEDULED TASKS"); if($this->isParameterSet('--disable-scheduled-tasks')) { $this->log("Scheduled tasks are disabled with --disable-scheduled-tasks"); return; } $this->log("Starting Scheduled tasks... "); $tasksOutput = $this->request("?module=API&method=CoreAdminHome.runScheduledTasks&format=csv&convertToUnicode=0&token_auth=" . $this->token_auth); if ($tasksOutput == \Piwik\DataTable\Renderer\Csv::NO_DATA_AVAILABLE) { $tasksOutput = " No task to run"; } $this->log($tasksOutput); $this->log("done"); $this->logSection(""); } /** * Checks the config file is found. * * @param $piwikUrl * @throws Exception */ protected function initConfigObject($piwikUrl) { // HOST is required for the Config object $parsed = parse_url($piwikUrl); Url::setHost($parsed['host']); Config::getInstance()->clear(); try { Config::getInstance()->checkLocalConfigFound(); } catch (Exception $e) { throw new Exception("The configuration file for Piwik could not be found. " . "Please check that config/config.ini.php is readable by the user " . get_current_user()); } } /** * Returns base URL to process reports for the $idsite on a given $period */ private function getVisitsRequestUrl($idsite, $period, $lastTimestampWebsiteProcessed = false) { $dateLastMax = self::DEFAULT_DATE_LAST; if($period=='year') { $dateLastMax = self::DEFAULT_DATE_LAST_YEARS; } elseif($period == 'week') { $dateLastMax = self::DEFAULT_DATE_LAST_WEEKS; } if (empty($lastTimestampWebsiteProcessed)) { $lastTimestampWebsiteProcessed = strtotime( \Piwik\Site::getCreationDateFor($idsite) ); } // Enforcing last2 at minimum to work around timing issues and ensure we make most archives available $dateLast = floor((time() - $lastTimestampWebsiteProcessed) / 86400) + 2; if ($dateLast > $dateLastMax) { $dateLast = $dateLastMax; } $dateLastForced = $this->isParameterSet('--force-date-last-n', true); if(!empty($dateLastForced)){ $dateLast = $dateLastForced; } return "?module=API&method=VisitsSummary.getVisits&idSite=$idsite&period=$period&date=last" . $dateLast . "&format=php&token_auth=" . $this->token_auth; } private function initSegmentsToArchive() { $segments = APICoreAdminHome::getInstance()->getKnownSegmentsToArchive(); if (empty($segments)) { return array(); } $this->log("- Will pre-process " . count($segments) . " Segments for each website and each period: " . implode(", ", $segments)); return $segments; } private function getSegmentsForSite($idsite) { $segmentsAllSites = $this->segments; $segmentsThisSite = \Piwik\SettingsPiwik::getKnownSegmentsToArchiveForSite($idsite); if (!empty($segmentsThisSite)) { $this->log("Will pre-process the following " . count($segmentsThisSite) . " Segments for this website (id = $idsite): " . implode(", ", $segmentsThisSite)); } $segments = array_unique(array_merge($segmentsAllSites, $segmentsThisSite)); return $segments; } /** * Will trigger API requests for the specified Website $idsite, * for the specified $period, for all segments that are pre-processed for this website. * Requests are triggered using cURL multi handle * * @param $idsite int * @param $period * @param $lastTimestampWebsiteProcessed * @param Timer $timerWebsite * @return bool True on success, false if some request failed */ private function archiveVisitsAndSegments($idsite, $period, $lastTimestampWebsiteProcessed, Timer $timerWebsite = null) { $timer = new Timer; $aCurl = array(); $mh = false; $url = $this->piwikUrl; $url .= $this->getVisitsRequestUrl($idsite, $period, $lastTimestampWebsiteProcessed); $url .= self::APPEND_TO_API_REQUEST; // already processed above for "day" if ($period != "day") { $ch = $this->getNewCurlHandle($url); $this->addCurlHandleToMulti($mh, $ch); $aCurl[$url] = $ch; $this->requests++; } $urlNoSegment = $url; foreach ($this->getSegmentsForSite($idsite) as $segment) { $segmentUrl = $url . '&segment=' . urlencode($segment); $ch = $this->getNewCurlHandle($segmentUrl); $this->addCurlHandleToMulti($mh, $ch); $aCurl[$segmentUrl] = $ch; $this->requests++; } $success = true; $visitsAllDaysInPeriod = false; if (!empty($aCurl)) { $running = null; do { usleep(1000); curl_multi_exec($mh, $running); } while ($running > 0); foreach ($aCurl as $url => $ch) { $content = curl_multi_getcontent($ch); $successResponse = $this->checkResponse($content, $url); $success = $successResponse && $success; if ($url == $urlNoSegment && $successResponse ) { $stats = @unserialize($content); if (!is_array($stats)) { $this->logError("Error unserializing the following response from $url: " . $content); } $visitsAllDaysInPeriod = @array_sum($stats); } } foreach ($aCurl as $ch) { curl_multi_remove_handle($mh, $ch); } curl_multi_close($mh); } $this->log("Archived website id = $idsite, period = $period, " . ($period != "day" ? (int)$visitsAllDaysInPeriod . " visits, " : "") . (!empty($timerWebsite) ? $timerWebsite->__toString() : $timer->__toString())); return $success; } private function addCurlHandleToMulti(&$mh, $ch) { if (!$mh) { $mh = curl_multi_init(); } curl_multi_add_handle($mh, $ch); } private function getNewCurlHandle($url) { $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); if ($this->acceptInvalidSSLCertificate) { curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); } curl_setopt($ch, CURLOPT_USERAGENT, Http::getUserAgent()); Http::configCurlCertificate($ch); return $ch; } /** * Logs a section in the output */ private function logSection($title = "") { $this->log("---------------------------"); if(!empty($title)) { $this->log($title); } } private function log($m) { $this->output .= $m . "\n"; try { Log::info($m); } catch(Exception $e) { print($m . "\n"); } } /** * Issues a request to $url */ private function request($url) { $url = $this->piwikUrl . $url . self::APPEND_TO_API_REQUEST; if($this->shouldStartProfiler) { $url .= "&xhprof=2"; } //$this->log($url); try { $response = Http::sendHttpRequestBy('curl', $url, $timeout = 300, $userAgent = null, $destinationPath = null, $file = null, $followDepth = 0, $acceptLanguage = false, $acceptInvalidSSLCertificate = $this->acceptInvalidSSLCertificate); } catch (Exception $e) { return $this->logNetworkError($url, $e->getMessage()); } if ($this->checkResponse($response, $url)) { return $response; } return false; } private function checkResponse($response, $url) { if (empty($response) || stripos($response, 'error') ) { return $this->logNetworkError($url, $response); } return true; } private function logError($m) { if (!defined('PIWIK_ARCHIVE_NO_TRUNCATE')) { $m = substr($m, 0, self::TRUNCATE_ERROR_MESSAGE_SUMMARY); } $this->errors[] = $m; $this->log("ERROR: $m"); } private function logNetworkError($url, $response) { $message = "Got invalid response from API request: $url. "; if (empty($response)) { $message .= "The response was empty. This usually means a server error. This solution to this error is generally to increase the value of 'memory_limit' in your php.ini file. Please check your Web server Error Log file for more details."; } else { $message .= "Response was '$response'"; } $this->logError($message); return false; } /** * Displays script usage */ private function usage() { echo self::getUsage(); } /** * Configures Piwik\Log so messages are written in output */ private function initLog() { $config = Config::getInstance(); $config->log['log_only_when_debug_parameter'] = 0; $config->log[\Piwik\Log::LOG_WRITERS_CONFIG_OPTION] = array("screen"); $config->log[\Piwik\Log::LOG_LEVEL_CONFIG_OPTION] = 'VERBOSE'; if (!function_exists("curl_multi_init")) { $this->log("ERROR: this script requires curl extension php_curl enabled in your CLI php.ini"); $this->usage(); exit; } } /** * Script does run on http:// ONLY if the SU token is specified */ private function initCheckCli() { if (Common::isPhpCliMode()) { return; } $token_auth = Common::getRequestVar('token_auth', '', 'string'); if ($token_auth != $this->token_auth || strlen($token_auth) != 32 ) { die('You must specify the Super User token_auth as a parameter to this script, eg. ?token_auth=XYZ if you wish to run this script through the browser.
However it is recommended to run it via cron in the command line, since it can take a long time to run.
In a shell, execute for example the following to trigger archiving on the local Piwik server:
$ /path/to/php /path/to/piwik/misc/cron/archive.php --url=http://your-website.org/path/to/piwik/'); } } /** * Init Piwik, connect DB, create log & config objects, etc. */ private function initCore() { try { FrontController::getInstance()->init(); } catch (Exception $e) { echo "ERROR: During Piwik init, Message: " . $e->getMessage(); //echo $e->getTraceAsString(); exit(1); } } private function displayHelp() { $displayHelp = $this->isParameterSet('help') || $this->isParameterSet('h'); if ($displayHelp) { $this->usage(); exit; } } /** * Initializes the various parameters to the script, based on input parameters. * */ private function initStateFromParameters() { $this->todayArchiveTimeToLive = Rules::getTodayArchiveTimeToLive(); $this->acceptInvalidSSLCertificate = $this->isParameterSet("accept-invalid-ssl-certificate"); $this->processPeriodsMaximumEverySeconds = $this->getDelayBetweenPeriodsArchives(); $this->shouldArchiveAllSites = (bool) $this->isParameterSet("force-all-websites"); $this->shouldStartProfiler = (bool) $this->isParameterSet("xhprof"); $restrictToIdSites = $this->isParameterSet("force-idsites", true); $skipIdSites = $this->isParameterSet("skip-idsites", true); $this->shouldArchiveSpecifiedSites = \Piwik\Site::getIdSitesFromIdSitesString($restrictToIdSites); $this->shouldSkipSpecifiedSites = \Piwik\Site::getIdSitesFromIdSitesString($skipIdSites); $this->lastSuccessRunTimestamp = Option::get(self::OPTION_ARCHIVING_FINISHED_TS); $this->shouldArchiveOnlySitesWithTrafficSince = $this->isShouldArchiveAllSitesWithTrafficSince(); if($this->shouldArchiveOnlySitesWithTrafficSince === false) { // force-all-periods is not set here if (empty($this->lastSuccessRunTimestamp)) { // First time we run the script $this->shouldArchiveOnlySitesWithTrafficSince = self::ARCHIVE_SITES_WITH_TRAFFIC_SINCE; } else { // there was a previous successful run $this->shouldArchiveOnlySitesWithTrafficSince = time() - $this->lastSuccessRunTimestamp; } } else { // force-all-periods is set here $this->archiveAndRespectTTL = false; if($this->shouldArchiveOnlySitesWithTrafficSince === true) { // force-all-periods without value $this->shouldArchiveOnlySitesWithTrafficSince = self::ARCHIVE_SITES_WITH_TRAFFIC_SINCE; } } } private function filterWebsiteIds(&$websiteIds) { // Keep only the websites that do exist $websiteIds = array_intersect($websiteIds, $this->allWebsites); /** * Triggered by the **archive.php** cron script so plugins can modify the list of * websites that the archiving process will be launched for. * * Plugins can use this hook to add websites to archive, remove websites to archive, or change * the order in which websites will be archived. * * @param array $websiteIds The list of website IDs to launch the archiving process for. */ Piwik::postEvent('CronArchive.filterWebsiteIds', array(&$websiteIds)); } /** * Returns the list of sites to loop over and archive. * @return array */ private function initWebsiteIds() { if(count($this->shouldArchiveSpecifiedSites) > 0) { $this->log("- Will process " . count($this->shouldArchiveSpecifiedSites) . " websites (--force-idsites)"); return $this->shouldArchiveSpecifiedSites; } if ($this->shouldArchiveAllSites) { $this->log("- Will process all " . count($this->allWebsites) . " websites"); return $this->allWebsites; } $websiteIds = array_merge( $this->addWebsiteIdsWithVisitsSinceLastRun(), $this->addWebsiteIdsToReprocess() ); $websiteIds = array_merge($websiteIds, $this->addWebsiteIdsInTimezoneWithNewDay($websiteIds)); return array_unique($websiteIds); } private function initTokenAuth() { $superUser = Db::get()->fetchRow("SELECT login, token_auth FROM " . Common::prefixTable("user") . " WHERE superuser_access = 1 ORDER BY date_registered ASC"); $this->login = $superUser['login']; $this->token_auth = $superUser['token_auth']; } private function initPiwikHost() { // If archive.php run as a web cron, we use the current hostname+path if (!Common::isPhpCliMode()) { if (!empty(self::$url)) { $piwikUrl = self::$url; } else { // example.org/piwik/misc/cron/ $piwikUrl = Common::sanitizeInputValue(Url::getCurrentUrlWithoutFileName()); // example.org/piwik/ $piwikUrl = $piwikUrl . "../../"; } } else { // If archive.php run as CLI/shell we require the piwik url to be set $piwikUrl = $this->isParameterSet("url", true); if (!$piwikUrl) { $this->logFatalErrorUrlExpected(); } if(!\Piwik\UrlHelper::isLookLikeUrl($piwikUrl)) { // try adding http:// in case it's missing $piwikUrl = "http://" . $piwikUrl; } if(!\Piwik\UrlHelper::isLookLikeUrl($piwikUrl)) { $this->logFatalErrorUrlExpected(); } // ensure there is a trailing slash if ($piwikUrl[strlen($piwikUrl) - 1] != '/') { $piwikUrl .= '/'; } } $this->initConfigObject($piwikUrl); if (Config::getInstance()->General['force_ssl'] == 1) { $piwikUrl = str_replace('http://', 'https://', $piwikUrl); } $this->piwikUrl = $piwikUrl . "index.php"; } /** * Returns if the requested parameter is defined in the command line arguments. * If $valuePossible is true, then a value is possibly set for this parameter, * ie. --force-timeout-for-periods=3600 would return 3600 * * @param $parameter * @param bool $valuePossible * @return true or the value (int,string) if set, false otherwise */ private function isParameterSet($parameter, $valuePossible = false) { if (!Common::isPhpCliMode()) { return false; } if($parameter == 'url' && self::$url) { return self::$url; } $parameters = array( "--$parameter", "-$parameter", $parameter ); foreach ($parameters as $parameter) { foreach ($_SERVER['argv'] as $arg) { if (strpos($arg, $parameter) === 0) { if ($valuePossible) { $parameterFound = $arg; if (($posEqual = strpos($parameterFound, '=')) !== false) { $return = substr($parameterFound, $posEqual + 1); if ($return !== false) { return $return; } } } return true; } } } return false; } /** * Return All websites that had reports in the past which were invalidated recently * (see API CoreAdminHome.invalidateArchivedReports) * eg. when using Python log import script * * @return array */ private function addWebsiteIdsToReprocess() { $this->idSitesInvalidatedOldReports = APICoreAdminHome::getWebsiteIdsToInvalidate(); if (count($this->idSitesInvalidatedOldReports) > 0) { $ids = ", IDs: " . implode(", ", $this->idSitesInvalidatedOldReports); $this->log("- Will process " . count($this->idSitesInvalidatedOldReports) . " other websites because some old data reports have been invalidated (eg. using the Log Import script) " . $ids); } return $this->idSitesInvalidatedOldReports; } /** * Returns all sites that had visits since specified time * * @return string */ private function addWebsiteIdsWithVisitsSinceLastRun() { $sitesIdWithVisits = APISitesManager::getInstance()->getSitesIdWithVisits(time() - $this->shouldArchiveOnlySitesWithTrafficSince); $websiteIds = !empty($sitesIdWithVisits) ? ", IDs: " . implode(", ", $sitesIdWithVisits) : ""; $prettySeconds = \Piwik\MetricsFormatter::getPrettyTimeFromSeconds( $this->shouldArchiveOnlySitesWithTrafficSince, true, false); $this->log("- Will process " . count($sitesIdWithVisits) . " websites with new visits since " . $prettySeconds . " " . $websiteIds); return $sitesIdWithVisits; } /** * Returns the list of timezones where the specified timestamp in that timezone * is on a different day than today in that timezone. * * @return array */ private function getTimezonesHavingNewDay() { $timestamp = time() - $this->shouldArchiveOnlySitesWithTrafficSince; $uniqueTimezones = APISitesManager::getInstance()->getUniqueSiteTimezones(); $timezoneToProcess = array(); foreach ($uniqueTimezones as &$timezone) { $processedDateInTz = Date::factory((int)$timestamp, $timezone); $currentDateInTz = Date::factory('now', $timezone); if ($processedDateInTz->toString() != $currentDateInTz->toString()) { $timezoneToProcess[] = $timezone; } } return $timezoneToProcess; } /** * Returns the list of websites in which timezones today is a new day * (compared to the last time archiving was executed) * * @param $websiteIds * @return array Website IDs */ private function addWebsiteIdsInTimezoneWithNewDay($websiteIds) { $timezones = $this->getTimezonesHavingNewDay(); $websiteDayHasFinishedSinceLastRun = APISitesManager::getInstance()->getSitesIdFromTimezones($timezones); $websiteDayHasFinishedSinceLastRun = array_diff($websiteDayHasFinishedSinceLastRun, $websiteIds); $this->websiteDayHasFinishedSinceLastRun = $websiteDayHasFinishedSinceLastRun; if (count($websiteDayHasFinishedSinceLastRun) > 0) { $ids = !empty($websiteDayHasFinishedSinceLastRun) ? ", IDs: " . implode(", ", $websiteDayHasFinishedSinceLastRun) : ""; $this->log("- Will process " . count($websiteDayHasFinishedSinceLastRun) . " other websites because the last time they were archived was on a different day (in the website's timezone) " . $ids); } return $websiteDayHasFinishedSinceLastRun; } /** * Test that the specified piwik URL is a valid Piwik endpoint. */ private function checkPiwikUrlIsValid() { $response = $this->request("?module=API&method=API.getDefaultMetricTranslations&format=original&serialize=1"); $responseUnserialized = @unserialize($response); if ($response === false || !is_array($responseUnserialized) ) { $this->logFatalError("The Piwik URL {$this->piwikUrl} does not seem to be pointing to a Piwik server. Response was '$response'."); } } private function logInitInfo() { $this->logSection("INIT"); $this->log("Querying Piwik API at: {$this->piwikUrl}"); $this->log("Running Piwik " . Version::VERSION . " as Super User: " . $this->login); } private function logArchiveTimeoutInfo() { $this->logSection("NOTES"); // Recommend to disable browser archiving when using this script if (Rules::isBrowserTriggerEnabled()) { $this->log("- If you execute this script at least once per hour (or more often) in a crontab, you may disable 'Browser trigger archiving' in Piwik UI > Settings > General Settings. "); $this->log(" See the doc at: http://piwik.org/docs/setup-auto-archiving/"); } $this->log("- Reports for today will be processed at most every " . $this->todayArchiveTimeToLive . " seconds. You can change this value in Piwik UI > Settings > General Settings."); $this->log("- Reports for the current week/month/year will be refreshed at most every " . $this->processPeriodsMaximumEverySeconds . " seconds."); // Try and not request older data we know is already archived if ($this->lastSuccessRunTimestamp !== false) { $dateLast = time() - $this->lastSuccessRunTimestamp; $this->log("- Archiving was last executed without error " . \Piwik\MetricsFormatter::getPrettyTimeFromSeconds($dateLast, true, $isHtml = false) . " ago"); } } /** * Returns the delay in seconds, that should be enforced, between calling archiving for Periods Archives. * It can be set by --force-timeout-for-periods=X * * @return int */ private function getDelayBetweenPeriodsArchives() { $forceTimeoutPeriod = $this->isParameterSet("force-timeout-for-periods", $valuePossible = true); if (empty($forceTimeoutPeriod) || $forceTimeoutPeriod === true) { return self::SECONDS_DELAY_BETWEEN_PERIOD_ARCHIVES; } // Ensure the cache for periods is at least as high as cache for today if ($forceTimeoutPeriod > $this->todayArchiveTimeToLive) { return $forceTimeoutPeriod; } $this->log("WARNING: Automatically increasing --force-timeout-for-periods from $forceTimeoutPeriod to " . $this->todayArchiveTimeToLive . " to match the cache timeout for Today's report specified in Piwik UI > Settings > General Settings"); return $this->todayArchiveTimeToLive; } private function isShouldArchiveAllSitesWithTrafficSince() { $shouldArchiveAllPeriodsSince = $this->isParameterSet("force-all-periods", $valuePossible = true); if(empty($shouldArchiveAllPeriodsSince)) { return false; } if ( is_numeric($shouldArchiveAllPeriodsSince) && $shouldArchiveAllPeriodsSince > 1 ) { return (int)$shouldArchiveAllPeriodsSince; } return true; } /** * @param $idsite */ protected function setSiteIsArchived($idsite) { $websiteIdsInvalidated = APICoreAdminHome::getWebsiteIdsToInvalidate(); if (count($websiteIdsInvalidated)) { $found = array_search($idsite, $websiteIdsInvalidated); if ($found !== false) { unset($websiteIdsInvalidated[$found]); // $this->log("Websites left to invalidate: " . implode(", ", $websiteIdsInvalidated)); Option::set(APICoreAdminHome::OPTION_INVALIDATED_IDSITES, serialize($websiteIdsInvalidated)); } } } private function logFatalErrorUrlExpected() { $this->logFatalError("archive.php expects the argument --url to be set to your Piwik URL, for example: --url=http://example.org/piwik/ " . "\n--help for more information", $backtrace = false); } }