diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d61737e34a501c16e0ab98d38b94691ece444a8..6a300ac3d845a6047c5141d3b3bdb64ba018b9b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,15 @@ This is a changelog for Piwik platform developers. All changes for our HTTP API' * The API parameter `countVisitorsToFetch` of the API method `Live.getLastVisitsDetails` has been deprecated as `filter_offset` and `filter_limit` work correctly now. ### Breaking Changes -* The API method `Live.getLastVisitsDetails` does no longer support the API parameter `filter_sort_column` to prevent possible memory issues when `filter_offset` is large. . +* The API method `Live.getLastVisitsDetails` does no longer support the API parameter `filter_sort_column` to prevent possible memory issues when `filter_offset` is large. + +### New commands +* There is now a `diagnostic:run` command to run the system check from the command line. ### APIs Improvements * Visitor details now additionally contain: `deviceTypeIcon`, `deviceBrand` and `deviceModel` +* In 2.6.0 we added the possibility to use `filter_limit` and `filter_offset` if an API returns an indexed array. This was not working in all cases and is fixed now. +* The API parameter `filter_pattern` and `filter_offset[]` can now be used if an API returns an indexed array. ## Piwik 2.12.0 diff --git a/composer.lock b/composer.lock index 3607544b662b60e8219f8427f8a48dbffbe464c9..48e17c8f112f49b4cc26fa4b4b2ea3b7ebe142b1 100644 --- a/composer.lock +++ b/composer.lock @@ -272,12 +272,12 @@ "source": { "type": "git", "url": "https://github.com/mnapoli/PHP-DI.git", - "reference": "ee5145095555c4532220eab991bf7782e9a645c6" + "reference": "92e2f01c7f2076649235aa50f415dda11b87e6da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mnapoli/PHP-DI/zipball/ee5145095555c4532220eab991bf7782e9a645c6", - "reference": "ee5145095555c4532220eab991bf7782e9a645c6", + "url": "https://api.github.com/repos/mnapoli/PHP-DI/zipball/92e2f01c7f2076649235aa50f415dda11b87e6da", + "reference": "92e2f01c7f2076649235aa50f415dda11b87e6da", "shasum": "" }, "require": { @@ -289,9 +289,9 @@ "php": ">=5.3.3" }, "require-dev": { - "mnapoli/phpunit-easymock": "~0.1.1", + "mnapoli/phpunit-easymock": "~0.1.4", "ocramius/proxy-manager": "~0.5", - "phpunit/phpunit": "~4.4" + "phpunit/phpunit": "~4.5" }, "suggest": { "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~0.5)" @@ -321,7 +321,7 @@ "dependency injection", "di" ], - "time": "2015-01-29 03:02:32" + "time": "2015-04-12 06:13:26" }, { "name": "mnapoli/phpdocreader", @@ -813,16 +813,16 @@ }, { "name": "piwik/device-detector", - "version": "3.0.1", + "version": "3.1.1", "source": { "type": "git", "url": "https://github.com/piwik/device-detector.git", - "reference": "87720bc573642ad96ae9bdc42d82a843a8e65899" + "reference": "3d36a5bd0710896311d69b10fd6eb1ca3b2d3c19" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/piwik/device-detector/zipball/87720bc573642ad96ae9bdc42d82a843a8e65899", - "reference": "87720bc573642ad96ae9bdc42d82a843a8e65899", + "url": "https://api.github.com/repos/piwik/device-detector/zipball/3d36a5bd0710896311d69b10fd6eb1ca3b2d3c19", + "reference": "3d36a5bd0710896311d69b10fd6eb1ca3b2d3c19", "shasum": "" }, "require": { @@ -859,7 +859,7 @@ "parser", "useragent" ], - "time": "2015-02-21 19:36:09" + "time": "2015-04-04 16:44:30" }, { "name": "piwik/ini", @@ -1384,7 +1384,7 @@ "performance", "profiling" ], - "time": "2014-08-28 17:34:52" + "time": "2015-02-26 14:37:51" }, { "name": "guzzle/guzzle", diff --git a/config/environment/cli.php b/config/environment/cli.php index 2852c010074e61599e95827576d814a53cae5ef9..0db3009ad4a3bb580511c1554f82d9117016d305 100644 --- a/config/environment/cli.php +++ b/config/environment/cli.php @@ -10,7 +10,7 @@ return array( // Log 'log.handlers' => array( - DI\link('Symfony\Bridge\Monolog\Handler\ConsoleHandler'), + DI\get('Symfony\Bridge\Monolog\Handler\ConsoleHandler'), ), 'Symfony\Bridge\Monolog\Handler\ConsoleHandler' => function (ContainerInterface $c) { // Override the default verbosity map to make it more verbose by default diff --git a/config/environment/dev.php b/config/environment/dev.php index 0a730d708c826d6ae9a422d5538f287d04194dbf..a8bedecca99b17065fd9340afeaa9ae257af538e 100644 --- a/config/environment/dev.php +++ b/config/environment/dev.php @@ -5,8 +5,8 @@ return array( 'Piwik\Cache\Backend' => DI\object('Piwik\Cache\Backend\ArrayCache'), 'Piwik\Translation\Loader\LoaderInterface' => DI\object('Piwik\Translation\Loader\LoaderCache') - ->constructor(DI\link('Piwik\Translation\Loader\DevelopmentLoader')), + ->constructor(DI\get('Piwik\Translation\Loader\DevelopmentLoader')), 'Piwik\Translation\Loader\DevelopmentLoader' => DI\object() - ->constructor(DI\link('Piwik\Translation\Loader\JsonFileLoader')), + ->constructor(DI\get('Piwik\Translation\Loader\JsonFileLoader')), ); diff --git a/config/global.ini.php b/config/global.ini.php index 4e075a22a448f73dfdb9a129a20c538995825fb6..526f8b3e243bb7de1d6cba7d4adeef141f08f6b0 100644 --- a/config/global.ini.php +++ b/config/global.ini.php @@ -462,7 +462,8 @@ enable_trusted_host_check = 1 ; Examples: ;cors_domains[] = http://example.com ;cors_domains[] = http://stats.example.com -; OR allow for all domains +; +; Or you may allow cross domain requests for all domains with: ;cors_domains[] = * ; If you use this Piwik instance over multiple hostnames, Piwik will need to know @@ -719,6 +720,7 @@ password = ; Proxy password: optional; if specified, username is mandatory Plugins[] = CorePluginsAdmin Plugins[] = CoreAdminHome Plugins[] = CoreHome +Plugins[] = Diagnostics Plugins[] = CoreVisualizations Plugins[] = Proxy Plugins[] = API diff --git a/config/global.php b/config/global.php index 0cb88f5b3dc00c2eb0a3c130a9b73c0f5c9082ee..8fa70111a19bc1158852015dff1ef2e73ad0e8dc 100644 --- a/config/global.php +++ b/config/global.php @@ -60,5 +60,6 @@ return array( 'Psr\Log\LoggerInterface' => DI\object('Psr\Log\NullLogger'), 'Piwik\Translation\Loader\LoaderInterface' => DI\object('Piwik\Translation\Loader\LoaderCache') - ->constructor(DI\link('Piwik\Translation\Loader\JsonFileLoader')), + ->constructor(DI\get('Piwik\Translation\Loader\JsonFileLoader')), + ); diff --git a/core/API/CORSHandler.php b/core/API/CORSHandler.php index 721d5a0ed36c7993b811ca4c01a272346c15d400..aeee2ad39d3875ba62e44da2f7d6db4726e3a94c 100644 --- a/core/API/CORSHandler.php +++ b/core/API/CORSHandler.php @@ -24,6 +24,13 @@ class CORSHandler public function handle() { + // allow Piwik to serve data to all domains + if (in_array("*", $this->domains)) { + header('Access-Control-Allow-Origin: *'); + return; + } + + // specifically allow if it is one of the whitelisted CORS domains if (!empty($_SERVER['HTTP_ORIGIN'])) { $origin = $_SERVER['HTTP_ORIGIN']; if (in_array($origin, $this->domains, true)) { diff --git a/core/API/Request.php b/core/API/Request.php index 2ed5dfc56761ea13eae94c9638c1ead23a705995..04d055f8598b3c7750022524f740f17a8f26bff7 100644 --- a/core/API/Request.php +++ b/core/API/Request.php @@ -263,7 +263,9 @@ class Request { // if a token_auth is specified in the API request, we load the right permissions $token_auth = Common::getRequestVar('token_auth', '', 'string', $request); - if ($token_auth) { + $access = Access::getInstance(); + + if ($token_auth && $token_auth !== $access->getTokenAuth()) { /** * Triggered when authenticating an API request, but only if the **token_auth** diff --git a/core/API/ResponseBuilder.php b/core/API/ResponseBuilder.php index 68e448b0b4dc82030110e20410747cac0d6b4b2f..9688aff83bcb57e705d0d598a4d82e228f46c7a7 100644 --- a/core/API/ResponseBuilder.php +++ b/core/API/ResponseBuilder.php @@ -14,8 +14,8 @@ use Piwik\DataTable; use Piwik\DataTable\Renderer; use Piwik\DataTable\DataTableInterface; use Piwik\DataTable\Filter\ColumnDelete; +use Piwik\DataTable\Filter\Pattern; use Piwik\Plugin\Report; -use Piwik\Plugins\API\Renderer\Original; /** */ @@ -189,6 +189,23 @@ class ResponseBuilder $isAssoc = !empty($firstArray) && is_numeric($firstKey) && is_array($firstArray) && count(array_filter(array_keys($firstArray), 'is_string')); + if (is_numeric($firstKey)) { + $columns = Common::getRequestVar('filter_column', false, 'array', $this->request); + $pattern = Common::getRequestVar('filter_pattern', '', 'string', $this->request); + + if ($columns != array(false) && $pattern !== '') { + $pattern = new Pattern(new DataTable(), $columns, $pattern); + $array = $pattern->filterArray($array); + } + + $limit = Common::getRequestVar('filter_limit', -1, 'integer', $this->request); + $offset = Common::getRequestVar('filter_offset', '0', 'integer', $this->request); + + if ($this->shouldApplyLimitOnArray($limit, $offset)) { + $array = array_slice($array, $offset, $limit, $firstKey !== 0); + } + } + if ($isAssoc) { $hideColumns = Common::getRequestVar('hideColumns', '', 'string', $this->request); $showColumns = Common::getRequestVar('showColumns', '', 'string', $this->request); @@ -196,18 +213,43 @@ class ResponseBuilder $columnDelete = new ColumnDelete(new DataTable(), $hideColumns, $showColumns); $array = $columnDelete->filter($array); } - } else if (is_numeric($firstKey)) { - $limit = Common::getRequestVar('filter_limit', -1, 'integer', $this->request); - $offset = Common::getRequestVar('filter_offset', '0', 'integer', $this->request); - - if (-1 !== $limit) { - $array = array_slice($array, $offset, $limit); - } } return $this->apiRenderer->renderArray($array); } + private function shouldApplyLimitOnArray($limit, $offset) + { + if ($limit === -1) { + // all fields are requested + return false; + } + + if ($offset > 0) { + // an offset is specified, we have to apply the limit + return true; + } + + // "api_datatable_default_limit" is set by API\Controller if no filter_limit is specified by the user. + // it holds the number of the configured default limit. + $limitSetBySystem = Common::getRequestVar('api_datatable_default_limit', -2, 'integer', $this->request); + + // we ignore the limit if the datatable_default_limit was set by the system as this default filter_limit is + // only meant for dataTables but not for arrays. This way we stay BC as filter_limit was not applied pre + // Piwik 2.6 and some fixes were made in Piwik 2.13. + $wasFilterLimitSetBySystem = $limitSetBySystem !== -2; + + // we check for "$limitSetBySystem === $limit" as an API method could request another API method with + // another limit. In this case we need to apply it again. + $isLimitStillDefaultLimit = $limitSetBySystem === $limit; + + if ($wasFilterLimitSetBySystem && $isLimitStillDefaultLimit) { + return false; + } + + return true; + } + private function sendHeaderIfEnabled() { if ($this->sendHeader) { diff --git a/core/Access.php b/core/Access.php index a5f6f604c043e9f5c623b1d97f6f97b576adbc57..d6706237558be0a2df4d683d72971e6a528c96eb 100644 --- a/core/Access.php +++ b/core/Access.php @@ -117,6 +117,11 @@ class Access * Constructor */ public function __construct() + { + $this->resetSites(); + } + + private function resetSites() { $this->idsitesByAccess = array( 'view' => array(), @@ -138,19 +143,15 @@ class Access */ public function reloadAccess(Auth $auth = null) { - if (!is_null($auth)) { - $this->auth = $auth; - } + $this->resetSites(); - // if the Auth wasn't set, we may be in the special case of setSuperUser(), otherwise we fail - if (is_null($this->auth)) { - if ($this->hasSuperUserAccess()) { - return $this->reloadAccessSuperUser(); - } + if (isset($auth)) { + $this->auth = $auth; } if ($this->hasSuperUserAccess()) { - return $this->reloadAccessSuperUser(); + $this->makeSureLoginNameIsSet(); + return true; } // if the Auth wasn't set, we may be in the special case of setSuperUser(), otherwise we fail TODO: docs + review @@ -170,18 +171,7 @@ class Access // case the superUser is logged in if ($result->hasSuperUserAccess()) { - return $this->reloadAccessSuperUser(); - } - - // in case multiple calls to API using different tokens, we ensure we reset it as not SU - $this->setSuperUserAccess(false); - - // we join with site in case there are rows in access for an idsite that doesn't exist anymore - // (backward compatibility ; before we deleted the site without deleting rows in _access table) - $accessRaw = $this->getRawSitesWithSomeViewAccess($this->login); - - foreach ($accessRaw as $access) { - $this->idsitesByAccess[$access['access']][] = $access['idsite']; + $this->setSuperUserAccess(true); } return true; @@ -210,27 +200,42 @@ class Access } /** - * Reload Super User access + * Make sure a login name is set * - * @return bool + * @return true */ - protected function reloadAccessSuperUser() + protected function makeSureLoginNameIsSet() { - $this->hasSuperUserAccess = true; - - try { - $allSitesId = Plugins\SitesManager\API::getInstance()->getAllSitesId(); - } catch (\Exception $e) { - $allSitesId = array(); - } - $this->idsitesByAccess['superuser'] = $allSitesId; - - if(empty($this->login)) { + if (empty($this->login)) { // flag to force non empty login so Super User is not mistaken for anonymous $this->login = 'super user was set'; } + } - return true; + private function loadSitesIfNeeded() + { + if ($this->hasSuperUserAccess) { + if (empty($this->idsitesByAccess['superuser'])) { + try { + $allSitesId = Plugins\SitesManager\API::getInstance()->getAllSitesId(); + } catch (\Exception $e) { + $allSitesId = array(); + } + $this->idsitesByAccess['superuser'] = $allSitesId; + } + } else if (isset($this->login)) { + if (empty($this->idsitesByAccess['view']) + && empty($this->idsitesByAccess['admin'])) { + + // we join with site in case there are rows in access for an idsite that doesn't exist anymore + // (backward compatibility ; before we deleted the site without deleting rows in _access table) + $accessRaw = $this->getRawSitesWithSomeViewAccess($this->login); + + foreach ($accessRaw as $access) { + $this->idsitesByAccess[$access['access']][] = $access['idsite']; + } + } + } } /** @@ -241,11 +246,12 @@ class Access */ public function setSuperUserAccess($bool = true) { + $this->hasSuperUserAccess = (bool) $bool; + if ($bool) { - $this->reloadAccessSuperUser(); + $this->makeSureLoginNameIsSet(); } else { - $this->hasSuperUserAccess = false; - $this->idsitesByAccess['superuser'] = array(); + $this->resetSites(); } } @@ -288,6 +294,8 @@ class Access */ public function getSitesIdWithAtLeastViewAccess() { + $this->loadSitesIfNeeded(); + return array_unique(array_merge( $this->idsitesByAccess['view'], $this->idsitesByAccess['admin'], @@ -303,6 +311,8 @@ class Access */ public function getSitesIdWithAdminAccess() { + $this->loadSitesIfNeeded(); + return array_unique(array_merge( $this->idsitesByAccess['admin'], $this->idsitesByAccess['superuser']) @@ -318,6 +328,8 @@ class Access */ public function getSitesIdWithViewAccess() { + $this->loadSitesIfNeeded(); + return $this->idsitesByAccess['view']; } diff --git a/core/Archive/ArchivePurger.php b/core/Archive/ArchivePurger.php index e41ce375c553076ec7f9ea88108f1b1fd052ce0a..f326558eb2c4bcb54df697ef3da5587ab0d4565f 100644 --- a/core/Archive/ArchivePurger.php +++ b/core/Archive/ArchivePurger.php @@ -10,12 +10,14 @@ namespace Piwik\Archive; use Piwik\ArchiveProcessor\Rules; use Piwik\Config; +use Piwik\Container\StaticContainer; use Piwik\DataAccess\ArchiveTableCreator; use Piwik\DataAccess\Model; use Piwik\Date; use Piwik\Db; -use Piwik\Log; use Piwik\Piwik; +use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; /** * Service that purges temporary, error-ed, invalid and custom range archives from archive tables. @@ -64,7 +66,12 @@ class ArchivePurger */ private $now; - public function __construct(Model $model = null, Date $purgeCustomRangesOlderThan = null) + /** + * @var LoggerInterface + */ + private $logger; + + public function __construct(Model $model = null, Date $purgeCustomRangesOlderThan = null, LoggerInterface $logger = null) { $this->model = $model ?: new Model(); @@ -73,6 +80,7 @@ class ArchivePurger $this->yesterday = Date::factory('yesterday'); $this->today = Date::factory('today'); $this->now = time(); + $this->logger = $logger ?: StaticContainer::get('Psr\Log\LoggerInterface'); } /** @@ -80,6 +88,7 @@ class ArchivePurger * table that stores data for `$date`. * * @param Date $date The date identifying the archive table. + * @return int The total number of archive rows deleted (from both the blog & numeric tables). */ public function purgeInvalidatedArchivesFrom(Date $date) { @@ -90,15 +99,27 @@ class ArchivePurger // the constraint will hit an INDEX and speed up the inner join that happens in getInvalidatedArchiveIdsSafeToDelete(). $idSites = $this->model->getSitesWithInvalidatedArchive($numericTable); if (empty($idSites)) { - return; + $this->logger->debug("No sites with invalidated archives found in {table}.", array('table' => $numericTable)); + return 0; } $archiveIds = $this->model->getInvalidatedArchiveIdsSafeToDelete($numericTable, $idSites); if (empty($archiveIds)) { - return; + $this->logger->debug("No invalidated archives found in {table} with newer, valid archives.", array('table' => $numericTable)); + return 0; } - $this->deleteArchiveIds($date, $archiveIds); + $this->logger->info("Found {countArchiveIds} invalidated archives safe to delete in {table}.", array( + 'table' => $numericTable, 'countArchiveIds' => count($archiveIds) + )); + + $deletedRowCount = $this->deleteArchiveIds($date, $archiveIds); + + $this->logger->debug("Deleted {count} rows in {table} and its associated blob table.", array( + 'table' => $numericTable, 'count' => $deletedRowCount + )); + + return $deletedRowCount; } /** @@ -106,20 +127,32 @@ class ArchivePurger * (meaning they are marked with a done flag of ArchiveWriter::DONE_OK_TEMPORARY or ArchiveWriter::DONE_ERROR) * * @param Date $dateStart Only the month will be used + * @return int Returns the total number of rows deleted. */ public function purgeOutdatedArchives(Date $dateStart) { $purgeArchivesOlderThan = $this->getOldestTemporaryArchiveToKeepThreshold(); + $deletedRowCount = 0; $idArchivesToDelete = $this->getOutdatedArchiveIds($dateStart, $purgeArchivesOlderThan); if (!empty($idArchivesToDelete)) { - $this->deleteArchiveIds($dateStart, $idArchivesToDelete); + $deletedRowCount = $this->deleteArchiveIds($dateStart, $idArchivesToDelete); + + $this->logger->info("Deleted {count} rows in archive tables (numeric + blob) for {date}.", array( + 'count' => $deletedRowCount, + 'date' => $dateStart + )); + } else { + $this->logger->debug("No outdated archives found in archive numeric table for {date}.", array('date' => $dateStart)); } - Log::debug("Purging temporary archives: done [ purged archives older than %s in %s ] [Deleted IDs: %s]", - $purgeArchivesOlderThan, - $dateStart->toString("Y-m"), - implode(',', $idArchivesToDelete)); + $this->logger->debug("Purging temporary archives: done [ purged archives older than {date} in {yearMonth} ] [Deleted IDs: {deletedIds}]", array( + 'date' => $purgeArchivesOlderThan, + 'yearMonth' => $dateStart->toString('Y-m'), + 'deletedIds' => implode(',', $idArchivesToDelete) + )); + + return $deletedRowCount; } protected function getOutdatedArchiveIds(Date $date, $purgeArchivesOlderThan) @@ -142,16 +175,26 @@ class ArchivePurger * Deleting "Custom Date Range" reports after 1 day, since they can be re-processed and would take up un-necessary space. * * @param $date Date + * @return int The total number of rows deleted from both the numeric & blob table. */ public function purgeArchivesWithPeriodRange(Date $date) { $numericTable = ArchiveTableCreator::getNumericTable($date); $blobTable = ArchiveTableCreator::getBlobTable($date); - $this->model->deleteArchivesWithPeriod($numericTable, $blobTable, Piwik::$idPeriods['range'], $this->purgeCustomRangesOlderThan); + $deletedCount = $this->model->deleteArchivesWithPeriod( + $numericTable, $blobTable, Piwik::$idPeriods['range'], $this->purgeCustomRangesOlderThan); + + $level = $deletedCount == 0 ? LogLevel::DEBUG : LogLevel::INFO; + $this->logger->log($level, "Purged {count} range archive rows from {numericTable} & {blobTable}.", array( + 'count' => $deletedCount, + 'numericTable' => $numericTable, + 'blobTable' => $blobTable + )); + + $this->logger->debug(" [ purged archives older than {threshold} ]", array('threshold' => $this->purgeCustomRangesOlderThan)); - Log::debug("Purging Custom Range archives: done [ purged archives older than %s from %s / blob ]", - $this->purgeCustomRangesOlderThan, $numericTable); + return $deletedCount; } /** @@ -159,6 +202,7 @@ class ArchivePurger * * @param Date $date * @param $idArchivesToDelete + * @return int Number of rows deleted from both numeric + blob table. */ protected function deleteArchiveIds(Date $date, $idArchivesToDelete) { @@ -166,9 +210,11 @@ class ArchivePurger $numericTable = ArchiveTableCreator::getNumericTable($date); $blobTable = ArchiveTableCreator::getBlobTable($date); + $deletedCount = 0; foreach ($batches as $idsToDelete) { - $this->model->deleteArchiveIds($numericTable, $blobTable, $idsToDelete); + $deletedCount += $this->model->deleteArchiveIds($numericTable, $blobTable, $idsToDelete); } + return $deletedCount; } /** diff --git a/core/CliMulti/CliPhp.php b/core/CliMulti/CliPhp.php index 9ee5b191b1b7c50af37bcfb852d795c9b77cd1d1..3bf7b3888e839eb76962548244ab94fee157bbee 100644 --- a/core/CliMulti/CliPhp.php +++ b/core/CliMulti/CliPhp.php @@ -9,7 +9,6 @@ namespace Piwik\CliMulti; use Piwik\CliMulti; use Piwik\Common; -use Piwik\Plugins\Installation\SystemCheck; class CliPhp { @@ -67,8 +66,9 @@ class CliPhp private function isValidPhpVersion($bin) { + global $piwik_minimumPHPVersion; $cliVersion = $this->getPhpVersion($bin); - $isCliVersionValid = SystemCheck::isPhpVersionValid($cliVersion); + $isCliVersionValid = version_compare($piwik_minimumPHPVersion, $cliVersion) <= 0; return $isCliVersionValid; } diff --git a/core/CliMulti/Process.php b/core/CliMulti/Process.php index c63dd5a94620a68987c8e4d03fe1b0875afb2266..bae7e316d6071a22e95c7476d0ff58c371440a73 100644 --- a/core/CliMulti/Process.php +++ b/core/CliMulti/Process.php @@ -144,7 +144,7 @@ class Process } $lockedPID = trim($content); - $runningPIDs = explode("\n", trim( `ps ex | awk '{print $1}'` )); + $runningPIDs = self::getRunningProcesses(); return !empty($lockedPID) && in_array($lockedPID, $runningPIDs); } @@ -173,6 +173,10 @@ class Process return false; } + if (count(self::getRunningProcesses()) > 0) { + return true; + } + if (!self::isProcFSMounted()) { return false; } @@ -236,4 +240,18 @@ class Process return strpos($type, 'proc') === 0; } + /** + * @return int[] The ids of the currently running processes + */ + static function getRunningProcesses() + { + $ids = explode("\n", trim(`ps ex 2>/dev/null | awk '{print $1}' 2>/dev/null`)); + + $ids = array_map('intval', $ids); + $ids = array_filter($ids, function ($id) { + return $id > 0; + }); + + return $ids; + } } diff --git a/core/Common.php b/core/Common.php index 1ed8d4893574662dd572ade8ca4665c333eb299b..14007045996dfa7a454584982ca8ce8d74a02465 100644 --- a/core/Common.php +++ b/core/Common.php @@ -379,9 +379,13 @@ class Common */ private static function undoMagicQuotes($value) { - if (version_compare(PHP_VERSION, '5.4', '<') && - get_magic_quotes_gpc()) { + static $shouldUndo; + if (!isset($shouldUndo)) { + $shouldUndo = version_compare(PHP_VERSION, '5.4', '<') && get_magic_quotes_gpc(); + } + + if ($shouldUndo) { $value = stripslashes($value); } @@ -470,7 +474,7 @@ class Common } $value = self::sanitizeInputValues($requestArrayToUse[$varName]); - if (!is_null($varType)) { + if (isset($varType)) { $ok = false; if ($varType === 'string') { @@ -1199,7 +1203,8 @@ class Common 401 => 'Unauthorized', 403 => 'Forbidden', 404 => 'Not Found', - 500 => 'Internal Server Error' + 500 => 'Internal Server Error', + 503 => 'Service Unavailable', ); if (!array_key_exists($code, $messages)) { diff --git a/core/Config.php b/core/Config.php index 1ceb2594da87cad556591fc0b0e7953172d37a37..3e8ab98797ba862821d8385bbd6075b75e2bdee1 100644 --- a/core/Config.php +++ b/core/Config.php @@ -354,15 +354,29 @@ class Config extends Singleton return $section; } + /** + * @api + */ public function getFromGlobalConfig($name) { return $this->settings->getIniFileChain()->getFrom($this->getGlobalPath(), $name); } + /** + * @api + */ public function getFromCommonConfig($name) { return $this->settings->getIniFileChain()->getFrom($this->getCommonPath(), $name); } + + /** + * @api + */ + public function getFromLocalConfig($name) + { + return $this->settings->getIniFileChain()->getFrom($this->getLocalPath(), $name); + } /** * Sets a configuration value or section. @@ -443,4 +457,4 @@ class Config extends Singleton $path = "config/" . basename($this->getLocalPath()); return new Exception(Piwik::translate('General_ConfigFileIsNotWritable', array("(" . $path . ")", ""))); } -} \ No newline at end of file +} diff --git a/core/Config/IniFileChain.php b/core/Config/IniFileChain.php index 4d264b2ec294836a02a2597e24f6b0305c20d729..3f6f076cd8d6176ae7487702f68aa8834db0ad4f 100644 --- a/core/Config/IniFileChain.php +++ b/core/Config/IniFileChain.php @@ -8,11 +8,9 @@ namespace Piwik\Config; use Piwik\Common; -use Piwik\Config; use Piwik\Ini\IniReader; use Piwik\Ini\IniReadingException; use Piwik\Ini\IniWriter; -use Piwik\Piwik; /** * Manages a list of INI files where the settings in each INI file merge with or override the @@ -119,7 +117,7 @@ class IniFileChain */ public function dump($header = '') { - return $this->dumpSettingsArray($header, $this->mergedSettings); + return $this->dumpSettings($this->mergedSettings, $header); } /** @@ -189,7 +187,7 @@ class IniFileChain } }); - return $this->dumpSettingsArray($header, $configToWrite); + return $this->dumpSettings($configToWrite, $header); } else { return null; } @@ -210,7 +208,8 @@ class IniFileChain foreach ($this->settingsChain as $file => $ignore) { if (is_readable($file)) { try { - $this->settingsChain[$file] = $reader->readFile($file); + $contents = $reader->readFile($file); + $this->settingsChain[$file] = $this->decodeValues($contents); } catch (IniReadingException $ex) { throw new IniReadingException('Unable to read INI file {' . $file . '}: ' . $ex->getMessage() . "\n Your host may have disabled parse_ini_file()."); } @@ -405,53 +404,51 @@ class IniFileChain return array_search($sectionName, $settingsDataSectionNames); } - /** - * Decode HTML entities in setting data. + * Encode HTML entities * * @param mixed $values * @return mixed */ - private function decodeValues(&$values) + protected function encodeValues(&$values) { if (is_array($values)) { foreach ($values as &$value) { - $value = self::decodeValues($value); + $value = $this->encodeValues($value); } - return $values; + } elseif (is_float($values)) { + $values = Common::forceDotAsSeparatorForDecimalPoint($values); } elseif (is_string($values)) { - return html_entity_decode($values, ENT_COMPAT, 'UTF-8'); + $values = htmlentities($values, ENT_COMPAT, 'UTF-8'); + $values = str_replace('$', '$', $values); } return $values; } - /** - * Encode HTML entities + * Decode HTML entities * * @param mixed $values * @return mixed */ - protected function encodeValues(&$values) + protected function decodeValues(&$values) { if (is_array($values)) { foreach ($values as &$value) { - $value = $this->encodeValues($value); + $value = $this->decodeValues($value); } - } elseif (is_float($values)) { - $values = Common::forceDotAsSeparatorForDecimalPoint($values); + return $values; } elseif (is_string($values)) { - $values = htmlentities($values, ENT_COMPAT, 'UTF-8'); - $values = str_replace('$', '$', $values); + return html_entity_decode($values, ENT_COMPAT, 'UTF-8'); } return $values; } - private function dumpSettingsArray($header, $settings) + private function dumpSettings($values, $header) { - $writer = new IniWriter(); + $values = $this->encodeValues($values); - $this->encodeValues($settings); - return $writer->writeToString($settings, $header); + $writer = new IniWriter(); + return $writer->writeToString($values, $header); } } \ No newline at end of file diff --git a/core/Container/IniConfigDefinitionSource.php b/core/Container/IniConfigDefinitionSource.php index 2fb724a928c19aca3b58784a692f40e6589ff5fd..4b56d1aa9c36abd61d22a5cf0274a8f7603055eb 100644 --- a/core/Container/IniConfigDefinitionSource.php +++ b/core/Container/IniConfigDefinitionSource.php @@ -9,14 +9,18 @@ namespace Piwik\Container; use DI\Definition\Exception\DefinitionException; -use DI\Definition\Source\ChainableDefinitionSource; +use DI\Definition\Source\DefinitionSource; use DI\Definition\ValueDefinition; use Piwik\Application\Kernel\GlobalSettingsProvider; /** - * Import the old INI config into PHP-DI. + * Expose the INI config into PHP-DI. + * + * The INI config can be used by prefixing `ini.` before the setting we want to get: + * + * $maintenanceMode = $container->get('ini.General.maintenance_mode'); */ -class IniConfigDefinitionSource extends ChainableDefinitionSource +class IniConfigDefinitionSource implements DefinitionSource { /** * @var GlobalSettingsProvider @@ -38,7 +42,10 @@ class IniConfigDefinitionSource extends ChainableDefinitionSource $this->prefix = $prefix; } - protected function findDefinition($name) + /** + * {@inheritdoc} + */ + public function getDefinition($name) { if (strpos($name, $this->prefix) !== 0) { return null; diff --git a/core/CronArchive.php b/core/CronArchive.php index e5040be34f7f16e63d1f68781099e28ffd140c07..2c3cefc7a4d17d43ca97d9722417f04e041db2b5 100644 --- a/core/CronArchive.php +++ b/core/CronArchive.php @@ -277,15 +277,10 @@ class CronArchive $websitesIds = $this->initWebsiteIds(); $this->filterWebsiteIds($websitesIds); - if (!empty($this->shouldArchiveSpecifiedSites) - || !empty($this->shouldArchiveAllSites) - || !SharedSiteIds::isSupported()) { - $this->websites = new FixedSiteIds($websitesIds); - } else { - $this->websites = new SharedSiteIds($websitesIds); - if ($this->websites->getInitialSiteIds() != $websitesIds) { - $this->log('Will ignore websites and help finish a previous started queue instead. IDs: ' . implode(', ', $this->websites->getInitialSiteIds())); - } + $this->websites = $this->createSitesToArchiveQueue($websitesIds); + + if ($this->websites->getInitialSiteIds() != $websitesIds) { + $this->log('Will ignore websites and help finish a previous started queue instead. IDs: ' . implode(', ', $this->websites->getInitialSiteIds())); } if ($this->shouldStartProfiler) { @@ -427,16 +422,12 @@ class CronArchive return; } - $this->log("Starting Scheduled tasks... "); + API\Request::processRequest("CoreAdminHome.runScheduledTasks", array( + 'token_auth' => $this->token_auth, + 'format' => 'original', // so exceptions get thrown + 'trigger' => 'archivephp' + )); - $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(""); } @@ -1016,17 +1007,14 @@ class CronArchive return array_unique($websiteIds); } - private function initTokenAuth() + public function initTokenAuth() { - $tokens = array(); - - /** - * @ignore - */ - Piwik::postEvent('CronArchive.getTokenAuth', array(&$tokens)); + $tokens = self::getSuperUserTokenAuths(); $this->validTokenAuths = $tokens; $this->token_auth = array_shift($tokens); + + return $this->token_auth; } public function isTokenAuthSuperUserToken($token_auth) @@ -1504,11 +1492,20 @@ class CronArchive private function loadCustomDateRangeToPreProcess() { $customDateRangesToProcessForSites = array(); + // For all users who have selected this website to load by default, // we load the default period/date that will be loaded for this user // and make sure it's pre-archived - $userPreferences = APIUsersManager::getInstance()->getAllUsersPreferences(array(APIUsersManager::PREFERENCE_DEFAULT_REPORT_DATE, APIUsersManager::PREFERENCE_DEFAULT_REPORT)); - foreach ($userPreferences as $userLogin => $userPreferences) { + $allUsersPreferences = APIUsersManager::getInstance()->getAllUsersPreferences(array( + APIUsersManager::PREFERENCE_DEFAULT_REPORT_DATE, + APIUsersManager::PREFERENCE_DEFAULT_REPORT + )); + + foreach ($allUsersPreferences as $userLogin => $userPreferences) { + + if (!isset($userPreferences[APIUsersManager::PREFERENCE_DEFAULT_REPORT_DATE])) { + continue; + } $defaultDate = $userPreferences[APIUsersManager::PREFERENCE_DEFAULT_REPORT_DATE]; $preference = new UserPreferences(); @@ -1543,6 +1540,18 @@ class CronArchive return $this->piwikUrl . $url . self::APPEND_TO_API_REQUEST; } + public static function getSuperUserTokenAuths() + { + $tokens = array(); + + /** + * @ignore + */ + Piwik::postEvent('CronArchive.getTokenAuth', array(&$tokens)); + + return $tokens; + } + /** * @param $idSite * @param $period @@ -1562,4 +1571,19 @@ class CronArchive } return $urlsWithSegment; } + + private function createSitesToArchiveQueue($websitesIds) + { + // use synchronous, single process queue if --force-idsites is used or sharing site IDs isn't supported + if (!SharedSiteIds::isSupported() || !empty($this->shouldArchiveSpecifiedSites)) { + return new FixedSiteIds($websitesIds); + } + + // use separate shared queue if --force-all-websites is used + if (!empty($this->shouldArchiveAllSites)) { + return new SharedSiteIds($websitesIds, SharedSiteIds::OPTION_ALL_WEBSITES); + } + + return new SharedSiteIds($websitesIds); + } } \ No newline at end of file diff --git a/core/CronArchive/SharedSiteIds.php b/core/CronArchive/SharedSiteIds.php index 50f95ea2dd294af9df17a7be1b913643b71756fc..facc331a6dd727acfcfbdd9ad6db7bbad72c88fa 100644 --- a/core/CronArchive/SharedSiteIds.php +++ b/core/CronArchive/SharedSiteIds.php @@ -18,12 +18,22 @@ use Piwik\Option; */ class SharedSiteIds { + const OPTION_DEFAULT = 'SharedSiteIdsToArchive'; + const OPTION_ALL_WEBSITES = 'SharedSiteIdsToArchive_AllWebsites'; + + /** + * @var string + */ + private $optionName; + private $siteIds = array(); private $currentSiteId; private $done = false; - public function __construct($websiteIds) + public function __construct($websiteIds, $optionName = self::OPTION_DEFAULT) { + $this->optionName = $optionName; + if (empty($websiteIds)) { $websiteIds = array(); } @@ -86,16 +96,16 @@ class SharedSiteIds public function setSiteIdsToArchive($siteIds) { if (!empty($siteIds)) { - Option::set('SharedSiteIdsToArchive', implode(',', $siteIds)); + Option::set($this->optionName, implode(',', $siteIds)); } else { - Option::delete('SharedSiteIdsToArchive'); + Option::delete($this->optionName); } } public function getAllSiteIdsToArchive() { - Option::clearCachedOption('SharedSiteIdsToArchive'); - $siteIdsToArchive = Option::get('SharedSiteIdsToArchive'); + Option::clearCachedOption($this->optionName); + $siteIdsToArchive = Option::get($this->optionName); if (empty($siteIdsToArchive)) { return array(); diff --git a/core/DataAccess/Model.php b/core/DataAccess/Model.php index 0980983fe31f3a49301361ce61d61abef61b9ce8..8df49d16a31d170a5fb6be4598cb9855d466c75f 100644 --- a/core/DataAccess/Model.php +++ b/core/DataAccess/Model.php @@ -10,9 +10,11 @@ namespace Piwik\DataAccess; use Exception; use Piwik\Common; +use Piwik\Container\StaticContainer; use Piwik\Db; use Piwik\DbHelper; use Piwik\Sequence; +use Psr\Log\LoggerInterface; /** * Cleans up outdated archives @@ -21,6 +23,15 @@ use Piwik\Sequence; */ class Model { + /** + * @var LoggerInterface + */ + private $logger; + + public function __construct(LoggerInterface $logger = null) + { + $this->logger = $logger ?: StaticContainer::get('Psr\Log\LoggerInterface'); + } /** * Returns the archives IDs that have already been invalidated and have been since re-processed. @@ -118,13 +129,21 @@ class Model $query = "DELETE FROM %s WHERE period = ? AND ts_archived < ?"; $bind = array($period, $date); - Db::query(sprintf($query, $numericTable), $bind); + $queryObj = Db::query(sprintf($query, $numericTable), $bind); + $deletedRows = $queryObj->rowCount(); try { - Db::query(sprintf($query, $blobTable), $bind); + $queryObj = Db::query(sprintf($query, $blobTable), $bind); + $deletedRows += $queryObj->rowCount(); } catch (Exception $e) { // Individual blob tables could be missing + $this->logger->debug("Unable to delete archives by period from {blobTable}.", array( + 'blobTable' => $blobTable, + 'exception' => $e + )); } + + return $deletedRows; } public function deleteArchiveIds($numericTable, $blobTable, $idsToDelete) @@ -132,13 +151,21 @@ class Model $idsToDelete = array_values($idsToDelete); $query = "DELETE FROM %s WHERE idarchive IN (" . Common::getSqlStringFieldsArray($idsToDelete) . ")"; - Db::query(sprintf($query, $numericTable), $idsToDelete); + $queryObj = Db::query(sprintf($query, $numericTable), $idsToDelete); + $deletedRows = $queryObj->rowCount(); try { - Db::query(sprintf($query, $blobTable), $idsToDelete); + $queryObj = Db::query(sprintf($query, $blobTable), $idsToDelete); + $deletedRows += $queryObj->rowCount(); } catch (Exception $e) { // Individual blob tables could be missing + $this->logger->debug("Unable to delete archive IDs from {blobTable}.", array( + 'blobTable' => $blobTable, + 'exception' => $e + )); } + + return $deletedRows; } public function getArchiveIdAndVisits($numericTable, $idSite, $period, $dateStartIso, $dateEndIso, $minDatetimeIsoArchiveProcessedUTC, $doneFlags, $doneFlagValues) diff --git a/core/DataTable/Filter/Pattern.php b/core/DataTable/Filter/Pattern.php index 832fb856a210cf7140400351023f3bf598e052c9..1327b4f40a6ec58b52e083e611cc2709aa2c85d8 100644 --- a/core/DataTable/Filter/Pattern.php +++ b/core/DataTable/Filter/Pattern.php @@ -23,6 +23,9 @@ use Piwik\DataTable\BaseFilter; */ class Pattern extends BaseFilter { + /** + * @var string|array + */ private $columnToFilter; private $patternToSearch; private $patternToSearchQuoted; @@ -93,4 +96,30 @@ class Pattern extends BaseFilter } } } + + /** + * See {@link Pattern}. + * + * @param array $array + * @return array + */ + public function filterArray($array) + { + $newArray = array(); + + foreach ($array as $key => $row) { + foreach ($this->columnToFilter as $column) { + if (!array_key_exists($column, $row)) { + continue; + } + + if (self::match($this->patternToSearchQuoted, $row[$column], $this->invertedMatch)) { + $newArray[$key] = $row; + continue 2; + } + } + } + + return $newArray; + } } diff --git a/core/EventDispatcher.php b/core/EventDispatcher.php index 87e8ca59c2f12837ae79ff8e5998b27948c8e348..47ae487cde594e5695cce454a31b2f065838bf75 100644 --- a/core/EventDispatcher.php +++ b/core/EventDispatcher.php @@ -52,6 +52,8 @@ class EventDispatcher extends Singleton */ private $pluginManager; + private $pluginHooks = array(); + /** * Constructor. */ @@ -78,29 +80,36 @@ class EventDispatcher extends Singleton $this->pendingEvents[] = array($eventName, $params); } + $manager = $this->getPluginManager(); + if (empty($plugins)) { - $plugins = $this->getPluginManager()->getPluginsLoadedAndActivated(); + $plugins = $manager->getPluginsLoadedAndActivated(); } $callbacks = array(); // collect all callbacks to execute - foreach ($plugins as $plugin) { - if (is_string($plugin)) { - $plugin = $this->getPluginManager()->getLoadedPlugin($plugin); + foreach ($plugins as $pluginName) { + if (!is_string($pluginName)) { + $pluginName = $pluginName->getPluginName(); } - if (empty($plugin)) { - return; // may happen in unit tests + if (!isset($this->pluginHooks[$pluginName])) { + $plugin = $manager->getLoadedPlugin($pluginName); + $this->pluginHooks[$pluginName] = $plugin->getListHooksRegistered(); } - - $hooks = $plugin->getListHooksRegistered(); + $hooks = $this->pluginHooks[$pluginName]; if (isset($hooks[$eventName])) { list($pluginFunction, $callbackGroup) = $this->getCallbackFunctionAndGroupNumber($hooks[$eventName]); - $callbacks[$callbackGroup][] = is_string($pluginFunction) ? array($plugin, $pluginFunction) : $pluginFunction; + if (is_string($pluginFunction)) { + $plugin = $manager->getLoadedPlugin($pluginName); + $callbacks[$callbackGroup][] = array($plugin, $pluginFunction) ; + } else { + $callbacks[$callbackGroup][] = $pluginFunction; + } } } diff --git a/core/FrontController.php b/core/FrontController.php index d4aed5c31300681ac0b4403c8e9e775636cbbda3..8ee4f0c84a75a5e4cfba7b0ae8aca262ec9dc0f7 100644 --- a/core/FrontController.php +++ b/core/FrontController.php @@ -18,6 +18,7 @@ use Piwik\Exception\DatabaseSchemaIsNewerThanCodebaseException; use Piwik\Http\ControllerResolver; use Piwik\Http\Router; use Piwik\Plugin\Report; +use Piwik\Plugins\CoreAdminHome\CustomLogo; use Piwik\Session; /** @@ -389,28 +390,28 @@ class FrontController extends Singleton protected function handleMaintenanceMode() { - if (Config::getInstance()->General['maintenance_mode'] == 1 - && !Common::isPhpCliMode() - ) { - $format = Common::getRequestVar('format', ''); - - $message = "Piwik is in scheduled maintenance. Please come back later." - . " The administrator can disable maintenance by editing the file piwik/config/config.ini.php and removing the following: " - . " maintenance_mode=1 "; - if (Config::getInstance()->Tracker['record_statistics'] == 0) { - $message .= ' and record_statistics=0'; - } + if ((Config::getInstance()->General['maintenance_mode'] != 1) || Common::isPhpCliMode()) { + return; + } + Common::sendResponseCode(503); - $exception = new Exception($message); - // extend explain how to re-enable - // show error message when record stats = 0 - if (empty($format)) { - throw $exception; - } - $response = new ResponseBuilder($format); - echo $response->getResponseException($exception); - exit; + $logoUrl = null; + $faviconUrl = null; + try { + $logo = new CustomLogo(); + $logoUrl = $logo->getHeaderLogoUrl(); + $faviconUrl = $logo->getPathUserFavicon(); + } catch (Exception $ex) { } + $logoUrl = $logoUrl ?: 'plugins/Morpheus/images/logo-header.png'; + $faviconUrl = $faviconUrl ?: 'plugins/CoreHome/images/favicon.ico'; + + $page = file_get_contents(PIWIK_INCLUDE_PATH . '/plugins/Morpheus/templates/maintenance.tpl'); + $page = str_replace('%logoUrl%', $logoUrl, $page); + $page = str_replace('%faviconUrl%', $faviconUrl, $page); + $page = str_replace('%piwikTitle%', Piwik::getRandomTitle(), $page); + echo $page; + exit; } protected function handleSSLRedirection() diff --git a/core/Http.php b/core/Http.php index ffd0bf438304a74ba6b67acb8742eb29913879a7..82809c7fdb72c409a9c231ed8ec0d8905865e6d2 100644 --- a/core/Http.php +++ b/core/Http.php @@ -22,7 +22,7 @@ class Http /** * Returns the "best" available transport method for {@link sendHttpRequest()} calls. * - * @return string Either `'curl'`, `'fopen'` or `'socket'`. + * @return string|null Either curl, fopen, socket or null if no method is supported. * @api */ public static function getTransportMethod() diff --git a/core/Piwik.php b/core/Piwik.php index c846acec82d688e8dcb5be836fafe1bee040ea8e..fdb38ed753049d28505711f320b7c5f97645adb1 100644 --- a/core/Piwik.php +++ b/core/Piwik.php @@ -147,9 +147,6 @@ class Piwik 'Analytics', 'Real Time Analytics', 'Analytics in Real time', - 'Free/Libre Web Analytics', - 'Free Website Analytics', - 'Free Web Analytics', 'Analytics Platform', 'Data Platform', ); diff --git a/core/Plugin/Manager.php b/core/Plugin/Manager.php index dca1b19e0b4ba4c1ea08cb048777857937dc0b52..499278538088e11d15b126aab0177e2d43092faa 100644 --- a/core/Plugin/Manager.php +++ b/core/Plugin/Manager.php @@ -50,6 +50,8 @@ class Manager protected $doLoadPlugins = true; + private $pluginsLoadedAndActivated; + /** * @var Plugin[] */ @@ -441,6 +443,7 @@ class Manager */ private function clearCache($pluginName) { + $this->resetTransientCache(); Filesystem::deleteAllCacheOnUpdate($pluginName); } @@ -693,6 +696,7 @@ class Manager */ public function loadPlugins(array $pluginsToLoad) { + $this->resetTransientCache(); $this->pluginsToLoad = $this->makePluginsToLoad($pluginsToLoad); $this->reloadActivatedPlugins(); } @@ -780,15 +784,21 @@ class Manager */ public function getPluginsLoadedAndActivated() { - $plugins = $this->getLoadedPlugins(); - $enabled = $this->getActivatedPlugins(); + if (is_null($this->pluginsLoadedAndActivated)) { + $enabled = $this->getActivatedPlugins(); - if (empty($enabled)) { - return array(); + if (empty($enabled)) { + return array(); + } + + $plugins = $this->getLoadedPlugins(); + $enabled = array_combine($enabled, $enabled); + $plugins = array_intersect_key($plugins, $enabled); + + $this->pluginsLoadedAndActivated = $plugins; } - $enabled = array_combine($enabled, $enabled); - $plugins = array_intersect_key($plugins, $enabled); - return $plugins; + + return $this->pluginsLoadedAndActivated; } /** @@ -950,6 +960,11 @@ class Manager return "\\Piwik\\Plugins\\$pluginName\\$className"; } + private function resetTransientCache() + { + $this->pluginsLoadedAndActivated = null; + } + /** * Unload plugin * @@ -958,6 +973,8 @@ class Manager */ public function unloadPlugin($plugin) { + $this->resetTransientCache(); + if (!($plugin instanceof Plugin)) { $oPlugin = $this->loadPlugin($plugin); if ($oPlugin === null) { @@ -976,6 +993,8 @@ class Manager */ public function unloadPlugins() { + $this->resetTransientCache(); + $pluginsLoaded = $this->getLoadedPlugins(); foreach ($pluginsLoaded as $plugin) { $this->unloadPlugin($plugin); @@ -1006,6 +1025,8 @@ class Manager */ public function addLoadedPlugin($pluginName, Plugin $newPlugin) { + $this->resetTransientCache(); + $this->loadedPlugins[$pluginName] = $newPlugin; } diff --git a/core/Scheduler/Scheduler.php b/core/Scheduler/Scheduler.php index 7156e1be9c6ff24c7bc936e17d54b8a514d46bd5..4080cc89d182cc10cd626064c4a916b724c9111e 100644 --- a/core/Scheduler/Scheduler.php +++ b/core/Scheduler/Scheduler.php @@ -97,11 +97,13 @@ class Scheduler // remove from timetable tasks that are not active anymore $this->timetable->removeInactiveTasks($tasks); + $this->logger->info("Starting Scheduled tasks... "); + // for every priority level, starting with the highest and concluding with the lowest $executionResults = array(); - for ($priority = Task::HIGHEST_PRIORITY; - $priority <= Task::LOWEST_PRIORITY; - ++$priority) { + for ($priority = Task::HIGHEST_PRIORITY; $priority <= Task::LOWEST_PRIORITY; ++$priority) { + $this->logger->debug("Executing tasks with priority {priority}:", array('priority' => $priority)); + // loop through each task foreach ($tasks as $task) { // if the task does not have the current priority level, don't execute it yet @@ -113,19 +115,31 @@ class Scheduler $shouldExecuteTask = $this->timetable->shouldExecuteTask($taskName); if ($this->timetable->taskShouldBeRescheduled($taskName)) { - $this->timetable->rescheduleTask($task); + $rescheduledDate = $this->timetable->rescheduleTask($task); + + $this->logger->debug("Task {task} is scheduled to run again for {date}.", array('task' => $taskName, 'date' => $rescheduledDate)); } if ($shouldExecuteTask) { + $this->logger->info("Scheduler: executing task {taskName}...", array('taskName' => $taskName)); + + $timer = new Timer(); + $this->isRunningTask = true; $message = $this->executeTask($task); $this->isRunningTask = false; + $this->logger->info("Scheduler: finished. {timeElapsed}", array( + 'taskName' => $taskName, 'timeElapsed' => $timer + )); + $executionResults[] = array('task' => $taskName, 'output' => $message); } } } + $this->logger->info("done"); + return $executionResults; } diff --git a/core/Scheduler/Timetable.php b/core/Scheduler/Timetable.php index 5c9bc69696dec197225d74842d19675fb31e6ff7..c3c01ab1671d1db2223c4781986a81dd039740bb 100644 --- a/core/Scheduler/Timetable.php +++ b/core/Scheduler/Timetable.php @@ -105,9 +105,13 @@ class Timetable public function rescheduleTask(Task $task) { + $rescheduledTime = $task->getRescheduledTime(); + // update the scheduled time - $this->timetable[$task->getName()] = $task->getRescheduledTime(); + $this->timetable[$task->getName()] = $rescheduledTime; $this->save(); + + return Date::factory($rescheduledTime); } public function save() diff --git a/core/Settings/Setting.php b/core/Settings/Setting.php index 32883876715e9aaf8c44e4e87fc4bbfea1aad1a3..4b1b36e5a14f8dbcf49f20aff460b0261f449753 100644 --- a/core/Settings/Setting.php +++ b/core/Settings/Setting.php @@ -147,8 +147,6 @@ abstract class Setting protected $key; protected $name; - protected $writableByCurrentUser = false; - protected $readableByCurrentUser = false; /** * @var StorageInterface @@ -188,7 +186,7 @@ abstract class Setting */ public function isWritableByCurrentUser() { - return $this->writableByCurrentUser; + return false; } /** @@ -198,7 +196,7 @@ abstract class Setting */ public function isReadableByCurrentUser() { - return $this->readableByCurrentUser; + return false; } /** diff --git a/core/Settings/SystemSetting.php b/core/Settings/SystemSetting.php index 935aec13883ba96a346c4917495f5c307d46954e..94f7416c19c70e7d2e0dd94f2d380af79c2b18fb 100644 --- a/core/Settings/SystemSetting.php +++ b/core/Settings/SystemSetting.php @@ -31,6 +31,11 @@ class SystemSetting extends Setting */ public $readableByCurrentUser = false; + /** + * @var bool + */ + private $writableByCurrentUser = false; + /** * Constructor. * @@ -45,6 +50,27 @@ class SystemSetting extends Setting $this->readableByCurrentUser = $this->writableByCurrentUser; } + /** + * Returns `true` if this setting is writable for the current user, `false` if otherwise. In case it returns + * writable for the current user it will be visible in the Plugin settings UI. + * + * @return bool + */ + public function isWritableByCurrentUser() + { + return $this->writableByCurrentUser; + } + + /** + * Returns `true` if this setting can be displayed for the current user, `false` if otherwise. + * + * @return bool + */ + public function isReadableByCurrentUser() + { + return $this->readableByCurrentUser; + } + /** * Returns the display order. System settings are displayed before user settings. * diff --git a/core/Settings/UserSetting.php b/core/Settings/UserSetting.php index 80cf66ebf21a63a7053149b1a1a58e8a0851e563..60f63e3037b8b33a2acb9e1ce8be7518d3786a78 100644 --- a/core/Settings/UserSetting.php +++ b/core/Settings/UserSetting.php @@ -22,6 +22,12 @@ class UserSetting extends Setting { private $userLogin = null; + /** + * Null while not initialized, bool otherwise. + * @var null|bool + */ + private $hasReadAndWritePermission = null; + /** * Constructor. * @@ -34,9 +40,32 @@ class UserSetting extends Setting parent::__construct($name, $title); $this->setUserLogin($userLogin); + } + + /** + * Returns `true` if this setting can be displayed for the current user, `false` if otherwise. + * + * @return bool + */ + public function isReadableByCurrentUser() + { + return $this->isWritableByCurrentUser(); + } + + /** + * Returns `true` if this setting can be displayed for the current user, `false` if otherwise. + * + * @return bool + */ + public function isWritableByCurrentUser() + { + if (isset($this->hasReadAndWritePermission)) { + return $this->hasReadAndWritePermission; + } + + $this->hasReadAndWritePermission = Piwik::isUserHasSomeViewAccess(); - $this->writableByCurrentUser = Piwik::isUserHasSomeViewAccess(); - $this->readableByCurrentUser = Piwik::isUserHasSomeViewAccess(); + return $this->hasReadAndWritePermission; } /** diff --git a/core/Tracker.php b/core/Tracker.php index ff5694e3a07f688a9b69da403590cb8c73b1269e..2c38955ea08987b807997a14e49bb5b197d8f7f7 100644 --- a/core/Tracker.php +++ b/core/Tracker.php @@ -132,8 +132,6 @@ class Tracker if ($request->isEmptyRequest()) { Common::printDebug("The request is empty"); } else { - $this->loadTrackerPlugins(); - Common::printDebug("Current datetime: " . date("Y-m-d H:i:s", $request->getCurrentTimestamp())); $visit = Visit\Factory::make(); @@ -171,6 +169,13 @@ class Tracker } } + public static function restoreTrackerPlugins() + { + if (SettingsServer::isTrackerApiRequest() && Tracker::$initTrackerMode) { + Plugin\Manager::getInstance()->loadTrackerPlugins(); + } + } + public function getCountOfLoggedRequests() { return $this->countOfLoggedRequests; @@ -245,7 +250,9 @@ class Tracker } // Do not run scheduled tasks during tests - TrackerConfig::setConfigValue('scheduled_tasks_min_interval', 0); + if (!defined('DEBUG_FORCE_SCHEDULED_TASKS')) { + TrackerConfig::setConfigValue('scheduled_tasks_min_interval', 0); + } // if nothing found in _GET/_POST and we're doing a POST, assume bulk request. in which case, // we have to bypass authentication diff --git a/core/Tracker/Action.php b/core/Tracker/Action.php index b8928667816f750960fdf846ca40ab698cdee35a..6775a0a65dccde7981d0dc01e243e69ce7395ee2 100644 --- a/core/Tracker/Action.php +++ b/core/Tracker/Action.php @@ -125,7 +125,12 @@ abstract class Action private static function getAllActions(Request $request) { - $actions = Manager::getInstance()->findMultipleComponents('Actions', '\\Piwik\\Tracker\\Action'); + static $actions; + + if (is_null($actions)) { + $actions = Manager::getInstance()->findMultipleComponents('Actions', '\\Piwik\\Tracker\\Action'); + } + $instances = array(); foreach ($actions as $action) { diff --git a/core/Tracker/Cache.php b/core/Tracker/Cache.php index 22b57a9cc2e10866e3f62ff0f92cbfd57a5e4cdf..1c333069eb137946eb7e752b230f29c3e688eb2f 100644 --- a/core/Tracker/Cache.php +++ b/core/Tracker/Cache.php @@ -15,6 +15,8 @@ use Piwik\Common; use Piwik\Config; use Piwik\Option; use Piwik\Piwik; +use Piwik\Plugin; +use Piwik\SettingsServer; use Piwik\Tracker; /** @@ -105,6 +107,8 @@ class Cache $cache->save($cacheId, $content, self::getTtl()); } + Tracker::restoreTrackerPlugins(); + return $content; } @@ -160,6 +164,9 @@ class Cache Piwik::postEvent('Tracker.setTrackerCacheGeneral', array(&$cacheContent)); self::setCacheGeneral($cacheContent); Common::printDebug("General tracker cache was re-created."); + + Tracker::restoreTrackerPlugins(); + return $cacheContent; } diff --git a/core/Tracker/Request.php b/core/Tracker/Request.php index 23f01a555b1f889b3af551aa241f55e17ab77675..7194101ee38848369be9380804fe1ccea39bac56 100644 --- a/core/Tracker/Request.php +++ b/core/Tracker/Request.php @@ -20,6 +20,7 @@ use Piwik\Network\IPUtils; use Piwik\Piwik; use Piwik\Plugins\CustomVariables\CustomVariables; use Piwik\Tracker; +use Piwik\Cache as PiwikCache; /** * The Request object holding the http parameters for this tracking request. Use getParam() to fetch a named parameter. @@ -27,6 +28,10 @@ use Piwik\Tracker; */ class Request { + private $cdtCache; + private $idSiteCache; + private $paramsCache = array(); + /** * @var array */ @@ -108,13 +113,29 @@ class Request if ($shouldAuthenticate) { + try { + $idSite = $this->getIdSite(); + } catch (Exception $e) { + $this->isAuthenticated = false; + return; + } + if (empty($tokenAuth)) { $tokenAuth = Common::getRequestVar('token_auth', false, 'string', $this->params); } + $cache = PiwikCache::getTransientCache(); + $cacheKey = 'tracker_request_authentication_' . $idSite . '_' . $tokenAuth; + + if ($cache->contains($cacheKey)) { + Common::printDebug("token_auth is authenticated in cache!"); + $this->isAuthenticated = $cache->fetch($cacheKey); + return; + } + try { - $idSite = $this->getIdSite(); $this->isAuthenticated = self::authenticateSuperUserOrAdmin($tokenAuth, $idSite); + $cache->save($cacheKey, $this->isAuthenticated); } catch (Exception $e) { $this->isAuthenticated = false; } @@ -294,11 +315,11 @@ class Request // other 'bots' => array(0, 'int'), 'dp' => array(0, 'int'), - 'rec' => array(false, 'int'), + 'rec' => array(0, 'int'), 'new_visit' => array(0, 'int'), // Ecommerce - 'ec_id' => array(false, 'string'), + 'ec_id' => array('', 'string'), 'ec_st' => array(false, 'float'), 'ec_tx' => array(false, 'float'), 'ec_sh' => array(false, 'float'), @@ -306,24 +327,24 @@ class Request 'ec_items' => array('', 'json'), // Events - 'e_c' => array(false, 'string'), - 'e_a' => array(false, 'string'), - 'e_n' => array(false, 'string'), + 'e_c' => array('', 'string'), + 'e_a' => array('', 'string'), + 'e_n' => array('', 'string'), 'e_v' => array(false, 'float'), // some visitor attributes can be overwritten - 'cip' => array(false, 'string'), - 'cdt' => array(false, 'string'), - 'cid' => array(false, 'string'), - 'uid' => array(false, 'string'), + 'cip' => array('', 'string'), + 'cdt' => array('', 'string'), + 'cid' => array('', 'string'), + 'uid' => array('', 'string'), // Actions / pages - 'cs' => array(false, 'string'), + 'cs' => array('', 'string'), 'download' => array('', 'string'), 'link' => array('', 'string'), 'action_name' => array('', 'string'), 'search' => array('', 'string'), - 'search_cat' => array(false, 'string'), + 'search_cat' => array('', 'string'), 'search_count' => array(-1, 'int'), 'gt_ms' => array(-1, 'int'), @@ -334,6 +355,10 @@ class Request 'c_i' => array('', 'string'), ); + if (isset($this->paramsCache[$name])) { + return $this->paramsCache[$name]; + } + if (!isset($supportedParams[$name])) { throw new Exception("Requested parameter $name is not a known Tracking API Parameter."); } @@ -341,9 +366,18 @@ class Request $paramDefaultValue = $supportedParams[$name][0]; $paramType = $supportedParams[$name][1]; - $value = Common::getRequestVar($name, $paramDefaultValue, $paramType, $this->params); + if ($this->hasParam($name)) { + $this->paramsCache[$name] = Common::getRequestVar($name, $paramDefaultValue, $paramType, $this->params); + } else { + $this->paramsCache[$name] = $paramDefaultValue; + } + + return $this->paramsCache[$name]; + } - return $value; + private function hasParam($name) + { + return isset($this->params[$name]); } public function getParams() @@ -353,10 +387,12 @@ class Request public function getCurrentTimestamp() { - $cdt = $this->getCustomTimestamp(); + if (!isset($this->cdtCache)) { + $this->cdtCache = $this->getCustomTimestamp(); + } - if (!empty($cdt)) { - return $cdt; + if (!empty($this->cdtCache)) { + return $this->cdtCache; } return $this->timestamp; @@ -369,6 +405,10 @@ class Request protected function getCustomTimestamp() { + if (!$this->hasParam('cdt')) { + return false; + } + $cdt = $this->getParam('cdt'); if (empty($cdt)) { @@ -418,6 +458,10 @@ class Request public function getIdSite() { + if (isset($this->idSiteCache)) { + return $this->idSiteCache; + } + $idSite = Common::getRequestVar('idsite', 0, 'int', $this->params); /** @@ -438,6 +482,8 @@ class Request throw new UnexpectedWebsiteFoundException('Invalid idSite: \'' . $idSite . '\''); } + $this->idSiteCache = $idSite; + return $idSite; } diff --git a/core/Tracker/Response.php b/core/Tracker/Response.php index 5258d65cd2ba2e1c060e083bff91300496d9420b..3edd2b27c69b98e0b386c016e156b78bf02a59f3 100644 --- a/core/Tracker/Response.php +++ b/core/Tracker/Response.php @@ -74,6 +74,7 @@ class Response $this->outputApiResponse($tracker); Common::printDebug("Logging disabled, display transparent logo"); } elseif (!$tracker->hasLoggedRequests()) { + Common::sendResponseCode(400); Common::printDebug("Empty request => Piwik page"); echo "<a href='/'>Piwik</a> is a free/libre web <a href='http://piwik.org'>analytics</a> that lets you keep control of your data."; } else { diff --git a/core/Tracker/ScheduledTasksRunner.php b/core/Tracker/ScheduledTasksRunner.php index 2a4683a4cb3e904c5581141d944387b7c741ef1f..332f2b704714eec66b64d26b21ca754b514f7b9b 100644 --- a/core/Tracker/ScheduledTasksRunner.php +++ b/core/Tracker/ScheduledTasksRunner.php @@ -10,6 +10,7 @@ namespace Piwik\Tracker; +use Piwik\CliMulti; use Piwik\Common; use Piwik\Config; use Piwik\CronArchive; @@ -17,7 +18,6 @@ use Piwik\Option; use Piwik\Piwik; use Piwik\SettingsPiwik; use Piwik\Tracker; -use Piwik\Translate; class ScheduledTasksRunner { @@ -78,20 +78,20 @@ class ScheduledTasksRunner // Scheduled tasks assume Super User is running Piwik::setUserHasSuperUserAccess(); - ob_start(); - CronArchive::$url = SettingsPiwik::getPiwikUrl(); - $cronArchive = new CronArchive(); - $cronArchive->runScheduledTasksInTrackerMode(); + $tokens = CronArchive::getSuperUserTokenAuths(); + $tokenAuth = reset($tokens); - $resultTasks = ob_get_contents(); - ob_clean(); + $invokeScheduledTasksUrl = SettingsPiwik::getPiwikUrl() + . "?module=API&format=csv&convertToUnicode=0&method=CoreAdminHome.runScheduledTasks&trigger=archivephp&token_auth=$tokenAuth"; + + $cliMulti = new CliMulti(); + $responses = $cliMulti->request(array($invokeScheduledTasksUrl)); + $resultTasks = reset($responses); // restore original user privilege Piwik::setUserHasSuperUserAccess($isSuperUser); - foreach (explode('</pre>', $resultTasks) as $resultTask) { - Common::printDebug(str_replace('<pre>', '', $resultTask)); - } + Common::printDebug($resultTasks); Common::printDebug('Finished Scheduled Tasks.'); } else { diff --git a/core/Tracker/Visit.php b/core/Tracker/Visit.php index f672cd96da20727c1d358cd6778c9ac5a22f351c..4d81bc2f3baf2414d9756e19a9128f64a679bf56 100644 --- a/core/Tracker/Visit.php +++ b/core/Tracker/Visit.php @@ -54,6 +54,8 @@ class Visit implements VisitInterface protected $userSettings; protected $visitorCustomVariables = array(); + public static $dimensions; + /** * @param Request $request */ @@ -584,16 +586,18 @@ class Visit implements VisitInterface protected function getAllVisitDimensions() { - $dimensions = VisitDimension::getAllDimensions(); + if (is_null(self::$dimensions)) { + self::$dimensions = VisitDimension::getAllDimensions(); - $dimensionNames = array(); - foreach($dimensions as $dimension) { - $dimensionNames[] = $dimension->getColumnName(); - } + $dimensionNames = array(); + foreach(self::$dimensions as $dimension) { + $dimensionNames[] = $dimension->getColumnName(); + } - Common::printDebug("Following dimensions have been collected from plugins: " . implode(", ", $dimensionNames)); + Common::printDebug("Following dimensions have been collected from plugins: " . implode(", ", $dimensionNames)); + } - return $dimensions; + return self::$dimensions; } private function getVisitStandardLength() diff --git a/core/Tracker/Visit/Factory.php b/core/Tracker/Visit/Factory.php index 71362dddeacb5cd14007ae0d0d757c98e8aeaa6f..1d9349dfb1900e0a3b38360f3b734d6ba28cf572 100644 --- a/core/Tracker/Visit/Factory.php +++ b/core/Tracker/Visit/Factory.php @@ -37,7 +37,7 @@ class Factory */ Piwik::postEvent('Tracker.makeNewVisitObject', array(&$visit)); - if (is_null($visit)) { + if (!isset($visit)) { $visit = new Visit(); } elseif (!($visit instanceof VisitInterface)) { throw new Exception("The Visit object set in the plugin must implement VisitInterface"); diff --git a/core/Tracker/Visitor.php b/core/Tracker/Visitor.php index d9aadb640e1d226891713c0071c8852cb2b1c557..4088e724f1bb5bcdbd8552d316a08e8c2ea19434 100644 --- a/core/Tracker/Visitor.php +++ b/core/Tracker/Visitor.php @@ -173,54 +173,58 @@ class Visitor */ private function getVisitFieldsPersist() { - $fields = array( - 'idvisitor', - 'idvisit', - 'user_id', - - 'visit_exit_idaction_url', - 'visit_exit_idaction_name', - 'visitor_returning', - 'visitor_days_since_first', - 'visitor_days_since_order', - 'visitor_count_visits', - 'visit_goal_buyer', - - 'location_country', - 'location_region', - 'location_city', - 'location_latitude', - 'location_longitude', - - 'referer_name', - 'referer_keyword', - 'referer_type', - ); - - $dimensions = VisitDimension::getAllDimensions(); - - foreach ($dimensions as $dimension) { - if ($dimension->hasImplementedEvent('onExistingVisit')) { - $fields[] = $dimension->getColumnName(); - } + static $fields; + + if (is_null($fields)) { + $fields = array( + 'idvisitor', + 'idvisit', + 'user_id', + + 'visit_exit_idaction_url', + 'visit_exit_idaction_name', + 'visitor_returning', + 'visitor_days_since_first', + 'visitor_days_since_order', + 'visitor_count_visits', + 'visit_goal_buyer', + + 'location_country', + 'location_region', + 'location_city', + 'location_latitude', + 'location_longitude', + + 'referer_name', + 'referer_keyword', + 'referer_type', + ); + + $dimensions = VisitDimension::getAllDimensions(); + + foreach ($dimensions as $dimension) { + if ($dimension->hasImplementedEvent('onExistingVisit')) { + $fields[] = $dimension->getColumnName(); + } - foreach ($dimension->getRequiredVisitFields() as $field) { - $fields[] = $field; + foreach ($dimension->getRequiredVisitFields() as $field) { + $fields[] = $field; + } } - } - /** - * This event collects a list of [visit entity](/guides/persistence-and-the-mysql-backend#visits) properties that should be loaded when reading - * the existing visit. Properties that appear in this list will be available in other tracking - * events such as 'onExistingVisit'. - * - * Plugins can use this event to load additional visit entity properties for later use during tracking. - */ - Piwik::postEvent('Tracker.getVisitFieldsToPersist', array(&$fields)); - - array_unshift($fields, 'visit_first_action_time'); - array_unshift($fields, 'visit_last_action_time'); - $fields = array_unique($fields); + /** + * This event collects a list of [visit entity](/guides/persistence-and-the-mysql-backend#visits) properties that should be loaded when reading + * the existing visit. Properties that appear in this list will be available in other tracking + * events such as 'onExistingVisit'. + * + * Plugins can use this event to load additional visit entity properties for later use during tracking. + */ + Piwik::postEvent('Tracker.getVisitFieldsToPersist', array(&$fields)); + + array_unshift($fields, 'visit_first_action_time'); + array_unshift($fields, 'visit_last_action_time'); + $fields = array_unique($fields); + } return $fields; } diff --git a/core/Updates/2.13.0-b3.php b/core/Updates/2.13.0-b3.php new file mode 100644 index 0000000000000000000000000000000000000000..3d6cd83cea705727367cbf2fff2cbc20b0c84305 --- /dev/null +++ b/core/Updates/2.13.0-b3.php @@ -0,0 +1,27 @@ +<?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\Updates; + +use Piwik\Config; +use Piwik\Updater; +use Piwik\Updates; + +class Updates_2_13_0_b3 extends Updates +{ + public function doUpdate(Updater $updater) + { + $pluginManager = \Piwik\Plugin\Manager::getInstance(); + + try { + $pluginManager->activatePlugin('Diagnostics'); + } catch(\Exception $e) { + } + } +} diff --git a/core/UrlHelper.php b/core/UrlHelper.php index cf2eb468c8bf828858d7371c70a0704764d90393..20501515f2c1a2e467f115f6fe8f61ac5bc762ef 100644 --- a/core/UrlHelper.php +++ b/core/UrlHelper.php @@ -155,6 +155,14 @@ class UrlHelper if (strlen($urlQuery) == 0) { return array(); } + + $cache = Cache::getTransientCache(); + $cacheKey = 'arrayFromQuery' . $urlQuery; + + if ($cache->contains($cacheKey)) { + return $cache->fetch($cacheKey); + } + if ($urlQuery[0] == '?') { $urlQuery = substr($urlQuery, 1); } @@ -200,6 +208,9 @@ class UrlHelper $nameToValue[$name] = $value; } } + + $cache->save($cacheKey, $nameToValue); + return $nameToValue; } @@ -214,6 +225,7 @@ class UrlHelper public static function getParameterFromQueryString($urlQuery, $parameter) { $nameToValue = self::getArrayFromQueryString($urlQuery); + if (isset($nameToValue[$parameter])) { return $nameToValue[$parameter]; } diff --git a/core/Version.php b/core/Version.php index 67c8215c61958d38e87f65ac547e4d0d344289e6..1b63ba15cf88f9e72fc8d983a1e0959f598dba61 100644 --- a/core/Version.php +++ b/core/Version.php @@ -20,7 +20,7 @@ final class Version * The current Piwik version. * @var string */ - const VERSION = '2.13.0-b1'; + const VERSION = '2.13.0-b3'; public function isStableVersion($version) { diff --git a/core/testMinimumPhpVersion.php b/core/testMinimumPhpVersion.php index eec5a50d1b997a0bb9f33bf9b5fcec71c9f7f4c5..ee0671247b72651d64690a6ea42d8d3cddc9bac1 100644 --- a/core/testMinimumPhpVersion.php +++ b/core/testMinimumPhpVersion.php @@ -76,6 +76,12 @@ if (!function_exists('Piwik_GetErrorMessagePage')) { { $bool = (defined('PIWIK_PRINT_ERROR_BACKTRACE') && PIWIK_PRINT_ERROR_BACKTRACE) || !empty($GLOBALS['PIWIK_TRACKER_DEBUG']); + + try { + $bool = $bool || \Piwik\Development::isEnabled(); + } catch (Exception $e) { + } + return $bool; } @@ -113,7 +119,7 @@ if (!function_exists('Piwik_GetErrorMessagePage')) { } if ($optionalTrace) { - $optionalTrace = '<span class="exception-backtrace">Backtrace:<br /><pre>' . $optionalTrace . '</pre></span>'; + $optionalTrace = '<h2>Stack trace</h2><pre>' . $optionalTrace . '</pre></span>'; } if ($isCli === null) { @@ -130,7 +136,7 @@ if (!function_exists('Piwik_GetErrorMessagePage')) { </ul>'; } if ($optionalLinkBack) { - $optionalLinkBack = '<a href="javascript:window.history.back();">Go Back</a><br/>'; + $optionalLinkBack = '<a href="javascript:window.history.back();">Go Back</a>'; } $headerPage = file_get_contents(PIWIK_INCLUDE_PATH . '/plugins/Morpheus/templates/simpleLayoutHeader.tpl'); @@ -141,10 +147,10 @@ if (!function_exists('Piwik_GetErrorMessagePage')) { $headerPage = str_replace('{$HTML_TITLE}', PAGE_TITLE_WHEN_ERROR, $headerPage); - $content = '<p>' . $message . '</p> + $content = '<h2>' . $message . '</h2> <p>' . $optionalLinkBack - . '<a href="index.php">Go to Piwik</a><br/> + . ' | <a href="index.php">Go to Piwik</a> | <a href="index.php?module=Login">Login</a>' . '</p>' . ' ' . (Piwik_ShouldPrintBackTraceWithMessage() ? $optionalTrace : '') diff --git a/lang/en.json b/lang/en.json index 0c23f98ef5e6ce45c3094a6466db78cf6547f218..d8b02f220466c2ae5170d50d2270c91372e8fdf8 100644 --- a/lang/en.json +++ b/lang/en.json @@ -306,6 +306,8 @@ "OverlayRowActionTooltipTitle": "Open Page Overlay", "Overview": "Overview", "Pages": "Pages", + "Pagination": "%s - %s of %s", + "PaginationWithoutTotal": "%s - %s", "ParameterMustIntegerBetween": "Parameter %s must be an integer value between %s and %s.", "Password": "Password", "Period": "Period", diff --git a/misc/log-analytics b/misc/log-analytics index 770f7758b3285004d88ea0ff9634f6d410199c33..ef6fbd1bd55d1f924e57e26c82be4240573eaa71 160000 --- a/misc/log-analytics +++ b/misc/log-analytics @@ -1 +1 @@ -Subproject commit 770f7758b3285004d88ea0ff9634f6d410199c33 +Subproject commit ef6fbd1bd55d1f924e57e26c82be4240573eaa71 diff --git a/plugins/API/Controller.php b/plugins/API/Controller.php index d11f953704f79b25e0652ee1bdeba08476b056c1..17dbe3fd124bbf66013ea64deedc127f01d19cd1 100644 --- a/plugins/API/Controller.php +++ b/plugins/API/Controller.php @@ -14,6 +14,7 @@ use Piwik\API\Request; use Piwik\Common; use Piwik\Config; use Piwik\Piwik; +use Piwik\Plugin\Report; use Piwik\Url; use Piwik\View; @@ -24,12 +25,15 @@ class Controller extends \Piwik\Plugin\Controller { function index() { + $token = 'token_auth=' . Common::getRequestVar('token_auth', 'anonymous', 'string'); + // when calling the API through http, we limit the number of returned results if (!isset($_GET['filter_limit'])) { $_GET['filter_limit'] = Config::getInstance()->General['API_datatable_default_limit']; + $token .= '&api_datatable_default_limit=' . $_GET['filter_limit']; } - $request = new Request('token_auth=' . Common::getRequestVar('token_auth', 'anonymous', 'string')); + $request = new Request($token); $response = $request->process(); if (is_array($response)) { diff --git a/plugins/Actions/templates/indexSiteSearch.twig b/plugins/Actions/templates/indexSiteSearch.twig index 7d6093c6ace93d2adf2c9a4420e046b4f104d5aa..8d9eaa0909f01ccc224012e404d488a272f1ac05 100644 --- a/plugins/Actions/templates/indexSiteSearch.twig +++ b/plugins/Actions/templates/indexSiteSearch.twig @@ -1,17 +1,21 @@ -<div id='leftcolumn'> - <h2 piwik-enriched-headline>{{ 'Actions_WidgetSearchKeywords'|translate }}</h2> - {{ keywords|raw }} +<div class="row"> - <h2 piwik-enriched-headline>{{ 'Actions_WidgetSearchNoResultKeywords'|translate }}</h2> - {{ noResultKeywords|raw }} + <div class="col-md-6"> + <h2 piwik-enriched-headline>{{ 'Actions_WidgetSearchKeywords'|translate }}</h2> + {{ keywords|raw }} - {% if categories is defined %} - <h2 piwik-enriched-headline>{{ 'Actions_WidgetSearchCategories'|translate }}</h2> - {{ categories|raw }} - {% endif %} -</div> + <h2 piwik-enriched-headline>{{ 'Actions_WidgetSearchNoResultKeywords'|translate }}</h2> + {{ noResultKeywords|raw }} + + {% if categories is defined %} + <h2 piwik-enriched-headline>{{ 'Actions_WidgetSearchCategories'|translate }}</h2> + {{ categories|raw }} + {% endif %} + </div> + + <div class="col-md-6"> + <h2 piwik-enriched-headline>{{ 'Actions_WidgetPageUrlsFollowingSearch'|translate }}</h2> + {{ pagesUrlsFollowingSiteSearch|raw }} + </div> -<div id='rightcolumn'> - <h2 piwik-enriched-headline>{{ 'Actions_WidgetPageUrlsFollowingSearch'|translate }}</h2> - {{ pagesUrlsFollowingSiteSearch|raw }} </div> diff --git a/plugins/CoreAdminHome/API.php b/plugins/CoreAdminHome/API.php index 6329fc8993640b2102e0ac8bdc0fd351427c9510..2b809b792a20bfbf963ed0ceba3b0a68e3c409e4 100644 --- a/plugins/CoreAdminHome/API.php +++ b/plugins/CoreAdminHome/API.php @@ -15,12 +15,23 @@ use Piwik\Db; use Piwik\Piwik; use Piwik\Scheduler\Scheduler; use Piwik\Site; +use Psr\Log\LoggerInterface; /** * @method static \Piwik\Plugins\CoreAdminHome\API getInstance() */ class API extends \Piwik\Plugin\API { + /** + * @var Scheduler + */ + private $scheduler; + + public function __construct(Scheduler $scheduler) + { + $this->scheduler = $scheduler; + } + /** * Will run all scheduled tasks due to run at this time. * @@ -31,10 +42,7 @@ class API extends \Piwik\Plugin\API { Piwik::checkUserHasSuperUserAccess(); - /** @var Scheduler $scheduler */ - $scheduler = StaticContainer::getContainer()->get('Piwik\Scheduler\Scheduler'); - - return $scheduler->run(); + return $this->scheduler->run(); } /** diff --git a/plugins/CoreAdminHome/Commands/PurgeOldArchiveData.php b/plugins/CoreAdminHome/Commands/PurgeOldArchiveData.php index bad6710d9fca959dd07a7cca22869b53706cad14..de1d58745bc4142c9f52b436f65f62fe48a4d5a1 100644 --- a/plugins/CoreAdminHome/Commands/PurgeOldArchiveData.php +++ b/plugins/CoreAdminHome/Commands/PurgeOldArchiveData.php @@ -15,6 +15,7 @@ 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; @@ -28,11 +29,6 @@ class PurgeOldArchiveData extends ConsoleCommand { const ALL_DATES_STRING = 'all'; - /** - * @var ArchivePurger - */ - private $archivePurger; - /** * For tests. * @@ -40,11 +36,16 @@ class PurgeOldArchiveData extends ConsoleCommand */ public static $todayOverride = null; + /** + * @var ArchivePurger + */ + private $archivePurger; + public function __construct(ArchivePurger $archivePurger = null) { parent::__construct(); - - $this->archivePurger = $archivePurger ?: new ArchivePurger(); + + $this->archivePurger = $archivePurger; } protected function configure() @@ -68,7 +69,14 @@ class PurgeOldArchiveData extends ConsoleCommand protected function execute(InputInterface $input, OutputInterface $output) { - $archivePurger = $this->archivePurger; + // 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); diff --git a/plugins/CoreAdminHome/Tasks.php b/plugins/CoreAdminHome/Tasks.php index 700c7d69c279141ae92c0b79d5aa9f541dec70fe..7a3ff406b52488c6f60681a25e5b2537946ca4a4 100644 --- a/plugins/CoreAdminHome/Tasks.php +++ b/plugins/CoreAdminHome/Tasks.php @@ -14,9 +14,8 @@ use Piwik\Container\StaticContainer; use Piwik\DataAccess\ArchiveTableCreator; use Piwik\Date; use Piwik\Db; -use Piwik\Log; use Piwik\Plugins\CoreAdminHome\Tasks\ArchivesToPurgeDistributedList; -use Piwik\SettingsServer; +use Psr\Log\LoggerInterface; class Tasks extends \Piwik\Plugin\Tasks { @@ -25,9 +24,15 @@ class Tasks extends \Piwik\Plugin\Tasks */ private $archivePurger; - public function __construct(ArchivePurger $archivePurger = null) + /** + * @var LoggerInterface + */ + private $logger; + + public function __construct(ArchivePurger $archivePurger, LoggerInterface $logger) { - $this->archivePurger = $archivePurger ?: new ArchivePurger(); + $this->archivePurger = $archivePurger; + $this->logger = $logger; } public function schedule() @@ -44,16 +49,14 @@ class Tasks extends \Piwik\Plugin\Tasks public function purgeOutdatedArchives() { - $logger = StaticContainer::get('Psr\Log\LoggerInterface'); - if ($this->willPurgingCausePotentialProblemInUI()) { - $logger->info("Purging temporary archives: skipped (browser triggered archiving not enabled & not running after core:archive)"); + $this->logger->info("Purging temporary archives: skipped (browser triggered archiving not enabled & not running after core:archive)"); return false; } $archiveTables = ArchiveTableCreator::getTablesArchivesInstalled(); - $logger->info("Purging archives in {tableCount} archive tables.", array('tableCount' => count($archiveTables))); + $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) @@ -64,15 +67,19 @@ class Tasks extends \Piwik\Plugin\Tasks list($year, $month) = explode('_', $date); // Somehow we may have archive tables created with older dates, prevent exception from being thrown - if ($year > 1990 - && empty($datesPurged[$date]) - ) { - $dateObj = Date::factory("$year-$month-15"); - - $this->archivePurger->purgeOutdatedArchives($dateObj); - $this->archivePurger->purgeArchivesWithPeriodRange($dateObj); - - $datesPurged[$date] = true; + 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)); } } } diff --git a/plugins/CoreAdminHome/stylesheets/pluginSettings.less b/plugins/CoreAdminHome/stylesheets/pluginSettings.less index 0d8c8469cfd46787da45076d5efec0b233c9f7a3..b7e90637e952bacc97d774dd54313a9bc1b73a05 100644 --- a/plugins/CoreAdminHome/stylesheets/pluginSettings.less +++ b/plugins/CoreAdminHome/stylesheets/pluginSettings.less @@ -1,6 +1,6 @@ #pluginSettings { width: 975px; - border-spacing: 0px 15px; + margin: 15px 0; .columnTitle { width:400px @@ -21,7 +21,7 @@ } .settingIntroduction { - padding-bottom: 0px; + padding-bottom: 0; } .form-description { @@ -39,6 +39,6 @@ .submitSeparator { background-color: #DADADA; height: 1px; - border: 0px; + border: 0; } } \ No newline at end of file diff --git a/plugins/CoreAdminHome/tests/Integration/TasksTest.php b/plugins/CoreAdminHome/tests/Integration/TasksTest.php index da3800e9c199042488b3979f123e8c75f199eea2..ff4822fbce1b96ebd6878ff2eaf99f2f70df7fdd 100644 --- a/plugins/CoreAdminHome/tests/Integration/TasksTest.php +++ b/plugins/CoreAdminHome/tests/Integration/TasksTest.php @@ -13,6 +13,7 @@ use Piwik\Plugins\CoreAdminHome\Tasks; use Piwik\Plugins\CoreAdminHome\Tasks\ArchivesToPurgeDistributedList; use Piwik\Tests\Fixtures\RawArchiveDataWithTempAndInvalidated; use Piwik\Tests\Framework\TestCase\IntegrationTestCase; +use Psr\Log\NullLogger; /** * @group Core @@ -51,7 +52,7 @@ class TasksTest extends IntegrationTestCase $archivePurger->setYesterdayDate(Date::factory('2015-02-26')); $archivePurger->setNow(Date::factory('2015-02-27 08:00:00')->getTimestamp()); - $this->tasks = new Tasks($archivePurger); + $this->tasks = new Tasks($archivePurger, new NullLogger()); } public function test_purgeInvalidatedArchives_PurgesCorrectInvalidatedArchives_AndOnlyPurgesDataForDatesAndSites_InInvalidatedReportsDistributedList() diff --git a/plugins/CoreConsole/Commands/CoreArchiver.php b/plugins/CoreConsole/Commands/CoreArchiver.php index ec151bf0ef405f39a7f9f8c605392d9a7dce5c2d..b44245988abe7801fa8d4151100c213ca62f4039 100644 --- a/plugins/CoreConsole/Commands/CoreArchiver.php +++ b/plugins/CoreConsole/Commands/CoreArchiver.php @@ -87,7 +87,7 @@ class CoreArchiver extends ConsoleCommand * If you have any suggestion about this script, please let the team know at feedback@piwik.org * Enjoy!"); $command->addOption('url', null, InputOption::VALUE_REQUIRED, "Mandatory option as an alternative to '--piwik-domain'. Must be set to the Piwik base URL.\nFor example: --url=http://analytics.example.org/ or --url=https://example.org/piwik/"); - $command->addOption('force-all-websites', null, InputOption::VALUE_NONE, "If specified, the script will trigger archiving on all websites.\nUse with --force-all-periods=[seconds] to also process those websites\nthat had visits in the last [seconds] seconds."); + $command->addOption('force-all-websites', null, InputOption::VALUE_NONE, "If specified, the script will trigger archiving on all websites.\nUse with --force-all-periods=[seconds] to also process those websites that had visits in the last [seconds] seconds.\n##Launching several processes with this option will make them share the list of sites to process."); $command->addOption('force-all-periods', null, InputOption::VALUE_OPTIONAL, "Limits archiving to websites with some traffic in the last [seconds] seconds. \nFor example --force-all-periods=86400 will archive websites that had visits in the last 24 hours. \nIf [seconds] is not specified, all websites with visits in the last " . CronArchive::ARCHIVE_SITES_WITH_TRAFFIC_SINCE . "\n seconds (" . round(CronArchive::ARCHIVE_SITES_WITH_TRAFFIC_SINCE / 86400) . " days) will be archived."); $command->addOption('force-timeout-for-periods', null, InputOption::VALUE_OPTIONAL, "The current week/ current month/ current year will be processed at most every [seconds].\nIf not specified, defaults to " . CronArchive::SECONDS_DELAY_BETWEEN_PERIOD_ARCHIVES . "."); $command->addOption('skip-idsites', null, InputOption::VALUE_OPTIONAL, 'If specified, archiving will be skipped for these websites (in case these website ids would have been archived).'); diff --git a/plugins/CoreHome/angularjs/siteselector/siteselector-model.service.js b/plugins/CoreHome/angularjs/siteselector/siteselector-model.service.js index 7fe4d524e1c02531b64e125cc4f68ef7da363c7b..f74624459f72fbbcec1ee5d75c5a3146ec97aa84 100644 --- a/plugins/CoreHome/angularjs/siteselector/siteselector-model.service.js +++ b/plugins/CoreHome/angularjs/siteselector/siteselector-model.service.js @@ -35,6 +35,13 @@ angular.forEach(sites, function (site) { if (site.group) site.name = '[' + site.group + '] ' + site.name; + if (!site.name) { + return; + } + // Escape site names, see https://github.com/piwik/piwik/issues/7531 + site.name = site.name.replace(/[\u0000-\u2666]/g, function(c) { + return '&#'+c.charCodeAt(0)+';'; + }); }); model.sites = $filter('orderBy')(sites, '+name'); diff --git a/plugins/CoreHome/angularjs/siteselector/siteselector.controller.js b/plugins/CoreHome/angularjs/siteselector/siteselector.controller.js index 48befb08e6caaf6115721323613f0b1f15fedc2f..79e2439dad1e275b9241ec598a5661c7b9a37156 100644 --- a/plugins/CoreHome/angularjs/siteselector/siteselector.controller.js +++ b/plugins/CoreHome/angularjs/siteselector/siteselector.controller.js @@ -16,7 +16,6 @@ $scope.autocompleteMinSites = AUTOCOMPLETE_MIN_SITES; $scope.selectedSite = {id: '', name: ''}; $scope.activeSiteId = piwik.idSite; - $scope.selectedSiteNameHtml = ''; $scope.switchSite = function (site) { $scope.selectedSite = {id: site.idsite, name: site.name}; @@ -40,15 +39,6 @@ '#' + piwik.helper.getQueryStringWithParametersModified(hash.substring(1), newParameters); }; - $scope.$watch('selectedSite', function (site) { - if (!site.name) { - return; - } - $scope.selectedSiteNameHtml = site.name.replace(/[\u0000-\u2666]/g, function(c) { - return '&#'+c.charCodeAt(0)+';'; - }); - }); - siteSelectorModel.loadInitialSites(); } })(); diff --git a/plugins/CoreHome/angularjs/siteselector/siteselector.directive.html b/plugins/CoreHome/angularjs/siteselector/siteselector.directive.html index dc53f3986d955f1ad66e2918bdad12c423129376..5f6a3cac8ad029ca3c96fbed82095c33e6e2cc74 100644 --- a/plugins/CoreHome/angularjs/siteselector/siteselector.directive.html +++ b/plugins/CoreHome/angularjs/siteselector/siteselector.directive.html @@ -16,7 +16,7 @@ href="javascript:void(0)" class="custom_select_main_link" ng-class="{'loading': model.isLoading}"> - <span ng-bind-html="selectedSiteNameHtml || model.firstSiteName">?</span> + <span ng-bind-html="selectedSite.name || model.firstSiteName">?</span> </a> <div ng-show="view.showSitesList" class="custom_select_block"> @@ -29,7 +29,8 @@ ng-repeat="site in model.sites" ng-hide="!showSelectedSite && activeSiteId==site.idsite"> <a piwik-ignore-click href="{{ getUrlForSiteId(site.idsite) }}" - piwik-autocomplete-matched="view.searchTerm">{{ site.name }}</a> + piwik-autocomplete-matched="view.searchTerm" + ng-bind-html="site.name"></a> </li> </ul> <ul ng-show="!model.sites.length && view.searchTerm" class="ui-autocomplete ui-front ui-menu ui-widget ui-widget-content ui-corner-all siteSelect"> diff --git a/plugins/CoreHome/angularjs/siteselector/siteselector.directive.less b/plugins/CoreHome/angularjs/siteselector/siteselector.directive.less index 4f1bd44a2019b304cd2e39d49324554113efa217..195178a8bf21d2c5fc5a5b3b2b81a3b4615e5e73 100644 --- a/plugins/CoreHome/angularjs/siteselector/siteselector.directive.less +++ b/plugins/CoreHome/angularjs/siteselector/siteselector.directive.less @@ -27,7 +27,7 @@ table.dataTable tr td .sites_autocomplete a { .top_bar_sites_selector > .sites_autocomplete { position: static; - padding-left: 12px; + margin-left: 12px; } .autocompleteMatched { @@ -52,9 +52,7 @@ table.dataTable tr td .sites_autocomplete a { text-decoration: none; background: none; cursor: default; - height:1.4em; - padding-top: 9px; - padding-bottom: 9px; + padding: 9px 5px; } #content.admin .adminTable .sites_autocomplete a { @@ -69,7 +67,7 @@ table.dataTable tr td .sites_autocomplete a { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - padding: 0 20px 0 4px; + padding: 0 10px 0 4px; } .sites_autocomplete--dropdown .custom_select_main_link:not(.loading):before { @@ -168,7 +166,7 @@ table.dataTable tr td .sites_autocomplete a { overflow: hidden; max-width: inherit; visibility: visible; - padding-bottom: 7px; + padding: 0 5px 7px 5px; } .custom_select_block_show { diff --git a/plugins/CoreHome/stylesheets/_donate.less b/plugins/CoreHome/stylesheets/_donate.less index 5e907d491a5817de852c9162090fc584dc90cff0..b5ae42f6b79a5b3810bbd5502e122d18416521c5 100755 --- a/plugins/CoreHome/stylesheets/_donate.less +++ b/plugins/CoreHome/stylesheets/_donate.less @@ -1,8 +1,8 @@ .piwik-donate-call { - padding: 1em; + padding: 13px; border: 1px solid #CCC; border-radius: 4px; - max-width: 432px; + max-width: 458px; position: relative; } @@ -50,11 +50,9 @@ .piwik-donate-slider .slider-donate-amount { display: inline-block; - padding: .3em .5em .3em .5em; margin: 16px 8px 0 0; vertical-align: top; - width: 48px; text-align: center; background-color: #CCC; color: #333; diff --git a/plugins/CoreHome/stylesheets/coreHome.less b/plugins/CoreHome/stylesheets/coreHome.less index 848b7429b4da80c9b17b14f5045575208486d9eb..3f45859af5e546de3b1f4a1cd6290bb7bbb48403 100644 --- a/plugins/CoreHome/stylesheets/coreHome.less +++ b/plugins/CoreHome/stylesheets/coreHome.less @@ -6,29 +6,24 @@ h1 { clear: both; } -h2 { - font-size: 18px; - font-weight: normal; - color: #7e7363; - padding: 12px 0 7px 0; - clear: both; -} - -h2 a { - text-decoration: none; - color: #7e7363; -} - -h3 { - font-size: 1.3em; - margin-top: 2em; - color: #1D3256; -} - -.home p { - padding-bottom: 1em; - margin-right: 1em; - margin-left: 1em; +.home { + h2 { + font-size: 18px; + padding: 12px 0 7px 0; + clear: both; + border: none; + margin: 0; + } + + h3 { + font-size: 1.3em; + margin-top: 2em; + } + p { + padding-bottom: 1em; + margin-right: 1em; + margin-left: 1em; + } } .nav_sep { @@ -38,13 +33,8 @@ h3 { } .pageWrap { - border-left: 1px solid #DDDDDD; - border-right: 1px solid #DDDDDD; - min-height: 10px; - overflow: visible; - padding: 15px 15px 0; - position: relative; - font-size: 13px; + margin-top: 15px; + font-size: 13px; } /* Content */ diff --git a/plugins/CoreHome/stylesheets/dataTable/_dataTable.less b/plugins/CoreHome/stylesheets/dataTable/_dataTable.less index b4b69508549d250bed855d87821d78f9db9c5f66..90efbd892bd4e5bb675229bb194dc27a00d81ef1 100644 --- a/plugins/CoreHome/stylesheets/dataTable/_dataTable.less +++ b/plugins/CoreHome/stylesheets/dataTable/_dataTable.less @@ -318,7 +318,7 @@ div.dataTable, div.dataTable > .dataTableWrapper { margin-right: auto; background-color: @theme-color-background-base; border: 1px solid @theme-color-background-tinyContrast; - height: 7px; + height: 9px; width: 70px; -webkit-border-bottom-left-radius: 10px; -webkit-border-bottom-right-radius: 10px; @@ -369,7 +369,7 @@ div.dataTable, div.dataTable > .dataTableWrapper { border-top-left-radius: 10px; border-top-right-radius: 10px; line-height: 0px; - height: 7px; + height: 9px; width: 70px; clear: both; @@ -444,7 +444,7 @@ div.dataTable, div.dataTable > .dataTableWrapper { } .dataTableFooterIcons { - min-height: 20px; + min-height: 26px; white-space: nowrap; font-size: 10px; padding: 6px 5px 0px; diff --git a/plugins/CoreHome/stylesheets/dataTable/_limitSelection.less b/plugins/CoreHome/stylesheets/dataTable/_limitSelection.less index 1e77d030d7fc8148e7ecfa261808ffd132648e32..ffdc9e598537f6307063e0f953c72ad95900edbb 100644 --- a/plugins/CoreHome/stylesheets/dataTable/_limitSelection.less +++ b/plugins/CoreHome/stylesheets/dataTable/_limitSelection.less @@ -16,8 +16,8 @@ background: url(plugins/Morpheus/images/sort_subtable_desc_light.png) no-repeat right 2px; padding: 0 14px 0 4px; display: block; - width: 24px; - height: 20px; + width: 41px; + height: 26px; cursor: pointer; } diff --git a/plugins/CoreHome/stylesheets/menu.less b/plugins/CoreHome/stylesheets/menu.less index 703470a95683a7ab0882b4cedbadd8ca716f9bf7..76c8b9023808fa5a7673f78f672d547e4b918121 100644 --- a/plugins/CoreHome/stylesheets/menu.less +++ b/plugins/CoreHome/stylesheets/menu.less @@ -68,7 +68,7 @@ display: block; float: left; padding: 8px 27px 0; - height: 25px; + height: 50px; text-decoration: none; font-weight: normal; } diff --git a/plugins/CoreHome/templates/_indexContent.twig b/plugins/CoreHome/templates/_indexContent.twig index c0bc872bd7db78d60d65ecde27e4012617bbde7e..46d2f135796b3fdc96ead5940d3bd986e4108fd0 100644 --- a/plugins/CoreHome/templates/_indexContent.twig +++ b/plugins/CoreHome/templates/_indexContent.twig @@ -1,5 +1,5 @@ {% import 'ajaxMacros.twig' as ajax %} -<div class="pageWrap"> +<div class="pageWrap container-fluid"> <a name="main"></a> {% include "@CoreHome/_notifications.twig" %} <div class="top_controls"> @@ -17,5 +17,3 @@ </div> <div class="clear"></div> </div> - -<br/><br/> diff --git a/plugins/CorePluginsAdmin/stylesheets/marketplace.less b/plugins/CorePluginsAdmin/stylesheets/marketplace.less index ade343711498801ceab9f626152239a0cf17aebe..dba8ce15233e844c0b1ffdf04cc5f7f8b7e4ba27 100644 --- a/plugins/CorePluginsAdmin/stylesheets/marketplace.less +++ b/plugins/CorePluginsAdmin/stylesheets/marketplace.less @@ -80,12 +80,9 @@ clear: right; .plugin { - width: 280px; - float: left; border: 1px solid #dadada; padding: 15px; background-color: #f2f2f2; - margin-right: 14px; margin-bottom: 15px; position: relative; @@ -117,7 +114,7 @@ &.even { padding-right: 0px; - width: 140px; + width: 130px; overflow: hidden; white-space: nowrap; } @@ -212,7 +209,6 @@ height: 250px; } .footer { - height: 55px; position: absolute; bottom: 7px; left: 0px; diff --git a/plugins/CorePluginsAdmin/templates/browsePlugins.twig b/plugins/CorePluginsAdmin/templates/browsePlugins.twig index 068d9e80abee4e58dabe24e580f01542c1897a35..6dad365703d553e33dc01dea1bc238ee90ae537a 100644 --- a/plugins/CorePluginsAdmin/templates/browsePlugins.twig +++ b/plugins/CorePluginsAdmin/templates/browsePlugins.twig @@ -25,22 +25,24 @@ </div> {% endif %} - <div class="pluginslist"> + <div class="row pluginslist"> {% if plugins|length %} {% for plugin in plugins %} - <div class="plugin"> - <div class="content" data-pluginName="{{ plugin.name }}"> - {% include "@CorePluginsAdmin/pluginOverview.twig" %} - </div> - - <div class="footer" data-pluginName="{{ plugin.name }}"> - {% if plugin.featured %} - {{ pluginsMacro.featuredIcon('right') }} - {% endif %} - {% include "@CorePluginsAdmin/pluginMetadata.twig" %} + <div class="col-md-4"> + <div class="plugin"> + <div class="content" data-pluginName="{{ plugin.name }}"> + {% include "@CorePluginsAdmin/pluginOverview.twig" %} + </div> + + <div class="footer" data-pluginName="{{ plugin.name }}"> + {% if plugin.featured %} + {{ pluginsMacro.featuredIcon('right') }} + {% endif %} + {% include "@CorePluginsAdmin/pluginMetadata.twig" %} + </div> </div> </div> diff --git a/plugins/CorePluginsAdmin/templates/browseThemes.twig b/plugins/CorePluginsAdmin/templates/browseThemes.twig index 685ef4ebb3c3f282e373e643f1ef4e58a31c9de0..1c8066b3f7e906e5b315632a44b3404b524b0d6f 100644 --- a/plugins/CorePluginsAdmin/templates/browseThemes.twig +++ b/plugins/CorePluginsAdmin/templates/browseThemes.twig @@ -24,18 +24,20 @@ </div> {% endif %} - <div class="pluginslist themes"> + <div class="row pluginslist themes"> {% if plugins|length %} {% for plugin in plugins %} - <div class="plugin"> - <div class="content" data-pluginName="{{ plugin.name }}"> - {% include "@CorePluginsAdmin/themeOverview.twig" %} - </div> + <div class="col-md-4"> + <div class="plugin"> + <div class="content" data-pluginName="{{ plugin.name }}"> + {% include "@CorePluginsAdmin/themeOverview.twig" %} + </div> - <div class="footer" data-pluginName="{{ plugin.name }}"> - {% include "@CorePluginsAdmin/pluginMetadata.twig" %} + <div class="footer" data-pluginName="{{ plugin.name }}"> + {% include "@CorePluginsAdmin/pluginMetadata.twig" %} + </div> </div> </div> diff --git a/plugins/CoreUpdater/Controller.php b/plugins/CoreUpdater/Controller.php index d15dc231e6178337233078d85a97d3f367e82b4b..47a06f226d77cd9cd032d194f88c9d0521808e4f 100644 --- a/plugins/CoreUpdater/Controller.php +++ b/plugins/CoreUpdater/Controller.php @@ -42,6 +42,8 @@ class Controller extends \Piwik\Plugin\Controller public function __construct(Updater $updater) { $this->updater = $updater; + + parent::__construct(); } public function newVersionAvailable() @@ -53,6 +55,7 @@ class Controller extends \Piwik\Plugin\Controller $view = new View('@CoreUpdater/newVersionAvailable'); $this->addCustomLogoInfo($view); + $this->setBasicVariablesView($view); $view->piwik_version = Version::VERSION; $view->piwik_new_version = $newVersion; @@ -102,11 +105,22 @@ class Controller extends \Piwik\Plugin\Controller public function oneClickResults() { - $view = new View('@CoreUpdater/oneClickResults'); - $view->error = Common::getRequestVar('error', '', 'string', $_POST); - $view->feedbackMessages = safe_unserialize(Common::unsanitizeInputValue(Common::getRequestVar('messages', '', 'string', $_POST))); - $view->httpsFail = (bool) Common::getRequestVar('httpsFail', 0, 'int', $_POST); + $httpsFail = (bool) Common::getRequestVar('httpsFail', 0, 'int', $_POST); + $error = Common::getRequestVar('error', '', 'string', $_POST); + + if ($httpsFail) { + $view = new View('@CoreUpdater/updateHttpsError'); + $view->error = $error; + } elseif ($error) { + $view = new View('@CoreUpdater/updateHttpError'); + $view->error = $error; + $view->feedbackMessages = safe_unserialize(Common::unsanitizeInputValue(Common::getRequestVar('messages', '', 'string', $_POST))); + } else { + $view = new View('@CoreUpdater/updateSuccess'); + } + $this->addCustomLogoInfo($view); + $this->setBasicVariablesView($view); return $view->render(); } @@ -158,9 +172,11 @@ class Controller extends \Piwik\Plugin\Controller $viewWelcome = new View($welcomeTemplate); $this->addCustomLogoInfo($viewWelcome); + $this->setBasicVariablesView($viewWelcome); $viewDone = new View($doneTemplate); $this->addCustomLogoInfo($viewDone); + $this->setBasicVariablesView($viewDone); $doExecuteUpdates = Common::getRequestVar('updateCorePlugins', 0, 'integer') == 1; @@ -191,7 +207,7 @@ class Controller extends \Piwik\Plugin\Controller private function doWelcomeUpdates($view, $componentsWithUpdateFile) { $view->new_piwik_version = Version::VERSION; - $view->commandUpgradePiwik = "<br /><code>php " . Filesystem::getPathToPiwikRoot() . "/console core:update </code>"; + $view->commandUpgradePiwik = "php " . Filesystem::getPathToPiwikRoot() . "/console core:update"; $pluginNamesToUpdate = array(); $dimensionsToUpdate = array(); $coreToUpdate = false; diff --git a/plugins/CoreUpdater/Diagnostic/HttpsUpdateCheck.php b/plugins/CoreUpdater/Diagnostic/HttpsUpdateCheck.php new file mode 100644 index 0000000000000000000000000000000000000000..f07e00449930a23428eb53c35cd82971640bd461 --- /dev/null +++ b/plugins/CoreUpdater/Diagnostic/HttpsUpdateCheck.php @@ -0,0 +1,38 @@ +<?php + +namespace Piwik\Plugins\CoreUpdater\Diagnostic; + +use Piwik\Config; +use Piwik\Plugins\CoreUpdater; +use Piwik\Plugins\Diagnostics\Diagnostic\Diagnostic; +use Piwik\Plugins\Diagnostics\Diagnostic\DiagnosticResult; +use Piwik\Translation\Translator; + +/** + * Check the HTTPS update. + */ +class HttpsUpdateCheck implements Diagnostic +{ + /** + * @var Translator + */ + private $translator; + + public function __construct(Translator $translator) + { + $this->translator = $translator; + } + + public function execute() + { + $label = $this->translator->translate('Installation_SystemCheckUpdateHttps'); + + if (CoreUpdater\Controller::isUpdatingOverHttps()) { + return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_OK)); + } + + $comment = $this->translator->translate('Installation_SystemCheckUpdateHttpsNotSupported'); + + return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_WARNING, $comment)); + } +} diff --git a/plugins/CoreUpdater/config/config.php b/plugins/CoreUpdater/config/config.php index 419e43cc7befa12bfe265e17767609b75923b778..b9c87c9f63bcdcd72837315c3feddf7209fb7260 100644 --- a/plugins/CoreUpdater/config/config.php +++ b/plugins/CoreUpdater/config/config.php @@ -2,5 +2,9 @@ return array( 'Piwik\Plugins\CoreUpdater\Updater' => DI\object() - ->constructorParameter('tmpPath', DI\link('path.tmp')), + ->constructorParameter('tmpPath', DI\get('path.tmp')), + + 'diagnostics.optional' => DI\add(array( + DI\get('Piwik\Plugins\CoreUpdater\Diagnostic\HttpsUpdateCheck'), + )), ); diff --git a/plugins/CoreUpdater/javascripts/updateLayout.js b/plugins/CoreUpdater/javascripts/updateLayout.js index a3325675126d4ce939752e61a80c77bd7ff949c7..bffd8181f7dc55b0ced5c35f146af60849b5101f 100644 --- a/plugins/CoreUpdater/javascripts/updateLayout.js +++ b/plugins/CoreUpdater/javascripts/updateLayout.js @@ -1,5 +1,6 @@ $(document).ready(function () { - $('#showSql').click(function () { + $('#showSql').click(function (e) { + e.preventDefault(); $('#sqlQueries').toggle(); }); $('#upgradeCorePluginsForm').submit(function () { diff --git a/plugins/CoreUpdater/lang/en.json b/plugins/CoreUpdater/lang/en.json index 09651ab0c88d48145e026361d61ae898de2d6437..8429c9b597651151253350d6dd502ee9fc1bc024 100644 --- a/plugins/CoreUpdater/lang/en.json +++ b/plugins/CoreUpdater/lang/en.json @@ -47,6 +47,13 @@ "UpdateAutomatically": "Update Automatically", "UpdateHasBeenCancelledExplanation": "Piwik One Click Update has been cancelled. If you can't fix the above error message, it is recommended that you manually update Piwik. %1$s Please check out the %2$sUpdate documentation%3$s to get started!", "UpdateTitle": "Update", + "UpdateSuccessTitle": "Piwik has been successfully updated!", + "UpdateErrorTitle": "Update error", + "ThankYouUpdatePiwik": "Thank you for using Piwik and keeping it up to date!", + "PostUpdateMessage": "Piwik will always be free to download and use, but it needs your continued support to grow and improve.", + "PostUpdateSupport": "If you need help using Piwik, you can get support from its creators:", + "EnterpriseSolutions": "Enterprise solutions", + "CloudHosting": "Cloud hosting", "Updating": "Updating", "UpdateUsingHttpsFailed": "Downloading the latest Piwik version over secure HTTPS connection did not succeed, because of the following error:", "UpdateUsingHttpsFailedHelp": "Why did it fail? Downloading the latest Piwik version (over secure HTTPS connection) can fail for various reasons, for example because of a network error, slow network speed or wrong system configuration. Note that it could also mean that your server is the target of a MITM attack and someone is trying to replace the update with a malicious version of Piwik.", diff --git a/plugins/CoreUpdater/stylesheets/updateLayout.css b/plugins/CoreUpdater/stylesheets/updateLayout.css index 1975a4b4b47af1d60eeb5794546b668402b93d92..fe334626b1ffd21e98b831243f195fea3616e796 100644 --- a/plugins/CoreUpdater/stylesheets/updateLayout.css +++ b/plugins/CoreUpdater/stylesheets/updateLayout.css @@ -9,19 +9,27 @@ line-height: 1.33; } -#donate-form-container { - margin: 0 0 2em 2em; +.piwik-donate-call { + border: none; + padding: 0; + max-width: none; +} +.piwik-donate-call > .piwik-donate-message p { + margin-left: 0; } code { - background-color: #F0F7FF; - border: 1px dashed #00008B; - border-left: 5px solid; + font-size: 13px; + color: #F3F3F3; + background-color: #4D4D4D; + border: none; + border-radius: 3px; direction: ltr; display: block; margin: 2px 2px 20px; - padding: 4px; + padding: 20px; text-align: left; + overflow-x: hidden; } li { diff --git a/plugins/CoreUpdater/templates/layout.twig b/plugins/CoreUpdater/templates/layout.twig index 36b0dc1224a7aeea58f7368803d29ae4481d7ceb..3ea05aa2e01023d68c6996b805238ebe31cf23e5 100644 --- a/plugins/CoreUpdater/templates/layout.twig +++ b/plugins/CoreUpdater/templates/layout.twig @@ -42,13 +42,17 @@ <body id="simple" ng-app="app" class="old-ie"> <![endif]--> <!--[if (gte IE 9)|!(IE)]><!--> <body id="simple" ng-app="app"><!--<![endif]--> -<div id="contentsimple"> - <div id="title"> - <img title='Piwik' alt="Piwik" src="plugins/Morpheus/images/logo-header.png" style="margin-left:10px;"/> - <span id="subh1"> # {{ 'General_OpenSourceWebAnalytics'|translate }}</span> - </div> + +<div class="logo"> + <img title="Piwik" alt="Piwik" src="{{ logoHeader }}"/> + <br/> + {{ 'General_OpenSourceWebAnalytics'|translate }} +</div> + +<div class="box"> {% block content %} {% endblock %} </div> + </body> </html> diff --git a/plugins/CoreUpdater/templates/newVersionAvailable.twig b/plugins/CoreUpdater/templates/newVersionAvailable.twig index 8691d975952a6822186edcb146dac97b0dc1ba30..348b78923b59dd125ba6060f91e600737f7b5bb9 100644 --- a/plugins/CoreUpdater/templates/newVersionAvailable.twig +++ b/plugins/CoreUpdater/templates/newVersionAvailable.twig @@ -2,41 +2,49 @@ {% import '@CorePluginsAdmin/macros.twig' as pluginsMacro %} {% block content %} -<br/> -<p><strong>{{ 'CoreUpdater_ThereIsNewVersionAvailableForUpdate'|translate }}</strong></p> - -{% if can_auto_update %} - <p>{{ 'CoreUpdater_YouCanUpgradeAutomaticallyOrDownloadPackage'|translate(piwik_new_version) }}</p> -{% else %} - <p>{{ 'Installation_SystemCheckAutoUpdateHelp'|translate }}</p> - <p>{{ 'CoreUpdater_YouMustDownloadPackageOrFixPermissions'|translate(piwik_new_version) }} - {{ makeWritableCommands|raw }} - </p> -{% endif %} - -{% if incompatiblePlugins %} - <p>{{ 'CoreUpdater_IncompatbilePluginsWillBeDisabledInfo'|translate(piwik_new_version) }}</p> - - <ul style="list-style: disc;"> - {% for plugin in incompatiblePlugins %} - <li>{{ pluginsMacro.missingRequirementsInfo(plugin.getPluginName, plugin.getInformation, plugin.getMissingDependencies(piwik_new_version), marketplacePlugins) }}</li> - {% endfor %} - </ul> - <p> </p> -{% endif %} - -{% if can_auto_update %} -<form id="oneclickupdate" action="index.php"> - <input type="hidden" name="module" value="CoreUpdater"/> - <input type="hidden" name="action" value="oneClickUpdate"/> - <input id="updateAutomatically" type="submit" class="submit" value="{{ 'CoreUpdater_UpdateAutomatically'|translate }}"/> - {% endif %} - <a style="margin-left:50px;" class="submit button" - href="{{ piwik_latest_version_url }}?cb={{ piwik_new_version }}">{{ 'CoreUpdater_DownloadX'|translate(piwik_new_version) }}</a><br/> - {% if can_auto_update %} -</form> -{% endif %} -<br/> -<a href="index.php">« {{ 'General_BackToPiwik'|translate }}</a> + + <div class="header"> + <h1>{{ 'CoreUpdater_ThereIsNewVersionAvailableForUpdate'|translate }}</h1> + </div> + + <div class="content"> + + {% if can_auto_update %} + <p>{{ 'CoreUpdater_YouCanUpgradeAutomaticallyOrDownloadPackage'|translate(piwik_new_version) }}</p> + {% else %} + <p>{{ 'Installation_SystemCheckAutoUpdateHelp'|translate }}</p> + <p>{{ 'CoreUpdater_YouMustDownloadPackageOrFixPermissions'|translate(piwik_new_version) }} + {{ makeWritableCommands|raw }} + </p> + {% endif %} + + {% if can_auto_update %} + <form id="oneclickupdate" action="index.php"> + <input type="hidden" name="module" value="CoreUpdater"/> + <input type="hidden" name="action" value="oneClickUpdate"/> + <input id="updateAutomatically" type="submit" class="btn" value="{{ 'CoreUpdater_UpdateAutomatically'|translate }}"/> + {% endif %} + <a class="btn" + href="{{ piwik_latest_version_url }}?cb={{ piwik_new_version }}">{{ 'CoreUpdater_DownloadX'|translate(piwik_new_version) }}</a><br/> + {% if can_auto_update %} + </form> + {% endif %} + + {% if incompatiblePlugins %} + <p>{{ 'CoreUpdater_IncompatbilePluginsWillBeDisabledInfo'|translate(piwik_new_version) }}</p> + + <ul> + {% for plugin in incompatiblePlugins %} + <li>{{ pluginsMacro.missingRequirementsInfo(plugin.getPluginName, plugin.getInformation, plugin.getMissingDependencies(piwik_new_version), marketplacePlugins) }}</li> + {% endfor %} + </ul> + {% endif %} + + </div> + + <div class="footer"> + <a href="index.php">« {{ 'General_BackToPiwik'|translate }}</a> + </div> + {% endblock %} diff --git a/plugins/CoreUpdater/templates/runUpdaterAndExit_done.twig b/plugins/CoreUpdater/templates/runUpdaterAndExit_done.twig index ceb7f44e53bb9a057e05f69f85ebc43983b62289..c97204bc6a66878ce671ae0c70a5667d695a90dc 100644 --- a/plugins/CoreUpdater/templates/runUpdaterAndExit_done.twig +++ b/plugins/CoreUpdater/templates/runUpdaterAndExit_done.twig @@ -4,75 +4,82 @@ {% endset %} {% block content %} + {% if coreError %} - <br/> - <br/> - <div class="error"> - <img src="plugins/Morpheus/images/error_medium.png"/> {{ 'CoreUpdater_CriticalErrorDuringTheUpgradeProcess'|translate }} - {% for message in errorMessages %} - <pre>{{ message }}</pre> - <br/> - {% endfor %} + <div class="header"> + <h1>{{ 'CoreUpdater_UpdateErrorTitle'|translate }}</h1> </div> - <br/> - <p>{{ 'CoreUpdater_HelpMessageIntroductionWhenError'|translate }} - <ul> - <li>{{ helpMessage }}</li> - </ul> - </p> - <p>{{ 'CoreUpdater_ErrorDIYHelp'|translate }} - <ul> - <li>{{ 'CoreUpdater_ErrorDIYHelp_1'|translate }}</li> - <li>{{ 'CoreUpdater_ErrorDIYHelp_2'|translate }}</li> - <li>{{ 'CoreUpdater_ErrorDIYHelp_3'|translate }} <a href='https://piwik.org/faq/how-to-update/#faq_179' rel='noreferrer' target='_blank'>(see FAQ)</a></li> - <li>{{ 'CoreUpdater_ErrorDIYHelp_4'|translate }}</li> - <li>{{ 'CoreUpdater_ErrorDIYHelp_5'|translate }}</li> - </ul> - </p> -{% else %} - {% if warningMessages|length > 0 %} - <div class="warning"> - <p><img src="plugins/Morpheus/images/warning_medium.png"/> {{ 'CoreUpdater_WarningMessages'|translate }}</p> - {% for message in warningMessages %} - <pre>{{ message }}</pre> - <br/> + <div class="content"> + <div class="alert alert-danger"> + {{ 'CoreUpdater_CriticalErrorDuringTheUpgradeProcess'|translate }} + {% for message in errorMessages %} + <br/><strong>{{ message }}</strong> {% endfor %} </div> + <p>{{ 'CoreUpdater_HelpMessageIntroductionWhenError'|translate }}</p> + <ul> + <li>{{ helpMessage }}</li> + </ul> + <p>{{ 'CoreUpdater_ErrorDIYHelp'|translate }}</p> + <ul> + <li>{{ 'CoreUpdater_ErrorDIYHelp_1'|translate }}</li> + <li>{{ 'CoreUpdater_ErrorDIYHelp_2'|translate }}</li> + <li>{{ 'CoreUpdater_ErrorDIYHelp_3'|translate }} <a href='https://piwik.org/faq/how-to-update/#faq_179' rel='noreferrer' target='_blank'>(see FAQ)</a></li> + <li>{{ 'CoreUpdater_ErrorDIYHelp_4'|translate }}</li> + <li>{{ 'CoreUpdater_ErrorDIYHelp_5'|translate }}</li> + </ul> + </div> +{% else %} + + {% if errorMessages|length == 0 and warningMessages|length == 0 %} + <div class="header"> + <h1>{{ 'CoreUpdater_PiwikHasBeenSuccessfullyUpgraded'|translate }}</h1> + </div> {% endif %} - {% if errorMessages|length > 0 %} - <div class="warning"> - <p><img src="plugins/Morpheus/images/error_medium.png"/> {{ 'CoreUpdater_ErrorDuringPluginsUpdates'|translate }}</p> - {% for message in errorMessages %} - <pre>{{ message }}</pre> - <br/> - {% endfor %} + <div class="content"> + {% if warningMessages|length > 0 %} + <div class="alert alert-warning"> + <p>{{ 'CoreUpdater_WarningMessages'|translate }}</p> + {% for message in warningMessages %} + <br/><strong>{{ message }}</strong> + {% endfor %} + </div> + {% endif %} + + {% if errorMessages|length > 0 %} + <div class="alert alert-warning"> + <p>{{ 'CoreUpdater_ErrorDuringPluginsUpdates'|translate }}</p> + {% for message in errorMessages %} + <br/><strong>{{ message }}</strong> + {% endfor %} + </div> {% if deactivatedPlugins is defined and deactivatedPlugins|length > 0 %} {% set listOfDeactivatedPlugins=deactivatedPlugins|join(', ') %} - <p style="color:red;"> - <img src="plugins/Morpheus/images/error_medium.png"/> + <div class="alert alert-danger"> {{ 'CoreUpdater_WeAutomaticallyDeactivatedTheFollowingPlugins'|translate(listOfDeactivatedPlugins) }} - </p> + </div> {% endif %} - </div> - {% endif %} + {% endif %} + + {% if errorMessages|length > 0 or warningMessages|length > 0 %} + <p>{{ 'CoreUpdater_HelpMessageIntroductionWhenWarning'|translate }}</p> + <ul> + <li>{{ helpMessage }}</li> + </ul> + {% else %} + <div id="donate-form-container"> + {% include "@CoreHome/_donate.twig" %} + </div> + {% endif %} + + </div> + + <div class="footer"> + <a href="index.php">{{ 'General_ContinueToPiwik'|translate }}</a> + </div> - {% if errorMessages|length > 0 or warningMessages|length > 0 %} - <br/> - <p>{{ 'CoreUpdater_HelpMessageIntroductionWhenWarning'|translate }} - <ul> - <li>{{ helpMessage }}</li> - </ul> - </p> - {% else %} - <p class="success">{{ 'CoreUpdater_PiwikHasBeenSuccessfullyUpgraded'|translate }}</p> - <div id="donate-form-container"> - {% include "@CoreHome/_donate.twig" %} - </div> - {% endif %} - <form action="index.php"> - <input type="submit" class="submit" value="{{ 'General_ContinueToPiwik'|translate }}"/> - </form> {% endif %} + {% endblock %} diff --git a/plugins/CoreUpdater/templates/runUpdaterAndExit_welcome.twig b/plugins/CoreUpdater/templates/runUpdaterAndExit_welcome.twig index 305741ed14de3f51b8da3b649d242835a0e30ea7..ed0a987f4de07c80800409ce3a9e2f446201260f 100644 --- a/plugins/CoreUpdater/templates/runUpdaterAndExit_welcome.twig +++ b/plugins/CoreUpdater/templates/runUpdaterAndExit_welcome.twig @@ -1,106 +1,105 @@ {% extends '@CoreUpdater/layout.twig' %} {% block content %} -{% spaceless %} -<span style="float:right;">{{ postEvent('Template.topBar')|raw }}</span> -{% set helpMessage %} - {{ 'CoreUpdater_HelpMessageContent'|translate('<a target="_blank" href="?module=Proxy&action=redirect&url=http://piwik.org/faq/">','</a>','</li><li>')|raw }} -{% endset %} -{% if coreError %} - <br/> - <br/> - <div class="error"> - <img src="plugins/Morpheus/images/error_medium.png"/> {{ 'CoreUpdater_CriticalErrorDuringTheUpgradeProcess'|translate }} - {% for message in errorMessages %} - <pre>{{ message|raw }}</pre> - {% endfor %} - </div> - <br/> - <p>{{ 'CoreUpdater_HelpMessageIntroductionWhenError'|translate }} - <ul> - <li>{{ helpMessage|raw }}</li> - </ul> - </p> -{% else %} - {% if coreToUpdate or pluginNamesToUpdate|length > 0 or dimensionsToUpdate|length > 0 %} - <p style='font-size:110%;padding-top:1em;'><strong id='titleUpdate'>{{ 'CoreUpdater_DatabaseUpgradeRequired'|translate }}</strong></p> + <div class="header"> + <h1>{{ 'CoreUpdater_DatabaseUpgradeRequired'|translate }}</h1> <p>{{ 'CoreUpdater_YourDatabaseIsOutOfDate'|translate }}</p> - {% if coreToUpdate %} - <p>{{ 'CoreUpdater_PiwikWillBeUpgradedFromVersionXToVersionY'|translate(current_piwik_version,new_piwik_version) }}</p> - {% endif %} + {{ postEvent('Template.topBar')|raw }} + </div> - {% if pluginNamesToUpdate|length > 0 %} - {% set listOfPlugins=pluginNamesToUpdate|join(', ') %} - <p>{{ 'CoreUpdater_TheFollowingPluginsWillBeUpgradedX'|translate(listOfPlugins) }}</p> - {% endif %} + <div class="content text-left"> - {% if dimensionsToUpdate|length > 0 %} - {% set listOfDimensions=dimensionsToUpdate|join(', ') %} - <p>{{ 'CoreUpdater_TheFollowingDimensionsWillBeUpgradedX'|translate(listOfDimensions) }}</p> - {% endif %} + {% set helpMessage %} + {{ 'CoreUpdater_HelpMessageContent'|translate('<a target="_blank" href="?module=Proxy&action=redirect&url=http://piwik.org/faq/">','</a>','</li><li>')|raw }} + {% endset %} - <h3 id='titleUpdate'>{{ 'CoreUpdater_NoteForLargePiwikInstances'|translate }}</h3> - {% if isMajor %} - <p class="warning normalFontSize"> - {{ 'CoreUpdater_MajorUpdateWarning1'|translate }}<br/> - {{ 'CoreUpdater_MajorUpdateWarning2'|translate }} - </p> - {% endif %} - <ul> - <li>{{ 'CoreUpdater_TheUpgradeProcessMayFailExecuteCommand'|translate(commandUpgradePiwik)|raw }}</li> - <li>{{ 'CoreUpdater_HighTrafficPiwikServerEnableMaintenance'|translate('<a target="_blank" href="?module=Proxy&action=redirect&url=http%3A%2F%2Fpiwik.org%2Ffaq%2Fhow-to%2F%23faq_111">', '</a>')|raw }}</li> - <li>{{ 'CoreUpdater_YouCouldManuallyExecuteSqlQueries'|translate }}<br/> - <a href="#titleUpdate" id="showSql" style="margin-left:20px;">› {{ 'CoreUpdater_ClickHereToViewSqlQueries'|translate }}</a> + {% if coreError %} + <div class="alert alert-danger"> + {{ 'CoreUpdater_CriticalErrorDuringTheUpgradeProcess'|translate }} + {% for message in errorMessages %} + <br/><strong>{{ message|raw }}</strong> + {% endfor %} + </div> + <p>{{ 'CoreUpdater_HelpMessageIntroductionWhenError'|translate }}</p> + <ul> + <li>{{ helpMessage|raw }}</li> + </ul> + {% else %} + + {% if coreToUpdate or pluginNamesToUpdate|length > 0 or dimensionsToUpdate|length > 0 %} + {% if coreToUpdate %} + <p>{{ 'CoreUpdater_PiwikWillBeUpgradedFromVersionXToVersionY'|translate(current_piwik_version,new_piwik_version) }}</p> + {% endif %} + {% if pluginNamesToUpdate|length > 0 %} + {% set listOfPlugins=pluginNamesToUpdate|join(', ') %} + <p>{{ 'CoreUpdater_TheFollowingPluginsWillBeUpgradedX'|translate(listOfPlugins) }}</p> + {% endif %} + + {% if dimensionsToUpdate|length > 0 %} + {% set listOfDimensions=dimensionsToUpdate|join(', ') %} + <p>{{ 'CoreUpdater_TheFollowingDimensionsWillBeUpgradedX'|translate(listOfDimensions) }}</p> + {% endif %} + + <h2>{{ 'CoreUpdater_NoteForLargePiwikInstances'|translate }}</h2> + {% if isMajor %} + <div class="alert alert-danger"> + {{ 'CoreUpdater_MajorUpdateWarning1'|translate }} + {{ 'CoreUpdater_MajorUpdateWarning2'|translate }} + </div> + {% endif %} + <p>{{ 'CoreUpdater_TheUpgradeProcessMayFailExecuteCommand'|translate('') }}</p> + <code>{{ commandUpgradePiwik }}</code> + <p>{{ 'CoreUpdater_HighTrafficPiwikServerEnableMaintenance'|translate('<a target="_blank" href="?module=Proxy&action=redirect&url=http%3A%2F%2Fpiwik.org%2Ffaq%2Fhow-to%2F%23faq_111">', '</a>')|raw }}</p> + <p>{{ 'CoreUpdater_YouCouldManuallyExecuteSqlQueries'|translate }}</p> + <p><a href="#" id="showSql">› {{ 'CoreUpdater_ClickHereToViewSqlQueries'|translate }}</a></p> <div id="sqlQueries" style="display:none;"> - <br/> <code> # {{ 'CoreUpdater_NoteItIsExpectedThatQueriesFail'|translate }}<br/><br/> {% for query in queries %} - {{ query }} - <br/> + {{ query }} + <br/><br/> {% endfor %} </code> </div> - </li> - </ul> - <br/> - <br/> - <h4 id="titleUpdate">{{ 'CoreUpdater_ReadyToGo'|translate }}</h4> - <p>{{ 'CoreUpdater_TheUpgradeProcessMayTakeAWhilePleaseBePatient'|translate }}</p> - {% endif %} - {% if warningMessages|length > 0 %} - <p><em>{{ warningMessages[0] }}</em> - {% if warningMessages|length > 1 %} - <button id="more-results" class="ui-button ui-state-default ui-corner-all">{{ 'General_Details'|translate }}</button> + <h2>{{ 'CoreUpdater_ReadyToGo'|translate }}</h2> + <p>{{ 'CoreUpdater_TheUpgradeProcessMayTakeAWhilePleaseBePatient'|translate }}</p> {% endif %} - </p> - {% endif %} - {% if coreToUpdate or pluginNamesToUpdate|length > 0 or dimensionsToUpdate|length > 0 %} - <br/> - <form action="index.php" id="upgradeCorePluginsForm" data-updating="{{ 'CoreUpdater_Updating'|translate }}..."> - <input type="hidden" name="updateCorePlugins" value="1"/> - {% if queries|length == 1 %} - <input type="submit" class="submit" value="{{ 'General_ContinueToPiwik'|translate }}"/> + {% if warningMessages|length > 0 %} + <div class="alert alert-info"> + {{ warningMessages[0] }} + {% if warningMessages|length > 1 %} + <button id="more-results" class="ui-button ui-state-default ui-corner-all">{{ 'General_Details'|translate }}</button> + {% endif %} + </div> + {% endif %} + + {% if coreToUpdate or pluginNamesToUpdate|length > 0 or dimensionsToUpdate|length > 0 %} + <form action="index.php" id="upgradeCorePluginsForm" class="clearfix" data-updating="{{ 'CoreUpdater_Updating'|translate }}..."> + <input type="hidden" name="updateCorePlugins" value="1"/> + {% if queries|length == 1 %} + <input type="submit" class="submit" value="{{ 'General_ContinueToPiwik'|translate }}"/> + {% else %} + <input type="submit" class="submit" value="{{ 'CoreUpdater_UpgradePiwik'|translate }}"/> + {% endif %} + </form> {% else %} - <input type="submit" class="submit" value="{{ 'CoreUpdater_UpgradePiwik'|translate }}"/> + {% if warningMessages|length >= 0 %} + <div class="alert alert-success"> + {{ 'CoreUpdater_PiwikHasBeenSuccessfullyUpgraded'|translate }} + </div> + {% endif %} + <form action="index.php" class="clearfix"> + <input type="submit" class="submit" value="{{ 'General_ContinueToPiwik'|translate }}"/> + </form> {% endif %} - </form> - {% else %} - {% if warningMessages|length == 0 %} - <p class="success">{{ 'CoreUpdater_PiwikHasBeenSuccessfullyUpgraded'|translate }}</p> {% endif %} - <br/> - <form action="index.php"> - <input type="submit" class="submit" value="{{ 'General_ContinueToPiwik'|translate }}"/> - </form> - {% endif %} -{% endif %} -{% include "@Installation/_integrityDetails.twig" %} + {% include "@Installation/_integrityDetails.twig" %} + + </div> -{% endspaceless %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/plugins/CoreUpdater/templates/updateHttpError.twig b/plugins/CoreUpdater/templates/updateHttpError.twig new file mode 100644 index 0000000000000000000000000000000000000000..c7d6a90dfbd8ced0448edd739ca49e7670b319e6 --- /dev/null +++ b/plugins/CoreUpdater/templates/updateHttpError.twig @@ -0,0 +1,30 @@ +{% extends '@CoreUpdater/layout.twig' %} + +{% block content %} + + <div class="header"> + <h1>{{ 'CoreUpdater_UpdateErrorTitle'|translate }}</h1> + </div> + + <div class="content"> + + {% for message in feedbackMessages %} + <p>✓ {{ message }}</p> + {% endfor %} + + <div class="alert alert-danger"> + <strong>{{ 'CoreUpdater_UpdateErrorTitle'|translate }}:</strong> + {{ error }} + </div> + + <p> + {{ 'CoreUpdater_UpdateHasBeenCancelledExplanation'|translate("<br /><br />","<a target='_blank' href='?module=Proxy&action=redirect&url=http://piwik.org/docs/update/'>","</a>")|raw }} + </p> + + </div> + + <div class="footer"> + <a href="index.php">{{ 'General_ContinueToPiwik'|translate }}</a> + </div> + +{% endblock %} diff --git a/plugins/CoreUpdater/templates/oneClickResults.twig b/plugins/CoreUpdater/templates/updateHttpsError.twig similarity index 52% rename from plugins/CoreUpdater/templates/oneClickResults.twig rename to plugins/CoreUpdater/templates/updateHttpsError.twig index 664dc92097690ff93c17e355b2b64c699d8af37f..399908e1f662fdb95d053bdb505777be65d4fe03 100644 --- a/plugins/CoreUpdater/templates/oneClickResults.twig +++ b/plugins/CoreUpdater/templates/updateHttpsError.twig @@ -2,20 +2,21 @@ {% block content %} - {% for message in feedbackMessages %} - <p>✓ {{ message }}</p> - {% endfor %} + <div class="header"> + <h1>{{ 'CoreUpdater_UpdateErrorTitle'|translate }}</h1> + </div> - {% if httpsFail %} - <br/> - <br/> - <div class="warning"> - <img src="plugins/Morpheus/images/warning_medium.png"/> + <div class="content text-left"> + + <div class="alert alert-warning"> {{ 'CoreUpdater_UpdateUsingHttpsFailed'|translate }}<br/> - "{{ error }}" + <em>{{ error }}</em> </div> + <p>{{ 'CoreUpdater_UpdateUsingHttpsFailedHelp'|translate }}</p> + <p>{{ 'CoreUpdater_UpdateUsingHttpsFailedHelpWhatToDo'|translate }}</p> + <form action="index.php"> <input type="hidden" name="module" value="CoreUpdater"/> <input type="hidden" name="action" value="oneClickUpdate"/> @@ -24,6 +25,7 @@ {{ 'CoreUpdater_UsingHttps'|translate }} </form> <br/> + <form action="index.php"> <input type="hidden" name="module" value="CoreUpdater"/> <input type="hidden" name="action" value="oneClickUpdate"/> @@ -32,32 +34,15 @@ {{ 'CoreUpdater_UsingHttp'|translate }} </form> <br/> + <form action="index.php"> <input type="submit" value="{{ 'General_ContinueToPiwik'|translate }}"/> </form> - <br/> - {% elseif error %} - <br/> - <br/> - <div class="error"> - <img src="plugins/Morpheus/images/error_medium.png"/> - {{ error }} - </div> - <br/> - <br/> - <div class="warning"> - <img src="plugins/Morpheus/images/warning_medium.png"/> - {{ 'CoreUpdater_UpdateHasBeenCancelledExplanation'|translate("<br /><br />","<a target='_blank' href='?module=Proxy&action=redirect&url=http://piwik.org/docs/update/'>","</a>")|raw }} - </div> - <br/> - <br/> - <form action="index.php"> - <input type="submit" class="submit" value="{{ 'General_ContinueToPiwik'|translate }}"/> - </form> - {% else %} - <form action="index.php"> - <input type="submit" class="submit" value="{{ 'General_ContinueToPiwik'|translate }}"/> - </form> - {% endif %} + + </div> + + <div class="footer"> + <a href="index.php">{{ 'General_ContinueToPiwik'|translate }}</a> + </div> {% endblock %} diff --git a/plugins/CoreUpdater/templates/updateSuccess.twig b/plugins/CoreUpdater/templates/updateSuccess.twig new file mode 100644 index 0000000000000000000000000000000000000000..eb89e7df3d903823cbe3956dde0711484d898b61 --- /dev/null +++ b/plugins/CoreUpdater/templates/updateSuccess.twig @@ -0,0 +1,40 @@ +{% extends '@CoreUpdater/layout.twig' %} + +{% block content %} + + <div class="header"> + <h1>{{ 'CoreUpdater_UpdateSuccessTitle'|translate }}</h1> + </div> + + <div class="content"> + + <h2> + {{ 'CoreUpdater_ThankYouUpdatePiwik'|translate }} + </h2> + + <p> + {{ 'CoreUpdater_PostUpdateMessage'|translate }} + + </p> + <p></p> + + <h2> + {{ 'CoreUpdater_PostUpdateSupport'|translate }} + </h2> + + <div class="row"> + <div class="col-sm-4 col-sm-offset-2"> + <a href="https://piwik.pro/enterprise" class="btn btn-lg btn-block">{{ 'CoreUpdater_EnterpriseSolutions'|translate }}</a> + </div> + <div class="col-sm-4"> + <a href="https://piwik.pro/cloud" class="btn btn-lg btn-block">{{ 'CoreUpdater_CloudHosting'|translate }}</a> + </div> + </div> + + </div> + + <div class="footer"> + <a href="index.php">{{ 'General_ContinueToPiwik'|translate }}</a> + </div> + +{% endblock %} diff --git a/plugins/CoreVisualizations/stylesheets/jqplot.css b/plugins/CoreVisualizations/stylesheets/jqplot.css index 63c231a96721892b59f07770c6997494b8023a68..25b6535f5c9a6bb99bd072f6e98e9106dba13421 100644 --- a/plugins/CoreVisualizations/stylesheets/jqplot.css +++ b/plugins/CoreVisualizations/stylesheets/jqplot.css @@ -84,6 +84,7 @@ font-size: 16px; margin: 0; padding: 0; + border: none; } .rowevolution .metrics-container { diff --git a/plugins/Dashboard/javascripts/dashboardObject.js b/plugins/Dashboard/javascripts/dashboardObject.js index cd2d848253c73c646efa1c110c356f0b3774c28f..f80f485fb852c15c6ef60d7e295a8ed82108d10c 100644 --- a/plugins/Dashboard/javascripts/dashboardObject.js +++ b/plugins/Dashboard/javascripts/dashboardObject.js @@ -297,8 +297,49 @@ } } - for (var i = 0; i < columnCount; i++) { - $('.col', dashboardElement)[i].className = 'col width-' + columnWidth[i]; + switch (layout) { + case '100': + $('.col', dashboardElement).removeClass() + .addClass('col col-md-12'); + break; + case '50-50': + $('.col', dashboardElement).removeClass() + .addClass('col col-md-6'); + break; + case '67-33': + $('.col', dashboardElement)[0].className = 'col col-md-8'; + $('.col', dashboardElement)[1].className = 'col col-md-4'; + break; + case '33-67': + $('.col', dashboardElement)[0].className = 'col col-md-4'; + $('.col', dashboardElement)[1].className = 'col col-md-8'; + break; + case '33-33-33': + $('.col', dashboardElement)[0].className = 'col col-md-4'; + $('.col', dashboardElement)[1].className = 'col col-md-4'; + $('.col', dashboardElement)[2].className = 'col col-md-4'; + break; + case '40-30-30': + $('.col', dashboardElement)[0].className = 'col col-md-6'; + $('.col', dashboardElement)[1].className = 'col col-md-3'; + $('.col', dashboardElement)[2].className = 'col col-md-3'; + break; + case '30-40-30': + $('.col', dashboardElement)[0].className = 'col col-md-3'; + $('.col', dashboardElement)[1].className = 'col col-md-6'; + $('.col', dashboardElement)[2].className = 'col col-md-3'; + break; + case '30-30-40': + $('.col', dashboardElement)[0].className = 'col col-md-3'; + $('.col', dashboardElement)[1].className = 'col col-md-3'; + $('.col', dashboardElement)[2].className = 'col col-md-6'; + break; + case '25-25-25-25': + $('.col', dashboardElement)[0].className = 'col col-md-3'; + $('.col', dashboardElement)[1].className = 'col col-md-3'; + $('.col', dashboardElement)[2].className = 'col col-md-3'; + $('.col', dashboardElement)[3].className = 'col col-md-3'; + break; } makeWidgetsSortable(); diff --git a/plugins/Dashboard/stylesheets/dashboard.less b/plugins/Dashboard/stylesheets/dashboard.less index 57a916c010ccdc8d482738ec441ea36438969653..bb2c61b3e2f58fb1abb3df170fc571d7a2021c81 100644 --- a/plugins/Dashboard/stylesheets/dashboard.less +++ b/plugins/Dashboard/stylesheets/dashboard.less @@ -26,59 +26,39 @@ } } -#dashboardWidgetsArea { - padding-bottom: 100px; -} - -.col { - float: left; - min-height: 100px; -} - -.col.width-100 { - width: 100%; -} - -.col.width-75 { - width: 75%; -} - -.col.width-67 { - width: 66.67%; -} - -.col.width-50 { - width: 50%; -} - -.col.width-40 { - width: 40%; -} - -.col.width-33 { - width: 33.33%; -} - -.col.width-30 { - width: 30%; -} - -.col.width-25 { - width: 25%; -} +#dashboard { + .col { + min-height: 100px; + // Customize Bootstrap gutter between columns + padding-right: 7px; + padding-left: 7px; -.hover { - border: 2px dashed #E3E3E3; + >.sortable { + padding: 5px 0; + } + } } .widget { background: @theme-color-background-base; border: 1px solid #bbb6ad; - margin: 10px 7px; border-radius: 4px; overflow: hidden; font-size: 14px; z-index: 1; + + h3 { + font-size: 15px; + margin: 0; + font-weight: normal; + color: #0D0D0D; + text-shadow: none; + padding: 15px 15px 10px; + } +} + +.hover { + border: 2px dashed #E3E3E3; } .widgetHover { @@ -116,18 +96,6 @@ padding-bottom: 4px; } -.widgetTopHover { -} - -h3.widgetName { - font-size: 15px; - padding: 0px; - margin: 0px; - font-weight: normal; - color: #0D0D0D; - text-shadow: none; -} - .widgetNameOffScreen { overflow: hidden; width:1px; @@ -137,7 +105,11 @@ h3.widgetName { // Overriding some dataTable css for better dashboard display .widget .dataTableWrapper { width: 100% !important; + table * { + // Because of Bootstrap + box-sizing: content-box; } +} .widgetTop .button { cursor: pointer; @@ -327,31 +299,24 @@ h3.widgetName { #columnPreview .width-100 { width: 120px; } - #columnPreview .width-75 { width: 90px; } - #columnPreview .width-67 { width: 80.4px; } - #columnPreview .width-50 { width: 60px; } - #columnPreview .width-40 { width: 48px; } - #columnPreview .width-33 { width: 40px; } - #columnPreview .width-30 { width: 36px; } - #columnPreview .width-25 { width: 30px; } @@ -507,6 +472,10 @@ div.widgetpreview-preview { float: left; } +#dashboardWidgetsArea { + margin-top: 5px; +} + @media all and (max-width: 749px) { #dashboardWidgetsArea { padding-right: 7px; diff --git a/plugins/DevicesDetection/functions.php b/plugins/DevicesDetection/functions.php index f47ba4c175e0aa33ac3e0dc2edfba376226ebe5b..d19f0e3b46efa5c04dede0311ce4292968dc2714 100644 --- a/plugins/DevicesDetection/functions.php +++ b/plugins/DevicesDetection/functions.php @@ -127,6 +127,7 @@ function getDeviceTypeLabel($label) 'desktop' => 'General_Desktop', 'smartphone' => 'DevicesDetection_Smartphone', 'tablet' => 'DevicesDetection_Tablet', + 'phablet' => 'DevicesDetection_Phablet', 'feature phone' => 'DevicesDetection_FeaturePhone', 'console' => 'DevicesDetection_Console', 'tv' => 'DevicesDetection_TV', diff --git a/plugins/DevicesDetection/images/brand/3Q.ico b/plugins/DevicesDetection/images/brand/3Q.ico new file mode 100644 index 0000000000000000000000000000000000000000..8a2e5e2f1c3ffdff09edfba01cf06cf5c7d10922 Binary files /dev/null and b/plugins/DevicesDetection/images/brand/3Q.ico differ diff --git a/plugins/DevicesDetection/images/brand/BBK.ico b/plugins/DevicesDetection/images/brand/BBK.ico new file mode 100644 index 0000000000000000000000000000000000000000..6352fd1b02e383622f286899efa4340c59d5e03c Binary files /dev/null and b/plugins/DevicesDetection/images/brand/BBK.ico differ diff --git a/plugins/DevicesDetection/images/brand/Celkon.ico b/plugins/DevicesDetection/images/brand/Celkon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c35d5864b474124d97e6ef9bc1c5d70bac230be0 Binary files /dev/null and b/plugins/DevicesDetection/images/brand/Celkon.ico differ diff --git a/plugins/DevicesDetection/images/brand/Gionee.ico b/plugins/DevicesDetection/images/brand/Gionee.ico new file mode 100644 index 0000000000000000000000000000000000000000..704085f1bea9f738be9eab47593824b07d4c92d7 Binary files /dev/null and b/plugins/DevicesDetection/images/brand/Gionee.ico differ diff --git a/plugins/DevicesDetection/images/brand/Hyundai.ico b/plugins/DevicesDetection/images/brand/Hyundai.ico new file mode 100644 index 0000000000000000000000000000000000000000..3565bcf03f01c1ddba48d1b8f7856f1c84c644a9 Binary files /dev/null and b/plugins/DevicesDetection/images/brand/Hyundai.ico differ diff --git a/plugins/DevicesDetection/images/brand/Le_Pan.ico b/plugins/DevicesDetection/images/brand/Le_Pan.ico new file mode 100644 index 0000000000000000000000000000000000000000..cea70792e3339b11a80eaff60a8b5b11e0d8f15b Binary files /dev/null and b/plugins/DevicesDetection/images/brand/Le_Pan.ico differ diff --git a/plugins/DevicesDetection/images/brand/Smartfren.ico b/plugins/DevicesDetection/images/brand/Smartfren.ico new file mode 100644 index 0000000000000000000000000000000000000000..853f6404487a702f357e341f0db3a2e46ec6d40d Binary files /dev/null and b/plugins/DevicesDetection/images/brand/Smartfren.ico differ diff --git a/plugins/DevicesDetection/images/browsers/CR.gif b/plugins/DevicesDetection/images/browsers/CR.gif new file mode 100644 index 0000000000000000000000000000000000000000..7df03605da9fde90eceaf164917c5f51708cc1f3 Binary files /dev/null and b/plugins/DevicesDetection/images/browsers/CR.gif differ diff --git a/plugins/DevicesDetection/images/os/BMP.gif b/plugins/DevicesDetection/images/os/BMP.gif new file mode 100644 index 0000000000000000000000000000000000000000..acd95961191db57ecd1b0767c7a969ece764d014 Binary files /dev/null and b/plugins/DevicesDetection/images/os/BMP.gif differ diff --git a/plugins/DevicesDetection/lang/en.json b/plugins/DevicesDetection/lang/en.json index 7009992d3414ba05c5cc558292a7b3e05eaadf26..df313e9142b288a689373a87dd7a9d17ade9e84c 100644 --- a/plugins/DevicesDetection/lang/en.json +++ b/plugins/DevicesDetection/lang/en.json @@ -35,6 +35,7 @@ "PortableMediaPlayer": "Portable media player", "Devices": "Devices", "Tablet": "Tablet", + "Phablet": "Phablet", "TV": "Tv", "UserAgent": "User-Agent", "WidgetBrowsers": "Visitor Browser", diff --git a/plugins/DevicesDetection/templates/devices.twig b/plugins/DevicesDetection/templates/devices.twig index 0f93aa61be1d6f57b677ed69f2b08e68aeff8f40..a37079c9fd60c6f74d2024b6de0d96b67dd6ff16 100644 --- a/plugins/DevicesDetection/templates/devices.twig +++ b/plugins/DevicesDetection/templates/devices.twig @@ -1,15 +1,19 @@ -<div id='leftcolumn'> - <h2 piwik-enriched-headline>{{ "DevicesDetection_DeviceType"|translate }}</h2> - {{ deviceTypes | raw}} - <h2 piwik-enriched-headline>{{ "DevicesDetection_DeviceBrand"|translate }}</h2> - {{ deviceBrands | raw }} -</div> +<div class="row"> + + <div class="col-md-6"> + <h2 piwik-enriched-headline>{{ "DevicesDetection_DeviceType"|translate }}</h2> + {{ deviceTypes | raw}} + <h2 piwik-enriched-headline>{{ "DevicesDetection_DeviceBrand"|translate }}</h2> + {{ deviceBrands | raw }} + </div> + + <div class="col-md-6"> + <h2 piwik-enriched-headline>{{ "DevicesDetection_DeviceModel"|translate }}</h2> + {{ deviceModels | raw }} + {% if resolutions|default is not empty %} + <h2 piwik-enriched-headline>{{ 'Resolution_Resolutions'|translate }}</h2> + {{ resolutions|raw }} + {% endif %} + </div> -<div id='rightcolumn'> - <h2 piwik-enriched-headline>{{ "DevicesDetection_DeviceModel"|translate }}</h2> - {{ deviceModels | raw }} - {% if resolutions|default is not empty %} - <h2 piwik-enriched-headline>{{ 'Resolution_Resolutions'|translate }}</h2> - {{ resolutions|raw }} - {% endif %} </div> diff --git a/plugins/DevicesDetection/templates/software.twig b/plugins/DevicesDetection/templates/software.twig index 98b496d6f07af2fa64dcb41ef824e72087abf8b3..d2dfd0d1717c853540a3a3d9e18bcfe6eabbf18a 100644 --- a/plugins/DevicesDetection/templates/software.twig +++ b/plugins/DevicesDetection/templates/software.twig @@ -1,19 +1,23 @@ -<div id='leftcolumn'> - <h2 piwik-enriched-headline>{{ "DevicesDetection_OperatingSystems"|translate }}</h2> - {{ osReport | raw}} - {% if configurations|default is not empty %} - <h2 piwik-enriched-headline>{{ 'Resolution_Configurations'|translate }}</h2> - {{ configurations|raw }} - {% endif %} -</div> +<div class="row"> + + <div id="col-md-6"> + <h2 piwik-enriched-headline>{{ "DevicesDetection_OperatingSystems"|translate }}</h2> + {{ osReport | raw}} + {% if configurations|default is not empty %} + <h2 piwik-enriched-headline>{{ 'Resolution_Configurations'|translate }}</h2> + {{ configurations|raw }} + {% endif %} + </div> + + <div id="col-md-6"> + <h2 piwik-enriched-headline>{{ "DevicesDetection_Browsers"|translate }}</h2> + {{ browserReport | raw }} + <h2 piwik-enriched-headline>{{ "DevicesDetection_BrowserEngines"|translate }}</h2> + {{ browserEngineReport | raw }} + {% if browserPlugins|default is not empty %} + <h2 piwik-enriched-headline>{{ 'General_Plugins'|translate }}</h2> + {{ browserPlugins|raw }} + {% endif %} + </div> -<div id='rightcolumn'> - <h2 piwik-enriched-headline>{{ "DevicesDetection_Browsers"|translate }}</h2> - {{ browserReport | raw }} - <h2 piwik-enriched-headline>{{ "DevicesDetection_BrowserEngines"|translate }}</h2> - {{ browserEngineReport | raw }} - {% if browserPlugins|default is not empty %} - <h2 piwik-enriched-headline>{{ 'General_Plugins'|translate }}</h2> - {{ browserPlugins|raw }} - {% endif %} </div> diff --git a/plugins/Diagnostics/Commands/Run.php b/plugins/Diagnostics/Commands/Run.php new file mode 100644 index 0000000000000000000000000000000000000000..8014ac24ccdf9ace485fb58f7652126821dd1531 --- /dev/null +++ b/plugins/Diagnostics/Commands/Run.php @@ -0,0 +1,98 @@ +<?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\Diagnostics\Commands; + +use Piwik\Container\StaticContainer; +use Piwik\Plugin\ConsoleCommand; +use Piwik\Plugins\Diagnostics\Diagnostic\DiagnosticResult; +use Piwik\Plugins\Diagnostics\Diagnostic\DiagnosticResultItem; +use Piwik\Plugins\Diagnostics\DiagnosticService; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Run the diagnostics. + */ +class Run extends ConsoleCommand +{ + /** + * @var DiagnosticService + */ + private $diagnosticService; + + public function __construct() + { + // Replace this with dependency injection once available + $this->diagnosticService = StaticContainer::get('Piwik\Plugins\Diagnostics\DiagnosticService'); + + parent::__construct(); + } + + protected function configure() + { + $this->setName('diagnostics:run') + ->setDescription('Run diagnostics to check that Piwik is installed and runs correctly') + ->addOption('all', null, InputOption::VALUE_NONE, 'Show all diagnostics, including those that passed with success'); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $showAll = $input->getOption('all'); + + $report = $this->diagnosticService->runDiagnostics(); + + foreach ($report->getAllResults() as $result) { + $items = $result->getItems(); + + if (! $showAll && ($result->getStatus() === DiagnosticResult::STATUS_OK)) { + continue; + } + + if (count($items) === 1) { + $output->writeln($result->getLabel() . ': ' . $this->formatItem($items[0]), OutputInterface::OUTPUT_PLAIN); + continue; + } + + $output->writeln($result->getLabel() . ':'); + foreach ($items as $item) { + $output->writeln("\t- " . $this->formatItem($item), OutputInterface::OUTPUT_PLAIN); + } + } + + if ($report->hasWarnings()) { + $output->writeln(sprintf('<comment>%d warnings detected</comment>', $report->getWarningCount())); + } + if ($report->hasErrors()) { + $output->writeln(sprintf('<error>%d errors detected</error>', $report->getErrorCount())); + return 1; + } + + return 0; + } + + private function formatItem(DiagnosticResultItem $item) + { + if ($item->getStatus() === DiagnosticResult::STATUS_ERROR) { + $tag = 'error'; + } elseif ($item->getStatus() === DiagnosticResult::STATUS_WARNING) { + $tag = 'comment'; + } else { + $tag = 'info'; + } + + return sprintf( + '<%s>%s %s</%s>', + $tag, + strtoupper($item->getStatus()), + preg_replace('/\<br\s*\/?\>/i', "\n", $item->getComment()), + $tag + ); + } +} diff --git a/plugins/Diagnostics/Diagnostic/CronArchivingCheck.php b/plugins/Diagnostics/Diagnostic/CronArchivingCheck.php new file mode 100644 index 0000000000000000000000000000000000000000..b1c96f82716849d21e2f48654cc59c179a4741a0 --- /dev/null +++ b/plugins/Diagnostics/Diagnostic/CronArchivingCheck.php @@ -0,0 +1,42 @@ +<?php + +namespace Piwik\Plugins\Diagnostics\Diagnostic; + +use Piwik\CliMulti; +use Piwik\Config; +use Piwik\Http; +use Piwik\Translation\Translator; +use Piwik\Url; + +/** + * Check if cron archiving can run through CLI. + */ +class CronArchivingCheck implements Diagnostic +{ + /** + * @var Translator + */ + private $translator; + + public function __construct(Translator $translator) + { + $this->translator = $translator; + } + + public function execute() + { + $label = $this->translator->translate('Installation_SystemCheckCronArchiveProcess'); + $comment = $this->translator->translate('Installation_SystemCheckCronArchiveProcessCLI') . ': '; + + $process = new CliMulti(); + + if ($process->supportsAsync()) { + $comment .= $this->translator->translate('General_Ok'); + } else { + $comment .= $this->translator->translate('Installation_NotSupported') + . ' ' . $this->translator->translate('Goals_Optional'); + } + + return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_OK, $comment)); + } +} diff --git a/plugins/Diagnostics/Diagnostic/DbAdapterCheck.php b/plugins/Diagnostics/Diagnostic/DbAdapterCheck.php new file mode 100644 index 0000000000000000000000000000000000000000..71edf6540b79ca777e922fd95f1090d4b19ba1bd --- /dev/null +++ b/plugins/Diagnostics/Diagnostic/DbAdapterCheck.php @@ -0,0 +1,105 @@ +<?php + +namespace Piwik\Plugins\Diagnostics\Diagnostic; + +use Piwik\Db\Adapter; +use Piwik\SettingsServer; +use Piwik\Translation\Translator; + +/** + * Check supported DB adapters are available. + */ +class DbAdapterCheck implements Diagnostic +{ + /** + * @var Translator + */ + private $translator; + + public function __construct(Translator $translator) + { + $this->translator = $translator; + } + + public function execute() + { + $results = array(); + $results[] = $this->checkPdo(); + $results = array_merge($results, $this->checkDbAdapters()); + + return $results; + } + + private function checkPdo() + { + $label = 'PDO ' . $this->translator->translate('Installation_Extension'); + + if (in_array('PDO', $this->getPhpExtensionsLoaded())) { + $status = DiagnosticResult::STATUS_OK; + } else { + $status = DiagnosticResult::STATUS_WARNING; + } + + return DiagnosticResult::singleResult($label, $status); + } + + private function checkDbAdapters() + { + $results = array(); + $adapters = Adapter::getAdapters(); + + foreach ($adapters as $adapter => $port) { + $label = $adapter . ' ' . $this->translator->translate('Installation_Extension'); + + $results[] = DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_OK); + } + + if (empty($adapters)) { + $label = $this->translator->translate('Installation_SystemCheckDatabaseExtensions'); + $comment = $this->translator->translate('Installation_SystemCheckDatabaseHelp'); + + $result = DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_ERROR, $comment); + $result->setLongErrorMessage($this->getLongErrorMessage()); + + $results[] = $result; + } + + return $results; + } + + private function getPhpExtensionsLoaded() + { + return @get_loaded_extensions(); + } + + private function getLongErrorMessage() + { + $message = '<p>'; + + if (SettingsServer::isWindows()) { + $message .= $this->translator->translate( + 'Installation_SystemCheckWinPdoAndMysqliHelp', + array('<br /><br /><code>extension=php_mysqli.dll</code><br /><code>extension=php_pdo.dll</code><br /><code>extension=php_pdo_mysql.dll</code><br />') + ); + } else { + $message .= $this->translator->translate( + 'Installation_SystemCheckPdoAndMysqliHelp', + array( + '<br /><br /><code>--with-mysqli</code><br /><code>--with-pdo-mysql</code><br /><br />', + '<br /><br /><code>extension=mysqli.so</code><br /><code>extension=pdo.so</code><br /><code>extension=pdo_mysql.so</code><br />' + ) + ); + } + + $message .= $this->translator->translate('Installation_RestartWebServer') . '<br/><br/>'; + $message .= $this->translator->translate('Installation_SystemCheckPhpPdoAndMysqli', array( + '<a style="color:red" href="http://php.net/pdo">', + '</a>', + '<a style="color:red" href="http://php.net/mysqli">', + '</a>', + )); + $message .= '</p>'; + + return $message; + } +} diff --git a/plugins/Diagnostics/Diagnostic/Diagnostic.php b/plugins/Diagnostics/Diagnostic/Diagnostic.php new file mode 100644 index 0000000000000000000000000000000000000000..39c9be4c39db645332e7dcd117dfdafa15687d48 --- /dev/null +++ b/plugins/Diagnostics/Diagnostic/Diagnostic.php @@ -0,0 +1,44 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +namespace Piwik\Plugins\Diagnostics\Diagnostic; + +/** + * Performs a diagnostic on the system or Piwik. + * + * Example: + * + * class MyDiagnostic implements Diagnostic + * { + * public function execute() + * { + * $results = array(); + * + * // First check (error) + * $status = testSomethingIsOk() ? DiagnosticResult::STATUS_OK : DiagnosticResult::STATUS_ERROR; + * $results[] = DiagnosticResult::singleResult('First check', $status); + * + * // Second check (warning) + * $status = testSomethingElseIsOk() ? DiagnosticResult::STATUS_OK : DiagnosticResult::STATUS_WARNING; + * $results[] = DiagnosticResult::singleResult('Second check', $status); + * + * return $results; + * } + * } + * + * Diagnostics are loaded with dependency injection support. + * + * @api + */ +interface Diagnostic +{ + /** + * @return DiagnosticResult[] + */ + public function execute(); +} diff --git a/plugins/Diagnostics/Diagnostic/DiagnosticResult.php b/plugins/Diagnostics/Diagnostic/DiagnosticResult.php new file mode 100644 index 0000000000000000000000000000000000000000..68c543676310e4b75b5b74b18eeeb0bc845fc8ae --- /dev/null +++ b/plugins/Diagnostics/Diagnostic/DiagnosticResult.php @@ -0,0 +1,121 @@ +<?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\Diagnostics\Diagnostic; + +/** + * The result of a diagnostic. + * + * @api + */ +class DiagnosticResult +{ + const STATUS_ERROR = 'error'; + const STATUS_WARNING = 'warning'; + const STATUS_OK = 'ok'; + + /** + * @var string + */ + private $label; + + /** + * @var string + */ + private $longErrorMessage = ''; + + /** + * @var DiagnosticResultItem[] + */ + private $items = array(); + + public function __construct($label) + { + $this->label = $label; + } + + /** + * @param string $label + * @param string $status + * @param string $comment + * @return DiagnosticResult + */ + public static function singleResult($label, $status, $comment = '') + { + $result = new self($label); + $result->addItem(new DiagnosticResultItem($status, $comment)); + return $result; + } + + /** + * @return string + */ + public function getLabel() + { + return $this->label; + } + + /** + * @return DiagnosticResultItem[] + */ + public function getItems() + { + return $this->items; + } + + public function addItem(DiagnosticResultItem $item) + { + $this->items[] = $item; + } + + /** + * @param DiagnosticResultItem[] $items + */ + public function setItems(array $items) + { + $this->items = $items; + } + + /** + * @return string + */ + public function getLongErrorMessage() + { + return $this->longErrorMessage; + } + + /** + * @param string $longErrorMessage + */ + public function setLongErrorMessage($longErrorMessage) + { + $this->longErrorMessage = $longErrorMessage; + } + + /** + * Returns the worst status of the items. + * + * @return string One of the `STATUS_*` constants. + */ + public function getStatus() + { + $status = self::STATUS_OK; + + foreach ($this->getItems() as $item) { + if ($item->getStatus() === self::STATUS_ERROR) { + return self::STATUS_ERROR; + } + + if ($item->getStatus() === self::STATUS_WARNING) { + $status = self::STATUS_WARNING; + } + } + + return $status; + } +} diff --git a/plugins/Diagnostics/Diagnostic/DiagnosticResultItem.php b/plugins/Diagnostics/Diagnostic/DiagnosticResultItem.php new file mode 100644 index 0000000000000000000000000000000000000000..a39cb6dac413712d4595c1a571a212fc565d9146 --- /dev/null +++ b/plugins/Diagnostics/Diagnostic/DiagnosticResultItem.php @@ -0,0 +1,49 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +namespace Piwik\Plugins\Diagnostics\Diagnostic; + +/** + * @api + */ +class DiagnosticResultItem +{ + /** + * @var string + */ + private $status; + + /** + * Optional comment about the item. + * + * @var string + */ + private $comment; + + public function __construct($status, $comment = '') + { + $this->status = $status; + $this->comment = $comment; + } + + /** + * @return string + */ + public function getStatus() + { + return $this->status; + } + + /** + * @return string + */ + public function getComment() + { + return $this->comment; + } +} diff --git a/plugins/Diagnostics/Diagnostic/FileIntegrityCheck.php b/plugins/Diagnostics/Diagnostic/FileIntegrityCheck.php new file mode 100644 index 0000000000000000000000000000000000000000..c6730bc52e6fd6c6bbf5db1b2582092cd9111e53 --- /dev/null +++ b/plugins/Diagnostics/Diagnostic/FileIntegrityCheck.php @@ -0,0 +1,51 @@ +<?php + +namespace Piwik\Plugins\Diagnostics\Diagnostic; + +use Piwik\Filechecks; +use Piwik\Translation\Translator; + +/** + * Check the files integrity. + */ +class FileIntegrityCheck implements Diagnostic +{ + /** + * @var Translator + */ + private $translator; + + public function __construct(Translator $translator) + { + $this->translator = $translator; + } + + public function execute() + { + $label = $this->translator->translate('Installation_SystemCheckFileIntegrity'); + + $messages = Filechecks::getFileIntegrityInformation(); + $ok = array_shift($messages); + + if (empty($messages)) { + return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_OK)); + } + + if ($ok) { + $status = DiagnosticResult::STATUS_WARNING; + return array(DiagnosticResult::singleResult($label, $status, $messages[0])); + } + + $comment = $this->translator->translate('General_FileIntegrityWarningExplanation'); + + // Keep only the 20 first lines else it becomes unmanageable + if (count($messages) > 20) { + $messages = array_slice($messages, 0, 20); + $messages[] = '...'; + } + $comment .= '<br/><br/><pre style="overflow-x: scroll;max-width: 600px;">' + . implode("\n", $messages) . '</pre>'; + + return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_ERROR, $comment)); + } +} diff --git a/plugins/Diagnostics/Diagnostic/GdExtensionCheck.php b/plugins/Diagnostics/Diagnostic/GdExtensionCheck.php new file mode 100644 index 0000000000000000000000000000000000000000..00bb10a19d2692734fd97f2d6497eb510d60bbe6 --- /dev/null +++ b/plugins/Diagnostics/Diagnostic/GdExtensionCheck.php @@ -0,0 +1,40 @@ +<?php + +namespace Piwik\Plugins\Diagnostics\Diagnostic; + +use Piwik\Config; +use Piwik\SettingsServer; +use Piwik\Translation\Translator; + +/** + * Check that the GD extension is installed and the correct version. + */ +class GdExtensionCheck implements Diagnostic +{ + /** + * @var Translator + */ + private $translator; + + public function __construct(Translator $translator) + { + $this->translator = $translator; + } + + public function execute() + { + $label = $this->translator->translate('Installation_SystemCheckGDFreeType'); + + if (SettingsServer::isGdExtensionEnabled()) { + return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_OK)); + } + + $comment = sprintf( + '%s<br />%s', + $this->translator->translate('Installation_SystemCheckGDFreeType'), + $this->translator->translate('Installation_SystemCheckGDHelp') + ); + + return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_WARNING, $comment)); + } +} diff --git a/plugins/Diagnostics/Diagnostic/HttpClientCheck.php b/plugins/Diagnostics/Diagnostic/HttpClientCheck.php new file mode 100644 index 0000000000000000000000000000000000000000..998831a794e29893d078860111f1883c60abbaf4 --- /dev/null +++ b/plugins/Diagnostics/Diagnostic/HttpClientCheck.php @@ -0,0 +1,45 @@ +<?php + +namespace Piwik\Plugins\Diagnostics\Diagnostic; + +use Piwik\Config; +use Piwik\Filechecks; +use Piwik\Http; +use Piwik\Translation\Translator; + +/** + * Check that Piwik's HTTP client can work correctly. + */ +class HttpClientCheck implements Diagnostic +{ + /** + * @var Translator + */ + private $translator; + + public function __construct(Translator $translator) + { + $this->translator = $translator; + } + + public function execute() + { + $label = $this->translator->translate('Installation_SystemCheckOpenURL'); + + $httpMethod = Http::getTransportMethod(); + + if ($httpMethod) { + return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_OK, $httpMethod)); + } + + $canAutoUpdate = Filechecks::canAutoUpdate(); + + $comment = $this->translator->translate('Installation_SystemCheckOpenURLHelp'); + + if (! $canAutoUpdate) { + $comment .= '<br/>' . $this->translator->translate('Installation_SystemCheckAutoUpdateHelp'); + } + + return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_WARNING, $comment)); + } +} diff --git a/plugins/Diagnostics/Diagnostic/LoadDataInfileCheck.php b/plugins/Diagnostics/Diagnostic/LoadDataInfileCheck.php new file mode 100644 index 0000000000000000000000000000000000000000..a6caab824c2d07d530dd01b5d7bf1a5adcf9ab9c --- /dev/null +++ b/plugins/Diagnostics/Diagnostic/LoadDataInfileCheck.php @@ -0,0 +1,81 @@ +<?php + +namespace Piwik\Plugins\Diagnostics\Diagnostic; + +use Piwik\Common; +use Piwik\Config; +use Piwik\Db; +use Piwik\Translation\Translator; + +/** + * Check if Piwik can use LOAD DATA INFILE. + */ +class LoadDataInfileCheck implements Diagnostic +{ + /** + * @var Translator + */ + private $translator; + + public function __construct(Translator $translator) + { + $this->translator = $translator; + } + + public function execute() + { + $isPiwikInstalling = !Config::getInstance()->existsLocalConfig(); + if ($isPiwikInstalling) { + // Skip the diagnostic if Piwik is being installed + return array(); + } + + $label = $this->translator->translate('Installation_DatabaseAbilities'); + + $optionTable = Common::prefixTable('option'); + $testOptionNames = array('test_system_check1', 'test_system_check2'); + + $loadDataInfile = false; + $errorMessage = null; + try { + $loadDataInfile = Db\BatchInsert::tableInsertBatch( + $optionTable, + array('option_name', 'option_value'), + array( + array($testOptionNames[0], '1'), + array($testOptionNames[1], '2'), + ), + $throwException = true + ); + } catch (\Exception $ex) { + $errorMessage = str_replace("\n", "<br/>", $ex->getMessage()); + } + + // delete the temporary rows that were created + Db::exec("DELETE FROM `$optionTable` WHERE option_name IN ('" . implode("','", $testOptionNames) . "')"); + + if ($loadDataInfile) { + return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_OK, 'LOAD DATA INFILE')); + } + + $comment = sprintf( + 'LOAD DATA INFILE<br/>%s<br/>%s', + $this->translator->translate('Installation_LoadDataInfileUnavailableHelp', array( + 'LOAD DATA INFILE', + 'FILE', + )), + $this->translator->translate('Installation_LoadDataInfileRecommended') + ); + + if ($errorMessage) { + $comment .= sprintf( + '<br/><strong>%s:</strong> %s<br/>%s', + $this->translator->translate('General_Error'), + $errorMessage, + 'Troubleshooting: <a target="_blank" href="?module=Proxy&action=redirect&url=http://piwik.org/faq/troubleshooting/%23faq_194">FAQ on piwik.org</a>' + ); + } + + return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_WARNING, $comment)); + } +} diff --git a/plugins/Diagnostics/Diagnostic/MemoryLimitCheck.php b/plugins/Diagnostics/Diagnostic/MemoryLimitCheck.php new file mode 100644 index 0000000000000000000000000000000000000000..8f1ba1cf388a47350424dfc48ddae015a9b47382 --- /dev/null +++ b/plugins/Diagnostics/Diagnostic/MemoryLimitCheck.php @@ -0,0 +1,52 @@ +<?php + +namespace Piwik\Plugins\Diagnostics\Diagnostic; + +use Piwik\Config; +use Piwik\SettingsServer; +use Piwik\Translation\Translator; + +/** + * Check that the memory limit is enough. + */ +class MemoryLimitCheck implements Diagnostic +{ + /** + * @var Translator + */ + private $translator; + + /** + * @var int + */ + private $minimumMemoryLimit; + + public function __construct(Translator $translator, $minimumMemoryLimit) + { + $this->translator = $translator; + $this->minimumMemoryLimit = $minimumMemoryLimit; + } + + public function execute() + { + $label = $this->translator->translate('Installation_SystemCheckMemoryLimit'); + + SettingsServer::raiseMemoryLimitIfNecessary(); + + $memoryLimit = SettingsServer::getMemoryLimitValue(); + $comment = $memoryLimit . 'M'; + + if ($memoryLimit >= $this->minimumMemoryLimit) { + $status = DiagnosticResult::STATUS_OK; + } else { + $status = DiagnosticResult::STATUS_WARNING; + $comment .= sprintf( + '<br />%s<br />%s', + $this->translator->translate('Installation_SystemCheckMemoryLimitHelp'), + $this->translator->translate('Installation_RestartWebServer') + ); + } + + return array(DiagnosticResult::singleResult($label, $status, $comment)); + } +} diff --git a/plugins/Diagnostics/Diagnostic/NfsDiskCheck.php b/plugins/Diagnostics/Diagnostic/NfsDiskCheck.php new file mode 100644 index 0000000000000000000000000000000000000000..b563ae3d4a29d9b9373e8032148fd48765e9bbe4 --- /dev/null +++ b/plugins/Diagnostics/Diagnostic/NfsDiskCheck.php @@ -0,0 +1,50 @@ +<?php + +namespace Piwik\Plugins\Diagnostics\Diagnostic; + +use Piwik\Config; +use Piwik\Filesystem; +use Piwik\Translation\Translator; + +/** + * Checks if the filesystem Piwik stores sessions in is NFS or not. + * + * This check is done in order to avoid using file based sessions on NFS system, + * since on such a filesystem file locking can make file based sessions incredibly slow. + */ +class NfsDiskCheck implements Diagnostic +{ + /** + * @var Translator + */ + private $translator; + + public function __construct(Translator $translator) + { + $this->translator = $translator; + } + + public function execute() + { + $label = $this->translator->translate('Installation_Filesystem'); + + if (! Filesystem::checkIfFileSystemIsNFS()) { + return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_OK)); + } + + $isPiwikInstalling = !Config::getInstance()->existsLocalConfig(); + if ($isPiwikInstalling) { + $help = 'Installation_NfsFilesystemWarningSuffixInstall'; + } else { + $help = 'Installation_NfsFilesystemWarningSuffixAdmin'; + } + + $comment = sprintf( + '%s<br />%s', + $this->translator->translate('Installation_NfsFilesystemWarning'), + $this->translator->translate($help) + ); + + return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_WARNING, $comment)); + } +} diff --git a/plugins/Diagnostics/Diagnostic/PageSpeedCheck.php b/plugins/Diagnostics/Diagnostic/PageSpeedCheck.php new file mode 100644 index 0000000000000000000000000000000000000000..f7444b8823446d3cca140ddece682fb1fad3be17 --- /dev/null +++ b/plugins/Diagnostics/Diagnostic/PageSpeedCheck.php @@ -0,0 +1,75 @@ +<?php + +namespace Piwik\Plugins\Diagnostics\Diagnostic; + +use Piwik\Config; +use Piwik\Http; +use Piwik\Translation\Translator; +use Piwik\Url; +use Psr\Log\LoggerInterface; + +/** + * Check that mod_pagespeed is not enabled. + */ +class PageSpeedCheck implements Diagnostic +{ + /** + * @var Translator + */ + private $translator; + + /** + * @var LoggerInterface + */ + private $logger; + + public function __construct(Translator $translator, LoggerInterface $logger) + { + $this->translator = $translator; + $this->logger = $logger; + } + + public function execute() + { + $label = $this->translator->translate('Installation_SystemCheckPageSpeedDisabled'); + + if (! $this->isPageSpeedEnabled()) { + return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_OK)); + } + + $comment = $this->translator->translate('Installation_SystemCheckPageSpeedWarn', array( + '(eg. Apache, Nginx or IIS)', + )); + + return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_WARNING, $comment)); + } + + private function isPageSpeedEnabled() + { + $url = Url::getCurrentUrlWithoutQueryString() . '?module=Installation&action=getEmptyPageForSystemCheck'; + + try { + $page = Http::sendHttpRequest($url, + $timeout = 1, + $userAgent = null, + $destinationPath = null, + $followDepth = 0, + $acceptLanguage = false, + $byteRange = false, + + // Return headers + $getExtendedInfo = true + ); + } catch(\Exception $e) { + $this->logger->info('Unable to test if mod_pagespeed is enabled: the request to {url} failed', array( + 'url' => $url, + )); + // If the test failed, we assume Page speed is not enabled + return false; + } + + $headers = $page['headers']; + + return isset($headers['X-Mod-Pagespeed']) || isset($headers['X-Page-Speed']); + } +} diff --git a/plugins/Diagnostics/Diagnostic/PhpExtensionsCheck.php b/plugins/Diagnostics/Diagnostic/PhpExtensionsCheck.php new file mode 100644 index 0000000000000000000000000000000000000000..901c18c580f963e85734d21a80266bf6c4472bda --- /dev/null +++ b/plugins/Diagnostics/Diagnostic/PhpExtensionsCheck.php @@ -0,0 +1,84 @@ +<?php + +namespace Piwik\Plugins\Diagnostics\Diagnostic; + +use Piwik\Translation\Translator; + +/** + * Check the PHP extensions. + */ +class PhpExtensionsCheck implements Diagnostic +{ + /** + * @var Translator + */ + private $translator; + + public function __construct(Translator $translator) + { + $this->translator = $translator; + } + + public function execute() + { + $label = $this->translator->translate('Installation_SystemCheckExtensions'); + + $result = new DiagnosticResult($label); + $longErrorMessage = ''; + + $requiredExtensions = $this->getRequiredExtensions(); + $loadedExtensions = @get_loaded_extensions(); + + foreach ($requiredExtensions as $extension) { + if (! in_array($extension, $loadedExtensions)) { + $status = DiagnosticResult::STATUS_ERROR; + $comment = $extension . ': ' . $this->translator->translate('Installation_RestartWebServer'); + $longErrorMessage .= '<p>' . $this->getHelpMessage($extension) . '</p>'; + } else { + $status = DiagnosticResult::STATUS_OK; + $comment = $extension; + } + + $result->addItem(new DiagnosticResultItem($status, $comment)); + } + + $result->setLongErrorMessage($longErrorMessage); + + return array($result); + } + + /** + * @return string[] + */ + private function getRequiredExtensions() + { + $requiredExtensions = array( + 'zlib', + 'SPL', + 'iconv', + 'json', + 'mbstring', + ); + + if (! defined('HHVM_VERSION')) { + // HHVM provides the required subset of Reflection but lists Reflections as missing + $requiredExtensions[] = 'Reflection'; + } + + return $requiredExtensions; + } + + private function getHelpMessage($missingExtension) + { + $messages = array( + 'zlib' => 'Installation_SystemCheckZlibHelp', + 'SPL' => 'Installation_SystemCheckSplHelp', + 'iconv' => 'Installation_SystemCheckIconvHelp', + 'json' => 'Installation_SystemCheckWarnJsonHelp', + 'mbstring' => 'Installation_SystemCheckMbstringHelp', + 'Reflection' => 'Required extension that is built in PHP, see http://www.php.net/manual/en/book.reflection.php', + ); + + return $this->translator->translate($messages[$missingExtension]); + } +} diff --git a/plugins/Diagnostics/Diagnostic/PhpFunctionsCheck.php b/plugins/Diagnostics/Diagnostic/PhpFunctionsCheck.php new file mode 100644 index 0000000000000000000000000000000000000000..7e68c9538edebb956dc916985cd3ef46d5d79a35 --- /dev/null +++ b/plugins/Diagnostics/Diagnostic/PhpFunctionsCheck.php @@ -0,0 +1,106 @@ +<?php + +namespace Piwik\Plugins\Diagnostics\Diagnostic; + +use Piwik\Translation\Translator; + +/** + * Check the enabled PHP functions. + */ +class PhpFunctionsCheck implements Diagnostic +{ + /** + * @var Translator + */ + private $translator; + + public function __construct(Translator $translator) + { + $this->translator = $translator; + } + + public function execute() + { + $label = $this->translator->translate('Installation_SystemCheckFunctions'); + + $result = new DiagnosticResult($label); + + foreach ($this->getRequiredFunctions() as $function) { + if (! self::functionExists($function)) { + $status = DiagnosticResult::STATUS_ERROR; + $comment = sprintf( + '%s <br/><br/><em>%s</em><br/><em>%s</em><br/>', + $function, + $this->getHelpMessage($function), + $this->translator->translate('Installation_RestartWebServer') + ); + } else { + $status = DiagnosticResult::STATUS_OK; + $comment = $function; + } + + $result->addItem(new DiagnosticResultItem($status, $comment)); + } + + return array($result); + } + + /** + * @return string[] + */ + private function getRequiredFunctions() + { + return array( + 'debug_backtrace', + 'create_function', + 'eval', + 'gzcompress', + 'gzuncompress', + 'pack', + ); + } + + /** + * Tests if a function exists. Also handles the case where a function is disabled via Suhosin. + * + * @param string $function + * @return bool + */ + public static function functionExists($function) + { + // eval() is a language construct + if ($function == 'eval') { + // does not check suhosin.executor.eval.whitelist (or blacklist) + if (extension_loaded('suhosin')) { + return @ini_get("suhosin.executor.disable_eval") != "1"; + } + return true; + } + + $exists = function_exists($function); + + if (extension_loaded('suhosin')) { + $blacklist = @ini_get("suhosin.executor.func.blacklist"); + if (!empty($blacklist)) { + $blacklistFunctions = array_map('strtolower', array_map('trim', explode(',', $blacklist))); + return $exists && !in_array($function, $blacklistFunctions); + } + } + + return $exists; + } + + private function getHelpMessage($missingFunction) + { + $messages = array( + 'debug_backtrace' => 'Installation_SystemCheckDebugBacktraceHelp', + 'create_function' => 'Installation_SystemCheckCreateFunctionHelp', + 'eval' => 'Installation_SystemCheckEvalHelp', + 'gzcompress' => 'Installation_SystemCheckGzcompressHelp', + 'gzuncompress' => 'Installation_SystemCheckGzuncompressHelp', + 'pack' => 'Installation_SystemCheckPackHelp', + ); + + return $this->translator->translate($messages[$missingFunction]); + } +} diff --git a/plugins/Diagnostics/Diagnostic/PhpSettingsCheck.php b/plugins/Diagnostics/Diagnostic/PhpSettingsCheck.php new file mode 100644 index 0000000000000000000000000000000000000000..6760c1d6be06a6311425dbbebe9a9ef86ccb7042 --- /dev/null +++ b/plugins/Diagnostics/Diagnostic/PhpSettingsCheck.php @@ -0,0 +1,75 @@ +<?php + +namespace Piwik\Plugins\Diagnostics\Diagnostic; + +use Piwik\Translation\Translator; + +/** + * Check some PHP INI settings. + */ +class PhpSettingsCheck implements Diagnostic +{ + /** + * @var Translator + */ + private $translator; + + public function __construct(Translator $translator) + { + $this->translator = $translator; + } + + public function execute() + { + $label = $this->translator->translate('Installation_SystemCheckSettings'); + + $result = new DiagnosticResult($label); + + foreach ($this->getRequiredSettings() as $setting) { + list($settingName, $requiredValue) = explode('=', $setting); + + $currentValue = (int) ini_get($settingName); + + if ($currentValue != $requiredValue) { + $status = DiagnosticResult::STATUS_ERROR; + $comment = sprintf( + '%s <br/><br/><em>%s</em><br/><em>%s</em><br/>', + $setting, + $this->translator->translate('Installation_SystemCheckPhpSetting', array($setting)), + $this->translator->translate('Installation_RestartWebServer') + ); + } else { + $status = DiagnosticResult::STATUS_OK; + $comment = $setting; + } + + $result->addItem(new DiagnosticResultItem($status, $comment)); + } + + return array($result); + } + + /** + * @return string[] + */ + private function getRequiredSettings() + { + $requiredSettings = array( + // setting = required value + // Note: value must be an integer only + 'session.auto_start=0', + ); + + if ($this->isPhpVersionAtLeast56()) { + // always_populate_raw_post_data must be -1 + $requiredSettings[] = 'always_populate_raw_post_data=-1'; + } + + return $requiredSettings; + } + + private function isPhpVersionAtLeast56() + { + return version_compare(PHP_VERSION, '5.6', '>='); + } +} diff --git a/plugins/Diagnostics/Diagnostic/PhpVersionCheck.php b/plugins/Diagnostics/Diagnostic/PhpVersionCheck.php new file mode 100644 index 0000000000000000000000000000000000000000..e7010804a0fce1e3965ea7f370360998fdce076c --- /dev/null +++ b/plugins/Diagnostics/Diagnostic/PhpVersionCheck.php @@ -0,0 +1,53 @@ +<?php + +namespace Piwik\Plugins\Diagnostics\Diagnostic; + +use Piwik\Translation\Translator; + +/** + * Check the PHP version. + */ +class PhpVersionCheck implements Diagnostic +{ + /** + * @var Translator + */ + private $translator; + + public function __construct(Translator $translator) + { + $this->translator = $translator; + } + + public function execute() + { + global $piwik_minimumPHPVersion; + + $actualVersion = PHP_VERSION; + + $label = sprintf( + '%s >= %s', + $this->translator->translate('Installation_SystemCheckPhp'), + $piwik_minimumPHPVersion + ); + + if ($this->isPhpVersionValid($piwik_minimumPHPVersion, $actualVersion)) { + $status = DiagnosticResult::STATUS_OK; + $comment = $actualVersion; + } else { + $status = DiagnosticResult::STATUS_ERROR; + $comment = sprintf( + '%s: %s', + $this->translator->translate('General_Error'), + $this->translator->translate('General_Required', array($label)) + ); + } + + return array(DiagnosticResult::singleResult($label, $status, $comment)); + } + + private function isPhpVersionValid($requiredVersion, $actualVersion) + { + return version_compare($requiredVersion, $actualVersion) <= 0; + } +} diff --git a/plugins/Diagnostics/Diagnostic/RecommendedExtensionsCheck.php b/plugins/Diagnostics/Diagnostic/RecommendedExtensionsCheck.php new file mode 100644 index 0000000000000000000000000000000000000000..c68a65d0a629fdf654ef8f35b46ef9be88c476e1 --- /dev/null +++ b/plugins/Diagnostics/Diagnostic/RecommendedExtensionsCheck.php @@ -0,0 +1,69 @@ +<?php + +namespace Piwik\Plugins\Diagnostics\Diagnostic; + +use Piwik\Translation\Translator; + +/** + * Check the PHP extensions that are not required but recommended. + */ +class RecommendedExtensionsCheck implements Diagnostic +{ + /** + * @var Translator + */ + private $translator; + + public function __construct(Translator $translator) + { + $this->translator = $translator; + } + + public function execute() + { + $label = $this->translator->translate('Installation_SystemCheckOtherExtensions'); + + $result = new DiagnosticResult($label); + + $loadedExtensions = @get_loaded_extensions(); + + foreach ($this->getRecommendedExtensions() as $extension) { + if (! in_array($extension, $loadedExtensions)) { + $status = DiagnosticResult::STATUS_WARNING; + $comment = $extension . '<br/>' . $this->getHelpMessage($extension); + } else { + $status = DiagnosticResult::STATUS_OK; + $comment = $extension; + } + + $result->addItem(new DiagnosticResultItem($status, $comment)); + } + + return array($result); + } + + /** + * @return string[] + */ + private function getRecommendedExtensions() + { + return array( + 'json', + 'libxml', + 'dom', + 'SimpleXML', + ); + } + + private function getHelpMessage($missingExtension) + { + $messages = array( + 'json' => 'Installation_SystemCheckWarnJsonHelp', + 'libxml' => 'Installation_SystemCheckWarnLibXmlHelp', + 'dom' => 'Installation_SystemCheckWarnDomHelp', + 'SimpleXML' => 'Installation_SystemCheckWarnSimpleXMLHelp', + ); + + return $this->translator->translate($messages[$missingExtension]); + } +} diff --git a/plugins/Diagnostics/Diagnostic/RecommendedFunctionsCheck.php b/plugins/Diagnostics/Diagnostic/RecommendedFunctionsCheck.php new file mode 100644 index 0000000000000000000000000000000000000000..eedf8e92249e9c8e8195338f689939e1bf1ab83b --- /dev/null +++ b/plugins/Diagnostics/Diagnostic/RecommendedFunctionsCheck.php @@ -0,0 +1,69 @@ +<?php + +namespace Piwik\Plugins\Diagnostics\Diagnostic; + +use Piwik\Translation\Translator; + +/** + * Check the PHP functions that are not required but recommended. + */ +class RecommendedFunctionsCheck implements Diagnostic +{ + /** + * @var Translator + */ + private $translator; + + public function __construct(Translator $translator) + { + $this->translator = $translator; + } + + public function execute() + { + $label = $this->translator->translate('Installation_SystemCheckOtherFunctions'); + + $result = new DiagnosticResult($label); + + foreach ($this->getRecommendedFunctions() as $function) { + if (! PhpFunctionsCheck::functionExists($function)) { + $status = DiagnosticResult::STATUS_WARNING; + $comment = $function . '<br/>' . $this->getHelpMessage($function); + } else { + $status = DiagnosticResult::STATUS_OK; + $comment = $function; + } + + $result->addItem(new DiagnosticResultItem($status, $comment)); + } + + return array($result); + } + + /** + * @return string[] + */ + private function getRecommendedFunctions() + { + return array( + 'set_time_limit', + 'mail', + 'parse_ini_file', + 'glob', + 'gzopen', + ); + } + + private function getHelpMessage($function) + { + $messages = array( + 'set_time_limit' => 'Installation_SystemCheckTimeLimitHelp', + 'mail' => 'Installation_SystemCheckMailHelp', + 'parse_ini_file' => 'Installation_SystemCheckParseIniFileHelp', + 'glob' => 'Installation_SystemCheckGlobHelp', + 'gzopen' => 'Installation_SystemCheckZlibHelp', + ); + + return $this->translator->translate($messages[$function]); + } +} diff --git a/plugins/Diagnostics/Diagnostic/TimezoneCheck.php b/plugins/Diagnostics/Diagnostic/TimezoneCheck.php new file mode 100644 index 0000000000000000000000000000000000000000..dfecd498bd824022b74753194fa927602ed1b9b0 --- /dev/null +++ b/plugins/Diagnostics/Diagnostic/TimezoneCheck.php @@ -0,0 +1,40 @@ +<?php + +namespace Piwik\Plugins\Diagnostics\Diagnostic; + +use Piwik\Config; +use Piwik\SettingsServer; +use Piwik\Translation\Translator; + +/** + * Check that the PHP timezone setting is set. + */ +class TimezoneCheck implements Diagnostic +{ + /** + * @var Translator + */ + private $translator; + + public function __construct(Translator $translator) + { + $this->translator = $translator; + } + + public function execute() + { + $label = $this->translator->translate('SitesManager_Timezone'); + + if (SettingsServer::isTimezoneSupportEnabled()) { + return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_OK)); + } + + $comment = sprintf( + '%s<br />%s.', + $this->translator->translate('SitesManager_AdvancedTimezoneSupportNotFound'), + '<a href="http://php.net/manual/en/datetime.installation.php" rel="noreferrer" target="_blank">Timezone PHP documentation</a>' + ); + + return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_WARNING, $comment)); + } +} diff --git a/plugins/Diagnostics/Diagnostic/TrackerCheck.php b/plugins/Diagnostics/Diagnostic/TrackerCheck.php new file mode 100644 index 0000000000000000000000000000000000000000..1d2a95febf23624a2fa068077d3340bf5a005231 --- /dev/null +++ b/plugins/Diagnostics/Diagnostic/TrackerCheck.php @@ -0,0 +1,44 @@ +<?php + +namespace Piwik\Plugins\Diagnostics\Diagnostic; + +use Piwik\Common; +use Piwik\Translation\Translator; + +/** + * Check that the tracker is working correctly. + */ +class TrackerCheck implements Diagnostic +{ + /** + * @var Translator + */ + private $translator; + + public function __construct(Translator $translator) + { + $this->translator = $translator; + } + + public function execute() + { + $label = $this->translator->translate('Installation_SystemCheckTracker'); + + $trackerStatus = Common::getRequestVar('trackerStatus', 0, 'int'); + + if ($trackerStatus == 0) { + $status = DiagnosticResult::STATUS_OK; + $comment = ''; + } else { + $status = DiagnosticResult::STATUS_WARNING; + $comment = sprintf( + '%s<br />%s<br />%s', + $trackerStatus, + $this->translator->translate('Installation_SystemCheckTrackerHelp'), + $this->translator->translate('Installation_RestartWebServer') + ); + } + + return array(DiagnosticResult::singleResult($label, $status, $comment)); + } +} diff --git a/plugins/Diagnostics/Diagnostic/WriteAccessCheck.php b/plugins/Diagnostics/Diagnostic/WriteAccessCheck.php new file mode 100644 index 0000000000000000000000000000000000000000..b4e9a4c8b8be6eef668b85bc22130c42b6b7fdbf --- /dev/null +++ b/plugins/Diagnostics/Diagnostic/WriteAccessCheck.php @@ -0,0 +1,94 @@ +<?php + +namespace Piwik\Plugins\Diagnostics\Diagnostic; + +use Piwik\DbHelper; +use Piwik\Filechecks; +use Piwik\Translation\Translator; + +/** + * Check the permissions for some directories. + */ +class WriteAccessCheck implements Diagnostic +{ + /** + * @var Translator + */ + private $translator; + + /** + * Path to the temp directory. + * @var string + */ + private $tmpPath; + + /** + * @param Translator $translator + * @param string $tmpPath Path to the temp directory. + */ + public function __construct(Translator $translator, $tmpPath) + { + $this->translator = $translator; + $this->tmpPath = $tmpPath; + } + + public function execute() + { + $label = $this->translator->translate('Installation_SystemCheckWriteDirs'); + + $result = new DiagnosticResult($label); + + $directories = Filechecks::checkDirectoriesWritable($this->getDirectories()); + + $error = false; + foreach ($directories as $directory => $isWritable) { + if ($isWritable) { + $status = DiagnosticResult::STATUS_OK; + } else { + $status = DiagnosticResult::STATUS_ERROR; + $error = true; + } + + $result->addItem(new DiagnosticResultItem($status, $directory)); + } + + if ($error) { + $longErrorMessage = $this->translator->translate('Installation_SystemCheckWriteDirsHelp'); + $longErrorMessage .= '<ul>'; + foreach ($directories as $directory => $isWritable) { + if (! $isWritable) { + $longErrorMessage .= sprintf('<li><pre>chmod a+w %s</pre></li>', $directory); + } + } + $longErrorMessage .= '</ul>'; + $result->setLongErrorMessage($longErrorMessage); + } + + return array($result); + } + + /** + * @return string[] + */ + private function getDirectories() + { + $directoriesToCheck = array( + $this->tmpPath, + $this->tmpPath . '/assets/', + $this->tmpPath . '/cache/', + $this->tmpPath . '/climulti/', + $this->tmpPath . '/latest/', + $this->tmpPath . '/logs/', + $this->tmpPath . '/sessions/', + $this->tmpPath . '/tcpdf/', + $this->tmpPath . '/templates_c/', + ); + + if (! DbHelper::isInstalled()) { + // at install, need /config to be writable (so we can create config.ini.php) + $directoriesToCheck[] = '/config/'; + } + + return $directoriesToCheck; + } +} diff --git a/plugins/Diagnostics/DiagnosticReport.php b/plugins/Diagnostics/DiagnosticReport.php new file mode 100644 index 0000000000000000000000000000000000000000..865225a66d7474034bfd61342e4457144b90f72a --- /dev/null +++ b/plugins/Diagnostics/DiagnosticReport.php @@ -0,0 +1,113 @@ +<?php + +namespace Piwik\Plugins\Diagnostics; + +use Piwik\Plugins\Diagnostics\Diagnostic\DiagnosticResult; + +/** + * A diagnostic report contains all the results of all the diagnostics. + */ +class DiagnosticReport +{ + /** + * @var DiagnosticResult[] + */ + private $mandatoryDiagnosticResults; + + /** + * @var DiagnosticResult[] + */ + private $optionalDiagnosticResults; + + /** + * @var int + */ + private $errorCount = 0; + + /** + * @var int + */ + private $warningCount = 0; + + /** + * @param DiagnosticResult[] $mandatoryDiagnosticResults + * @param DiagnosticResult[] $optionalDiagnosticResults + */ + public function __construct(array $mandatoryDiagnosticResults, array $optionalDiagnosticResults) + { + $this->mandatoryDiagnosticResults = $mandatoryDiagnosticResults; + $this->optionalDiagnosticResults = $optionalDiagnosticResults; + + $this->computeErrorAndWarningCount(); + } + + /** + * @return bool + */ + public function hasErrors() + { + return $this->getErrorCount() > 0; + } + + /** + * @return bool + */ + public function hasWarnings() + { + return $this->getWarningCount() > 0; + } + + /** + * @return int + */ + public function getErrorCount() + { + return $this->errorCount; + } + + /** + * @return int + */ + public function getWarningCount() + { + return $this->warningCount; + } + + /** + * @return DiagnosticResult[] + */ + public function getAllResults() + { + return array_merge($this->mandatoryDiagnosticResults, $this->optionalDiagnosticResults); + } + + /** + * @return DiagnosticResult[] + */ + public function getMandatoryDiagnosticResults() + { + return $this->mandatoryDiagnosticResults; + } + + /** + * @return DiagnosticResult[] + */ + public function getOptionalDiagnosticResults() + { + return $this->optionalDiagnosticResults; + } + + private function computeErrorAndWarningCount() + { + foreach ($this->getAllResults() as $result) { + switch ($result->getStatus()) { + case DiagnosticResult::STATUS_ERROR: + $this->errorCount++; + break; + case DiagnosticResult::STATUS_WARNING: + $this->warningCount++; + break; + } + } + } +} diff --git a/plugins/Diagnostics/DiagnosticService.php b/plugins/Diagnostics/DiagnosticService.php new file mode 100644 index 0000000000000000000000000000000000000000..0ce53fb5ca33ed338213b43f619b6209293a4ba0 --- /dev/null +++ b/plugins/Diagnostics/DiagnosticService.php @@ -0,0 +1,68 @@ +<?php + +namespace Piwik\Plugins\Diagnostics; + +use Piwik\Plugins\Diagnostics\Diagnostic\Diagnostic; +use Piwik\Plugins\Diagnostics\Diagnostic\DiagnosticResult; + +/** + * Runs the Piwik diagnostics. + * + * @api + */ +class DiagnosticService +{ + /** + * @var Diagnostic[] + */ + private $mandatoryDiagnostics; + + /** + * @var Diagnostic[] + */ + private $optionalDiagnostics; + + /** + * @param Diagnostic[] $mandatoryDiagnostics + * @param Diagnostic[] $optionalDiagnostics + * @param Diagnostic[] $disabledDiagnostics + */ + public function __construct(array $mandatoryDiagnostics, array $optionalDiagnostics, array $disabledDiagnostics) + { + $this->mandatoryDiagnostics = $this->removeDisabledDiagnostics($mandatoryDiagnostics, $disabledDiagnostics); + $this->optionalDiagnostics = $this->removeDisabledDiagnostics($optionalDiagnostics, $disabledDiagnostics); + } + + /** + * @return DiagnosticReport + */ + public function runDiagnostics() + { + return new DiagnosticReport( + $this->run($this->mandatoryDiagnostics), + $this->run($this->optionalDiagnostics) + ); + } + + /** + * @param Diagnostic[] $diagnostics + * @return DiagnosticResult[] + */ + private function run(array $diagnostics) + { + $results = array(); + + foreach ($diagnostics as $diagnostic) { + $results = array_merge($results, $diagnostic->execute()); + } + + return $results; + } + + private function removeDisabledDiagnostics(array $diagnostics, array $disabledDiagnostics) + { + return array_filter($diagnostics, function (Diagnostic $diagnostic) use ($disabledDiagnostics) { + return ! in_array($diagnostic, $disabledDiagnostics, true); + }); + } +} diff --git a/plugins/Diagnostics/Diagnostics.php b/plugins/Diagnostics/Diagnostics.php new file mode 100644 index 0000000000000000000000000000000000000000..f69d1f45d128a5bc0eb829b5c38e62c84cb41e1f --- /dev/null +++ b/plugins/Diagnostics/Diagnostics.php @@ -0,0 +1,15 @@ +<?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\Diagnostics; + +use Piwik\Plugin; + +class Diagnostics extends Plugin +{ +} diff --git a/plugins/Diagnostics/Test/Mock/DiagnosticWithError.php b/plugins/Diagnostics/Test/Mock/DiagnosticWithError.php new file mode 100644 index 0000000000000000000000000000000000000000..bc7d6db966214167c1d56509ef561a8885d4de38 --- /dev/null +++ b/plugins/Diagnostics/Test/Mock/DiagnosticWithError.php @@ -0,0 +1,22 @@ +<?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\Diagnostics\Test\Mock; + +use Piwik\Plugins\Diagnostics\Diagnostic\Diagnostic; +use Piwik\Plugins\Diagnostics\Diagnostic\DiagnosticResult; + +class DiagnosticWithError implements Diagnostic +{ + public function execute() + { + return array( + DiagnosticResult::singleResult('Error', DiagnosticResult::STATUS_ERROR, 'Comment'), + ); + } +} diff --git a/plugins/Diagnostics/Test/Mock/DiagnosticWithSuccess.php b/plugins/Diagnostics/Test/Mock/DiagnosticWithSuccess.php new file mode 100644 index 0000000000000000000000000000000000000000..ad01d957106060890647ba060bc25bd6cfeeec5d --- /dev/null +++ b/plugins/Diagnostics/Test/Mock/DiagnosticWithSuccess.php @@ -0,0 +1,22 @@ +<?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\Diagnostics\Test\Mock; + +use Piwik\Plugins\Diagnostics\Diagnostic\Diagnostic; +use Piwik\Plugins\Diagnostics\Diagnostic\DiagnosticResult; + +class DiagnosticWithSuccess implements Diagnostic +{ + public function execute() + { + return array( + DiagnosticResult::singleResult('Success', DiagnosticResult::STATUS_OK, 'Comment'), + ); + } +} diff --git a/plugins/Diagnostics/Test/Mock/DiagnosticWithWarning.php b/plugins/Diagnostics/Test/Mock/DiagnosticWithWarning.php new file mode 100644 index 0000000000000000000000000000000000000000..a7e891500509199dccb223392332955641fa276c --- /dev/null +++ b/plugins/Diagnostics/Test/Mock/DiagnosticWithWarning.php @@ -0,0 +1,22 @@ +<?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\Diagnostics\Test\Mock; + +use Piwik\Plugins\Diagnostics\Diagnostic\Diagnostic; +use Piwik\Plugins\Diagnostics\Diagnostic\DiagnosticResult; + +class DiagnosticWithWarning implements Diagnostic +{ + public function execute() + { + return array( + DiagnosticResult::singleResult('Warning', DiagnosticResult::STATUS_WARNING, 'Comment'), + ); + } +} diff --git a/plugins/Diagnostics/Test/Unit/Diagnostic/DiagnosticResultTest.php b/plugins/Diagnostics/Test/Unit/Diagnostic/DiagnosticResultTest.php new file mode 100644 index 0000000000000000000000000000000000000000..efddeb74f2adc1ff24434f72a9907c62220367d7 --- /dev/null +++ b/plugins/Diagnostics/Test/Unit/Diagnostic/DiagnosticResultTest.php @@ -0,0 +1,37 @@ +<?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\Diagnostics\Test\Unit\Diagnostic; + +use Piwik\Plugins\Diagnostics\Diagnostic\DiagnosticResult; +use Piwik\Plugins\Diagnostics\Diagnostic\DiagnosticResultItem; + +class DiagnosticResultTest extends \PHPUnit_Framework_TestCase +{ + public function test_getStatus_shouldReturnTheWorstStatus() + { + $result = new DiagnosticResult('Label'); + + $this->assertEquals(DiagnosticResult::STATUS_OK, $result->getStatus()); + + $result->addItem(new DiagnosticResultItem(DiagnosticResult::STATUS_WARNING)); + $this->assertEquals(DiagnosticResult::STATUS_WARNING, $result->getStatus()); + + $result->addItem(new DiagnosticResultItem(DiagnosticResult::STATUS_ERROR)); + $this->assertEquals(DiagnosticResult::STATUS_ERROR, $result->getStatus()); + } + + public function test_singleResult_shouldReturnAResultWithASingleItem() + { + $result = DiagnosticResult::singleResult('Label', DiagnosticResult::STATUS_ERROR); + + $this->assertInstanceOf('Piwik\Plugins\Diagnostics\Diagnostic\DiagnosticResult', $result); + $this->assertEquals('Label', $result->getLabel()); + $this->assertEquals(DiagnosticResult::STATUS_ERROR, $result->getStatus()); + } +} diff --git a/plugins/Diagnostics/Test/Unit/DiagnosticReportTest.php b/plugins/Diagnostics/Test/Unit/DiagnosticReportTest.php new file mode 100644 index 0000000000000000000000000000000000000000..bdd673b3c0fa6a4ea3f07cebc5e1be3fdb9438ae --- /dev/null +++ b/plugins/Diagnostics/Test/Unit/DiagnosticReportTest.php @@ -0,0 +1,47 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +namespace Piwik\Plugins\Diagnostics\Test\Unit; + +use Piwik\Plugins\Diagnostics\Diagnostic\DiagnosticResult; +use Piwik\Plugins\Diagnostics\DiagnosticReport; + +class DiagnosticReportTest extends \PHPUnit_Framework_TestCase +{ + public function test_shouldComputeErrorAndWarningCount() + { + $report = new DiagnosticReport( + array(DiagnosticResult::singleResult('Error', DiagnosticResult::STATUS_ERROR, 'Comment')), + array(DiagnosticResult::singleResult('Warning', DiagnosticResult::STATUS_WARNING, 'Comment')) + ); + + $this->assertEquals(1, $report->getErrorCount()); + $this->assertTrue($report->hasErrors()); + $this->assertEquals(1, $report->getWarningCount()); + $this->assertTrue($report->hasWarnings()); + + $report = new DiagnosticReport(array(), array()); + + $this->assertEquals(0, $report->getErrorCount()); + $this->assertFalse($report->hasErrors()); + $this->assertEquals(0, $report->getWarningCount()); + $this->assertFalse($report->hasWarnings()); + } + + public function test_getAllResults() + { + $report = new DiagnosticReport( + array(DiagnosticResult::singleResult('Error', DiagnosticResult::STATUS_ERROR, 'Comment')), + array(DiagnosticResult::singleResult('Warning', DiagnosticResult::STATUS_WARNING, 'Comment')) + ); + + $this->assertCount(1, $report->getMandatoryDiagnosticResults()); + $this->assertCount(1, $report->getOptionalDiagnosticResults()); + $this->assertCount(2, $report->getAllResults()); + } +} diff --git a/plugins/Diagnostics/Test/Unit/DiagnosticServiceTest.php b/plugins/Diagnostics/Test/Unit/DiagnosticServiceTest.php new file mode 100644 index 0000000000000000000000000000000000000000..cfe11dcc8d6a9c0fdab4cb6684fe690d372adb55 --- /dev/null +++ b/plugins/Diagnostics/Test/Unit/DiagnosticServiceTest.php @@ -0,0 +1,43 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +namespace Piwik\Plugins\Diagnostics\Test\Unit; + +use Piwik\Plugins\Diagnostics\Diagnostic\DiagnosticResult; +use Piwik\Plugins\Diagnostics\DiagnosticService; +use Piwik\Plugins\Diagnostics\Test\Mock\DiagnosticWithError; +use Piwik\Plugins\Diagnostics\Test\Mock\DiagnosticWithSuccess; +use Piwik\Plugins\Diagnostics\Test\Mock\DiagnosticWithWarning; + +class DiagnosticServiceTest extends \PHPUnit_Framework_TestCase +{ + public function test_runDiagnostics() + { + $mandatoryDiagnostics = array( + new DiagnosticWithError(), + ); + $optionalDiagnostics = array( + new DiagnosticWithWarning(), + new DiagnosticWithSuccess(), + ); + + $service = new DiagnosticService($mandatoryDiagnostics, $optionalDiagnostics, array()); + + $report = $service->runDiagnostics(); + + $results = $report->getAllResults(); + + $this->assertCount(3, $results); + $this->assertEquals('Error', $results[0]->getLabel()); + $this->assertEquals(DiagnosticResult::STATUS_ERROR, $results[0]->getStatus()); + $this->assertEquals('Warning', $results[1]->getLabel()); + $this->assertEquals(DiagnosticResult::STATUS_WARNING, $results[1]->getStatus()); + $this->assertEquals('Success', $results[2]->getLabel()); + $this->assertEquals(DiagnosticResult::STATUS_OK, $results[2]->getStatus()); + } +} diff --git a/plugins/Diagnostics/config/config.php b/plugins/Diagnostics/config/config.php new file mode 100644 index 0000000000000000000000000000000000000000..566b4765354af50e0a21c9f579b2a5182419d895 --- /dev/null +++ b/plugins/Diagnostics/config/config.php @@ -0,0 +1,39 @@ +<?php + +return array( + // Diagnostics for everything that is required for Piwik to run + 'diagnostics.required' => array( + DI\get('Piwik\Plugins\Diagnostics\Diagnostic\PhpVersionCheck'), + DI\get('Piwik\Plugins\Diagnostics\Diagnostic\DbAdapterCheck'), + DI\get('Piwik\Plugins\Diagnostics\Diagnostic\PhpExtensionsCheck'), + DI\get('Piwik\Plugins\Diagnostics\Diagnostic\PhpFunctionsCheck'), + DI\get('Piwik\Plugins\Diagnostics\Diagnostic\PhpSettingsCheck'), + DI\get('Piwik\Plugins\Diagnostics\Diagnostic\WriteAccessCheck'), + ), + // Diagnostics for recommended features + 'diagnostics.optional' => array( + DI\get('Piwik\Plugins\Diagnostics\Diagnostic\FileIntegrityCheck'), + DI\get('Piwik\Plugins\Diagnostics\Diagnostic\TrackerCheck'), + DI\get('Piwik\Plugins\Diagnostics\Diagnostic\MemoryLimitCheck'), + DI\get('Piwik\Plugins\Diagnostics\Diagnostic\TimezoneCheck'), + DI\get('Piwik\Plugins\Diagnostics\Diagnostic\HttpClientCheck'), + DI\get('Piwik\Plugins\Diagnostics\Diagnostic\PageSpeedCheck'), + DI\get('Piwik\Plugins\Diagnostics\Diagnostic\GdExtensionCheck'), + DI\get('Piwik\Plugins\Diagnostics\Diagnostic\RecommendedExtensionsCheck'), + DI\get('Piwik\Plugins\Diagnostics\Diagnostic\RecommendedFunctionsCheck'), + DI\get('Piwik\Plugins\Diagnostics\Diagnostic\NfsDiskCheck'), + DI\get('Piwik\Plugins\Diagnostics\Diagnostic\CronArchivingCheck'), + DI\get('Piwik\Plugins\Diagnostics\Diagnostic\LoadDataInfileCheck'), + ), + // Allows other plugins to disable diagnostics that were previously registered + 'diagnostics.disabled' => array(), + + 'Piwik\Plugins\Diagnostics\DiagnosticService' => DI\object() + ->constructor(DI\get('diagnostics.required'), DI\get('diagnostics.optional'), DI\get('diagnostics.disabled')), + + 'Piwik\Plugins\Diagnostics\Diagnostic\MemoryLimitCheck' => DI\object() + ->constructorParameter('minimumMemoryLimit', DI\get('ini.General.minimum_memory_limit')), + + 'Piwik\Plugins\Diagnostics\Diagnostic\WriteAccessCheck' => DI\object() + ->constructorParameter('tmpPath', DI\get('path.tmp')), +); diff --git a/plugins/Diagnostics/plugin.json b/plugins/Diagnostics/plugin.json new file mode 100644 index 0000000000000000000000000000000000000000..da53096d70316d8aa9958e9672c43afaecc98d4a --- /dev/null +++ b/plugins/Diagnostics/plugin.json @@ -0,0 +1,3 @@ +{ + "description": "Performs diagnostics to check that Piwik is installed and runs correctly." +} \ No newline at end of file diff --git a/plugins/ExamplePlugin/tests/UI/expected-ui-screenshots/SimpleUITest_simplePage.png b/plugins/ExamplePlugin/tests/UI/expected-ui-screenshots/SimpleUITest_simplePage.png index 7b84c80fc159c6e13c8278c4adeb0d7400b29776..8026c389a618f1c6781b5b62324385c9cfa9d8c3 100644 Binary files a/plugins/ExamplePlugin/tests/UI/expected-ui-screenshots/SimpleUITest_simplePage.png and b/plugins/ExamplePlugin/tests/UI/expected-ui-screenshots/SimpleUITest_simplePage.png differ diff --git a/plugins/ExamplePlugin/tests/UI/expected-ui-screenshots/SimpleUITest_simplePagePartial.png b/plugins/ExamplePlugin/tests/UI/expected-ui-screenshots/SimpleUITest_simplePagePartial.png index 386280a3b83dcdebf00cc1da1a7e3f2bf7f028b0..39af7946e30ae85e9a429469b0727282ed90abc9 100644 Binary files a/plugins/ExamplePlugin/tests/UI/expected-ui-screenshots/SimpleUITest_simplePagePartial.png and b/plugins/ExamplePlugin/tests/UI/expected-ui-screenshots/SimpleUITest_simplePagePartial.png differ diff --git a/plugins/Goals/templates/getOverviewView.twig b/plugins/Goals/templates/getOverviewView.twig index 281d65952c03258ab9b6f8177d307382b4c0eede..798c8fc285d62db02beb0d587607bb3b945d5a74 100644 --- a/plugins/Goals/templates/getOverviewView.twig +++ b/plugins/Goals/templates/getOverviewView.twig @@ -15,20 +15,21 @@ </a> </h2> - <div id='leftcolumn'> - <div class="sparkline">{{ sparkline(goal.urlSparklineConversions) }} - {{ 'Goals_Conversions'|translate("<strong>"~nb_conversions~"</strong>")|raw }} - {% if goal.goalAllowMultipleConversionsPerVisit %} - ({{ 'General_NVisits'|translate("<strong>"~nb_visits_converted~"</strong>") | raw }}) - {% endif %} + <div class="row"> + <div class="col-md-6"> + <div class="sparkline">{{ sparkline(goal.urlSparklineConversions) }} + {{ 'Goals_Conversions'|translate("<strong>"~nb_conversions~"</strong>")|raw }} + {% if goal.goalAllowMultipleConversionsPerVisit %} + ({{ 'General_NVisits'|translate("<strong>"~nb_visits_converted~"</strong>") | raw }}) + {% endif %} + </div> </div> - </div> - <div id='rightcolumn'> - <div class="sparkline">{{ sparkline(goal.urlSparklineConversionRate) }} - {{ 'Goals_ConversionRate'|translate("<strong>"~conversion_rate~"</strong>")|raw }} + <div class="col-md-6"> + <div class="sparkline">{{ sparkline(goal.urlSparklineConversionRate) }} + {{ 'Goals_ConversionRate'|translate("<strong>"~conversion_rate~"</strong>")|raw }} + </div> </div> </div> - <br class="clear"/> </div> {% endfor %} diff --git a/plugins/Installation/Controller.php b/plugins/Installation/Controller.php index cdce51d111a41dbe4e0069aa9dee8d0c25d7ac86..d2c391fdb358500d1ed38455533487f17a35500d 100644 --- a/plugins/Installation/Controller.php +++ b/plugins/Installation/Controller.php @@ -23,9 +23,8 @@ use Piwik\Http; use Piwik\Option; use Piwik\Piwik; use Piwik\Plugin\Manager; -use Piwik\Plugins\CoreUpdater\CoreUpdater; +use Piwik\Plugins\Diagnostics\DiagnosticService; use Piwik\Plugins\LanguagesManager\LanguagesManager; -use Piwik\Plugins\PrivacyManager\IPAnonymizer; use Piwik\Plugins\SitesManager\API as APISitesManager; use Piwik\Plugins\UserCountry\LocationProvider; use Piwik\Plugins\UsersManager\API as APIUsersManager; @@ -112,15 +111,12 @@ class Controller extends \Piwik\Plugin\ControllerAdmin __FUNCTION__ ); - $view->duringInstall = true; + // Do not use dependency injection because this service requires a lot of sub-services across plugins + /** @var DiagnosticService $diagnosticService */ + $diagnosticService = StaticContainer::get('Piwik\Plugins\Diagnostics\DiagnosticService'); + $view->diagnosticReport = $diagnosticService->runDiagnostics(); - $this->setupSystemCheckView($view); - - $view->showNextStep = !$view->problemWithSomeDirectories - && $view->infos['phpVersion_ok'] - && count($view->infos['adapters']) - && !count($view->infos['missing_extensions']) - && !count($view->infos['missing_functions']); + $view->showNextStep = !$view->diagnosticReport->hasErrors(); // On the system check page, if all is green, display Next link at the top $view->showNextStepAtTop = $view->showNextStep; @@ -445,15 +441,6 @@ class Controller extends \Piwik\Plugin\ControllerAdmin return 'Hello, world!'; } - /** - * Get system information - */ - public static function getSystemInformation() - { - $systemCheck = new SystemCheck(); - return $systemCheck->getSystemInformation(); - } - /** * This controller action renders an admin tab that runs the installation * system check, so people can see if there are any issues w/ their running @@ -472,13 +459,9 @@ class Controller extends \Piwik\Plugin\ControllerAdmin ); $this->setBasicVariablesView($view); - $view->duringInstall = false; - - $this->setupSystemCheckView($view); - - $infos = $view->infos; - $infos['extra'] = SystemCheck::performAdminPageOnlySystemCheck(); - $view->infos = $infos; + /** @var DiagnosticService $diagnosticService */ + $diagnosticService = StaticContainer::get('Piwik\Plugins\Diagnostics\DiagnosticService'); + $view->diagnosticReport = $diagnosticService->runDiagnostics(); return $view->render(); } @@ -645,20 +628,6 @@ class Controller extends \Piwik\Plugin\ControllerAdmin } } - /** - * Utility function, sets up a view that will display system check info. - * - * @param View $view - */ - private function setupSystemCheckView($view) - { - $view->infos = self::getSystemInformation(); - - $view->helpMessages = $this->getSystemCheckHelpMessages(); - - $view->problemWithSomeDirectories = (false !== array_search(false, $view->infos['directories'])); - } - private function createSuperUser($login, $password, $email) { $self = $this; @@ -746,44 +715,4 @@ class Controller extends \Piwik\Plugin\ControllerAdmin return $result; }); } - - /** - * @return array - */ - private function getSystemCheckHelpMessages() - { - $helpMessages = array( - // Extensions - 'zlib' => 'Installation_SystemCheckZlibHelp', - 'gzopen' => 'Installation_SystemCheckZlibHelp', - 'SPL' => 'Installation_SystemCheckSplHelp', - 'iconv' => 'Installation_SystemCheckIconvHelp', - 'mbstring' => 'Installation_SystemCheckMbstringHelp', - 'Reflection' => 'Required extension that is built in PHP, see http://www.php.net/manual/en/book.reflection.php', - 'json' => 'Installation_SystemCheckWarnJsonHelp', - 'libxml' => 'Installation_SystemCheckWarnLibXmlHelp', - 'dom' => 'Installation_SystemCheckWarnDomHelp', - 'SimpleXML' => 'Installation_SystemCheckWarnSimpleXMLHelp', - - // Functions - 'set_time_limit' => 'Installation_SystemCheckTimeLimitHelp', - 'mail' => 'Installation_SystemCheckMailHelp', - 'parse_ini_file' => 'Installation_SystemCheckParseIniFileHelp', - 'glob' => 'Installation_SystemCheckGlobHelp', - 'debug_backtrace' => 'Installation_SystemCheckDebugBacktraceHelp', - 'create_function' => 'Installation_SystemCheckCreateFunctionHelp', - 'eval' => 'Installation_SystemCheckEvalHelp', - 'gzcompress' => 'Installation_SystemCheckGzcompressHelp', - 'gzuncompress' => 'Installation_SystemCheckGzuncompressHelp', - 'pack' => 'Installation_SystemCheckPackHelp', - 'php5-json' => 'Installation_SystemCheckJsonHelp', - ); - - // Add standard message for required PHP.ini settings - $requiredSettings = SystemCheck::getRequiredPhpSettings(); - foreach($requiredSettings as $requiredSetting) { - $helpMessages[$requiredSetting] = Piwik::translate('Installation_SystemCheckPhpSetting', $requiredSetting); - } - return $helpMessages; - } } diff --git a/plugins/Installation/SystemCheck.php b/plugins/Installation/SystemCheck.php deleted file mode 100644 index 93ed1ac75f162843772dda856b6c26beb4468818..0000000000000000000000000000000000000000 --- a/plugins/Installation/SystemCheck.php +++ /dev/null @@ -1,520 +0,0 @@ -<?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\Installation; - -use Piwik\CliMulti; -use Piwik\Common; -use Piwik\Config; -use Piwik\Container\StaticContainer; -use Piwik\Db; -use Piwik\Db\Adapter; -use Piwik\DbHelper; -use Piwik\Filechecks; -use Piwik\Filesystem; -use Piwik\Http; -use Piwik\Piwik; -use Piwik\Plugins\CoreUpdater; -use Piwik\Plugins\UserCountry\LocationProvider; -use Piwik\SettingsServer; -use Piwik\Url; - -class SystemCheck -{ - /** - * Get system information - */ - public static function getSystemInformation() - { - global $piwik_minimumPHPVersion; - - $infos = array(); - - $infos['directories'] = self::getDirectoriesWritableStatus(); - $infos['can_auto_update'] = Filechecks::canAutoUpdate(); - - self::initServerFilesForSecurity(); - - $infos['phpVersion_minimum'] = $piwik_minimumPHPVersion; - $infos['phpVersion'] = PHP_VERSION; - $infos['phpVersion_ok'] = self::isPhpVersionValid($infos['phpVersion']); - - // critical errors - $infos['needed_extensions'] = self::getRequiredExtensions(); - $infos['missing_extensions'] = self::getRequiredExtensionsMissing(); - - $infos['pdo_ok'] = self::isPhpExtensionLoaded('PDO'); - $infos['adapters'] = Adapter::getAdapters(); - - $infos['needed_functions'] = self::getRequiredFunctions(); - $infos['missing_functions'] = self::getRequiredFunctionsMissing(); - - // warnings - $infos['desired_extensions'] = self::getRecommendedExtensions(); - $infos['missing_desired_extensions'] = self::getRecommendedExtensionsMissing(); - - $infos['desired_functions'] = self::getRecommendedFunctions(); - $infos['missing_desired_functions'] = self::getRecommendedFunctionsMissing(); - - $infos['needed_settings'] = self::getRequiredPhpSettings(); - $infos['missing_settings'] = self::getMissingPhpSettings(); - - $infos['pagespeed_module_disabled_ok'] = self::isPageSpeedDisabled(); - - $infos['openurl'] = Http::getTransportMethod(); - $infos['gd_ok'] = SettingsServer::isGdExtensionEnabled(); - $infos['serverVersion'] = addslashes(isset($_SERVER['SERVER_SOFTWARE']) ?: ''); - $infos['serverOs'] = @php_uname(); - $infos['serverTime'] = date('H:i:s'); - - $infos['memoryMinimum'] = self::getMinimumRecommendedMemoryLimit(); - $infos['memory_ok'] = true; - $infos['memoryCurrent'] = ''; - - SettingsServer::raiseMemoryLimitIfNecessary(); - if (($memoryValue = SettingsServer::getMemoryLimitValue()) > 0) { - $infos['memoryCurrent'] = $memoryValue . 'M'; - $infos['memory_ok'] = $memoryValue >= self::getMinimumRecommendedMemoryLimit(); - } - - $infos['isWindows'] = SettingsServer::isWindows(); - - $integrityInfo = Filechecks::getFileIntegrityInformation(); - $infos['integrity'] = $integrityInfo[0]; - - $infos['integrityErrorMessages'] = array(); - if (isset($integrityInfo[1])) { - if ($infos['integrity'] == false) { - $infos['integrityErrorMessages'][] = Piwik::translate('General_FileIntegrityWarningExplanation'); - } - $infos['integrityErrorMessages'] = array_merge($infos['integrityErrorMessages'], array_slice($integrityInfo, 1)); - } - - $infos['timezone'] = SettingsServer::isTimezoneSupportEnabled(); - - $process = new CliMulti(); - $infos['cli_process_ok'] = $process->supportsAsync(); - - $infos['tracker_status'] = Common::getRequestVar('trackerStatus', 0, 'int'); - - $infos['is_nfs'] = Filesystem::checkIfFileSystemIsNFS(); - - $infos['https_update'] = CoreUpdater\Controller::isUpdatingOverHttps(); - - $infos = self::enrichSystemChecks($infos); - - return $infos; - } - - /** - * This can be overriden to provide a Customised System Check. - * - * @api - * @param $infos - * @return mixed - */ - public static function enrichSystemChecks($infos) - { - // determine whether there are any errors/warnings from the checks done above - $infos['has_errors'] = false; - $infos['has_warnings'] = false; - if (in_array(0, $infos['directories']) // if a directory is not writable - || !$infos['phpVersion_ok'] - || !empty($infos['missing_extensions']) - || empty($infos['adapters']) - || !empty($infos['missing_functions']) - ) { - $infos['has_errors'] = true; - } - - if ( !empty($infos['missing_desired_extensions']) - || !empty($infos['missing_desired_functions']) - || !empty($infos['missing_settings']) - || !$infos['pagespeed_module_disabled_ok'] - || !$infos['gd_ok'] - || !$infos['memory_ok'] - || !empty($infos['integrityErrorMessages']) - || !$infos['timezone'] // if timezone support isn't available - || $infos['tracker_status'] != 0 - || $infos['is_nfs'] - ) { - $infos['has_warnings'] = true; - } - return $infos; - } - - /** - * @return array - */ - protected static function getDirectoriesShouldBeWritable() - { - $tmpPath = StaticContainer::get('path.tmp'); - - $directoriesToCheck = array( - $tmpPath, - $tmpPath . '/assets/', - $tmpPath . '/cache/', - $tmpPath . '/climulti/', - $tmpPath . '/latest/', - $tmpPath . '/logs/', - $tmpPath . '/sessions/', - $tmpPath . '/tcpdf/', - $tmpPath . '/templates_c/', - ); - - if (!DbHelper::isInstalled()) { - // at install, need /config to be writable (so we can create config.ini.php) - $directoriesToCheck[] = '/config/'; - } - return $directoriesToCheck; - } - - /** - * @return array - */ - protected static function getRequiredFunctions() - { - return array( - 'debug_backtrace', - 'create_function', - 'eval', - 'gzcompress', - 'gzuncompress', - 'pack', - ); - } - - /** - * @return array - */ - protected static function getRecommendedExtensions() - { - return array( - 'json', - 'libxml', - 'dom', - 'SimpleXML', - ); - } - - /** - * @return array - */ - protected static function getRecommendedFunctions() - { - return array( - 'set_time_limit', - 'mail', - 'parse_ini_file', - 'glob', - 'gzopen', - ); - } - - /** - * @return array - */ - protected static function getRequiredExtensions() - { - $requiredExtensions = array( - 'zlib', - 'SPL', - 'iconv', - 'json', - 'mbstring', - ); - - if (!defined('HHVM_VERSION')) { - // HHVM provides the required subset of Reflection but lists Reflections as missing - $requiredExtensions[] = 'Reflection'; - } - - return $requiredExtensions; - } - - /** - * Performs extra system checks for the 'System Check' admin page. These - * checks are not performed during Installation. - * - * The following checks are performed: - * - Check for whether LOAD DATA INFILE can be used. The result of the check - * is stored in $result['load_data_infile_available']. The error message is - * stored in $result['load_data_infile_error']. - * - * - Check whether geo location is setup correctly - * - * @return array - */ - public static function performAdminPageOnlySystemCheck() - { - $result = array(); - self::checkLoadDataInfile($result); - self::checkGeolocation($result); - return $result; - } - - /** - * Test if function exists. Also handles case where function is disabled via Suhosin. - * - * @param string $functionName Function name - * @return bool True if function exists (not disabled); False otherwise. - */ - protected static function functionExists($functionName) - { - // eval() is a language construct - if ($functionName == 'eval') { - // does not check suhosin.executor.eval.whitelist (or blacklist) - if (extension_loaded('suhosin')) { - return @ini_get("suhosin.executor.disable_eval") != "1"; - } - return true; - } - - $exists = function_exists($functionName); - if (extension_loaded('suhosin')) { - $blacklist = @ini_get("suhosin.executor.func.blacklist"); - if (!empty($blacklist)) { - $blacklistFunctions = array_map('strtolower', array_map('trim', explode(',', $blacklist))); - return $exists && !in_array($functionName, $blacklistFunctions); - } - } - return $exists; - } - - private static function checkGeolocation(&$result) - { - $currentProviderId = LocationProvider::getCurrentProviderId(); - $allProviders = LocationProvider::getAllProviderInfo(); - $isRecommendedProvider = in_array($currentProviderId, array( LocationProvider\GeoIp\Php::ID, $currentProviderId == LocationProvider\GeoIp\Pecl::ID)); - $isProviderInstalled = ($allProviders[$currentProviderId]['status'] == LocationProvider::INSTALLED); - - $result['geolocation_using_non_recommended'] = $result['geolocation_ok'] = false; - if ($isRecommendedProvider && $isProviderInstalled) { - $result['geolocation_ok'] = true; - } elseif ($isProviderInstalled) { - $result['geolocation_using_non_recommended'] = true; - } - } - - private static function checkLoadDataInfile(&$result) - { - // check if LOAD DATA INFILE works - $optionTable = Common::prefixTable('option'); - $testOptionNames = array('test_system_check1', 'test_system_check2'); - - $result['load_data_infile_available'] = false; - try { - $result['load_data_infile_available'] = \Piwik\Db\BatchInsert::tableInsertBatch( - $optionTable, - array('option_name', 'option_value'), - array( - array($testOptionNames[0], '1'), - array($testOptionNames[1], '2'), - ), - $throwException = true - ); - } catch (\Exception $ex) { - $result['load_data_infile_error'] = str_replace("\n", "<br/>", $ex->getMessage()); - } - - // delete the temporary rows that were created - Db::exec("DELETE FROM `$optionTable` WHERE option_name IN ('" . implode("','", $testOptionNames) . "')"); - } - - protected static function initServerFilesForSecurity() - { - ServerFilesGenerator::createWebConfigFiles(); - ServerFilesGenerator::createHtAccessFiles(); - ServerFilesGenerator::createWebRootFiles(); - } - - /** - * @param string $phpVersion - * @return bool - */ - public static function isPhpVersionValid($phpVersion) - { - global $piwik_minimumPHPVersion; - return version_compare($piwik_minimumPHPVersion, $phpVersion) <= 0; - } - - /** - * @return array - */ - protected static function getDirectoriesWritableStatus() - { - $directoriesToCheck = self::getDirectoriesShouldBeWritable(); - $directoriesWritableStatus = Filechecks::checkDirectoriesWritable($directoriesToCheck); - return $directoriesWritableStatus; - } - - /** - * @return array - */ - protected static function getLoadedExtensions() - { - static $extensions = null; - - if(is_null($extensions)) { - $extensions = @get_loaded_extensions(); - } - return $extensions; - } - - - /** - * @param $needed_extension - * @return bool - */ - protected static function isPhpExtensionLoaded($needed_extension) - { - return in_array($needed_extension, self::getLoadedExtensions()); - } - - /** - * @return array - */ - protected static function getRequiredExtensionsMissing() - { - $missingExtensions = array(); - foreach (self::getRequiredExtensions() as $requiredExtension) { - if (!self::isPhpExtensionLoaded($requiredExtension)) { - $missingExtensions[] = $requiredExtension; - } - } - - // Special case for mbstring - if (!function_exists('mb_get_info') - || ((int)ini_get('mbstring.func_overload')) != 0) { - $missingExtensions[] = 'mbstring'; - } - - return $missingExtensions; - } - - /** - * @return array - */ - protected static function getRecommendedExtensionsMissing() - { - return array_diff(self::getRecommendedExtensions(), self::getLoadedExtensions()); - } - - /** - * @return array - */ - protected static function getRecommendedFunctionsMissing() - { - return self::getFunctionsMissing(self::getRecommendedFunctions()); - } - - /** - * @return array - */ - protected static function getRequiredFunctionsMissing() - { - return self::getFunctionsMissing(self::getRequiredFunctions()); - } - - protected static function getFunctionsMissing($functionsToTestFor) - { - $missingFunctions = array(); - foreach ($functionsToTestFor as $function) { - if (!self::functionExists($function)) { - $missingFunctions[] = $function; - } - } - return $missingFunctions; - } - - /** - * @return mixed - */ - protected static function getMinimumRecommendedMemoryLimit() - { - return Config::getInstance()->General['minimum_memory_limit']; - } - - private static function isPhpVersionAtLeast56() - { - return version_compare( PHP_VERSION, '5.6', '>='); - } - - /** - * @return array - */ - public static function getRequiredPhpSettings() - { - $requiredPhpSettings = array( - // setting = required value - // Note: value must be an integer only - 'session.auto_start=0', - ); - - if (self::isPhpVersionAtLeast56()) { - // always_populate_raw_post_data must be -1 - $requiredPhpSettings[] = 'always_populate_raw_post_data=-1'; - } - return $requiredPhpSettings; - } - - /** - * @return array - */ - protected static function getMissingPhpSettings() - { - $missingPhpSettings = array(); - foreach(self::getRequiredPhpSettings() as $requiredSetting) { - list($requiredSettingName, $requiredSettingValue) = explode('=', $requiredSetting); - - $currentValue = ini_get($requiredSettingName); - $currentValue = (int)$currentValue; - - if($currentValue != $requiredSettingValue) { - $missingPhpSettings[] = $requiredSetting; - } - } - return $missingPhpSettings; - } - - protected static function isPageSpeedDisabled() - { - $url = Url::getCurrentUrlWithoutQueryString() . '?module=Installation&action=getEmptyPageForSystemCheck'; - - try { - $page = Http::sendHttpRequest($url, - $timeout = 1, - $userAgent = null, - $destinationPath = null, - $followDepth = 0, - $acceptLanguage = false, - $byteRange = false, - - // Return headers - $getExtendedInfo = true - ); - } catch(\Exception $e) { - - // If the test failed, we assume Page speed is not enabled - return true; - } - - $headers = $page['headers']; - - return !self::isPageSpeedHeaderFound($headers); - } - - /** - * @param $headers - * @return bool - */ - protected static function isPageSpeedHeaderFound($headers) - { - return isset($headers['X-Mod-Pagespeed']) || isset($headers['X-Page-Speed']); - } -} \ No newline at end of file diff --git a/plugins/Installation/lang/en.json b/plugins/Installation/lang/en.json index 2d3afbf8c92ee76aa12f10efaf865b76c14f2e49..689760ef0ebd58709d2c92c050974f94b9a68d9b 100644 --- a/plugins/Installation/lang/en.json +++ b/plugins/Installation/lang/en.json @@ -65,6 +65,7 @@ "SystemCheck": "System Check", "SystemCheckAutoUpdateHelp": "Note: Piwik's One Click update requires write-permissions to the Piwik folder and its contents.", "SystemCheckCreateFunctionHelp": "Piwik uses anonymous functions for callbacks.", + "SystemCheckDatabaseExtensions": "MySQL extensions", "SystemCheckDatabaseHelp": "Piwik requires either the mysqli extension or both the PDO and pdo_mysql extensions.", "SystemCheckDebugBacktraceHelp": "View::factory won't be able to create views for the calling module.", "SystemCheckError": "An error occured - must be fixed before you proceed", diff --git a/plugins/Installation/templates/_integrityDetails.twig b/plugins/Installation/templates/_integrityDetails.twig index b285bb46f9ea6e7fcc50cafbd6178be979da3346..ddf9712ec0e07ddfa659e9199b41bfae93e34680 100644 --- a/plugins/Installation/templates/_integrityDetails.twig +++ b/plugins/Installation/templates/_integrityDetails.twig @@ -1,6 +1,3 @@ -{% if warningMessages is not defined %} - {% set warningMessages=infos.integrityErrorMessages %} -{% endif %} <div id="integrity-results" title="{{ 'Installation_SystemCheckFileIntegrity'|translate }}" style="display:none; font-size: 62.5%;"> <table> {% for msg in warningMessages %} diff --git a/plugins/Installation/templates/_systemCheckSection.twig b/plugins/Installation/templates/_systemCheckSection.twig index b56db9b6726e8e6cf2da6b8b8926abc44a610f38..c1f8c14332c723f6a646a648299bcd4dc86c97a4 100755 --- a/plugins/Installation/templates/_systemCheckSection.twig +++ b/plugins/Installation/templates/_systemCheckSection.twig @@ -1,372 +1,50 @@ -{% set ok %}<img src='plugins/Morpheus/images/ok.png' />{% endset %} -{% set error %}<img src='plugins/Morpheus/images/error.png' />{% endset %} -{% set warning %}<img src='plugins/Morpheus/images/warning.png' />{% endset %} -{% set link %}<img src='plugins/Morpheus/images/link.gif' />{% endset %} +{% import _self as local %} <table class="infosServer" id="systemCheckRequired"> - <tr> - {% set MinPHP %}{{ 'Installation_SystemCheckPhp'|translate }} >= {{ infos.phpVersion_minimum }}{% endset %} - <td class="label">{{ MinPHP }}</td> - - <td> - {% if infos.phpVersion_ok %} - {{ ok }} {{ infos.phpVersion }} - {% else %} - {{ error }} <span class="err">{{ 'General_Error'|translate }}: {{ 'General_Required'|translate(MinPHP)|raw }}</span> - {% endif %} - </td> - </tr> - <tr> - <td class="label">PDO {{ 'Installation_Extension'|translate }}</td> - <td> - {% if infos.pdo_ok %} - {{- ok -}} - {% else %} - - - {% endif %} - </td> - </tr> - {% for adapter, port in infos.adapters %} - <tr> - <td class="label">{{ adapter }} {{ 'Installation_Extension'|translate }}</td> - <td>{{ ok }}</td> - </tr> - {% endfor %} - {% if infos.adapters|length == 0 %} - <tr> - <td colspan="2" class="error" style="font-size: small;"> - {{ 'Installation_SystemCheckDatabaseHelp'|translate }} - <p> - {% if infos.isWindows %} - {{ 'Installation_SystemCheckWinPdoAndMysqliHelp'|translate("<br /><br /><code>extension=php_mysqli.dll</code><br /><code>extension=php_pdo.dll</code><br /><code>extension=php_pdo_mysql.dll</code><br />")|raw|nl2br }} - {% else %} - {{ 'Installation_SystemCheckPdoAndMysqliHelp'|translate("<br /><br /><code>--with-mysqli</code><br /><code>--with-pdo-mysql</code><br /><br />","<br /><br /><code>extension=mysqli.so</code><br /><code>extension=pdo.so</code><br /><code>extension=pdo_mysql.so</code><br />")|raw }} - {% endif %} - {{ 'Installation_RestartWebServer'|translate }} - <br/> - <br/> - {{ 'Installation_SystemCheckPhpPdoAndMysqli'|translate("<a style=\"color:red\" href=\"http:\/\/php.net\/pdo\">","<\/a>","<a style=\"color:red\" href=\"http:\/\/php.net\/mysqli\">","<\/a>")|raw|nl2br }} - </p> - </td> - </tr> - {% endif %} - <tr> - <td class="label">{{ 'Installation_SystemCheckExtensions'|translate }}</td> - <td> - {% for needed_extension in infos.needed_extensions %} - {% if needed_extension in infos.missing_extensions %} - {{ error }} - <br/>{{ 'Installation_RestartWebServer'|translate }} - {% else %} - {{ ok }} - {% endif %} - {{ needed_extension }} - <br/> - {% endfor %} - </td> - </tr> - {% if infos.missing_extensions|length > 0 %} - <tr> - <td colspan="2" class="error" style="font-size: small;"> - {% for missing_extension in infos.missing_extensions %} - <p> - <em>{{ helpMessages[missing_extension]|translate }}</em> - </p> - {% endfor %} - </td> - </tr> - {% endif %} - <tr> - <td class="label">{{ 'Installation_SystemCheckFunctions'|translate }}</td> - <td> - {% for needed_function in infos.needed_functions %} - {% if needed_function in infos.missing_functions %} - {{ error }} - <span class='err'>{{ needed_function }}</span> - <p> - <em> - {{ helpMessages[needed_function]|translate }} - <br/>{{ 'Installation_RestartWebServer'|translate }} - </em> - </p> - {% else %} - {{ ok }} {{ needed_function }} - <br/> - {% endif %} - {% endfor %} - </td> - </tr> - <tr> - <td class="label">{{ 'Installation_SystemCheckSettings'|translate }}</td> - <td> - {% for needed_setting in infos.needed_settings %} - {% if needed_setting in infos.missing_settings %} - {{ error }} - <span class='err'>{{ needed_setting }}</span> - <p> - <em> - {{ helpMessages[needed_setting]|translate }} - <br/>{{ 'Installation_RestartWebServer'|translate }} - </em> - </p> - {% else %} - {{ ok }} {{ needed_setting }} - <br/> - {% endif %} - {% endfor %} - </td> - </tr> - <tr> - <td valign="top"> - {{ 'Installation_SystemCheckWriteDirs'|translate }} - </td> - <td style="font-size: small;"> - {% for dir, bool in infos.directories %} - {% if bool %} - {{ ok }} - {% else %} - <span style="color:red;">{{ error }}</span> - {% endif %} - {{ dir }} - <br/> - {% endfor %} - </td> - </tr> - {% if problemWithSomeDirectories %} - <tr> - <td colspan="2" class="error"> - {{ 'Installation_SystemCheckWriteDirsHelp'|translate }}: - {% for dir,bool in infos.directories %} - <ul> - {% if not bool %} - <li> - <pre>chmod a+w {{ dir }}</pre> - </li> - {% endif %} - </ul> - {% endfor %} - </td> - </tr> - {% endif %} + {{ local.diagnosticTable(diagnosticReport.getMandatoryDiagnosticResults()) }} </table> -<br/> <h3>{{ 'Installation_Optional'|translate }}</h3> + <table class="infos" id="systemCheckOptional"> - <tr> - <td class="label">{{ 'Installation_SystemCheckFileIntegrity'|translate }}</td> - <td> - {% if infos.integrityErrorMessages is empty %} - {{ ok }} - {% else %} - {% if infos.integrity %} - {{ warning }} - <em>{{ infos.integrityErrorMessages[0] }}</em> - {% else %} - {{ error }} - <em>{{ infos.integrityErrorMessages[0] }}</em> - {% endif %} - {% if infos.integrityErrorMessages|length > 1 %} - <button id="more-results" class="ui-button ui-state-default ui-corner-all">{{ 'General_Details'|translate }}</button> - {% endif %} - {% endif %} - </td> - </tr> - <tr> - <td class="label">{{ 'Installation_SystemCheckTracker'|translate }}</td> - <td> - {% if infos.tracker_status == 0 %} - {{ ok }} - {% else %} - {{ warning }} - <span class="warn">{{ infos.tracker_status }} - <br/>{{ 'Installation_SystemCheckTrackerHelp'|translate }} </span> - <br/> - {{ 'Installation_RestartWebServer'|translate }} - {% endif %} - </td> - </tr> - <tr> - <td class="label">{{ 'Installation_SystemCheckMemoryLimit'|translate }}</td> - <td> - {% if infos.memory_ok %} - {{ ok }} {{ infos.memoryCurrent }} - {% else %} - {{ warning }} - <span class="warn">{{ infos.memoryCurrent }}</span> - <br/> - {{ 'Installation_SystemCheckMemoryLimitHelp'|translate }} - {{ 'Installation_RestartWebServer'|translate }} - {% endif %} - </td> - </tr> - <tr> - <td class="label">{{ 'SitesManager_Timezone'|translate }}</td> - <td> - {% if infos.timezone %} - {{ ok }} - {% else %} - {{ warning }} - <span class="warn">{{ 'SitesManager_AdvancedTimezoneSupportNotFound'|translate }} </span> - <br/> - <a href="http://php.net/manual/en/datetime.installation.php" rel="noreferrer" target="_blank">Timezone PHP documentation</a> - . - {% endif %} - </td> - </tr> - <tr> - <td class="label">{{ 'Installation_SystemCheckOpenURL'|translate }}</td> - <td> - {% if infos.openurl %} - {{ ok }} {{ infos.openurl }} - {% else %} - {{ warning }} - <span class="warn">{{ 'Installation_SystemCheckOpenURLHelp'|translate }}</span> - {% endif %} - {% if not infos.can_auto_update %} - <br/> - {{ warning }} <span class="warn">{{ 'Installation_SystemCheckAutoUpdateHelp'|translate }}</span> - {% endif %} - </td> - </tr> - <tr> - <td class="label">{{ 'Installation_SystemCheckPageSpeedDisabled'|translate }}</td> - <td> - {% if infos.pagespeed_module_disabled_ok %} - {{ ok }} - {% else %} - {{ warning }} <span class="warn">{{ 'Installation_SystemCheckPageSpeedWarn'|translate('(eg. Apache, Nginx or IIS)') }}</span> - {% endif %} - </td> - </tr> - <tr> - <td class="label">{{ 'Installation_SystemCheckGDFreeType'|translate }}</td> - <td> - {% if infos.gd_ok %} - {{ ok }} - {% else %} - {{ warning }} <span class="warn">{{ 'Installation_SystemCheckGDFreeType'|translate }} - <br/> - {{ 'Installation_SystemCheckGDHelp'|translate }} </span> - {% endif %} - </td> - </tr> - <tr> - <td class="label">{{ 'Installation_SystemCheckOtherExtensions'|translate }}</td> - <td> - {% for desired_extension in infos.desired_extensions %} - {% if desired_extension in infos.missing_desired_extensions %} - {{ warning }}<span class="warn">{{ desired_extension }}</span> - <p>{{ helpMessages[desired_extension]|translate }}</p> - {% else %} - {{ ok }} {{ desired_extension }} - <br/> - {% endif %} - {% endfor %} - </td> - </tr> - <tr> - <td class="label">{{ 'Installation_SystemCheckOtherFunctions'|translate }}</td> - <td> - {% for desired_function in infos.desired_functions %} - {% if desired_function in infos.missing_desired_functions %} - {{ warning }} - <span class="warn">{{ desired_function }}</span> - <p>{{ helpMessages[desired_function]|translate }}</p> - {% else %} - {{ ok }} {{ desired_function }} - <br/> - {% endif %} - {% endfor %} - </td> - </tr> - <tr> - <td class="label">{{ 'Installation_Filesystem'|translate }}</td> - <td> - {% if not infos.is_nfs %} - {{ ok }} {{ 'General_Ok'|translate }} - <br/> - {% else %} - {{ warning }} - <span class="warn">{{ 'Installation_NfsFilesystemWarning'|translate }}</span> - {% if duringInstall is not empty %} - <p>{{ 'Installation_NfsFilesystemWarningSuffixInstall'|translate }}</p> - {% else %} - <p>{{ 'Installation_NfsFilesystemWarningSuffixAdmin'|translate }}</p> - {% endif %} - {% endif %} - </td> - </tr> + {{ local.diagnosticTable(diagnosticReport.getOptionalDiagnosticResults()) }} +</table> - <tr> - <td class="label">{{ 'Installation_SystemCheckCronArchiveProcess'|translate }}</td> - <td> - {# Both results are OK, but we report the status to help troubleshoot #} - {% if infos.cli_process_ok %} - {{ ok }} {{ 'Installation_SystemCheckCronArchiveProcessCLI'|translate }}: {{ 'General_Ok'|translate }} - {% else %} - {{ ok }} {{ 'Installation_SystemCheckCronArchiveProcessCLI'|translate }}: - {{ 'Installation_NotSupported'|translate }} {{ 'Goals_Optional'|translate }} - {% endif %} - </td> - </tr> +{% macro diagnosticTable(results) %} - {% if duringInstall is empty %} - <tr> - <td class="label">{{ 'UserCountry_Geolocation'|translate }}</td> - <td> - {% if infos.extra.geolocation_ok %} - {{ ok }} {{ 'General_Ok'|translate }} - <br/> - {% elseif infos.extra.geolocation_using_non_recommended %} - {{ warning }} - <span class="warn">{{ 'UserCountry_GeoIpLocationProviderNotRecomnended'|translate }} - {{ 'UserCountry_GeoIpLocationProviderDesc_ServerBased2'|translate('<a href="http://piwik.org/docs/geo-locate/" rel="noreferrer" target="_blank">', '', '', '</a>')|raw }}</span> - <br/> - {% else %} - {{ warning }} - <span class="warn">{{ 'UserCountry_DefaultLocationProviderDesc1'|translate }} - {{ 'UserCountry_DefaultLocationProviderDesc2'|translate('<a href="http://piwik.org/docs/geo-locate/" rel="noreferrer" target="_blank">', '', '', '</a>')|raw }} </span> - </span> - {% endif %} - </td> - </tr> - {% endif %} - {% if infos.extra.load_data_infile_available is defined %} + {% set error = constant('Piwik\\Plugins\\Diagnostics\\Diagnostic\\DiagnosticResult::STATUS_ERROR') %} + {% set warning = constant('Piwik\\Plugins\\Diagnostics\\Diagnostic\\DiagnosticResult::STATUS_WARNING') %} + + {% set errorIcon %}<img src='plugins/Morpheus/images/error.png' />{% endset %} + {% set warningIcon %}<img src='plugins/Morpheus/images/warning.png' />{% endset %} + {% set okIcon %}<img src='plugins/Morpheus/images/ok.png' />{% endset %} + + {% for result in results %} <tr> - <td class="label">{{ 'Installation_DatabaseAbilities'|translate }}</td> + <td class="label">{{ result.label }}</td> <td> - {% if infos.extra.load_data_infile_available %} - {{ ok }} LOAD DATA INFILE - <br/> - {% else %} - {{ warning }} - <span class="warn">LOAD DATA INFILE</span> - <br/> - <br/> - <p>{{ 'Installation_LoadDataInfileUnavailableHelp'|translate("LOAD DATA INFILE","FILE") }}</p> - <p>{{ 'Installation_LoadDataInfileRecommended'|translate }}</p> - {% if infos.extra.load_data_infile_error is defined %} - <em><strong>{{ 'General_Error'|translate }}:</strong></em> - {{ infos.extra.load_data_infile_error|raw }} + {% for item in result.items %} + + {% if item.status == error %} + {{ errorIcon }} <span class="err">{{ item.comment|raw }}</span> + {% elseif item.status == warning %} + {{ warningIcon }} {{ item.comment|raw }} + {% else %} + {{ okIcon }} {{ item.comment|raw }} {% endif %} - <p>Troubleshooting: <a target='_blank' href="?module=Proxy&action=redirect&url=http://piwik.org/faq/troubleshooting/%23faq_194">FAQ on piwik.org</a></p> - {% endif %} - </td> - </tr> - {% endif %} - <tr> - <td class="label">{{ 'Installation_SystemCheckUpdateHttps'|translate }}</td> - <td> - {% if infos.https_update %} - {{ ok }} - {% else %} - {{ warning }} {{ 'Installation_SystemCheckUpdateHttpsNotSupported'|translate }} - {% endif %} - </td> - </tr> + <br/> -</table> + {% endfor %} + </td> + </tr> + {% if result.longErrorMessage %} + <tr> + <td colspan="2" class="error" style="font-size: small;"> + {{ result.longErrorMessage|raw }} + </td> + </tr> + {% endif %} + {% endfor %} -{% include "@Installation/_integrityDetails.twig" %} +{% endmacro %} diff --git a/plugins/Installation/templates/systemCheckPage.twig b/plugins/Installation/templates/systemCheckPage.twig index b66c493eea23f7dc9519d2c44cf608b1f2f43534..88f6b42688da23db3905b0799649caf16c6a7aa8 100755 --- a/plugins/Installation/templates/systemCheckPage.twig +++ b/plugins/Installation/templates/systemCheckPage.twig @@ -1,20 +1,24 @@ {% extends 'admin.twig' %} {% block content %} -{% if isSuperUser %} + <h2 piwik-enriched-headline>{{ 'Installation_SystemCheck'|translate }}</h2> + <p style="margin-left:0.2em;"> - {% if infos.has_errors %} + {% if diagnosticReport.hasErrors() %} <img src="plugins/Morpheus/images/error.png"/> - {{ 'Installation_SystemCheckSummaryThereWereErrors'|translate('<strong>','</strong>','<strong><em>','</em></strong>')|raw }} {{ 'Installation_SeeBelowForMoreInfo'|translate }} - {% elseif infos.has_warnings %} + {{ 'Installation_SystemCheckSummaryThereWereErrors'|translate('<strong>','</strong>','<strong><em>','</em></strong>')|raw }} + {{ 'Installation_SeeBelowForMoreInfo'|translate }} + {% elseif diagnosticReport.hasWarnings() %} <img src="plugins/Morpheus/images/warning.png"/> - {{ 'Installation_SystemCheckSummaryThereWereWarnings'|translate }} {{ 'Installation_SeeBelowForMoreInfo'|translate }} + {{ 'Installation_SystemCheckSummaryThereWereWarnings'|translate }} + {{ 'Installation_SeeBelowForMoreInfo'|translate }} {% else %} <img src="plugins/Morpheus/images/ok.png"/> {{ 'Installation_SystemCheckSummaryNoProblems'|translate }} {% endif %} </p> + {% include "@Installation/_systemCheckSection.twig" %} -{% endif %} + {% endblock %} diff --git a/plugins/LanguagesManager/angularjs/translationsearch/translationsearch.controller.js b/plugins/LanguagesManager/angularjs/translationsearch/translationsearch.controller.js index ed701d8ef182315480ab7e9d4f988b9955659cd4..36e057ea9889f7e64d3b546c0377758702a7d0af 100644 --- a/plugins/LanguagesManager/angularjs/translationsearch/translationsearch.controller.js +++ b/plugins/LanguagesManager/angularjs/translationsearch/translationsearch.controller.js @@ -19,6 +19,7 @@ function fetchTranslations() { piwikApi.fetch({ method: 'LanguagesManager.getTranslationsForLanguage', + filter_limit: -1, languageCode: 'en' }).then(function (response) { if (response) { diff --git a/plugins/LeftMenu/stylesheets/theme.less b/plugins/LeftMenu/stylesheets/theme.less index 8e9ea0eab8eded6cdf93d557d12880b2f557f8ca..869d5608281f0f3213a040562e32259902062e58 100644 --- a/plugins/LeftMenu/stylesheets/theme.less +++ b/plugins/LeftMenu/stylesheets/theme.less @@ -126,8 +126,7 @@ .pageWrap { margin-left: 240px; - border-width: 0; - padding-top: 0; + margin-top: 0; max-height: none; } diff --git a/plugins/Live/javascripts/live.js b/plugins/Live/javascripts/live.js index 17e08cded38390d586cb1041acc907318ba937ac..fd634ec61ce0b24cc8831ce0fd386881e6f9b85c 100644 --- a/plugins/Live/javascripts/live.js +++ b/plugins/Live/javascripts/live.js @@ -82,7 +82,7 @@ if (that.isStarted) { window.clearTimeout(that.updateInterval); - if ($(that.element).closest('body').length) { + if (that.element.length && $.contains(document, that.element[0])) { that.updateInterval = window.setTimeout(function() { that._update() }, that.currentInterval); } } @@ -211,7 +211,7 @@ $(function() { var refreshWidget = function (element, refreshAfterXSecs) { // if the widget has been removed from the DOM, abort - if ($(element).parent().length == 0) { + if (!element.length || !$.contains(document, element[0])) { return; } diff --git a/plugins/Live/stylesheets/visitor_profile.less b/plugins/Live/stylesheets/visitor_profile.less index 88b34ff152e0f6f7d8d9d78fc765bcfad98feabb..7b82aee711a459b411ed1616d0ff45b3d94c7204 100644 --- a/plugins/Live/stylesheets/visitor_profile.less +++ b/plugins/Live/stylesheets/visitor_profile.less @@ -33,6 +33,7 @@ padding:0; font-weight:bold; color:black; + border: none; } span.truncated-text-line { @@ -303,7 +304,6 @@ } .visitor-profile-pages-visited { - height:42px; overflow-y:auto; position:relative; margin-right:10px; @@ -317,6 +317,7 @@ .visitor-profile-visits-container { overflow-y:auto; + overflow-x: hidden; position:relative; margin-right:10px; border-bottom:none!important; diff --git a/plugins/Live/templates/getVisitList.twig b/plugins/Live/templates/getVisitList.twig index 4d1fec2a0c1e6950c3cf487a262f80c25e13fa73..18047bf3c2eb7201e3b94ad9ede74bdb17a872d2 100644 --- a/plugins/Live/templates/getVisitList.twig +++ b/plugins/Live/templates/getVisitList.twig @@ -4,13 +4,14 @@ <div class="visitor-profile-visit-title-row"> <h2 class="visitor-profile-visit-title" data-idvisit="{{ visitInfo.getColumn('idVisit') }}" title="{{ 'Live_ClickToViewMoreAboutVisit'|translate }}"> {{ 'General_Visit'|translate }} #{{ startCounter }} + + {% if visitInfo.getColumn('visitDuration') != 0 %} + <span> - ({{ visitInfo.getColumn('visitDurationPretty')|raw }})</span> + {% endif %} + <span class="visitor-profile-date" title="{{ visitInfo.getColumn('serverDatePrettyFirstAction') }} {{ visitInfo.getColumn('serverTimePrettyFirstAction') }}"> + {{ visitInfo.getColumn('serverDatePrettyFirstAction') }} {{ visitInfo.getColumn('serverTimePrettyFirstAction') }} + </span> </h2> - {% if visitInfo.getColumn('visitDuration') != 0 %} - <span> - ({{ visitInfo.getColumn('visitDurationPretty')|raw }})</span> - {% endif %} - <span class="visitor-profile-date" title="{{ visitInfo.getColumn('serverDatePrettyFirstAction') }} {{ visitInfo.getColumn('serverTimePrettyFirstAction') }}"> - {{ visitInfo.getColumn('serverDatePrettyFirstAction') }} {{ visitInfo.getColumn('serverTimePrettyFirstAction') }} - </span> </div> <ol class="visitor-profile-actions"> {% include "@Live/_actionsList.twig" with {'actionDetails': visitInfo.getColumn('actionDetails'), diff --git a/plugins/Login/stylesheets/login.less b/plugins/Login/stylesheets/login.less index d957f2014ce79e8b28cc1668d6a12510b256f0fe..98dd41624aec5de4d3ac6ba42137c2744d014ea8 100644 --- a/plugins/Login/stylesheets/login.less +++ b/plugins/Login/stylesheets/login.less @@ -62,7 +62,7 @@ .loginSection { background-color: @login-section-background; - width: 360px; + width: 420px; padding: 30px; margin: 50px auto 0 auto; border-radius: 3px; diff --git a/plugins/Monolog/config/config.php b/plugins/Monolog/config/config.php index 7005573ced9ee32fa39cacf0427bd99df967406b..3fd3fcf63a75067280d2abdfaaba8caee9f9e19b 100644 --- a/plugins/Monolog/config/config.php +++ b/plugins/Monolog/config/config.php @@ -7,7 +7,7 @@ use Piwik\Log; return array( 'Psr\Log\LoggerInterface' => DI\object('Monolog\Logger') - ->constructor('piwik', DI\link('log.handlers'), DI\link('log.processors')), + ->constructor('piwik', DI\get('log.handlers'), DI\get('log.processors')), 'log.handlers' => DI\factory(function (ContainerInterface $c) { if ($c->has('ini.log.log_writers')) { @@ -31,25 +31,25 @@ return array( }), 'log.processors' => array( - DI\link('Piwik\Plugins\Monolog\Processor\SprintfProcessor'), - DI\link('Piwik\Plugins\Monolog\Processor\ClassNameProcessor'), - DI\link('Piwik\Plugins\Monolog\Processor\RequestIdProcessor'), - DI\link('Piwik\Plugins\Monolog\Processor\ExceptionToTextProcessor'), - DI\link('Monolog\Processor\PsrLogMessageProcessor'), - DI\link('Piwik\Plugins\Monolog\Processor\TokenProcessor'), + DI\get('Piwik\Plugins\Monolog\Processor\SprintfProcessor'), + DI\get('Piwik\Plugins\Monolog\Processor\ClassNameProcessor'), + DI\get('Piwik\Plugins\Monolog\Processor\RequestIdProcessor'), + DI\get('Piwik\Plugins\Monolog\Processor\ExceptionToTextProcessor'), + DI\get('Monolog\Processor\PsrLogMessageProcessor'), + DI\get('Piwik\Plugins\Monolog\Processor\TokenProcessor'), ), 'Piwik\Plugins\Monolog\Handler\FileHandler' => DI\object() - ->constructor(DI\link('log.file.filename'), DI\link('log.level')) - ->method('setFormatter', DI\link('Piwik\Plugins\Monolog\Formatter\LineMessageFormatter')), + ->constructor(DI\get('log.file.filename'), DI\get('log.level')) + ->method('setFormatter', DI\get('Piwik\Plugins\Monolog\Formatter\LineMessageFormatter')), 'Piwik\Plugins\Monolog\Handler\DatabaseHandler' => DI\object() - ->constructor(DI\link('log.level')) - ->method('setFormatter', DI\link('Piwik\Plugins\Monolog\Formatter\LineMessageFormatter')), + ->constructor(DI\get('log.level')) + ->method('setFormatter', DI\get('Piwik\Plugins\Monolog\Formatter\LineMessageFormatter')), 'Piwik\Plugins\Monolog\Handler\WebNotificationHandler' => DI\object() - ->constructor(DI\link('log.level')) - ->method('setFormatter', DI\link('Piwik\Plugins\Monolog\Formatter\LineMessageFormatter')), + ->constructor(DI\get('log.level')) + ->method('setFormatter', DI\get('Piwik\Plugins\Monolog\Formatter\LineMessageFormatter')), 'log.level' => DI\factory(function (ContainerInterface $c) { if ($c->has('ini.log.log_level')) { @@ -88,7 +88,7 @@ return array( }), 'Piwik\Plugins\Monolog\Formatter\LineMessageFormatter' => DI\object() - ->constructor(DI\link('log.format')), + ->constructor(DI\get('log.format')), 'log.format' => DI\factory(function (ContainerInterface $c) { if ($c->has('ini.log.string_message_format')) { diff --git a/plugins/Morpheus/stylesheets/base.less b/plugins/Morpheus/stylesheets/base.less index 4d7ca9a78815138da90e8051254b5bee0ef3189a..49077a6aa3a64c036bda009e11f271b8c702830b 100644 --- a/plugins/Morpheus/stylesheets/base.less +++ b/plugins/Morpheus/stylesheets/base.less @@ -33,4 +33,8 @@ @import "uibase/_loading.less"; /* Remote components */ -@import "../../CoreHome/stylesheets/_donate.less"; \ No newline at end of file +@import "../../CoreHome/stylesheets/_donate.less"; + +@import "bootstrap.css"; + +@import "ui/_alerts"; diff --git a/plugins/Morpheus/stylesheets/bootstrap.css b/plugins/Morpheus/stylesheets/bootstrap.css new file mode 100755 index 0000000000000000000000000000000000000000..38d8c40900596a652b848acdb7ae88f5b981da12 --- /dev/null +++ b/plugins/Morpheus/stylesheets/bootstrap.css @@ -0,0 +1,1011 @@ +/*! + * Bootstrap v3.3.2 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ + +/*! + * Generated using the Bootstrap Customizer (http://getbootstrap.com/customize/?id=e32d98c35d1ff80598b4) + * Config saved to config.json and https://gist.github.com/e32d98c35d1ff80598b4 + */ +/*! normalize.css v3.0.2 | MIT License | git.io/normalize */ +html { + font-family: sans-serif; + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; +} +body { + margin: 0; +} +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block; +} +audio, +canvas, +progress, +video { + display: inline-block; + vertical-align: baseline; +} +audio:not([controls]) { + display: none; + height: 0; +} +[hidden], +template { + display: none; +} +a { + background-color: transparent; +} +a:active, +a:hover { + outline: 0; +} +abbr[title] { + border-bottom: 1px dotted; +} +b, +strong { + font-weight: bold; +} +dfn { + font-style: italic; +} +h1 { + font-size: 2em; + margin: 0.67em 0; +} +mark { + background: #ff0; + color: #000; +} +small { + font-size: 80%; +} +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} +sup { + top: -0.5em; +} +sub { + bottom: -0.25em; +} +img { + border: 0; +} +svg:not(:root) { + overflow: hidden; +} +figure { + margin: 1em 40px; +} +hr { + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + box-sizing: content-box; + height: 0; +} +pre { + overflow: auto; +} +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} +button, +input, +optgroup, +select, +textarea { + color: inherit; + font: inherit; + margin: 0; +} +button { + overflow: visible; +} +button, +select { + text-transform: none; +} +button, +html input[type="button"], +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; + cursor: pointer; +} +button[disabled], +html input[disabled] { + cursor: default; +} +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} +input { + line-height: normal; +} +input[type="checkbox"], +input[type="radio"] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + padding: 0; +} +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} +input[type="search"] { + -webkit-appearance: textfield; + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + box-sizing: content-box; +} +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} +legend { + border: 0; + padding: 0; +} +textarea { + overflow: auto; +} +optgroup { + font-weight: bold; +} +table { + border-collapse: collapse; + border-spacing: 0; +} +td, +th { + padding: 0; +} +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +*:before, +*:after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +html { + font-size: 10px; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} +body { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 1.42857143; + color: #333333; + background-color: #ffffff; +} +input, +button, +select, +textarea { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} +a { + color: #337ab7; + text-decoration: none; +} +a:hover, +a:focus { + color: #23527c; + text-decoration: underline; +} +a:focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +figure { + margin: 0; +} +img { + vertical-align: middle; +} +.img-responsive { + display: block; + max-width: 100%; + height: auto; +} +.img-rounded { + border-radius: 6px; +} +.img-thumbnail { + padding: 4px; + line-height: 1.42857143; + background-color: #ffffff; + border: 1px solid #dddddd; + border-radius: 4px; + -webkit-transition: all 0.2s ease-in-out; + -o-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; + display: inline-block; + max-width: 100%; + height: auto; +} +.img-circle { + border-radius: 50%; +} +hr { + margin-top: 20px; + margin-bottom: 20px; + border: 0; + border-top: 1px solid #eeeeee; +} +.sr-only { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} +.sr-only-focusable:active, +.sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; +} +.container { + margin-right: auto; + margin-left: auto; + padding-left: 15px; + padding-right: 15px; +} +@media (min-width: 768px) { + .container { + width: 750px; + } +} +@media (min-width: 992px) { + .container { + width: 970px; + } +} +@media (min-width: 1200px) { + .container { + width: 1170px; + } +} +.container-fluid { + margin-right: auto; + margin-left: auto; + padding-left: 15px; + padding-right: 15px; +} +.row { + margin-left: -15px; + margin-right: -15px; +} +.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 { + position: relative; + min-height: 1px; + padding-left: 15px; + padding-right: 15px; +} +.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 { + float: left; +} +.col-xs-12 { + width: 100%; +} +.col-xs-11 { + width: 91.66666667%; +} +.col-xs-10 { + width: 83.33333333%; +} +.col-xs-9 { + width: 75%; +} +.col-xs-8 { + width: 66.66666667%; +} +.col-xs-7 { + width: 58.33333333%; +} +.col-xs-6 { + width: 50%; +} +.col-xs-5 { + width: 41.66666667%; +} +.col-xs-4 { + width: 33.33333333%; +} +.col-xs-3 { + width: 25%; +} +.col-xs-2 { + width: 16.66666667%; +} +.col-xs-1 { + width: 8.33333333%; +} +.col-xs-pull-12 { + right: 100%; +} +.col-xs-pull-11 { + right: 91.66666667%; +} +.col-xs-pull-10 { + right: 83.33333333%; +} +.col-xs-pull-9 { + right: 75%; +} +.col-xs-pull-8 { + right: 66.66666667%; +} +.col-xs-pull-7 { + right: 58.33333333%; +} +.col-xs-pull-6 { + right: 50%; +} +.col-xs-pull-5 { + right: 41.66666667%; +} +.col-xs-pull-4 { + right: 33.33333333%; +} +.col-xs-pull-3 { + right: 25%; +} +.col-xs-pull-2 { + right: 16.66666667%; +} +.col-xs-pull-1 { + right: 8.33333333%; +} +.col-xs-pull-0 { + right: auto; +} +.col-xs-push-12 { + left: 100%; +} +.col-xs-push-11 { + left: 91.66666667%; +} +.col-xs-push-10 { + left: 83.33333333%; +} +.col-xs-push-9 { + left: 75%; +} +.col-xs-push-8 { + left: 66.66666667%; +} +.col-xs-push-7 { + left: 58.33333333%; +} +.col-xs-push-6 { + left: 50%; +} +.col-xs-push-5 { + left: 41.66666667%; +} +.col-xs-push-4 { + left: 33.33333333%; +} +.col-xs-push-3 { + left: 25%; +} +.col-xs-push-2 { + left: 16.66666667%; +} +.col-xs-push-1 { + left: 8.33333333%; +} +.col-xs-push-0 { + left: auto; +} +.col-xs-offset-12 { + margin-left: 100%; +} +.col-xs-offset-11 { + margin-left: 91.66666667%; +} +.col-xs-offset-10 { + margin-left: 83.33333333%; +} +.col-xs-offset-9 { + margin-left: 75%; +} +.col-xs-offset-8 { + margin-left: 66.66666667%; +} +.col-xs-offset-7 { + margin-left: 58.33333333%; +} +.col-xs-offset-6 { + margin-left: 50%; +} +.col-xs-offset-5 { + margin-left: 41.66666667%; +} +.col-xs-offset-4 { + margin-left: 33.33333333%; +} +.col-xs-offset-3 { + margin-left: 25%; +} +.col-xs-offset-2 { + margin-left: 16.66666667%; +} +.col-xs-offset-1 { + margin-left: 8.33333333%; +} +.col-xs-offset-0 { + margin-left: 0%; +} +@media (min-width: 768px) { + .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 { + float: left; + } + .col-sm-12 { + width: 100%; + } + .col-sm-11 { + width: 91.66666667%; + } + .col-sm-10 { + width: 83.33333333%; + } + .col-sm-9 { + width: 75%; + } + .col-sm-8 { + width: 66.66666667%; + } + .col-sm-7 { + width: 58.33333333%; + } + .col-sm-6 { + width: 50%; + } + .col-sm-5 { + width: 41.66666667%; + } + .col-sm-4 { + width: 33.33333333%; + } + .col-sm-3 { + width: 25%; + } + .col-sm-2 { + width: 16.66666667%; + } + .col-sm-1 { + width: 8.33333333%; + } + .col-sm-pull-12 { + right: 100%; + } + .col-sm-pull-11 { + right: 91.66666667%; + } + .col-sm-pull-10 { + right: 83.33333333%; + } + .col-sm-pull-9 { + right: 75%; + } + .col-sm-pull-8 { + right: 66.66666667%; + } + .col-sm-pull-7 { + right: 58.33333333%; + } + .col-sm-pull-6 { + right: 50%; + } + .col-sm-pull-5 { + right: 41.66666667%; + } + .col-sm-pull-4 { + right: 33.33333333%; + } + .col-sm-pull-3 { + right: 25%; + } + .col-sm-pull-2 { + right: 16.66666667%; + } + .col-sm-pull-1 { + right: 8.33333333%; + } + .col-sm-pull-0 { + right: auto; + } + .col-sm-push-12 { + left: 100%; + } + .col-sm-push-11 { + left: 91.66666667%; + } + .col-sm-push-10 { + left: 83.33333333%; + } + .col-sm-push-9 { + left: 75%; + } + .col-sm-push-8 { + left: 66.66666667%; + } + .col-sm-push-7 { + left: 58.33333333%; + } + .col-sm-push-6 { + left: 50%; + } + .col-sm-push-5 { + left: 41.66666667%; + } + .col-sm-push-4 { + left: 33.33333333%; + } + .col-sm-push-3 { + left: 25%; + } + .col-sm-push-2 { + left: 16.66666667%; + } + .col-sm-push-1 { + left: 8.33333333%; + } + .col-sm-push-0 { + left: auto; + } + .col-sm-offset-12 { + margin-left: 100%; + } + .col-sm-offset-11 { + margin-left: 91.66666667%; + } + .col-sm-offset-10 { + margin-left: 83.33333333%; + } + .col-sm-offset-9 { + margin-left: 75%; + } + .col-sm-offset-8 { + margin-left: 66.66666667%; + } + .col-sm-offset-7 { + margin-left: 58.33333333%; + } + .col-sm-offset-6 { + margin-left: 50%; + } + .col-sm-offset-5 { + margin-left: 41.66666667%; + } + .col-sm-offset-4 { + margin-left: 33.33333333%; + } + .col-sm-offset-3 { + margin-left: 25%; + } + .col-sm-offset-2 { + margin-left: 16.66666667%; + } + .col-sm-offset-1 { + margin-left: 8.33333333%; + } + .col-sm-offset-0 { + margin-left: 0%; + } +} +@media (min-width: 992px) { + .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 { + float: left; + } + .col-md-12 { + width: 100%; + } + .col-md-11 { + width: 91.66666667%; + } + .col-md-10 { + width: 83.33333333%; + } + .col-md-9 { + width: 75%; + } + .col-md-8 { + width: 66.66666667%; + } + .col-md-7 { + width: 58.33333333%; + } + .col-md-6 { + width: 50%; + } + .col-md-5 { + width: 41.66666667%; + } + .col-md-4 { + width: 33.33333333%; + } + .col-md-3 { + width: 25%; + } + .col-md-2 { + width: 16.66666667%; + } + .col-md-1 { + width: 8.33333333%; + } + .col-md-pull-12 { + right: 100%; + } + .col-md-pull-11 { + right: 91.66666667%; + } + .col-md-pull-10 { + right: 83.33333333%; + } + .col-md-pull-9 { + right: 75%; + } + .col-md-pull-8 { + right: 66.66666667%; + } + .col-md-pull-7 { + right: 58.33333333%; + } + .col-md-pull-6 { + right: 50%; + } + .col-md-pull-5 { + right: 41.66666667%; + } + .col-md-pull-4 { + right: 33.33333333%; + } + .col-md-pull-3 { + right: 25%; + } + .col-md-pull-2 { + right: 16.66666667%; + } + .col-md-pull-1 { + right: 8.33333333%; + } + .col-md-pull-0 { + right: auto; + } + .col-md-push-12 { + left: 100%; + } + .col-md-push-11 { + left: 91.66666667%; + } + .col-md-push-10 { + left: 83.33333333%; + } + .col-md-push-9 { + left: 75%; + } + .col-md-push-8 { + left: 66.66666667%; + } + .col-md-push-7 { + left: 58.33333333%; + } + .col-md-push-6 { + left: 50%; + } + .col-md-push-5 { + left: 41.66666667%; + } + .col-md-push-4 { + left: 33.33333333%; + } + .col-md-push-3 { + left: 25%; + } + .col-md-push-2 { + left: 16.66666667%; + } + .col-md-push-1 { + left: 8.33333333%; + } + .col-md-push-0 { + left: auto; + } + .col-md-offset-12 { + margin-left: 100%; + } + .col-md-offset-11 { + margin-left: 91.66666667%; + } + .col-md-offset-10 { + margin-left: 83.33333333%; + } + .col-md-offset-9 { + margin-left: 75%; + } + .col-md-offset-8 { + margin-left: 66.66666667%; + } + .col-md-offset-7 { + margin-left: 58.33333333%; + } + .col-md-offset-6 { + margin-left: 50%; + } + .col-md-offset-5 { + margin-left: 41.66666667%; + } + .col-md-offset-4 { + margin-left: 33.33333333%; + } + .col-md-offset-3 { + margin-left: 25%; + } + .col-md-offset-2 { + margin-left: 16.66666667%; + } + .col-md-offset-1 { + margin-left: 8.33333333%; + } + .col-md-offset-0 { + margin-left: 0%; + } +} +@media (min-width: 1200px) { + .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 { + float: left; + } + .col-lg-12 { + width: 100%; + } + .col-lg-11 { + width: 91.66666667%; + } + .col-lg-10 { + width: 83.33333333%; + } + .col-lg-9 { + width: 75%; + } + .col-lg-8 { + width: 66.66666667%; + } + .col-lg-7 { + width: 58.33333333%; + } + .col-lg-6 { + width: 50%; + } + .col-lg-5 { + width: 41.66666667%; + } + .col-lg-4 { + width: 33.33333333%; + } + .col-lg-3 { + width: 25%; + } + .col-lg-2 { + width: 16.66666667%; + } + .col-lg-1 { + width: 8.33333333%; + } + .col-lg-pull-12 { + right: 100%; + } + .col-lg-pull-11 { + right: 91.66666667%; + } + .col-lg-pull-10 { + right: 83.33333333%; + } + .col-lg-pull-9 { + right: 75%; + } + .col-lg-pull-8 { + right: 66.66666667%; + } + .col-lg-pull-7 { + right: 58.33333333%; + } + .col-lg-pull-6 { + right: 50%; + } + .col-lg-pull-5 { + right: 41.66666667%; + } + .col-lg-pull-4 { + right: 33.33333333%; + } + .col-lg-pull-3 { + right: 25%; + } + .col-lg-pull-2 { + right: 16.66666667%; + } + .col-lg-pull-1 { + right: 8.33333333%; + } + .col-lg-pull-0 { + right: auto; + } + .col-lg-push-12 { + left: 100%; + } + .col-lg-push-11 { + left: 91.66666667%; + } + .col-lg-push-10 { + left: 83.33333333%; + } + .col-lg-push-9 { + left: 75%; + } + .col-lg-push-8 { + left: 66.66666667%; + } + .col-lg-push-7 { + left: 58.33333333%; + } + .col-lg-push-6 { + left: 50%; + } + .col-lg-push-5 { + left: 41.66666667%; + } + .col-lg-push-4 { + left: 33.33333333%; + } + .col-lg-push-3 { + left: 25%; + } + .col-lg-push-2 { + left: 16.66666667%; + } + .col-lg-push-1 { + left: 8.33333333%; + } + .col-lg-push-0 { + left: auto; + } + .col-lg-offset-12 { + margin-left: 100%; + } + .col-lg-offset-11 { + margin-left: 91.66666667%; + } + .col-lg-offset-10 { + margin-left: 83.33333333%; + } + .col-lg-offset-9 { + margin-left: 75%; + } + .col-lg-offset-8 { + margin-left: 66.66666667%; + } + .col-lg-offset-7 { + margin-left: 58.33333333%; + } + .col-lg-offset-6 { + margin-left: 50%; + } + .col-lg-offset-5 { + margin-left: 41.66666667%; + } + .col-lg-offset-4 { + margin-left: 33.33333333%; + } + .col-lg-offset-3 { + margin-left: 25%; + } + .col-lg-offset-2 { + margin-left: 16.66666667%; + } + .col-lg-offset-1 { + margin-left: 8.33333333%; + } + .col-lg-offset-0 { + margin-left: 0%; + } +} +.clearfix:before, +.clearfix:after, +.container:before, +.container:after, +.container-fluid:before, +.container-fluid:after, +.row:before, +.row:after { + content: " "; + display: table; +} +.clearfix:after, +.container:after, +.container-fluid:after, +.row:after { + clear: both; +} +.center-block { + display: block; + margin-left: auto; + margin-right: auto; +} +.pull-right { + float: right !important; +} +.pull-left { + float: left !important; +} +.hide { + display: none !important; +} +.show { + display: block !important; +} +.invisible { + visibility: hidden; +} +.text-hide { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} +.hidden { + display: none !important; + visibility: hidden !important; +} +.affix { + position: fixed; +} diff --git a/plugins/Morpheus/stylesheets/general/_admin.less b/plugins/Morpheus/stylesheets/general/_admin.less index 3d6aa21f61e528c8077c1777263d9773f97f5d09..8b13c25ad93da21e381cc34ab41721fb84b54026 100644 --- a/plugins/Morpheus/stylesheets/general/_admin.less +++ b/plugins/Morpheus/stylesheets/general/_admin.less @@ -35,21 +35,6 @@ } } -.admin { - h2 { - border-bottom: 1px solid @color-gray; - margin-right:0; - } - h3 { - color: @theme-color-text; - .font-default(18px, 24px); - font-weight: normal; - } - a:hover { - text-decoration:underline; - } -} - .ui-state-highlight { border-color: @color-silver-l80 !important; background: @color-silver-l95 !important; @@ -61,7 +46,7 @@ .adminTable { td { - padding: 0; + padding: 2px 0; } label { cursor: pointer; diff --git a/plugins/Morpheus/stylesheets/general/_forms.less b/plugins/Morpheus/stylesheets/general/_forms.less index 3a23231bcc1e4f7fb4fb49c5b75f1bc121425755..47277940af262166e078fe32896edded4a0c54fa 100644 --- a/plugins/Morpheus/stylesheets/general/_forms.less +++ b/plugins/Morpheus/stylesheets/general/_forms.less @@ -3,7 +3,7 @@ input:not([type="checkbox"]), select, textarea { .border-radius(0px); margin-left: 0; padding: 8px 10px; - min-height: 30px; + min-height: 20px; .box-sizing(border-box); background: @theme-color-background-base; } @@ -12,6 +12,7 @@ button, .add-cors-host, input[type="submit"], button[type="button"], +.btn, .submit { .border-radius(3px) !important; background: none !important; @@ -41,6 +42,14 @@ button[type="button"], } } } +.btn-lg { + padding: 12px 16px !important;; + font-size: 14px !important;; +} +.btn-block { + display: block; + width: 100%; +} .top_bar_sites_selector { .sites_autocomplete .custom_select { @@ -57,7 +66,7 @@ button[type="button"], .border-radius(0px); background: @theme-color-background-base; .box-shadow(~"inset 1px 1px 3px #d8d8d8"); - padding: 0px 5px 0px; + padding: 0; color: @theme-color-text; text-transform: uppercase; .font-default(10px, 12px); @@ -94,7 +103,7 @@ button[type="button"], color: @theme-color-brand; font-size: 0.7em; top: 2px; - right: 5px; + right: 10px; content: ''; border-left: 4px solid transparent; border-right: 4px solid transparent; diff --git a/plugins/Morpheus/stylesheets/general/_jqueryUI.less b/plugins/Morpheus/stylesheets/general/_jqueryUI.less index cff51214c55efacdb790ea8c3b2589d8cff70e56..66b2384011e2eb8cff72a44edec60d02734e0268 100644 --- a/plugins/Morpheus/stylesheets/general/_jqueryUI.less +++ b/plugins/Morpheus/stylesheets/general/_jqueryUI.less @@ -234,6 +234,7 @@ td.ui-datepicker-other-month.ui-state-hover.ui-datepicker-current-period { .ui-tooltip h3 { font-size: 12px; margin: 0 0 2px 0; + line-height: 14px; } body .ui-tooltip.small { diff --git a/plugins/Morpheus/stylesheets/general/_misc.less b/plugins/Morpheus/stylesheets/general/_misc.less index de419973f1e4132d766ab16275aafc7e32bb1156..e26c52f798d873e893a1b13f3973fb3ae9801288 100644 --- a/plugins/Morpheus/stylesheets/general/_misc.less +++ b/plugins/Morpheus/stylesheets/general/_misc.less @@ -2,10 +2,6 @@ color: #95AECB; } -.exception-backtrace { - color: #888; -} - .section-toggler-link { font-size: .8em; font-style: italic; diff --git a/plugins/Morpheus/stylesheets/general/_typography.less b/plugins/Morpheus/stylesheets/general/_typography.less index c20217655687f01e378038660c5c353233fb29cc..bbec765ed2eaf79e28645b5e8728f85d5d6ea2bb 100644 --- a/plugins/Morpheus/stylesheets/general/_typography.less +++ b/plugins/Morpheus/stylesheets/general/_typography.less @@ -121,3 +121,13 @@ body > a.ddmetric { .segment-element .segment-content .segment-input input { font-family: @theme-fontFamily-base !important; } + +.text-left { + text-align: left; +} +.text-center { + text-align: center; +} +.text-right { + text-align: right; +} diff --git a/plugins/Morpheus/stylesheets/simple_structure.css b/plugins/Morpheus/stylesheets/simple_structure.css index 6198916ad6c93faa4068858c284494f21662e62e..20239c8800cdd3ef5af7d99d5e91d278baa830e9 100644 --- a/plugins/Morpheus/stylesheets/simple_structure.css +++ b/plugins/Morpheus/stylesheets/simple_structure.css @@ -1,6 +1,98 @@ +body { + font-family: Verdana, sans-serif; +} body#simple { - background: #eee; + background: #fff; +} +#simple .logo { + color: #888; + text-align: center; + font-size: 12px; + margin-top: 30px; +} +#simple .logo a { + color: #888; + text-decoration: none; +} +#simple .box { + border-radius: 12px; + border: solid 1px #ccc; + max-width: 780px; + margin: 30px auto 60px auto; + overflow: hidden; + box-shadow: 0 1px 2px 0 #ccc; +} + +#simple .box .header { + background-color: #f0f0f0; + border-bottom: solid 1px #ccc; + padding: 40px 80px; + text-align: center; +} +#simple .box .header h1 { + font-size: 30px; + font-weight: normal; + margin: 0; +} +#simple .box .header p { + font-size: 13px; + margin-top: 10px; + padding: 0; } + +#simple .box .content { + margin: 1em 2em; + text-align: center; + padding: 30px 50px; + font-size: 14px; +} +#simple .box .content.text-left { + text-align: left; +} +#simple .box .content ul { + text-align: left; + list-style: disc; + width: 60%; + margin: 0 auto; +} +#simple .box .content.text-left ul { + width: 100%; +} +#simple .box .content form { + margin: 10px 0; + min-height: 0; +} +#simple .box .content .btn { + float: none; + margin: 0; +} +#simple .box .content h2 { + font-weight: normal; + font-size: 19px; + margin-bottom: 25px; + margin-top: 30px; +} +#simple .box .content h2:first-child { + margin-top: 0; +} +#simple .box .content pre { + overflow-x: scroll; + font-size: 11px; + text-align: left; +} + +#simple .box .footer { + background-color: #f0f0f0; + border-top: solid 1px #ccc; + padding: 15px; + text-align: center; +} +#simple .box .footer a { + text-decoration: none; +} + +/* Old style below */ + #contentsimple { background: #fff; color: #000; @@ -23,11 +115,6 @@ body#simple { vertical-align: bottom; } -#titleUpdate { - font-size: 18px; - color: #7e7363; - clear:both; -} #subh1 a { color: #444; text-decoration:none; @@ -42,11 +129,9 @@ body#simple { font:42px Georgia, serif; } p, dt { - line-height: 120%; + line-height: 1.5; padding-bottom: 1em; - margin:10px; } -a { color: #006; } #logo { margin-bottom: 2em; } .submit { font-size:18pt; diff --git a/plugins/Morpheus/stylesheets/theme.less b/plugins/Morpheus/stylesheets/theme.less index c4cbdec5a62afcb068f7f98f00f5da28c71a20f8..ed72fac4ba74e42459db0632b60a50bcd57a85ad 100644 --- a/plugins/Morpheus/stylesheets/theme.less +++ b/plugins/Morpheus/stylesheets/theme.less @@ -14,10 +14,24 @@ body { font-family: @theme-fontFamily-base; } -.pageWrap { - padding-left: 10px; - padding-right: 10px; - border: 0px; +h2 { + font-weight: normal; + border-bottom: 1px solid @color-gray; + margin: 15px -15px 20px 0; + font-size: 24px; + width: 100%; + a, a:hover { + text-decoration: none; + color: @color-black-piwik; + } +} +h3 { + color: @theme-color-text; + .font-default(18px, 24px); + font-weight: normal; +} +a:hover { + text-decoration:underline; } #content { @@ -55,7 +69,7 @@ table.entityTable tr td a:hover { } #root { - margin: 0; + margin: 0 0 100px 0; padding: 0; #header { @@ -164,6 +178,7 @@ table.entityTable tr td a:hover { .widgetize { width: auto; } + } .dashboardSettings { @@ -302,7 +317,7 @@ table.entityTable tr td a:hover { border: 1px solid @color-silver-l80; padding: 8px 10px 8px 10px; color: @theme-color-text-light; - height: 12px; + height: 30px; line-height:0.5em; .border-radius(0px); } @@ -564,11 +579,9 @@ div.sparkline { .widgetTop { background: @theme-color-widget-title-background; border-bottom: 1px solid @color-silver-l80; - .widgetName { + h3 { .font-default(15px, 18px); color: @theme-color-widget-title-text; - text-shadow: none; - padding: 15px 15px 10px; } } diff --git a/plugins/Morpheus/stylesheets/ui/_alerts.less b/plugins/Morpheus/stylesheets/ui/_alerts.less new file mode 100644 index 0000000000000000000000000000000000000000..d0b1b3f10551747369e8c5ed13d43c42eb44326b --- /dev/null +++ b/plugins/Morpheus/stylesheets/ui/_alerts.less @@ -0,0 +1,28 @@ +.alert { + padding: 20px; + margin-bottom: 20px; + border: 1px solid transparent; + border-radius: 2px; +} +.alert-success { + color: #3c763d; + background-color: #dff0d8; + border-color: #d6e9c6; +} +.alert-info { + color: #B3B3B3; + background-color: #F5F5F5; + border-color: #E5E5E5; + font-size: 13px; + padding: 15px 20px; +} +.alert-warning { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #faebcc; +} +.alert-danger { + color: #E0645D; + background: none; + border-color: #D4291F; +} diff --git a/plugins/Morpheus/stylesheets/ui/_components.less b/plugins/Morpheus/stylesheets/ui/_components.less index 7e93cf057bb33c56c2a276a310fd09aeb9b0f4b8..cf373a147c5cf180100379ea3ae9cafa4fd060e8 100644 --- a/plugins/Morpheus/stylesheets/ui/_components.less +++ b/plugins/Morpheus/stylesheets/ui/_components.less @@ -88,8 +88,8 @@ .font-default(12px, 14px); color: @theme-color-text; font-weight: 500; - margin: 0px; - min-height: auto; + margin: 0; + height: 30px; } } } diff --git a/plugins/Morpheus/templates/maintenance.tpl b/plugins/Morpheus/templates/maintenance.tpl new file mode 100644 index 0000000000000000000000000000000000000000..1f1545c0150a644c8f2d7c7414fc682c30f10296 --- /dev/null +++ b/plugins/Morpheus/templates/maintenance.tpl @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>Piwik is undergoing maintenance</title> + <link rel="shortcut icon" href="%faviconUrl%" /> + <link rel="stylesheet" type="text/css" href="plugins/Morpheus/stylesheets/simple_structure.css" /> +</head> +<body id="simple"> + +<div class="logo"> + <img title="Piwik" alt="Piwik" src="%logoUrl%" /> + <br/> + <a href='http://piwik.org/'>%piwikTitle%</a> +</div> + +<div class="box"> + + <div class="header"> + <h1>Piwik is undergoing maintenance</h1> + </div> + + <div class="content"> + <p> + We will be back up and running again soon, so please try again later. Your analytics data will continue to be tracked as normal. Thank you for your patience. + </p> + </div> + + <div class="footer"> + </div> + +</div> + +</body> +</html> diff --git a/plugins/Morpheus/templates/simpleLayoutFooter.tpl b/plugins/Morpheus/templates/simpleLayoutFooter.tpl index 9943ff0f851844f5daaccd7e1adca02a193936dc..f308a692e87a8592c6a866aa06db1db7ed651209 100644 --- a/plugins/Morpheus/templates/simpleLayoutFooter.tpl +++ b/plugins/Morpheus/templates/simpleLayoutFooter.tpl @@ -1,3 +1,9 @@ + </div> + + <div class="footer"> + <a href="index.php">« Back to Piwik</a> + </div> + </div> </body> </html> diff --git a/plugins/Morpheus/templates/simpleLayoutHeader.tpl b/plugins/Morpheus/templates/simpleLayoutHeader.tpl index 377e9a75391f3adc25ea7403a16eb1e64beecdfb..8d11a5b1129b7a30d93edf718b1c7e5b4bd456e5 100644 --- a/plugins/Morpheus/templates/simpleLayoutHeader.tpl +++ b/plugins/Morpheus/templates/simpleLayoutHeader.tpl @@ -7,5 +7,17 @@ <link rel="stylesheet" type="text/css" href="plugins/Morpheus/stylesheets/simple_structure.css" /> </head> <body id="simple"> -<div id="contentsimple"> - <div id="title"><img title="Piwik" alt="Piwik" src="%logoUrl%" style="margin-left:10px;" /><span id="subh1"> # <a href='http://piwik.org/'>Web Analytics</a></span></div> + +<div class="logo"> + <img title="Piwik" alt="Piwik" src="%logoUrl%"/> + <br/> + <a href='http://piwik.org/'>free/libre analytics platform</a> +</div> + +<div class="box"> + + <div class="header"> + <h1>An error occurred</h1> + </div> + + <div class="content"> \ No newline at end of file diff --git a/plugins/Morpheus/templates/user.twig b/plugins/Morpheus/templates/user.twig index 4234f0729693580a70b6ab517248181c13613978..346aa1f7f444922b73ffc291037681f59bd8ac5a 100644 --- a/plugins/Morpheus/templates/user.twig +++ b/plugins/Morpheus/templates/user.twig @@ -9,8 +9,10 @@ {% set topMenuModule = 'Feedback' %} {% set topMenuAction = 'index' %} {% else %} - {% set topMenuModule = 'UsersManager' %} - {% set topMenuAction = 'userSettings' %} + {% if currentModule != 'Feedback' %} + {% set topMenuModule = 'UsersManager' %} + {% set topMenuAction = 'userSettings' %} + {% endif %} {% endif %} {{ parent() }} {% endblock %} diff --git a/plugins/MultiSites/MultiSites.php b/plugins/MultiSites/MultiSites.php index c76551bb79e6f16dc3bca78a7be82daed333f175..f3f62ecae45a3790ea5a1b052e72c821116087aa 100644 --- a/plugins/MultiSites/MultiSites.php +++ b/plugins/MultiSites/MultiSites.php @@ -67,7 +67,7 @@ class MultiSites extends \Piwik\Plugin $translations[] = 'Actions_SubmenuSitesearch'; $translations[] = 'MultiSites_LoadingWebsites'; $translations[] = 'General_ErrorRequest'; - $translations[] = 'MultiSites_Pagination'; + $translations[] = 'General_Pagination'; } public function getJsFiles(&$jsFiles) diff --git a/plugins/MultiSites/angularjs/dashboard/dashboard.directive.html b/plugins/MultiSites/angularjs/dashboard/dashboard.directive.html index 7066fd5d1fcb34bdfa1f52aaa8cfa67f838c7d36..287ef25c98c926aa51620e398c03f99b3359f1cb 100644 --- a/plugins/MultiSites/angularjs/dashboard/dashboard.directive.html +++ b/plugins/MultiSites/angularjs/dashboard/dashboard.directive.html @@ -97,7 +97,7 @@ </span> <span class="dataTablePages"> <span id="counter"> - {{ 'MultiSites_Pagination'|translate:model.getCurrentPagingOffsetStart():model.getCurrentPagingOffsetEnd():model.getNumberOfFilteredSites() }} + {{ 'General_Pagination'|translate:model.getCurrentPagingOffsetStart():model.getCurrentPagingOffsetEnd():model.getNumberOfFilteredSites() }} </span> </span> <span id="next" class="next" ng-hide="model.currentPage >= model.getNumberOfPages()" ng-click="model.nextPage()"> diff --git a/plugins/MultiSites/templates/getSitesInfo.twig b/plugins/MultiSites/templates/getSitesInfo.twig index dde3f9898df8a648862f7c82d44da190f08aa588..d1bdb81df054e4ac8b433be40c7405d5c0a1c4fd 100644 --- a/plugins/MultiSites/templates/getSitesInfo.twig +++ b/plugins/MultiSites/templates/getSitesInfo.twig @@ -8,10 +8,9 @@ </div> {% endif %} -<div class="pageWrap" id="multisites"> +<div class="pageWrap container" id="multisites"> <div id="main"> <div piwik-multisites-dashboard - class="centerLargeDiv" display-revenue-column="{% if displayRevenueColumn %}true{% else %}false{%endif%}" page-size="{{ limit }}" show-sparklines="{% if show_sparklines %}true{% else %}false{%endif%}" diff --git a/plugins/Referrers/Columns/Base.php b/plugins/Referrers/Columns/Base.php index feacc90e55b0a64ec30d34577a5d5c944e242bb0..5c80aa63498752c63dc0b217d982706625ca8640 100644 --- a/plugins/Referrers/Columns/Base.php +++ b/plugins/Referrers/Columns/Base.php @@ -70,7 +70,7 @@ abstract class Base extends VisitDimension { $cacheKey = $referrerUrl . $currentUrl . $idSite; - if (array_key_exists($cacheKey, self::$cachedReferrer)) { + if (isset(self::$cachedReferrer[$cacheKey])) { return self::$cachedReferrer[$cacheKey]; } @@ -108,9 +108,7 @@ abstract class Base extends VisitDimension } } - if (!empty($this->referrerHost) - && !$referrerDetected - ) { + if (!$referrerDetected && !empty($this->referrerHost)) { $this->typeReferrerAnalyzed = Common::REFERRER_TYPE_WEBSITE; $this->nameReferrerAnalyzed = Common::mb_strtolower($this->referrerHost); } @@ -345,7 +343,6 @@ abstract class Base extends VisitDimension $type = $visitor->getVisitorColumn('referer_type'); $name = $visitor->getVisitorColumn('referer_name'); $keyword = $visitor->getVisitorColumn('referer_keyword'); - $time = $visitor->getVisitorColumn('visit_first_action_time'); // 0) In some (unknown!?) cases the campaign is not found in the attribution cookie, but the URL ref was found. // In this case we look up if the current visit is credited to a campaign and will credit this campaign rather than the URL ref (since campaigns have higher priority) @@ -359,7 +356,6 @@ abstract class Base extends VisitDimension $type = Common::REFERRER_TYPE_CAMPAIGN; $name = $referrerCampaignName; $keyword = $referrerCampaignKeyword; - $time = $referrerTimestamp; } // 2) Referrer URL parsing elseif (!empty($referrerUrl)) { @@ -371,7 +367,6 @@ abstract class Base extends VisitDimension $type = $referrer['referer_type']; $name = $referrer['referer_name']; $keyword = $referrer['referer_keyword']; - $time = $referrerTimestamp; } } diff --git a/plugins/Referrers/templates/getSearchEnginesAndKeywords.twig b/plugins/Referrers/templates/getSearchEnginesAndKeywords.twig index ce2f5f2e8d9b28a6249cb84c2cd6c247e0879dfa..29b2b3f0af8a38b9be8a8d69a2e6fbd24bc1511d 100644 --- a/plugins/Referrers/templates/getSearchEnginesAndKeywords.twig +++ b/plugins/Referrers/templates/getSearchEnginesAndKeywords.twig @@ -1,9 +1,13 @@ -<div id='leftcolumn'> - <h2 piwik-enriched-headline>{{ 'Referrers_Keywords'|translate }}</h2> - {{ keywords|raw }} -</div> +<div class="row"> + + <div class="col-md-6"> + <h2 piwik-enriched-headline>{{ 'Referrers_Keywords'|translate }}</h2> + {{ keywords|raw }} + </div> + + <div class="col-md-6"> + <h2 piwik-enriched-headline>{{ 'Referrers_SearchEngines'|translate }}</h2> + {{ searchEngines|raw }} + </div> -<div id='rightcolumn'> - <h2 piwik-enriched-headline>{{ 'Referrers_SearchEngines'|translate }}</h2> - {{ searchEngines|raw }} </div> diff --git a/plugins/Referrers/templates/index.twig b/plugins/Referrers/templates/index.twig index 3b07a4063f34e1ea1d1903dc241a51aabb1b88b3..c6294312105be99482223cfaea7568ba2bb51e40 100644 --- a/plugins/Referrers/templates/index.twig +++ b/plugins/Referrers/templates/index.twig @@ -4,51 +4,51 @@ <h2 piwik-enriched-headline>{{ 'Referrers_Type'|translate }}</h2> -<div id='leftcolumn'> - <div class="sparkline" style="padding-left: 12px;">{{ sparkline(urlSparklineDirectEntry) }} - {{ 'Referrers_TypeDirectEntries'|translate("<strong>"~visitorsFromDirectEntry~"</strong>")|raw }} - {% if visitorsFromDirectEntryPercent|default is not empty %}, - {{ 'Referrers_XPercentOfVisits'|translate("<strong>"~visitorsFromDirectEntryPercent~"</strong>")|raw }} - {% endif %} - {% if visitorsFromDirectEntryEvolution|default is not empty %} - {{ visitorsFromDirectEntryEvolution|raw }} - {% endif %} - </div> - <div class="sparkline" style="padding-left: 12px;">{{ sparkline(urlSparklineSearchEngines) }} - {{ 'Referrers_TypeSearchEngines'|translate("<strong>"~visitorsFromSearchEngines~"</strong>")|raw }} - {% if visitorsFromSearchEnginesPercent|default is not empty %}, - {{ 'Referrers_XPercentOfVisits'|translate("<strong>"~visitorsFromSearchEnginesPercent~"</strong>")|raw }} - {% endif %} - {% if visitorsFromSearchEnginesEvolution|default is not empty %} - {{ visitorsFromSearchEnginesEvolution|raw }} - {% endif %} - </div> -</div> -<div id='rightcolumn'> - <div class="sparkline">{{ sparkline(urlSparklineWebsites) }} - {{ 'Referrers_TypeWebsites'|translate("<strong>"~visitorsFromWebsites~"</strong>")|raw }} - {% if visitorsFromWebsitesPercent|default is not empty %}, - {{ 'Referrers_XPercentOfVisits'|translate("<strong>"~visitorsFromWebsitesPercent~"</strong>")|raw }} - {% endif %} - {% if visitorsFromWebsitesEvolution|default is not empty %} - {{ visitorsFromWebsitesEvolution|raw }} - {% endif %} +<div class="row"> + <div class="col-md-6"> + <div class="sparkline" style="padding-left: 12px;">{{ sparkline(urlSparklineDirectEntry) }} + {{ 'Referrers_TypeDirectEntries'|translate("<strong>"~visitorsFromDirectEntry~"</strong>")|raw }} + {% if visitorsFromDirectEntryPercent|default is not empty %}, + {{ 'Referrers_XPercentOfVisits'|translate("<strong>"~visitorsFromDirectEntryPercent~"</strong>")|raw }} + {% endif %} + {% if visitorsFromDirectEntryEvolution|default is not empty %} + {{ visitorsFromDirectEntryEvolution|raw }} + {% endif %} + </div> + <div class="sparkline" style="padding-left: 12px;">{{ sparkline(urlSparklineSearchEngines) }} + {{ 'Referrers_TypeSearchEngines'|translate("<strong>"~visitorsFromSearchEngines~"</strong>")|raw }} + {% if visitorsFromSearchEnginesPercent|default is not empty %}, + {{ 'Referrers_XPercentOfVisits'|translate("<strong>"~visitorsFromSearchEnginesPercent~"</strong>")|raw }} + {% endif %} + {% if visitorsFromSearchEnginesEvolution|default is not empty %} + {{ visitorsFromSearchEnginesEvolution|raw }} + {% endif %} + </div> </div> - <div class="sparkline">{{ sparkline(urlSparklineCampaigns) }} - {{ 'Referrers_TypeCampaigns'|translate("<strong>"~visitorsFromCampaigns~"</strong>")|raw }} - {% if visitorsFromCampaignsPercent|default is not empty %}, - {{ 'Referrers_XPercentOfVisits'|translate("<strong>"~visitorsFromCampaignsPercent~"</strong>")|raw }} - {% endif %} - {% if visitorsFromCampaignsEvolution|default is not empty %} - {{ visitorsFromCampaignsEvolution|raw }} - {% endif %} + <div class="col-md-6"> + <div class="sparkline">{{ sparkline(urlSparklineWebsites) }} + {{ 'Referrers_TypeWebsites'|translate("<strong>"~visitorsFromWebsites~"</strong>")|raw }} + {% if visitorsFromWebsitesPercent|default is not empty %}, + {{ 'Referrers_XPercentOfVisits'|translate("<strong>"~visitorsFromWebsitesPercent~"</strong>")|raw }} + {% endif %} + {% if visitorsFromWebsitesEvolution|default is not empty %} + {{ visitorsFromWebsitesEvolution|raw }} + {% endif %} + </div> + <div class="sparkline">{{ sparkline(urlSparklineCampaigns) }} + {{ 'Referrers_TypeCampaigns'|translate("<strong>"~visitorsFromCampaigns~"</strong>")|raw }} + {% if visitorsFromCampaignsPercent|default is not empty %}, + {{ 'Referrers_XPercentOfVisits'|translate("<strong>"~visitorsFromCampaignsPercent~"</strong>")|raw }} + {% endif %} + {% if visitorsFromCampaignsEvolution|default is not empty %} + {{ visitorsFromCampaignsEvolution|raw }} + {% endif %} + </div> </div> </div> -<div style="clear:both;"/> - -<div id="distinctReferrersByType"> - <div id='leftcolumn'> +<div id="distinctReferrersByType" class="row"> + <div class="col-md-6"> <div class="sparkline" style="padding-left: 12px;">{{ sparkline(urlSparklineDistinctSearchEngines) }} <strong>{{ numberDistinctSearchEngines }}</strong> {{ 'Referrers_DistinctSearchEngines'|translate }} {% if numberDistinctSearchEnginesEvolution|default is not empty %} @@ -62,7 +62,7 @@ {% endif %} </div> </div> - <div id='rightcolumn'> + <div class="col-md-6"> <div class="sparkline">{{ sparkline(urlSparklineDistinctWebsites) }} <strong>{{ numberDistinctWebsites }}</strong> {{ 'Referrers_DistinctWebsites'|translate }} {{ 'Referrers_UsingNDistinctUrls'|translate("<strong>"~numberDistinctWebsitesUrls~"</strong>")|raw }} @@ -80,8 +80,6 @@ <br/> </div> -<p style="clear:both;"/> - <div style="float:left;" class="relatedReferrerReports">{{ 'General_View'|translate }} <a href="javascript:broadcast.propagateAjax('module=Referrers&action=getSearchEnginesAndKeywords')">{{ 'Referrers_SubmenuSearchEngines'|translate }}</a>, <a href="javascript:broadcast.propagateAjax('module=Referrers&action=indexWebsites')">{{ 'Referrers_SubmenuWebsites'|translate }}</a>, diff --git a/plugins/Referrers/templates/indexWebsites.twig b/plugins/Referrers/templates/indexWebsites.twig index adfea9d6e108a90a6f65609b092b9c3105220aa2..cdfa6efe0184bad664ebc211ec2a94dafd2c4c37 100644 --- a/plugins/Referrers/templates/indexWebsites.twig +++ b/plugins/Referrers/templates/indexWebsites.twig @@ -1,9 +1,13 @@ -<div id='leftcolumn'> - <h2 piwik-enriched-headline>{{ 'Referrers_Websites'|translate }}</h2> - {{ websites|raw }} -</div> +<div class="row"> + + <div class="col-md-6"> + <h2 piwik-enriched-headline>{{ 'Referrers_Websites'|translate }}</h2> + {{ websites|raw }} + </div> + + <div class="col-md-6"> + <h2 piwik-enriched-headline>{{ 'Referrers_Socials'|translate }}</h2> + {{ socials|raw }} + </div> -<div id='rightcolumn'> - <h2 piwik-enriched-headline>{{ 'Referrers_Socials'|translate }}</h2> - {{ socials|raw }} </div> diff --git a/plugins/ScheduledReports/API.php b/plugins/ScheduledReports/API.php index 1f404765c3f4b140da85ece6093c224945fdedcd..f77fa4b2237265f523d9d49ba322a4379b288929 100644 --- a/plugins/ScheduledReports/API.php +++ b/plugins/ScheduledReports/API.php @@ -449,7 +449,7 @@ class API extends \Piwik\Plugin\API // render report $description = str_replace(array("\r", "\n"), ' ', $report['description']); - list($reportSubject, $reportTitle) = self::getReportSubjectAndReportTitle(Site::getNameFor($idSite), $report['reports']); + list($reportSubject, $reportTitle) = self::getReportSubjectAndReportTitle(Common::unsanitizeInputValue(Site::getNameFor($idSite)), $report['reports']); // if reporting for a segment, use the segment's name in the title if(is_array($segment) && strlen($segment['name'])) { diff --git a/plugins/SegmentEditor/stylesheets/segmentation.less b/plugins/SegmentEditor/stylesheets/segmentation.less index fdf4a1d579d5cc753806177fa96cecd5ff08b19e..126badb9481711d3f07ca1fa55eb0a304fc335cd 100644 --- a/plugins/SegmentEditor/stylesheets/segmentation.less +++ b/plugins/SegmentEditor/stylesheets/segmentation.less @@ -89,7 +89,6 @@ div.scrollable { .segment-element .custom_select_search { width: 146px; - height: 21px; background: url(plugins/SegmentEditor/images/bg-segment-search.png) 0 10px no-repeat; padding: 10px 0 0 0; margin: 10px 0 10px 15px; @@ -249,7 +248,7 @@ div.scrollable { .segment-element .segment-content .segment-add-or { text-shadow: 0 1px 0 #fff; display: inline-block; - width: 98%; + width: 100%; padding: 0 1%; background: #efefeb; border-radius: 3px 3px 3px 3px; @@ -569,7 +568,7 @@ div.scrollable { span.segmentationTitle { background: url(plugins/Morpheus/images/sort_subtable_desc.png) no-repeat right 0; padding-right: 20px; - min-width: 160px; + min-width: 180px; display: block; cursor: pointer; } @@ -744,7 +743,7 @@ a.metric_category { .segmentationTitle, .segment-element .segment-nav a.dropdown { - max-width: 160px; + max-width: 180px; } .segname { @@ -752,7 +751,7 @@ a.metric_category { } .segmentEditorPanel.visible .segmentationTitle { - max-width: 180px; + max-width: 200px; overflow: visible; /* restore default */ white-space: normal; /* restore default */ } diff --git a/plugins/SitesManager/API.php b/plugins/SitesManager/API.php index 8457778ab5b984b703e1341d62b29bacca0264fb..36e29a86f0ae16b89a60dadfc0caf74613b412a6 100644 --- a/plugins/SitesManager/API.php +++ b/plugins/SitesManager/API.php @@ -240,7 +240,7 @@ class API extends \Piwik\Plugin\API { Piwik::checkUserHasSuperUserAccess(); try { - return API::getInstance()->getSitesId(); + return $this->getSitesId(); } catch (Exception $e) { // can be called before Piwik tables are created so return empty return array(); @@ -278,16 +278,24 @@ class API extends \Piwik\Plugin\API * For the superUser it returns all the websites in the database. * * @param bool $fetchAliasUrls + * @param false|string $pattern + * @param false|int $limit * @return array for each site, an array of information (idsite, name, main_url, etc.) */ - public function getSitesWithAdminAccess($fetchAliasUrls = false) + public function getSitesWithAdminAccess($fetchAliasUrls = false, $pattern = false, $limit = false) { $sitesId = $this->getSitesIdWithAdminAccess(); - $sites = $this->getSitesFromIds($sitesId); + + if ($pattern === false) { + $sites = $this->getSitesFromIds($sitesId, $limit); + } else { + $sites = $this->getModel()->getPatternMatchSites($sitesId, $pattern, $limit); + Site::setSitesFromArray($sites); + } if ($fetchAliasUrls) { foreach ($sites as &$site) { - $site['alias_urls'] = API::getInstance()->getSiteUrlsFromId($site['idsite']); + $site['alias_urls'] = $this->getSiteUrlsFromId($site['idsite']); } } @@ -593,7 +601,7 @@ class API extends \Piwik\Plugin\API { Piwik::checkUserHasSuperUserAccess(); - $idSites = API::getInstance()->getSitesId(); + $idSites = $this->getSitesId(); if (!in_array($idSite, $idSites)) { throw new Exception("website id = $idSite not found"); } @@ -1048,7 +1056,8 @@ class API extends \Piwik\Plugin\API { Piwik::checkUserHasAdminAccess($idSite); - $idSites = API::getInstance()->getSitesId(); + $idSites = $this->getSitesId(); + if (!in_array($idSite, $idSites)) { throw new Exception("website id = $idSite not found"); } diff --git a/plugins/SitesManager/Controller.php b/plugins/SitesManager/Controller.php index b02a60c7b9e94975810732ca60487d0dc1c42e11..33b00235f617637751fa8bb8ca1277bc25a58359 100644 --- a/plugins/SitesManager/Controller.php +++ b/plugins/SitesManager/Controller.php @@ -28,6 +28,8 @@ class Controller extends \Piwik\Plugin\ControllerAdmin */ public function index() { + Piwik::checkUserHasSomeAdminAccess(); + return $this->renderTemplate('index'); } diff --git a/plugins/SitesManager/Model.php b/plugins/SitesManager/Model.php index 0646453633d050d3dab4913cce7e402f3a0f59f1..632792683ff690f6c779113e9769de1fad79f5ff 100644 --- a/plugins/SitesManager/Model.php +++ b/plugins/SitesManager/Model.php @@ -357,8 +357,11 @@ class Model OR s.main_url like ? OR s.`group` like ? $where ) - AND idsite in ($ids_str) - LIMIT " . (int) $limit; + AND idsite in ($ids_str)"; + + if ($limit !== false) { + $query .= " LIMIT " . (int) $limit; + } $db = $this->getDb(); $sites = $db->fetchAll($query, $bind); diff --git a/plugins/SitesManager/SitesManager.php b/plugins/SitesManager/SitesManager.php index cbd0d9283858d1d423357a47865ce8d2919270d9..8e45c733c4d83fb67b428af5d4fbb9f86096bcf4 100644 --- a/plugins/SitesManager/SitesManager.php +++ b/plugins/SitesManager/SitesManager.php @@ -88,6 +88,7 @@ class SitesManager extends \Piwik\Plugin $jsFiles[] = "plugins/SitesManager/angularjs/sites-manager/api-helper.service.js"; $jsFiles[] = "plugins/SitesManager/angularjs/sites-manager/api-site.service.js"; $jsFiles[] = "plugins/SitesManager/angularjs/sites-manager/api-core.service.js"; + $jsFiles[] = "plugins/SitesManager/angularjs/sites-manager/sites-manager-admin-sites-model.js"; $jsFiles[] = "plugins/SitesManager/angularjs/sites-manager/multiline-field.directive.js"; $jsFiles[] = "plugins/SitesManager/angularjs/sites-manager/edit-trigger.directive.js"; $jsFiles[] = "plugins/SitesManager/angularjs/sites-manager/scroll.directive.js"; @@ -261,6 +262,9 @@ class SitesManager extends \Piwik\Plugin $translationKeys[] = "General_Save"; $translationKeys[] = "General_OrCancel"; $translationKeys[] = "General_Actions"; + $translationKeys[] = "General_Search"; + $translationKeys[] = "General_Pagination"; + $translationKeys[] = "General_PaginationWithoutTotal"; $translationKeys[] = "Actions_SubmenuSitesearch"; $translationKeys[] = "SitesManager_OnlyOneSiteAtTime"; $translationKeys[] = "SitesManager_DeleteConfirm"; @@ -286,6 +290,7 @@ class SitesManager extends \Piwik\Plugin $translationKeys[] = "SitesManager_EnableSiteSearch"; $translationKeys[] = "SitesManager_DisableSiteSearch"; $translationKeys[] = "SitesManager_SearchUseDefault"; + $translationKeys[] = "SitesManager_Sites"; $translationKeys[] = "SitesManager_SiteSearchUse"; $translationKeys[] = "SitesManager_SearchKeywordLabel"; $translationKeys[] = "SitesManager_SearchCategoryLabel"; @@ -322,5 +327,6 @@ class SitesManager extends \Piwik\Plugin $translationKeys[] = "SitesManager_SelectDefaultCurrency"; $translationKeys[] = "SitesManager_AddSite"; $translationKeys[] = "Goals_Ecommerce"; + $translationKeys[] = "SitesManager_NotFound"; } } diff --git a/plugins/SitesManager/angularjs/sites-manager/api-site.service.js b/plugins/SitesManager/angularjs/sites-manager/api-site.service.js index 63a8aa7cba31cdced4749b980664817fc9bba84c..ad2d9d235184d7d657e2e216c0df8d77d0726a6b 100644 --- a/plugins/SitesManager/angularjs/sites-manager/api-site.service.js +++ b/plugins/SitesManager/angularjs/sites-manager/api-site.service.js @@ -13,20 +13,21 @@ return { getCurrencyList: getCurrencyList(), - getSitesWithAdminAccess: getSitesWithAdminAccess(), getTimezonesList: getTimezonesList(), isTimezoneSupportEnabled: isTimezoneSupportEnabled(), - getGlobalSettings: getGlobalSettings() + getGlobalSettings: getGlobalSettings(), + getSitesIdWithAdminAccess: getSitesIdWithAdminAccess() }; + function getSitesIdWithAdminAccess () { + return api.fetchApi('SitesManager.getSitesIdWithAdminAccess', api.noop, { + filter_limit: '-1', + }); + } function getCurrencyList () { return api.fetchApi('SitesManager.getCurrencyList', api.noop); } - function getSitesWithAdminAccess () { - return api.fetchApi('SitesManager.getSitesWithAdminAccess', api.noop, {fetchAliasUrls: true}); - } - function getTimezonesList () { return api.fetchApi('SitesManager.getTimezonesList', api.noop); } diff --git a/plugins/SitesManager/angularjs/sites-manager/sites-manager-admin-sites-model.js b/plugins/SitesManager/angularjs/sites-manager/sites-manager-admin-sites-model.js new file mode 100644 index 0000000000000000000000000000000000000000..537cbc4f7066a6eae088c018bf66b536287c9a2a --- /dev/null +++ b/plugins/SitesManager/angularjs/sites-manager/sites-manager-admin-sites-model.js @@ -0,0 +1,111 @@ +/** + * Model for Sites Manager. Fetches only sites one has at least Admin permission. + */ +(function () { + angular.module('piwikApp').factory('sitesManagerAdminSitesModel', sitesManagerAdminSitesModel); + + sitesManagerAdminSitesModel.$inject = ['piwikApi']; + + function sitesManagerAdminSitesModel(piwikApi) + { + var model = { + sites : [], + searchTerm : '', + isLoading : false, + pageSize : 25, + currentPage : 0, + offsetStart : 0, + offsetEnd : 25, + hasPrev : false, + hasNext : false, + previousPage: previousPage, + nextPage: nextPage, + searchSite: searchSite, + fetchLimitedSitesWithAdminAccess: fetchLimitedSitesWithAdminAccess + }; + + return model; + + function onError () + { + setSites([]); + } + + function setSites(sites) + { + model.sites = sites; + + var numSites = sites.length; + model.offsetStart = model.currentPage * model.pageSize; + model.offsetEnd = model.offsetStart + numSites; + model.hasPrev = model.currentPage >= 1; + model.hasNext = numSites === model.pageSize; + } + + function setCurrentPage(page) + { + if (page < 0) { + page = 0; + } + + model.currentPage = page; + } + + function previousPage() + { + setCurrentPage(model.currentPage - 1); + fetchLimitedSitesWithAdminAccess(); + } + + function nextPage() + { + setCurrentPage(model.currentPage + 1); + fetchLimitedSitesWithAdminAccess(); + } + + function searchSite (term) + { + model.searchTerm = term; + setCurrentPage(0); + fetchLimitedSitesWithAdminAccess(); + } + + function fetchLimitedSitesWithAdminAccess(searchTerm) + { + if (model.isLoading) { + piwikApi.abort(); + } + + model.isLoading = true; + + var limit = model.pageSize; + var offset = model.currentPage * model.pageSize; + + var params = { + method: 'SitesManager.getSitesWithAdminAccess', + fetchAliasUrls: true, + limit: limit + offset, // this is applied in SitesManager.getSitesWithAdminAccess API + filter_offset: offset, // filter_offset and filter_limit is applied in response builder + filter_limit: limit + }; + + if (model.searchTerm) { + params.pattern = model.searchTerm; + } + + return piwikApi.fetch(params).then(function (sites) { + + if (!sites) { + onError(); + return; + } + + setSites(sites); + + }, onError)['finally'](function () { + model.isLoading = false; + }); + } + + } +})(); diff --git a/plugins/SitesManager/angularjs/sites-manager/sites-manager.controller.js b/plugins/SitesManager/angularjs/sites-manager/sites-manager.controller.js index 2aea880d2498e29e3741e9164bcec47ca29111ee..1edc69490deb2b3ff9bf7d0a35b710595b437de1 100644 --- a/plugins/SitesManager/angularjs/sites-manager/sites-manager.controller.js +++ b/plugins/SitesManager/angularjs/sites-manager/sites-manager.controller.js @@ -7,25 +7,22 @@ (function () { angular.module('piwikApp').controller('SitesManagerController', SitesManagerController); - SitesManagerController.$inject = ['$scope', '$filter', 'coreAPI', 'sitesManagerAPI', 'piwik', 'sitesManagerApiHelper']; + SitesManagerController.$inject = ['$scope', '$filter', 'coreAPI', 'sitesManagerAPI', 'sitesManagerAdminSitesModel', 'piwik', 'sitesManagerApiHelper']; - function SitesManagerController($scope, $filter, coreAPI, sitesManagerAPI, piwik, sitesManagerApiHelper) { + function SitesManagerController($scope, $filter, coreAPI, sitesManagerAPI, adminSites, piwik, sitesManagerApiHelper) { var translate = $filter('translate'); var init = function () { - initModel(); - initActions(); - }; - - var initModel = function() { - $scope.period = piwik.broadcast.getValueFromUrl('period'); $scope.date = piwik.broadcast.getValueFromUrl('date'); - $scope.sites = []; + $scope.adminSites = adminSites; $scope.hasSuperUserAccess = piwik.hasSuperUserAccess; $scope.redirectParams = {showaddsite: false}; + $scope.siteIsBeingEdited = false; + $scope.cacheBuster = piwik.cacheBuster; + $scope.totalNumberOfSites = '?'; initSelectLists(); initUtcTime(); @@ -33,6 +30,8 @@ initCustomVariablesActivated(); initIsTimezoneSupportEnabled(); initGlobalParams(); + + initActions(); }; var initActions = function () { @@ -72,9 +71,16 @@ $scope.globalSettings.excludedQueryParametersGlobal = sitesManagerApiHelper.commaDelimitedFieldToArray($scope.globalSettings.excludedQueryParametersGlobal); $scope.globalSettings.excludedUserAgentsGlobal = sitesManagerApiHelper.commaDelimitedFieldToArray($scope.globalSettings.excludedUserAgentsGlobal); + hideLoading(); + initKeepURLFragmentsList(); - initSiteList(); + adminSites.fetchLimitedSitesWithAdminAccess(); + sitesManagerAPI.getSitesIdWithAdminAccess(function (siteIds) { + if (siteIds && siteIds.length) { + $scope.totalNumberOfSites = siteIds.length; + } + }); triggerAddSiteIfRequested(); }); @@ -176,7 +182,7 @@ }; var addSite = function() { - $scope.sites.push({}); + $scope.adminSites.sites.push({}); }; var saveGlobalSettings = function() { @@ -220,18 +226,6 @@ return sitesInEditMode[0]; }; - var initSiteList = function () { - - sitesManagerAPI.getSitesWithAdminAccess(function (sites) { - - angular.forEach(sites, function(site) { - $scope.sites.push(site); - }); - - hideLoading(); - }); - }; - var initCurrencyList = function () { sitesManagerAPI.getCurrencyList(function (currencies) { diff --git a/plugins/SitesManager/stylesheets/SitesManager.less b/plugins/SitesManager/stylesheets/SitesManager.less index d847d0e8a7727a458c287cc271570f1cec5d0156..f710c3e9dc68af9eab1aabb3f02ee226449cceff 100644 --- a/plugins/SitesManager/stylesheets/SitesManager.less +++ b/plugins/SitesManager/stylesheets/SitesManager.less @@ -15,6 +15,60 @@ text-align: justify; } +.SitesManager { + .bottomButtonBar { + margin-top: 5px + } + + .visible { + visibility: visible; + } + .hidden { + visibility: hidden; + } + + .sitesButtonBar { + width:100%; + text-align: center; + } + + .addSite { + float: left; + display: inline-block; + + .addRowSite { + font-size: 14px; + } + } + + .paging { + text-align: center; + display: inline-block; + min-width: 400px; + margin-top: 7px; + + .counter { + margin-left: 10px; + margin-right: 10px; + } + + } + + .search { + display: inline-block; + text-align: right; + float: right; + } +} + +.sitesManagerList { + clear:both; + + .isLoading { + opacity: 0.5; + } +} + .addRowSite { text-decoration: none; } diff --git a/plugins/SitesManager/templates/index.html b/plugins/SitesManager/templates/index.html index 9ecd346dff45f1869eab5185e874c76fb03977e8..1c5b9c4087f32d37395b7ed0b83a94589047039e 100644 --- a/plugins/SitesManager/templates/index.html +++ b/plugins/SitesManager/templates/index.html @@ -1,11 +1,15 @@ -<div ng-controller="SitesManagerController"> +<div ng-controller="SitesManagerController" class="SitesManager"> - <div ng-include="'plugins/SitesManager/templates/sites-manager-header.html'"></div> + <div ng-include="'plugins/SitesManager/templates/sites-manager-header.html?cb=' + cacheBuster"></div> - <div ng-include="'plugins/SitesManager/templates/loading.html'"></div> + <div ng-include="'plugins/SitesManager/templates/loading.html?cb=' + cacheBuster"></div> - <div ng-include="'plugins/SitesManager/templates/sites-list/sites-list.html'"></div> + <div ng-include="'plugins/SitesManager/templates/sites-list/add-site-link.html?cb=' + cacheBuster"></div> - <div ng-include="'plugins/SitesManager/templates/global-settings.html'"></div> + <div ng-include="'plugins/SitesManager/templates/sites-list/sites-list.html?cb=' + cacheBuster"></div> + <div class="bottomButtonBar" ng-include="'plugins/SitesManager/templates/sites-list/add-site-link.html?cb=' + cacheBuster"></div> + + <div ng-include="'plugins/SitesManager/templates/global-settings.html?cb=' + cacheBuster"></div> + </div> diff --git a/plugins/SitesManager/templates/index.twig b/plugins/SitesManager/templates/index.twig index 40cf84c0f2aea988726a79da3f96e5c284d6538e..1d4994fd5fb0bc5288755ad1e34d263da48ca098 100644 --- a/plugins/SitesManager/templates/index.twig +++ b/plugins/SitesManager/templates/index.twig @@ -2,6 +2,6 @@ {% block content %} - <div ng-include="'plugins/SitesManager/templates/index.html'"></div> + <div ng-include="'plugins/SitesManager/templates/index.html?cb={{ cacheBuster }}'"></div> {% endblock %} diff --git a/plugins/SitesManager/templates/loading.html b/plugins/SitesManager/templates/loading.html index 6aaa13d4367942ca91947adc2b38f959b7e05817..55195fd2bf8263c0377d45e633f9905dbb8e0d4a 100644 --- a/plugins/SitesManager/templates/loading.html +++ b/plugins/SitesManager/templates/loading.html @@ -1,4 +1,4 @@ -<div ng-show="loading"> +<div ng-class="{'hidden': !loading && !adminSites.isLoading}"> <div class="loadingPiwik"> <img src="plugins/Morpheus/images/loading-blue.gif" alt="{{ 'General_LoadingData'|translate }}" /> {{ 'General_LoadingData'|translate }} diff --git a/plugins/SitesManager/templates/sites-list/add-site-link.html b/plugins/SitesManager/templates/sites-list/add-site-link.html index 58a5effd5936b753cc0d660f0f5d456bd3ebed6c..225da15d5d0dbbb4beb22f4fa79a33ad2f55b54f 100644 --- a/plugins/SitesManager/templates/sites-list/add-site-link.html +++ b/plugins/SitesManager/templates/sites-list/add-site-link.html @@ -1,3 +1,32 @@ -<a ng-show="hasSuperUserAccess && !siteIsBeingEdited" class="addRowSite" ng-click="addSite()" tabindex="1"> - {{ 'SitesManager_AddSite'|translate }} -</a> +<div ng-show="!siteIsBeingEdited" class="sitesButtonBar"> + <div class="addSite"> + <a ng-show="hasSuperUserAccess" class="addRowSite" ng-click="addSite()" tabindex="1"> + {{ 'SitesManager_AddSite'|translate }} + </a> + </div> + <div class="paging"> + <a class="submit prev" + ng-class="{'visible': adminSites.hasPrev, 'hidden': !adminSites.hasPrev}" + ng-click="adminSites.previousPage()"> + <span style="cursor:pointer;">« {{ 'General_Previous'|translate }}</span> + </a> + <span class="counter" ng-show="adminSites.hasPrev || adminSites.hasNext"> + <span ng-if="adminSites.searchTerm"> + {{ 'General_PaginationWithoutTotal'|translate:adminSites.offsetStart:adminSites.offsetEnd }} + </span> + <span ng-if="!adminSites.searchTerm"> + {{ 'General_Pagination'|translate:adminSites.offsetStart:adminSites.offsetEnd:totalNumberOfSites }} + </span> + </span> + <a class="submit next" + ng-class="{'visible': adminSites.hasNext, 'hidden': !adminSites.hasNext}" + ng-click="adminSites.nextPage()"> + <span style="cursor:pointer;" class="pointer">{{ 'General_Next'|translate }} »</span> + </a> + </div> + <div class="search"> + <input ng-model="adminSites.search" piwik-onenter="adminSites.searchSite(adminSites.search)" + placeholder="{{ 'Actions_SubmenuSitesearch' | translate }}" type="text"> + <a class="submit" ng-click="adminSites.searchSite(adminSites.search)">{{ 'General_Search'|translate }}</a> + </div> +</div> diff --git a/plugins/SitesManager/templates/sites-list/sites-list.html b/plugins/SitesManager/templates/sites-list/sites-list.html index 94cfa8512b76820b679a87b1b982d96a8cbaa868..cae686dd5f1906c479f124f76298a35ababde579 100644 --- a/plugins/SitesManager/templates/sites-list/sites-list.html +++ b/plugins/SitesManager/templates/sites-list/sites-list.html @@ -1,10 +1,8 @@ -<div class="entityContainer"> +<div class="entityContainer sitesManagerList"> - <div ng-repeat="site in sites" ng-include="'plugins/SitesManager/templates/dialogs/dialogs.html'"></div> + <div ng-repeat="site in adminSites.sites" ng-include="'plugins/SitesManager/templates/dialogs/dialogs.html?cb=' + cacheBuster"></div> - <div ng-include="'plugins/SitesManager/templates/sites-list/add-site-link.html'"></div> - - <table class="entityTable dataTable"> + <table class="entityTable dataTable" ng-class="{'isLoading': adminSites.isLoading==true}"> <thead> <tr> <th>{{ 'General_Id'|translate }}</th> @@ -24,15 +22,17 @@ </tr> </thead> <tbody> - <tr - sites-manager-scroll - ng-controller="SitesManagerSiteController" - ng-repeat="site in sites" - ng-include="'plugins/SitesManager/templates/sites-list/site-fields.html'"> + <tr ng-show="adminSites.searchTerm && 0 === adminSites.sites.length && !adminSites.isLoading"> + <td colspan="12"> + {{ 'SitesManager_NotFound'|translate }} <strong>{{ adminSites.searchTerm }}</strong> + </td> + </tr> + <tr sites-manager-scroll + ng-controller="SitesManagerSiteController" + ng-repeat="site in adminSites.sites" + ng-include="'plugins/SitesManager/templates/sites-list/site-fields.html?cb=' + cacheBuster"> </tr> </tbody> </table> - <div ng-include="'plugins/SitesManager/templates/sites-list/add-site-link.html'"></div> - </div> diff --git a/plugins/SitesManager/templates/sites-manager-header.html b/plugins/SitesManager/templates/sites-manager-header.html index a817dd8511289316651e7d2719849741b6f9da09..59762777fdfad18df159546fe0c419b24e406452 100644 --- a/plugins/SitesManager/templates/sites-manager-header.html +++ b/plugins/SitesManager/templates/sites-manager-header.html @@ -8,7 +8,7 @@ <p> {{ 'SitesManager_MainDescription'|translate }} - <span ng-bind-html="'SitesManager_YouCurrentlyHaveAccessToNWebsites'|translate:'<strong>' + sites.length + '</strong>'"></span> + <span ng-bind-html="'SitesManager_YouCurrentlyHaveAccessToNWebsites'|translate:'<strong>' + totalNumberOfSites + '</strong>'"></span> <span ng-show="hasSuperUserAccess"> <br/> diff --git a/plugins/SitesManager/tests/Fixtures/ManySites.php b/plugins/SitesManager/tests/Fixtures/ManySites.php new file mode 100644 index 0000000000000000000000000000000000000000..fe3d2fd32524a9faeba88f92df2abc1ede8554a5 --- /dev/null +++ b/plugins/SitesManager/tests/Fixtures/ManySites.php @@ -0,0 +1,35 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ +namespace Piwik\Plugins\SitesManager\tests\Fixtures; + +use Piwik\Plugin; +use Piwik\Tests\Framework\Fixture; + +/** + * Track actions with bandwidth + */ +class ManySites extends Fixture +{ + public $dateTime = '2010-01-03 11:22:33'; + + public function setUp() + { + for ($idSite = 1; $idSite < 64; $idSite++) { + if (!self::siteCreated($idSite)) { + if ($idSite < 35) { + $siteName = 'SiteTest' . $idSite; // we generate different site names to be used in search + } else { + $siteName = 'Site ' . $idSite; + } + + self::createWebsite($this->dateTime, $ecommerce = 0, $siteName); + } + } + } + +} \ No newline at end of file diff --git a/plugins/SitesManager/tests/Integration/ApiTest.php b/plugins/SitesManager/tests/Integration/ApiTest.php index d4a4b6c87b8969f32486e0b0cb9c510ca5e2df9d..6d5bd6373f2c531232d25f69d3f5930a411374ac 100644 --- a/plugins/SitesManager/tests/Integration/ApiTest.php +++ b/plugins/SitesManager/tests/Integration/ApiTest.php @@ -23,6 +23,8 @@ use PHPUnit_Framework_Constraint_IsType; * Class Plugins_SitesManagerTest * * @group Plugins + * @group ApiTest + * @group SitesManager */ class ApiTest extends IntegrationTestCase { @@ -38,8 +40,6 @@ class ApiTest extends IntegrationTestCase /** * empty name -> exception - * - * @group Plugins */ public function testAddSiteEmptyName() { @@ -69,7 +69,6 @@ class ApiTest extends IntegrationTestCase * wrong urls -> exception * * @dataProvider getInvalidUrlData - * @group Plugins */ public function testAddSiteWrongUrls($url) { @@ -83,8 +82,6 @@ class ApiTest extends IntegrationTestCase /** * Test with valid IPs - * - * @group Plugins */ public function testAddSiteExcludedIpsAndtimezoneAndCurrencyAndExcludedQueryParametersValid() { @@ -138,7 +135,6 @@ class ApiTest extends IntegrationTestCase * Test with invalid IPs * * @dataProvider getInvalidIPsData - * @group Plugins */ public function testAddSiteExcludedIpsNotValid($ip) { @@ -153,8 +149,6 @@ class ApiTest extends IntegrationTestCase /** * one url -> one main_url and nothing inserted as alias urls - * - * @group Plugins */ public function testAddSiteOneUrl() { @@ -173,8 +167,6 @@ class ApiTest extends IntegrationTestCase /** * several urls -> one main_url and others as alias urls - * - * @group Plugins */ public function testAddSiteSeveralUrls() { @@ -192,8 +184,6 @@ class ApiTest extends IntegrationTestCase /** * strange name - * - * @group Plugins */ public function testAddSiteStrangeName() { @@ -228,8 +218,6 @@ class ApiTest extends IntegrationTestCase /** * no duplicate -> all the urls are saved - * - * @group Plugins */ public function testAddSiteUrlsnoDuplicate() { @@ -268,8 +256,6 @@ class ApiTest extends IntegrationTestCase /** * duplicate -> don't save the already existing URLs - * - * @group Plugins */ public function testAddSiteUrlsDuplicate() { @@ -294,8 +280,6 @@ class ApiTest extends IntegrationTestCase /** * case empty array => nothing happens - * - * @group Plugins */ public function testAddSiteUrlsNoUrlsToAdd1() { @@ -320,8 +304,6 @@ class ApiTest extends IntegrationTestCase /** * case array only duplicate => nothing happens - * - * @group Plugins */ public function testAddSiteUrlsNoUrlsToAdd2() { @@ -346,8 +328,6 @@ class ApiTest extends IntegrationTestCase /** * wrong format urls => exception - * - * @group Plugins */ public function testAddSiteUrlsWrongUrlsFormat3() { @@ -363,8 +343,6 @@ class ApiTest extends IntegrationTestCase /** * wrong idsite => no exception because simply no access to this resource - * - * @group Plugins */ public function testAddSiteUrlsWrongIdSite1() { @@ -379,8 +357,6 @@ class ApiTest extends IntegrationTestCase /** * wrong idsite => exception - * - * @group Plugins */ public function testAddSiteUrlsWrongIdSite2() { @@ -395,8 +371,6 @@ class ApiTest extends IntegrationTestCase /** * no Id -> empty array - * - * @group Plugins */ public function testGetAllSitesIdNoId() { @@ -406,8 +380,6 @@ class ApiTest extends IntegrationTestCase /** * several Id -> normal array - * - * @group Plugins */ public function testGetAllSitesIdSeveralId() { @@ -426,8 +398,6 @@ class ApiTest extends IntegrationTestCase /** * wrong id => exception - * - * @group Plugins */ public function testGetSiteFromIdWrongId1() { @@ -441,8 +411,6 @@ class ApiTest extends IntegrationTestCase /** * wrong id => exception - * - * @group Plugins */ public function testGetSiteFromIdWrongId2() { @@ -456,8 +424,6 @@ class ApiTest extends IntegrationTestCase /** * wrong id : no access => exception - * - * @group Plugins */ public function testGetSiteFromIdWrongId3() { @@ -478,8 +444,6 @@ class ApiTest extends IntegrationTestCase /** * normal case - * - * @group Plugins */ public function testGetSiteFromIdNormalId() { @@ -494,8 +458,6 @@ class ApiTest extends IntegrationTestCase /** * there is no admin site available -> array() - * - * @group Plugins */ public function testGetSitesWithAdminAccessNoResult() { @@ -507,10 +469,8 @@ class ApiTest extends IntegrationTestCase /** * normal case, admin and view and noaccess website => return only admin - * - * @group Plugins */ - public function testGetSitesWithAdminAccess() + public function testGetSitesWithAdminAccess_shouldOnlyReturnSitesHavingActuallyAdminAccess() { API::getInstance()->addSite("site1", array("http://piwik.net", "http://piwik.com/test/")); API::getInstance()->addSite("site2", array("http://piwik.com/test/")); @@ -531,10 +491,81 @@ class ApiTest extends IntegrationTestCase $this->assertEquals($resultWanted, $sites); } + public function testGetSitesWithAdminAccess_shouldApplyLimit_IfSet() + { + $this->createManySitesWithAdminAccess(40); + + // should return all sites by default + $sites = API::getInstance()->getSitesWithAdminAccess(); + $this->assertReturnedSitesContainsSiteIds(range(1, 40), $sites); + + // return only 5 sites + $sites = API::getInstance()->getSitesWithAdminAccess(false, false, 5); + $this->assertReturnedSitesContainsSiteIds(array(1, 2, 3, 4, 5), $sites); + + // return only 10 sites + $sites = API::getInstance()->getSitesWithAdminAccess(false, false, 10); + $this->assertReturnedSitesContainsSiteIds(range(1, 10), $sites); + } + + public function testGetSitesWithAdminAccess_shouldApplyPattern_IfSetAndFindBySiteName() + { + $this->createManySitesWithAdminAccess(40); + + // by site name + $sites = API::getInstance()->getSitesWithAdminAccess(false, 'site38'); + $this->assertReturnedSitesContainsSiteIds(array(38), $sites); + } + + public function testGetSitesWithAdminAccess_shouldApplyPattern_IfSetAndFindByUrl() + { + $this->createManySitesWithAdminAccess(40); + + $sites = API::getInstance()->getSitesWithAdminAccess(false, 'piwik38.o'); + $this->assertReturnedSitesContainsSiteIds(array(38), $sites); + } + + public function testGetSitesWithAdminAccess_shouldApplyPattern_AndFindMany() + { + $this->createManySitesWithAdminAccess(40); + + $sites = API::getInstance()->getSitesWithAdminAccess(false, '5'); + $this->assertReturnedSitesContainsSiteIds(array(5, 15, 25, 35), $sites); + } + + public function testGetSitesWithAdminAccess_shouldApplyPatternAndLimit() + { + $this->createManySitesWithAdminAccess(40); + + $sites = API::getInstance()->getSitesWithAdminAccess(false, '5', 2); + $this->assertReturnedSitesContainsSiteIds(array(5, 15), $sites); + } + + private function createManySitesWithAdminAccess($numSites) + { + for ($i = 1; $i <= $numSites; $i++) { + API::getInstance()->addSite("site" . $i, array("http://piwik$i.org")); + } + + FakeAccess::setIdSitesAdmin(range(1, $numSites)); + } + + private function assertReturnedSitesContainsSiteIds($expectedSiteIds, $sites) + { + $this->assertCount(count($expectedSiteIds), $sites); + + foreach ($sites as $site) { + $key = array_search($site['idsite'], $expectedSiteIds); + $this->assertNotFalse($key, 'Did not find expected siteId "' . $site['idsite'] . '" in the expected siteIds'); + unset($expectedSiteIds[$key]); + } + + $siteIds = var_export($expectedSiteIds, 1); + $this->assertEmpty($expectedSiteIds, 'Not all expected sites were found, remaining site ids: ' . $siteIds); + } + /** * there is no admin site available -> array() - * - * @group Plugins */ public function testGetSitesWithViewAccessNoResult() { @@ -547,8 +578,6 @@ class ApiTest extends IntegrationTestCase /** * normal case, admin and view and noaccess website => return only admin - * - * @group Plugins */ public function testGetSitesWithViewAccess() { @@ -573,8 +602,6 @@ class ApiTest extends IntegrationTestCase /** * there is no admin site available -> array() - * - * @group Plugins */ public function testGetSitesWithAtLeastViewAccessNoResult() { @@ -587,8 +614,6 @@ class ApiTest extends IntegrationTestCase /** * normal case, admin and view and noaccess website => return only admin - * - * @group Plugins */ public function testGetSitesWithAtLeastViewAccess() { @@ -613,8 +638,6 @@ class ApiTest extends IntegrationTestCase /** * no urls for this site => array() - * - * @group Plugins */ public function testGetSiteUrlsFromIdNoUrls() { @@ -626,8 +649,6 @@ class ApiTest extends IntegrationTestCase /** * normal case - * - * @group Plugins */ public function testGetSiteUrlsFromIdManyUrls() { @@ -650,8 +671,6 @@ class ApiTest extends IntegrationTestCase /** * wrongId => exception - * - * @group Plugins */ public function testGetSiteUrlsFromIdWrongId() { @@ -667,8 +686,6 @@ class ApiTest extends IntegrationTestCase /** * one url => no change to alias urls - * - * @group Plugins */ public function testUpdateSiteOneUrl() { @@ -717,8 +734,6 @@ class ApiTest extends IntegrationTestCase /** * strange name and NO URL => name ok, main_url not updated - * - * @group Plugins */ public function testUpdateSiteStrangeNameNoUrl() { @@ -737,8 +752,6 @@ class ApiTest extends IntegrationTestCase /** * several urls => both main and alias are updated * also test the update of group field - * - * @group Plugins */ public function testUpdateSiteSeveralUrlsAndGroup() { @@ -861,9 +874,6 @@ class ApiTest extends IntegrationTestCase $this->assertEmpty($siteInfo); } - /** - * @group Plugins - */ public function testGetSitesGroups() { $groups = array('group1', ' group1 ', '', 'group2'); @@ -886,7 +896,6 @@ class ApiTest extends IntegrationTestCase /** * * @dataProvider getInvalidTimezoneData - * @group Plugins */ public function testAddSitesInvalidTimezone($timezone) { @@ -899,9 +908,6 @@ class ApiTest extends IntegrationTestCase $this->fail('Expected exception not raised'); } - /** - * @group Plugins - */ public function testAddSitesInvalidCurrency() { try { @@ -914,9 +920,6 @@ class ApiTest extends IntegrationTestCase $this->fail('Expected exception not raised'); } - /** - * @group Plugins - */ public function testSetDefaultTimezoneAndCurrencyAndExcludedQueryParametersAndExcludedIps() { // test that they return default values @@ -983,9 +986,6 @@ class ApiTest extends IntegrationTestCase $this->assertFalse(Site::isEcommerceEnabledFor($idsite)); } - /** - * @group Plugins - */ public function testGetSitesIdFromSiteUrlSuperUser() { API::getInstance()->addSite("site1", array("http://piwik.net", "http://piwik.com")); @@ -1005,9 +1005,6 @@ class ApiTest extends IntegrationTestCase $this->assertTrue(count($idsites) == 3); } - /** - * @group Plugins - */ public function testGetSitesIdFromSiteUrlUser() { API::getInstance()->addSite("site1", array("http://www.piwik.net", "http://piwik.com")); @@ -1063,9 +1060,6 @@ class ApiTest extends IntegrationTestCase Access::setSingletonInstance($saveAccess); } - /** - * @group Plugins - */ public function testGetSitesFromTimezones() { API::getInstance()->addSite("site3", array("http://piwik.org"), null, $siteSearch = 1, $searchKeywordParameters = null, $searchCategoryParameters = null, null, null, 'UTC'); diff --git a/plugins/Transitions/stylesheets/transitions.less b/plugins/Transitions/stylesheets/transitions.less index 7610e928d756bc55098239a59f24a292c3d68b5d..8e293d91e1dfab8fcdb4bafc7fa51f6d4b2fc3f3 100644 --- a/plugins/Transitions/stylesheets/transitions.less +++ b/plugins/Transitions/stylesheets/transitions.less @@ -65,6 +65,7 @@ font-weight: bold; overflow: hidden; color: #255792; + margin: 0; } .Transitions_Pageviews { diff --git a/plugins/UserCountry/Diagnostic/GeolocationDiagnostic.php b/plugins/UserCountry/Diagnostic/GeolocationDiagnostic.php new file mode 100644 index 0000000000000000000000000000000000000000..720d24e2798ae91194e8c20f3e094c9e1e8eba6b --- /dev/null +++ b/plugins/UserCountry/Diagnostic/GeolocationDiagnostic.php @@ -0,0 +1,59 @@ +<?php + +namespace Piwik\Plugins\UserCountry\Diagnostic; + +use Piwik\Config; +use Piwik\Plugins\Diagnostics\Diagnostic\Diagnostic; +use Piwik\Plugins\Diagnostics\Diagnostic\DiagnosticResult; +use Piwik\Plugins\UserCountry\LocationProvider; +use Piwik\Translation\Translator; + +/** + * Check the geolocation setup. + */ +class GeolocationDiagnostic implements Diagnostic +{ + /** + * @var Translator + */ + private $translator; + + public function __construct(Translator $translator) + { + $this->translator = $translator; + } + + public function execute() + { + $isPiwikInstalling = !Config::getInstance()->existsLocalConfig(); + if ($isPiwikInstalling) { + // Skip the diagnostic if Piwik is being installed + return array(); + } + + $label = $this->translator->translate('UserCountry_Geolocation'); + + $currentProviderId = LocationProvider::getCurrentProviderId(); + $allProviders = LocationProvider::getAllProviderInfo(); + $isRecommendedProvider = in_array($currentProviderId, array(LocationProvider\GeoIp\Php::ID, $currentProviderId == LocationProvider\GeoIp\Pecl::ID)); + $isProviderInstalled = ($allProviders[$currentProviderId]['status'] == LocationProvider::INSTALLED); + + if ($isRecommendedProvider && $isProviderInstalled) { + return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_OK)); + } + + if ($isProviderInstalled) { + $comment = $this->translator->translate('UserCountry_GeoIpLocationProviderNotRecomnended') . ' '; + $comment .= $this->translator->translate('UserCountry_GeoIpLocationProviderDesc_ServerBased2', array( + '<a href="http://piwik.org/docs/geo-locate/" rel="noreferrer" target="_blank">', '', '', '</a>' + )); + } else { + $comment = $this->translator->translate('UserCountry_DefaultLocationProviderDesc1') . ' '; + $comment .= $this->translator->translate('UserCountry_DefaultLocationProviderDesc2', array( + '<a href="http://piwik.org/docs/geo-locate/" rel="noreferrer" target="_blank">', '', '', '</a>' + )); + } + + return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_WARNING, $comment)); + } +} diff --git a/plugins/UserCountry/config/config.php b/plugins/UserCountry/config/config.php new file mode 100644 index 0000000000000000000000000000000000000000..dd725049207865144424d3ee827a1b633b12f5bb --- /dev/null +++ b/plugins/UserCountry/config/config.php @@ -0,0 +1,7 @@ +<?php + +return array( + 'diagnostics.optional' => DI\add(array( + DI\get('Piwik\Plugins\UserCountry\Diagnostic\GeolocationDiagnostic'), + )), +); diff --git a/plugins/UserCountry/lang/en.json b/plugins/UserCountry/lang/en.json index 90b2ca7e7c835c99acf4a90fc5687a1c8e5f813d..0eb36c6775ce46c0fd6f86a2ea833b9fd95093b6 100644 --- a/plugins/UserCountry/lang/en.json +++ b/plugins/UserCountry/lang/en.json @@ -268,7 +268,7 @@ "country_tr": "Turkey", "country_tt": "Trinidad and Tobago", "country_tv": "Tuvalu", - "country_tw": "Taiwan, Province of China", + "country_tw": "Taiwan", "country_tz": "Tanzania, United Republic of", "country_ua": "Ukraine", "country_ug": "Uganda", @@ -371,4 +371,4 @@ "UpdaterWillRunNext": "It is next scheduled to run on %s.", "WidgetLocation": "Visitor Location" } -} \ No newline at end of file +} diff --git a/plugins/UserCountry/templates/index.twig b/plugins/UserCountry/templates/index.twig index 72d49613eb689aed42e01c3d5f843a0f428c4298..70ac5f8ba72d72110ee1aefe2d0d7218111eaf7d 100644 --- a/plugins/UserCountry/templates/index.twig +++ b/plugins/UserCountry/templates/index.twig @@ -1,26 +1,28 @@ -<div id="leftcolumn"> - {{ postEvent("Template.leftColumnUserCountry") }} +<div class="row"> - <h2 piwik-enriched-headline>{{ 'UserCountry_Continent'|translate }}</h2> - {{ dataTableContinent|raw }} + <div class="col-md-6"> + {{ postEvent("Template.leftColumnUserCountry") }} - <div class="sparkline"> - {{ sparkline(urlSparklineCountries) }} - {{ 'UserCountry_DistinctCountries'|translate("<strong>"~numberDistinctCountries~"</strong>")|raw }} - </div> + <h2 piwik-enriched-headline>{{ 'UserCountry_Continent'|translate }}</h2> + {{ dataTableContinent|raw }} - {{ postEvent("Template.footerUserCountry") }} + <div class="sparkline"> + {{ sparkline(urlSparklineCountries) }} + {{ 'UserCountry_DistinctCountries'|translate("<strong>"~numberDistinctCountries~"</strong>")|raw }} + </div> -</div> + {{ postEvent("Template.footerUserCountry") }} + </div> -<div id="rightcolumn"> - <h2 piwik-enriched-headline>{{ 'UserCountry_Country'|translate }}</h2> - {{ dataTableCountry|raw }} + <div class="col-md-6"> + <h2 piwik-enriched-headline>{{ 'UserCountry_Country'|translate }}</h2> + {{ dataTableCountry|raw }} - <h2 piwik-enriched-headline>{{ 'UserCountry_Region'|translate }}</h2> - {{ dataTableRegion|raw }} + <h2 piwik-enriched-headline>{{ 'UserCountry_Region'|translate }}</h2> + {{ dataTableRegion|raw }} - <h2 piwik-enriched-headline>{{ 'UserCountry_City'|translate }}</h2> - {{ dataTableCity|raw }} -</div> + <h2 piwik-enriched-headline>{{ 'UserCountry_City'|translate }}</h2> + {{ dataTableCity|raw }} + </div> +</div> diff --git a/plugins/UserCountryMap/javascripts/realtime-map.js b/plugins/UserCountryMap/javascripts/realtime-map.js index 5fe57e179057fa1fcc2ebfa382dc08bc2741613f..0881a198f2a0f23fd0bc428bf08ce748a89dbc2c 100644 --- a/plugins/UserCountryMap/javascripts/realtime-map.js +++ b/plugins/UserCountryMap/javascripts/realtime-map.js @@ -359,7 +359,7 @@ */ function gotNewReport(report) { // if the map has been destroyed, do nothing - if (!self.map) { + if (!self.map || !self.$element.length || !$.contains(document, self.$element[0])) { return; } diff --git a/plugins/UsersManager/API.php b/plugins/UsersManager/API.php index ccb4dac126e6c6b4ad54a00f91ec5869ab7d3b14..752f452924689ad0222ae9aa827decff43d173b3 100644 --- a/plugins/UsersManager/API.php +++ b/plugins/UsersManager/API.php @@ -101,7 +101,7 @@ class API extends \Piwik\Plugin\API { Piwik::checkUserHasSuperUserAccessOrIsTheUser($userLogin); - $optionValue = Option::get($this->getPreferenceId($userLogin, $preferenceName)); + $optionValue = $this->getPreferenceValue($userLogin, $preferenceName); if ($optionValue !== false) { return $optionValue; @@ -110,6 +110,25 @@ class API extends \Piwik\Plugin\API return $this->getDefaultUserPreference($preferenceName, $userLogin); } + /** + * Sets a user preference in the DB using the preference's default value. + * @param string $userLogin + * @param string $preferenceName + * @ignore + */ + public function initUserPreferenceWithDefault($userLogin, $preferenceName) + { + Piwik::checkUserHasSuperUserAccessOrIsTheUser($userLogin); + + $optionValue = $this->getPreferenceValue($userLogin, $preferenceName); + + if ($optionValue === false) { + $defaultValue = $this->getDefaultUserPreference($preferenceName, $userLogin); + + $this->setUserPreference($userLogin, $preferenceName, $defaultValue); + } + } + /** * Returns an array of Preferences * @param $preferenceNames array of preference names @@ -143,6 +162,11 @@ class API extends \Piwik\Plugin\API return $login . self::OPTION_NAME_PREFERENCE_SEPARATOR . $preference; } + private function getPreferenceValue($userLogin, $preferenceName) + { + return Option::get($this->getPreferenceId($userLogin, $preferenceName)); + } + private function getDefaultUserPreference($preferenceName, $login) { switch ($preferenceName) { diff --git a/plugins/UsersManager/Tasks.php b/plugins/UsersManager/Tasks.php new file mode 100644 index 0000000000000000000000000000000000000000..668ce559c992c6c5d9f04552c5902cb6b047a2e2 --- /dev/null +++ b/plugins/UsersManager/Tasks.php @@ -0,0 +1,49 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ +namespace Piwik\Plugins\UsersManager; + +use Piwik\Access; + +class Tasks extends \Piwik\Plugin\Tasks +{ + /** + * @var Model + */ + private $usersModel; + + /** + * @param API + */ + private $usersManagerApi; + + public function __construct(Model $usersModel, API $usersManagerApi) + { + $this->usersModel = $usersModel; + $this->usersManagerApi = $usersManagerApi; + } + + public function schedule() + { + $this->daily("setUserDefaultReportPreference"); + } + + public function setUserDefaultReportPreference() + { + // We initialize the default report user preference for each user (if it hasn't been inited before) for performance, + // doing this lets us avoid loading all siteIds (which can be 50k or more) when this preference is requested. + // getting the user preference can be called quite often when generating links etc (to get defaultWebsiteId). + $usersModel = $this->usersModel; + $usersManagerApi = $this->usersManagerApi; + Access::getInstance()->doAsSuperUser(function () use ($usersModel, $usersManagerApi) { + $allUsers = $usersModel->getUsers(array()); + foreach ($allUsers as $user) { + $usersManagerApi->initUserPreferenceWithDefault($user['login'], API::PREFERENCE_DEFAULT_REPORT); + } + }); + } +} diff --git a/plugins/UsersManager/tests/Integration/APITest.php b/plugins/UsersManager/tests/Integration/APITest.php index d743ca01a04756382764c11fb82b5c90bb2e6c95..f4e4c41f5dbc5c330b5167094ed8a5764354f9aa 100644 --- a/plugins/UsersManager/tests/Integration/APITest.php +++ b/plugins/UsersManager/tests/Integration/APITest.php @@ -8,6 +8,7 @@ namespace Piwik\Plugins\UsersManager\tests; use Piwik\Access; +use Piwik\Option; use Piwik\Piwik; use Piwik\Plugins\UsersManager\API; use Piwik\Tests\Framework\Fixture; @@ -25,6 +26,8 @@ class APITest extends IntegrationTestCase * @var API */ private $api; + + private $login = 'userLogin'; public function setUp() { @@ -39,7 +42,7 @@ class APITest extends IntegrationTestCase Fixture::createWebsite('2014-01-01 00:00:00'); Fixture::createWebsite('2014-01-01 00:00:00'); Fixture::createWebsite('2014-01-01 00:00:00'); - $this->api->addUser('userLogin', 'password', 'userlogin@password.de'); + $this->api->addUser($this->login, 'password', 'userlogin@password.de'); } public function test_setUserAccess_ShouldTriggerRemoveSiteAccessEvent_IfAccessToAWebsiteIsRemoved() @@ -48,11 +51,11 @@ class APITest extends IntegrationTestCase $self = $this; Piwik::addAction('UsersManager.removeSiteAccess', function ($login, $idSites) use (&$eventTriggered, $self) { $eventTriggered = true; - $self->assertEquals('userLogin', $login); + $self->assertEquals($this->login, $login); $self->assertEquals(array(1, 2), $idSites); }); - $this->api->setUserAccess('userLogin', 'noaccess', array(1, 2)); + $this->api->setUserAccess($this->login, 'noaccess', array(1, 2)); $this->assertTrue($eventTriggered, 'UsersManager.removeSiteAccess event was not triggered'); } @@ -64,7 +67,7 @@ class APITest extends IntegrationTestCase $eventTriggered = true; }); - $this->api->setUserAccess('userLogin', 'admin', array(1, 2)); + $this->api->setUserAccess($this->login, 'admin', array(1, 2)); $this->assertFalse($eventTriggered, 'UsersManager.removeSiteAccess event was triggered but should not'); } @@ -81,6 +84,48 @@ class APITest extends IntegrationTestCase $this->assertEmpty($preferences); } + public function test_getUserPreference_ShouldReturnADefaultPreference_IfNoneIsSet() + { + $siteId = $this->api->getUserPreference($this->login, API::PREFERENCE_DEFAULT_REPORT); + $this->assertEquals('1', $siteId); + } + + public function test_getUserPreference_ShouldReturnASetreference_IfNoneIsSet() + { + $this->api->setUserPreference($this->login, API::PREFERENCE_DEFAULT_REPORT, 5); + + $siteId = $this->api->getUserPreference($this->login, API::PREFERENCE_DEFAULT_REPORT); + $this->assertEquals('5', $siteId); + } + + public function test_initUserPreferenceWithDefault_ShouldSaveTheDefaultPreference_IfPreferenceIsNotSet() + { + // make sure there is no value saved so it will use default preference + $siteId = Option::get($this->getPreferenceId(API::PREFERENCE_DEFAULT_REPORT)); + $this->assertFalse($siteId); + + $this->api->initUserPreferenceWithDefault($this->login, API::PREFERENCE_DEFAULT_REPORT); + + // make sure it did save the preference + $siteId = Option::get($this->getPreferenceId(API::PREFERENCE_DEFAULT_REPORT)); + $this->assertEquals('1', $siteId); + } + + public function test_initUserPreferenceWithDefault_ShouldNotSaveTheDefaultPreference_IfPreferenceIsAlreadySet() + { + // set value so there will already be a default + Option::set($this->getPreferenceId(API::PREFERENCE_DEFAULT_REPORT), '999'); + + $siteId = Option::get($this->getPreferenceId(API::PREFERENCE_DEFAULT_REPORT)); + $this->assertEquals('999', $siteId); + + $this->api->initUserPreferenceWithDefault($this->login, API::PREFERENCE_DEFAULT_REPORT); + + // make sure it did not save the preference + $siteId = Option::get($this->getPreferenceId(API::PREFERENCE_DEFAULT_REPORT)); + $this->assertEquals('999', $siteId); + } + public function test_getAllUsersPreferences_shouldGetMultiplePreferences() { $user2 = 'userLogin2'; @@ -164,4 +209,9 @@ class APITest extends IntegrationTestCase ); $this->assertEquals($expected, $access); } + + private function getPreferenceId($preferenceName) + { + return $this->login . '_' . $preferenceName; + } } diff --git a/plugins/VisitFrequency/templates/_sparklines.twig b/plugins/VisitFrequency/templates/_sparklines.twig index 97697272e84385f46553fc4a6b27fbd768583a0b..e5639e2e367714aafb41a0d2e6ef29d8f1d198d8 100644 --- a/plugins/VisitFrequency/templates/_sparklines.twig +++ b/plugins/VisitFrequency/templates/_sparklines.twig @@ -1,26 +1,29 @@ -<div id="leftcolumn"> - <div class="sparkline"> - {{ sparkline(urlSparklineNbVisitsReturning) }} - {{ 'VisitFrequency_ReturnVisits'|translate("<strong>"~nbVisitsReturning~"</strong>")|raw }} +<div class="row"> + + <div class="col-md-6"> + <div class="sparkline"> + {{ sparkline(urlSparklineNbVisitsReturning) }} + {{ 'VisitFrequency_ReturnVisits'|translate("<strong>"~nbVisitsReturning~"</strong>")|raw }} + </div> + <div class="sparkline"> + {{ sparkline(urlSparklineNbActionsReturning) }} + {{ 'VisitFrequency_ReturnActions'|translate("<strong>"~nbActionsReturning~"</strong>")|raw }} + </div> + <div class="sparkline"> + {{ sparkline(urlSparklineActionsPerVisitReturning) }} + {{ 'VisitFrequency_ReturnAvgActions'|translate("<strong>"~nbActionsPerVisitReturning~"</strong>")|raw }} + </div> </div> - <div class="sparkline"> - {{ sparkline(urlSparklineNbActionsReturning) }} - {{ 'VisitFrequency_ReturnActions'|translate("<strong>"~nbActionsReturning~"</strong>")|raw }} + <div class="col-md-6"> + <div class="sparkline"> + {{ sparkline(urlSparklineAvgVisitDurationReturning) }} + {% set avgVisitDurationReturning=avgVisitDurationReturning|sumtime %} + {{ 'VisitFrequency_ReturnAverageVisitDuration'|translate("<strong>"~avgVisitDurationReturning~"</strong>")|raw }} + </div> + <div class="sparkline"> + {{ sparkline(urlSparklineBounceRateReturning) }} + {{ 'VisitFrequency_ReturnBounceRate'|translate("<strong>"~bounceRateReturning~"</strong>")|raw }} + </div> + {% include "_sparklineFooter.twig" %} </div> - <div class="sparkline"> - {{ sparkline(urlSparklineActionsPerVisitReturning) }} - {{ 'VisitFrequency_ReturnAvgActions'|translate("<strong>"~nbActionsPerVisitReturning~"</strong>")|raw }} - </div> -</div><div id="rightcolumn"> - <div class="sparkline"> - {{ sparkline(urlSparklineAvgVisitDurationReturning) }} - {% set avgVisitDurationReturning=avgVisitDurationReturning|sumtime %} - {{ 'VisitFrequency_ReturnAverageVisitDuration'|translate("<strong>"~avgVisitDurationReturning~"</strong>")|raw }} - </div> - <div class="sparkline"> - {{ sparkline(urlSparklineBounceRateReturning) }} - {{ 'VisitFrequency_ReturnBounceRate'|translate("<strong>"~bounceRateReturning~"</strong>")|raw }} - </div> - {% include "_sparklineFooter.twig" %} </div> -<div style="clear:both"></div> \ No newline at end of file diff --git a/plugins/VisitTime/templates/index.twig b/plugins/VisitTime/templates/index.twig index c0c2a85feb036dd24fa149fd7412a7b5294723bd..688cde0338343f218d54061c51cc71f58b7f8808 100644 --- a/plugins/VisitTime/templates/index.twig +++ b/plugins/VisitTime/templates/index.twig @@ -1,9 +1,13 @@ -<div id='leftcolumn'> - <h2 piwik-enriched-headline>{{ 'VisitTime_LocalTime'|translate }}</h2> - {{ dataTableVisitInformationPerLocalTime|raw }} -</div> +<div class="row"> + + <div class="col-md-6"> + <h2 piwik-enriched-headline>{{ 'VisitTime_LocalTime'|translate }}</h2> + {{ dataTableVisitInformationPerLocalTime|raw }} + </div> + + <div class="col-md-6"> + <h2 piwik-enriched-headline>{{ 'VisitTime_ServerTime'|translate }}</h2> + {{ dataTableVisitInformationPerServerTime|raw }} + </div> -<div id='rightcolumn'> - <h2 piwik-enriched-headline>{{ 'VisitTime_ServerTime'|translate }}</h2> - {{ dataTableVisitInformationPerServerTime|raw }} </div> diff --git a/plugins/VisitorInterest/templates/index.twig b/plugins/VisitorInterest/templates/index.twig index af98d8f1ffd3adb519df9c05d4a6bf2adcc89a95..332289e10648fd606b58da5618cce66cdc7193bb 100644 --- a/plugins/VisitorInterest/templates/index.twig +++ b/plugins/VisitorInterest/templates/index.twig @@ -1,20 +1,21 @@ -<br /> -<div id="leftcolumn"> - <h2 piwik-enriched-headline>{{ 'VisitorInterest_VisitsPerDuration'|translate }}</h2> - {{ dataTableNumberOfVisitsPerVisitDuration|raw }} +<div class="row"> + <div class="col-md-6"> + <h2 piwik-enriched-headline>{{ 'VisitorInterest_VisitsPerDuration'|translate }}</h2> + {{ dataTableNumberOfVisitsPerVisitDuration|raw }} + </div> + <div class="col-md-6"> + <h2 piwik-enriched-headline>{{ 'VisitorInterest_VisitsPerNbOfPages'|translate }}</h2> + {{ dataTableNumberOfVisitsPerPage|raw }} + </div> </div> -<div id="rightcolumn"> - <h2 piwik-enriched-headline>{{ 'VisitorInterest_VisitsPerNbOfPages'|translate }}</h2> - {{ dataTableNumberOfVisitsPerPage|raw }} + +<div class="row"> + <div class="col-md-6"> + <h2 piwik-enriched-headline>{{ 'VisitorInterest_visitsByVisitCount'|translate }}</h2> + {{ dataTableNumberOfVisitsByVisitNum|raw }} + </div> + <div class="col-md-6"> + <h2 piwik-enriched-headline>{{ 'VisitorInterest_VisitsByDaysSinceLast'|translate }}</h2> + {{ dataTableNumberOfVisitsByDaysSinceLast|raw }} + </div> </div> -<div style="clear:both"></div> -<br /> -<div id="leftcolumn"> - <h2 piwik-enriched-headline>{{ 'VisitorInterest_visitsByVisitCount'|translate }}</h2> - {{ dataTableNumberOfVisitsByVisitNum|raw }} -</div> -<div id="rightcolumn"> - <h2 piwik-enriched-headline>{{ 'VisitorInterest_VisitsByDaysSinceLast'|translate }}</h2> - {{ dataTableNumberOfVisitsByDaysSinceLast|raw }} -</div> -<div style="clear:both"></div> \ No newline at end of file diff --git a/plugins/VisitsSummary/templates/_sparklines.twig b/plugins/VisitsSummary/templates/_sparklines.twig index a61a31d2300e2db73f24c9e4b68182d2bebb6aff..dbef25aba55b803276c79ee533c7fe0f2ad0cee3 100644 --- a/plugins/VisitsSummary/templates/_sparklines.twig +++ b/plugins/VisitsSummary/templates/_sparklines.twig @@ -1,78 +1,81 @@ -<div id='leftcolumn'> - <div class="sparkline"> - {{ sparkline(urlSparklineNbVisits)|raw }} - {{ 'General_NVisits'|translate("<strong>"~nbVisits~"</strong>")|raw }}{% if displayUniqueVisitors %}, - {{ 'VisitsSummary_NbUniqueVisitors'|translate("<strong>"~nbUniqVisitors~"</strong>")|raw }}{% endif %} - </div> - {% if nbUsers > 0 %} - {# Most of users will not have used `setUserId` so this would be confusingly zero #} +<div class="row"> + + <div class="col-md-6"> <div class="sparkline"> - {{ sparkline(urlSparklineNbUsers)|raw }} - {{ 'General_NUsers'|translate("<strong>"~nbUsers~"</strong>")|raw }} + {{ sparkline(urlSparklineNbVisits)|raw }} + {{ 'General_NVisits'|translate("<strong>"~nbVisits~"</strong>")|raw }}{% if displayUniqueVisitors %}, + {{ 'VisitsSummary_NbUniqueVisitors'|translate("<strong>"~nbUniqVisitors~"</strong>")|raw }}{% endif %} </div> - {% endif %} - <div class="sparkline"> - {{ sparkline(urlSparklineAvgVisitDuration)|raw }} - {% set averageVisitDuration=averageVisitDuration|sumtime %} - {{ 'VisitsSummary_AverageVisitDuration'|translate("<strong>"~averageVisitDuration~"</strong>")|raw }} - </div> - <div class="sparkline"> - {{ sparkline(urlSparklineBounceRate)|raw }} - {{ 'VisitsSummary_NbVisitsBounced'|translate("<strong>"~bounceRate~"</strong>")|raw }} - </div> - <div class="sparkline"> - {{ sparkline(urlSparklineActionsPerVisit)|raw }} - {{ 'VisitsSummary_NbActionsPerVisit'|translate("<strong>"~nbActionsPerVisit~"</strong>")|raw }} - </div> - {% if showActionsPluginReports|default(false) %} - <div class="sparkline"> - {{ sparkline(urlSparklineAvgGenerationTime)|raw }} - {% set averageGenerationTime=averageGenerationTime|sumtime %} - {{ 'VisitsSummary_AverageGenerationTime'|translate("<strong>"~averageGenerationTime~"</strong>")|raw }} - </div> - {% endif %} -</div> - -<div id='rightcolumn'> - {% if showActionsPluginReports|default(false) %} - {% if showOnlyActions %} - <div class="sparkline"> - {{ sparkline(urlSparklineNbActions)|raw }} - {{ 'VisitsSummary_NbActionsDescription'|translate("<strong>"~nbActions~"</strong>")|raw }} - </div> - {% else %} + {% if nbUsers > 0 %} + {# Most of users will not have used `setUserId` so this would be confusingly zero #} <div class="sparkline"> - {{ sparkline(urlSparklineNbPageviews)|raw }} - {{ 'VisitsSummary_NbPageviewsDescription'|translate("<strong>"~nbPageviews~"</strong>")|trim|raw }}, - {{ 'VisitsSummary_NbUniquePageviewsDescription'|translate("<strong>"~nbUniquePageviews~"</strong>")|raw }} + {{ sparkline(urlSparklineNbUsers)|raw }} + {{ 'General_NUsers'|translate("<strong>"~nbUsers~"</strong>")|raw }} </div> - {% if displaySiteSearch %} + {% endif %} + <div class="sparkline"> + {{ sparkline(urlSparklineAvgVisitDuration)|raw }} + {% set averageVisitDuration=averageVisitDuration|sumtime %} + {{ 'VisitsSummary_AverageVisitDuration'|translate("<strong>"~averageVisitDuration~"</strong>")|raw }} + </div> + <div class="sparkline"> + {{ sparkline(urlSparklineBounceRate)|raw }} + {{ 'VisitsSummary_NbVisitsBounced'|translate("<strong>"~bounceRate~"</strong>")|raw }} + </div> + <div class="sparkline"> + {{ sparkline(urlSparklineActionsPerVisit)|raw }} + {{ 'VisitsSummary_NbActionsPerVisit'|translate("<strong>"~nbActionsPerVisit~"</strong>")|raw }} + </div> + {% if showActionsPluginReports|default(false) %} + <div class="sparkline"> + {{ sparkline(urlSparklineAvgGenerationTime)|raw }} + {% set averageGenerationTime=averageGenerationTime|sumtime %} + {{ 'VisitsSummary_AverageGenerationTime'|translate("<strong>"~averageGenerationTime~"</strong>")|raw }} + </div> + {% endif %} + </div> + + <div class="col-md-6"> + {% if showActionsPluginReports|default(false) %} + {% if showOnlyActions %} <div class="sparkline"> - {{ sparkline(urlSparklineNbSearches)|raw }} - {{ 'VisitsSummary_NbSearchesDescription'|translate("<strong>"~nbSearches~"</strong>")|trim|raw }}, - {{ 'VisitsSummary_NbKeywordsDescription'|translate("<strong>"~nbKeywords~"</strong>")|raw }} + {{ sparkline(urlSparklineNbActions)|raw }} + {{ 'VisitsSummary_NbActionsDescription'|translate("<strong>"~nbActions~"</strong>")|raw }} </div> - {% endif %} - <div class="sparkline"> - {{ sparkline(urlSparklineNbDownloads)|raw }} - {{ 'VisitsSummary_NbDownloadsDescription'|translate("<strong>"~nbDownloads~"</strong>")|trim|raw }}, - {{ 'VisitsSummary_NbUniqueDownloadsDescription'|translate("<strong>"~nbUniqueDownloads~"</strong>")|raw }} - </div> - <div class="sparkline"> - {{ sparkline(urlSparklineNbOutlinks)|raw }} - {{ 'VisitsSummary_NbOutlinksDescription'|translate("<strong>"~nbOutlinks~"</strong>")|trim|raw }}, - {{ 'VisitsSummary_NbUniqueOutlinksDescription'|translate("<strong>"~nbUniqueOutlinks~"</strong>")|raw }} - </div> - {% endif %} - {% endif %} - <div class="sparkline"> - {{ sparkline(urlSparklineMaxActions)|raw }} - {{ 'VisitsSummary_MaxNbActions'|translate("<strong>"~maxActions~"</strong>")|raw }} + {% else %} + <div class="sparkline"> + {{ sparkline(urlSparklineNbPageviews)|raw }} + {{ 'VisitsSummary_NbPageviewsDescription'|translate("<strong>"~nbPageviews~"</strong>")|trim|raw }}, + {{ 'VisitsSummary_NbUniquePageviewsDescription'|translate("<strong>"~nbUniquePageviews~"</strong>")|raw }} + </div> + {% if displaySiteSearch %} + <div class="sparkline"> + {{ sparkline(urlSparklineNbSearches)|raw }} + {{ 'VisitsSummary_NbSearchesDescription'|translate("<strong>"~nbSearches~"</strong>")|trim|raw }}, + {{ 'VisitsSummary_NbKeywordsDescription'|translate("<strong>"~nbKeywords~"</strong>")|raw }} + </div> + {% endif %} + <div class="sparkline"> + {{ sparkline(urlSparklineNbDownloads)|raw }} + {{ 'VisitsSummary_NbDownloadsDescription'|translate("<strong>"~nbDownloads~"</strong>")|trim|raw }}, + {{ 'VisitsSummary_NbUniqueDownloadsDescription'|translate("<strong>"~nbUniqueDownloads~"</strong>")|raw }} + </div> + <div class="sparkline"> + {{ sparkline(urlSparklineNbOutlinks)|raw }} + {{ 'VisitsSummary_NbOutlinksDescription'|translate("<strong>"~nbOutlinks~"</strong>")|trim|raw }}, + {{ 'VisitsSummary_NbUniqueOutlinksDescription'|translate("<strong>"~nbUniqueOutlinks~"</strong>")|raw }} + </div> + {% endif %} + {% endif %} + <div class="sparkline"> + {{ sparkline(urlSparklineMaxActions)|raw }} + {{ 'VisitsSummary_MaxNbActions'|translate("<strong>"~maxActions~"</strong>")|raw }} + </div> + + {{ postEvent('Template.VisitsSummaryOverviewSparklines') }} </div> - - {{ postEvent('Template.VisitsSummaryOverviewSparklines') }} + </div> -<div style="clear:both;"></div> {% include "_sparklineFooter.twig" %} diff --git a/plugins/Widgetize/stylesheets/widgetize.less b/plugins/Widgetize/stylesheets/widgetize.less index dafad78d9036389de133eeb48d0d2de63dc6d426..b4d7769d393f0a5155a656a0ce37cef566e0c5a6 100644 --- a/plugins/Widgetize/stylesheets/widgetize.less +++ b/plugins/Widgetize/stylesheets/widgetize.less @@ -31,4 +31,8 @@ #embedThisWidgetFlash, #embedThisWidgetEverywhere { margin-top: 5px; -} \ No newline at end of file +} + +body>.widget { + margin: 10px 7px; +} diff --git a/tests/LocalTracker.php b/tests/LocalTracker.php index 6e251e6d21acbf2e995c8aee7c71c663002e217c..fd98ccfd896de345f7e38dd180f065e9ac3a8ec0 100755 --- a/tests/LocalTracker.php +++ b/tests/LocalTracker.php @@ -45,6 +45,7 @@ class Piwik_LocalTracker extends PiwikTracker // unset cached values Cache::$cache = null; + Tracker\Visit::$dimensions = null; // save some values $plugins = Config::getInstance()->Plugins['Plugins']; @@ -77,6 +78,7 @@ class Piwik_LocalTracker extends PiwikTracker $request = new Tracker\RequestSet(); $request->setRequests($requests); + \Piwik\Plugin\Manager::getInstance()->loadTrackerPlugins(); $handler = Tracker\Handler\Factory::make(); $response = $localTracker->main($handler, $request); diff --git a/tests/PHPUnit/Fixtures/ManyVisitsWithGeoIP.php b/tests/PHPUnit/Fixtures/ManyVisitsWithGeoIP.php index a9d77d5813df0a4b40ff0f3feb3a2511e95aa320..02dd26cc9889a0065e5e3f2c8d61bef6b5221890 100644 --- a/tests/PHPUnit/Fixtures/ManyVisitsWithGeoIP.php +++ b/tests/PHPUnit/Fixtures/ManyVisitsWithGeoIP.php @@ -15,6 +15,7 @@ use Piwik\Plugins\UserCountry\LocationProvider; use Piwik\Tests\Framework\Fixture; use Exception; use Piwik\Tests\Framework\Mock\LocationProvider as MockLocationProvider; +use Piwik\Tracker\Visit; require_once PIWIK_INCLUDE_PATH . '/tests/PHPUnit/Framework/Mock/LocationProvider.php'; @@ -93,6 +94,7 @@ class ManyVisitsWithGeoIP extends Fixture if ($useLocal) { Cache::getTransientCache()->flushAll(); // make sure dimension cache is empty between local tracking runs + Visit::$dimensions = null; } // use local tracker so mock location provider can be used diff --git a/tests/PHPUnit/Fixtures/RawArchiveDataWithTempAndInvalidated.php b/tests/PHPUnit/Fixtures/RawArchiveDataWithTempAndInvalidated.php index a6ef25a89f3694cc9eca60959ec63447a60b001c..b93e6d42e7412f888eee432ae318bae3d6190460 100644 --- a/tests/PHPUnit/Fixtures/RawArchiveDataWithTempAndInvalidated.php +++ b/tests/PHPUnit/Fixtures/RawArchiveDataWithTempAndInvalidated.php @@ -22,6 +22,8 @@ use Piwik\Tests\Framework\Fixture; */ class RawArchiveDataWithTempAndInvalidated extends Fixture { + const ROWS_PER_ARCHIVE = 5; + private static $dummyArchiveData = array( // outdated temporary array( @@ -317,7 +319,7 @@ class RawArchiveDataWithTempAndInvalidated extends Fixture public function assertTemporaryArchivesPurged($isBrowserTriggeredArchivingEnabled, Date $date) { if ($isBrowserTriggeredArchivingEnabled) { - $expectedPurgedArchives = array(1,2,3,4,6,7); // only archives from 2 hours before "now" are purged + $expectedPurgedArchives = array(1,2,3,4,6,7,10); // only archives from 2 hours before "now" are purged } else { $expectedPurgedArchives = array(1,2,3,4,7); // only archives before start of "yesterday" are purged } diff --git a/tests/PHPUnit/Framework/Constraint/HttpResponseText.php b/tests/PHPUnit/Framework/Constraint/HttpResponseText.php new file mode 100644 index 0000000000000000000000000000000000000000..54d70982624494916d549b3243a7ee025d37e077 --- /dev/null +++ b/tests/PHPUnit/Framework/Constraint/HttpResponseText.php @@ -0,0 +1,58 @@ +<?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\Tests\Framework\Constraint; + +class HttpResponseText extends \PHPUnit_Framework_Constraint +{ + private $actualCode; + + /** + * @param string $value Expected response text. + */ + public function __construct($value) + { + parent::__construct(); + $this->value = $value; + } + + /** + * Evaluates the constraint for parameter $other. Returns TRUE if the + * constraint is met, FALSE otherwise. + * + * @param mixed $other Value or object to evaluate. + * @return bool + */ + public function matches($other) + { + $options = array( + CURLOPT_URL => $other, + CURLOPT_HEADER => false, + CURLOPT_TIMEOUT => 1, + CURLOPT_RETURNTRANSFER => true + ); + + $ch = curl_init(); + curl_setopt_array($ch, $options); + $response = @curl_exec($ch); + curl_close($ch); + + $this->actualCode = $response; + + return $this->value === $this->actualCode; + } + + /** + * Returns a string representation of the constraint. + * + * @return string + */ + public function toString() + { + return 'does not return response text ' . $this->exporter->export($this->value) . ' it is ' . $this->actualCode; + } +}?> \ No newline at end of file diff --git a/tests/PHPUnit/Framework/Fixture.php b/tests/PHPUnit/Framework/Fixture.php index 87ccf6d07199edd011174fe7c63b46926071a169..90f2ff95cc392eca70459a3507681ed153f4ae49 100644 --- a/tests/PHPUnit/Framework/Fixture.php +++ b/tests/PHPUnit/Framework/Fixture.php @@ -764,7 +764,7 @@ class Fixture extends \PHPUnit_Framework_Assert } } - protected static function executeLogImporter($logFile, $options) + public static function executeLogImporter($logFile, $options, $allowFailure = false) { $python = self::getPythonBinary(); @@ -793,7 +793,9 @@ class Fixture extends \PHPUnit_Framework_Assert // run the command exec($cmd, $output, $result); - if ($result !== 0) { + if ($result !== 0 + && !$allowFailure + ) { throw new Exception("log importer failed: " . implode("\n", $output) . "\n\ncommand used: $cmd"); } diff --git a/tests/PHPUnit/Framework/TestCase/SystemTestCase.php b/tests/PHPUnit/Framework/TestCase/SystemTestCase.php index f0a1c0f982269412949d3e788674e0872a51caf3..f8944bf880dab241d08966ba192c88ae6e788611 100755 --- a/tests/PHPUnit/Framework/TestCase/SystemTestCase.php +++ b/tests/PHPUnit/Framework/TestCase/SystemTestCase.php @@ -17,6 +17,7 @@ use Piwik\Db; use Piwik\DbHelper; use Piwik\ReportRenderer; use Piwik\Tests\Framework\Constraint\ResponseCode; +use Piwik\Tests\Framework\Constraint\HttpResponseText; use Piwik\Tests\Framework\TestRequest\ApiTestConfig; use Piwik\Tests\Framework\TestRequest\Collection; use Piwik\Tests\Framework\TestRequest\Response; @@ -596,6 +597,11 @@ abstract class SystemTestCase extends PHPUnit_Framework_TestCase } } + public function assertHttpResponseText($expectedResponseCode, $url, $message = '') + { + self::assertThat($url, new HttpResponseText($expectedResponseCode), $message); + } + public function assertResponseCode($expectedResponseCode, $url, $message = '') { self::assertThat($url, new ResponseCode($expectedResponseCode), $message); diff --git a/tests/PHPUnit/Integration/AccessTest.php b/tests/PHPUnit/Integration/AccessTest.php index cb2d201c846ab074de2b99b1eeeaeb9b623ba807..a105b7287455610774bb2aa57bcae7b922c1e440 100644 --- a/tests/PHPUnit/Integration/AccessTest.php +++ b/tests/PHPUnit/Integration/AccessTest.php @@ -11,6 +11,8 @@ namespace Piwik\Tests\Integration; use Exception; use Piwik\Access; use Piwik\AuthResult; +use Piwik\Db; +use Piwik\NoAccessException; use Piwik\Tests\Framework\TestCase\IntegrationTestCase; /** @@ -121,16 +123,49 @@ class AccessTest extends IntegrationTestCase $access->checkUserHasSomeAdminAccess(); } + /** + * @expectedException \Piwik\NoAccessException + */ + public function test_CheckUserHasSomeAdminAccessWithSomeAccessFails_IfUserHasPermissionsToSitesButIsNotAuthenticated() + { + $mock = $this->createAccessMockWithAccessToSitesButUnauthenticated(array(2, 9)); + $mock->checkUserHasSomeAdminAccess(); + } + + /** + * @expectedException \Piwik\NoAccessException + */ + public function test_checkUserHasAdminAccessFails_IfUserHasPermissionsToSitesButIsNotAuthenticated() + { + $mock = $this->createAccessMockWithAccessToSitesButUnauthenticated(array(2, 9)); + $mock->checkUserHasAdminAccess('2'); + } + + /** + * @expectedException \Piwik\NoAccessException + */ + public function test_checkUserHasSomeViewAccessFails_IfUserHasPermissionsToSitesButIsNotAuthenticated() + { + $mock = $this->createAccessMockWithAccessToSitesButUnauthenticated(array(2, 9)); + $mock->checkUserHasSomeViewAccess(); + } + + /** + * @expectedException \Piwik\NoAccessException + */ + public function test_checkUserHasViewAccessFails_IfUserHasPermissionsToSitesButIsNotAuthenticated() + { + $mock = $this->createAccessMockWithAccessToSitesButUnauthenticated(array(2, 9)); + $mock->checkUserHasViewAccess('2'); + } + public function testCheckUserHasSomeAdminAccessWithSomeAccess() { - $mock = $this->getMock( - 'Piwik\Access', - array('getSitesIdWithAdminAccess') - ); + $mock = $this->createAccessMockWithAuthenticatedUser(array('getRawSitesWithSomeViewAccess')); $mock->expects($this->once()) - ->method('getSitesIdWithAdminAccess') - ->will($this->returnValue(array(2, 9))); + ->method('getRawSitesWithSomeViewAccess') + ->will($this->returnValue($this->buildAdminAccessForSiteIds(array(2, 9)))); $mock->checkUserHasSomeAdminAccess(); } @@ -153,14 +188,11 @@ class AccessTest extends IntegrationTestCase public function testCheckUserHasSomeViewAccessWithSomeAccess() { - $mock = $this->getMock( - 'Piwik\Access', - array('getSitesIdWithAtLeastViewAccess') - ); + $mock = $this->createAccessMockWithAuthenticatedUser(array('getRawSitesWithSomeViewAccess')); $mock->expects($this->once()) - ->method('getSitesIdWithAtLeastViewAccess') - ->will($this->returnValue(array(1, 2, 3, 4))); + ->method('getRawSitesWithSomeViewAccess') + ->will($this->returnValue($this->buildViewAccessForSiteIds(array(1, 2, 3, 4)))); $mock->checkUserHasSomeViewAccess(); } @@ -183,28 +215,24 @@ class AccessTest extends IntegrationTestCase public function testCheckUserHasViewAccessWithSomeAccessSuccessIdSitesAsString() { - $mock = $this->getMock( - 'Piwik\Access', - array('getSitesIdWithAtLeastViewAccess') - ); + /** @var Access $mock */ + $mock = $this->createAccessMockWithAuthenticatedUser(array('getRawSitesWithSomeViewAccess')); $mock->expects($this->once()) - ->method('getSitesIdWithAtLeastViewAccess') - ->will($this->returnValue(array(1, 2, 3, 4))); + ->method('getRawSitesWithSomeViewAccess') + ->will($this->returnValue($this->buildViewAccessForSiteIds(array(1, 2, 3, 4)))); $mock->checkUserHasViewAccess('1,3'); } public function testCheckUserHasViewAccessWithSomeAccessSuccessAllSites() { - $mock = $this->getMock( - 'Piwik\Access', - array('getSitesIdWithAtLeastViewAccess') - ); + /** @var Access $mock */ + $mock = $this->createAccessMockWithAuthenticatedUser(array('getRawSitesWithSomeViewAccess')); $mock->expects($this->any()) - ->method('getSitesIdWithAtLeastViewAccess') - ->will($this->returnValue(array(1, 2, 3, 4))); + ->method('getRawSitesWithSomeViewAccess') + ->will($this->returnValue($this->buildViewAccessForSiteIds(array(1, 2, 3, 4)))); $mock->checkUserHasViewAccess('all'); } @@ -306,8 +334,7 @@ class AccessTest extends IntegrationTestCase public function testReloadAccessWithMockedAuthValid() { - $mock = $this->getMock('Piwik\\Auth', array('authenticate', 'getName', 'getTokenAuthSecret', 'getLogin', 'setTokenAuth', 'setLogin', - 'setPassword', 'setPasswordHash')); + $mock = $this->createPiwikAuthMockInstance(); $mock->expects($this->once()) ->method('authenticate') ->will($this->returnValue(new AuthResult(AuthResult::SUCCESS, 'login', 'token'))); @@ -319,6 +346,43 @@ class AccessTest extends IntegrationTestCase $this->assertFalse($access->hasSuperUserAccess()); } + public function test_reloadAccess_loadSitesIfNeeded_doesActuallyResetAllSiteIdsAndRequestThemAgain() + { + /** @var Access $mock */ + $mock = $this->createAccessMockWithAuthenticatedUser(array('getRawSitesWithSomeViewAccess')); + + $mock->expects($this->at(0)) + ->method('getRawSitesWithSomeViewAccess') + ->will($this->returnValue($this->buildAdminAccessForSiteIds(array(1,2,3,4)))); + + $mock->expects($this->at(1)) + ->method('getRawSitesWithSomeViewAccess') + ->will($this->returnValue($this->buildAdminAccessForSiteIds(array(1)))); + + // should succeed as permission to 1,2,3,4 + $mock->checkUserHasAdminAccess('1,3'); + + // should clear permissions + $mock->reloadAccess(); + + try { + // should fail as now only permission to site 1 + $mock->checkUserHasAdminAccess('1,3'); + $this->fail('An expected exception has not been triggered. Permissions were not resetted'); + } catch (NoAccessException $e) { + + } + + $mock->checkUserHasAdminAccess('1'); // it should have access to site "1" + + $mock->setSuperUserAccess(true); + + $mock->reloadAccess(); + + // should now have permission as it is a superuser + $mock->checkUserHasAdminAccess('1,3'); + } + public function test_doAsSuperUser_ChangesSuperUserAccessCorrectly() { Access::getInstance()->setSuperUserAccess(false); @@ -372,4 +436,65 @@ class AccessTest extends IntegrationTestCase AccessTest::assertTrue($access->hasSuperUserAccess()); }); } + + private function buildAdminAccessForSiteIds($siteIds) + { + $access = array(); + + foreach ($siteIds as $siteId) { + $access[] = array('access' => 'admin', 'idsite' => $siteId); + } + + return $access; + } + + private function buildViewAccessForSiteIds($siteIds) + { + $access = array(); + + foreach ($siteIds as $siteId) { + $access[] = array('access' => 'admin', 'idsite' => $siteId); + } + + return $access; + } + + private function createPiwikAuthMockInstance() + { + return $this->getMock('Piwik\\Auth', array('authenticate', 'getName', 'getTokenAuthSecret', 'getLogin', 'setTokenAuth', 'setLogin', + 'setPassword', 'setPasswordHash')); + } + + private function createAccessMockWithAccessToSitesButUnauthenticated($idSites) + { + $mock = $this->getMock('Piwik\Access', array('getRawSitesWithSomeViewAccess', 'loadSitesIfNeeded')); + + // this method will be actually never called as it is unauthenticated. The tests are supposed to fail if it + // suddenly does get called as we should not query for sites if it is not authenticated. + $mock->expects($this->any()) + ->method('getRawSitesWithSomeViewAccess') + ->will($this->returnValue($this->buildAdminAccessForSiteIds($idSites))); + + return $mock; + } + + private function createAccessMockWithAuthenticatedUser($methodsToMock = array()) + { + $methods = array('authenticate'); + + foreach ($methodsToMock as $methodToMock) { + $methods[] = $methodToMock; + } + + $authMock = $this->createPiwikAuthMockInstance(); + $authMock->expects($this->atLeast(1)) + ->method('authenticate') + ->will($this->returnValue(new AuthResult(AuthResult::SUCCESS, 'login', 'token'))); + + $mock = $this->getMock('Piwik\Access', $methods); + $mock->reloadAccess($authMock); + + return $mock; + } + } diff --git a/tests/PHPUnit/Integration/Archive/PurgerTest.php b/tests/PHPUnit/Integration/Archive/ArchivePurgerTest.php similarity index 78% rename from tests/PHPUnit/Integration/Archive/PurgerTest.php rename to tests/PHPUnit/Integration/Archive/ArchivePurgerTest.php index a42247347998e62aa15320aefc1eccc1019d998b..5fc57e3a52df8e4c7d265e757d1c78835bfee7c4 100644 --- a/tests/PHPUnit/Integration/Archive/PurgerTest.php +++ b/tests/PHPUnit/Integration/Archive/ArchivePurgerTest.php @@ -17,7 +17,7 @@ use Piwik\Tests\Framework\TestCase\IntegrationTestCase; /** * @group Core */ -class PurgerTest extends IntegrationTestCase +class ArchivePurgerTest extends IntegrationTestCase { /** * @var RawArchiveDataWithTempAndInvalidated @@ -62,40 +62,48 @@ class PurgerTest extends IntegrationTestCase { $this->enableBrowserTriggeredArchiving(); - $this->archivePurger->purgeOutdatedArchives($this->february); + $deletedRowCount = $this->archivePurger->purgeOutdatedArchives($this->february); self::$fixture->assertTemporaryArchivesPurged($browserTriggeringEnabled = true, $this->february); self::$fixture->assertCustomRangesNotPurged($this->february, $includeTemporary = false); self::$fixture->assertTemporaryArchivesNotPurged($this->january); + + $this->assertEquals(7 * RawArchiveDataWithTempAndInvalidated::ROWS_PER_ARCHIVE, $deletedRowCount); } public function test_purgeOutdatedArchives_PurgesCorrectTemporaryArchives_WhileKeepingNewerTemporaryArchives_WithBrowserTriggeringDisabled() { $this->disableBrowserTriggeredArchiving(); - $this->archivePurger->purgeOutdatedArchives($this->february); + $deletedRowCount = $this->archivePurger->purgeOutdatedArchives($this->february); self::$fixture->assertTemporaryArchivesPurged($browserTriggeringEnabled = false, $this->february); self::$fixture->assertCustomRangesNotPurged($this->february); self::$fixture->assertTemporaryArchivesNotPurged($this->january); + + $this->assertEquals(5 * RawArchiveDataWithTempAndInvalidated::ROWS_PER_ARCHIVE, $deletedRowCount); } public function test_purgeInvalidatedArchivesFrom_PurgesAllInvalidatedArchives_AndMarksDatesAndSitesAsInvalidated() { - $this->archivePurger->purgeInvalidatedArchivesFrom($this->february); + $deletedRowCount = $this->archivePurger->purgeInvalidatedArchivesFrom($this->february); self::$fixture->assertInvalidatedArchivesPurged($this->february); self::$fixture->assertInvalidatedArchivesNotPurged($this->january); + + $this->assertEquals(4 * RawArchiveDataWithTempAndInvalidated::ROWS_PER_ARCHIVE, $deletedRowCount); } public function test_purgeArchivesWithPeriodRange_PurgesAllRangeArchives() { - $this->archivePurger->purgeArchivesWithPeriodRange($this->february); + $deletedRowCount = $this->archivePurger->purgeArchivesWithPeriodRange($this->february); self::$fixture->assertCustomRangesPurged($this->february); self::$fixture->assertCustomRangesNotPurged($this->january); + + $this->assertEquals(3 * RawArchiveDataWithTempAndInvalidated::ROWS_PER_ARCHIVE, $deletedRowCount); } private function configureCustomRangePurging() @@ -114,4 +122,4 @@ class PurgerTest extends IntegrationTestCase } } -PurgerTest::$fixture = new RawArchiveDataWithTempAndInvalidated(); \ No newline at end of file +ArchivePurgerTest::$fixture = new RawArchiveDataWithTempAndInvalidated(); \ No newline at end of file diff --git a/tests/PHPUnit/Integration/CronArchive/SharedSiteIdsTest.php b/tests/PHPUnit/Integration/CronArchive/SharedSiteIdsTest.php index 8ad12f8b8bbda824e92a125876b6d69a599ead5d..2485ad2de7c64d95d3310255ba5fb74c27727945 100644 --- a/tests/PHPUnit/Integration/CronArchive/SharedSiteIdsTest.php +++ b/tests/PHPUnit/Integration/CronArchive/SharedSiteIdsTest.php @@ -55,9 +55,12 @@ class SharedSiteIdsTest extends IntegrationTestCase $this->assertNull($siteIds->getNextSiteId()); } - public function test_isSupported() + public function test_construct_withCustomOptionName() { - $this->assertTrue(SharedSiteIds::isSupported()); + $first = new SharedSiteIds(array(1, 2), 'SharedSiteIdsToArchive_Test'); + $second = new SharedSiteIds(array(), 'SharedSiteIdsToArchive_Test'); + $this->assertEquals(array(1, 2), $first->getAllSiteIdsToArchive()); + $this->assertEquals(array(1, 2), $second->getAllSiteIdsToArchive()); } public function test_getNumSites() diff --git a/tests/PHPUnit/Integration/Tracker/VisitTest.php b/tests/PHPUnit/Integration/Tracker/VisitTest.php index 919a918516cd556fd23c8d8164f53f363fc12486..a9407b88164ae94ac358a0d5e8f8149821e14d06 100644 --- a/tests/PHPUnit/Integration/Tracker/VisitTest.php +++ b/tests/PHPUnit/Integration/Tracker/VisitTest.php @@ -41,6 +41,7 @@ class VisitTest extends IntegrationTestCase Manager::getInstance()->loadTrackerPlugins(); Manager::getInstance()->loadPlugin('SitesManager'); + Visit::$dimensions = null; } /** @@ -450,6 +451,7 @@ class VisitTest extends IntegrationTestCase $cache = Cache::getTransientCache(); $cache->save(CacheId::pluginAware('VisitDimensions'), $dimensions); + Visit::$dimensions = null; } } diff --git a/tests/PHPUnit/System/BlobReportLimitingTest.php b/tests/PHPUnit/System/BlobReportLimitingTest.php index ba2ea49b46afb7e37c9ed09235c0a0b49582857a..8122fd195cbbcd5236403a297ef0002d4dfec281 100755 --- a/tests/PHPUnit/System/BlobReportLimitingTest.php +++ b/tests/PHPUnit/System/BlobReportLimitingTest.php @@ -8,6 +8,7 @@ namespace Piwik\Tests\System; use Piwik\Application\Kernel\GlobalSettingsProvider\IniSettingsProvider; +use Piwik\Cache; use Piwik\Config; use Piwik\Plugins\Actions\ArchivingHelper; use Piwik\Tests\Framework\Mock\TestConfig; @@ -23,6 +24,9 @@ use Piwik\Tests\Fixtures\ManyVisitsWithMockLocationProvider; */ class BlobReportLimitingTest extends SystemTestCase { + /** + * @var ManyVisitsWithMockLocationProvider + */ public static $fixture = null; // initialized below class definition public static function setUpBeforeClass() @@ -31,6 +35,12 @@ class BlobReportLimitingTest extends SystemTestCase parent::setUpBeforeClass(); } + public function setUp() + { + Cache::getTransientCache()->flushAll(); + parent::setUp(); + } + public function getApiForTesting() { // TODO: test Provider plugin? Not sure if it's possible. diff --git a/tests/PHPUnit/System/ImportLogsTest.php b/tests/PHPUnit/System/ImportLogsTest.php index fff22849ae19c4ae6a461310dd83c43b99741c7c..e622265dffcc27f666cb7911d7913f1f2aa81090 100755 --- a/tests/PHPUnit/System/ImportLogsTest.php +++ b/tests/PHPUnit/System/ImportLogsTest.php @@ -9,6 +9,7 @@ namespace Piwik\Tests\System; use Piwik\Access; use Piwik\Plugins\SitesManager\API; +use Piwik\Tests\Framework\Fixture; use Piwik\Tests\Framework\TestCase\SystemTestCase; use Piwik\Tests\Fixtures\ManySitesImportedLogs; @@ -23,6 +24,13 @@ class ImportLogsTest extends SystemTestCase /** @var ManySitesImportedLogs */ public static $fixture = null; // initialized below class definition + public function setUp() + { + parent::setUp(); + + $this->resetTestingEnvironmentChanges(); + } + /** * @dataProvider getApiForTesting */ @@ -102,10 +110,51 @@ class ImportLogsTest extends SystemTestCase $this->assertEquals(1, count($whateverDotCom)); } + public function test_LogImporter_RetriesWhenServerFails() + { + $this->simulateTrackerFailure(); + + $logFile = PIWIK_INCLUDE_PATH . '/tests/resources/access-logs/fake_logs_enable_all.log'; + + $options = array( + '--idsite' => self::$fixture->idSite, + '--token-auth' => 'asldfjksd', + '--retry-max-attempts' => 5, + '--retry-delay' => 1 + ); + + $output = Fixture::executeLogImporter($logFile, $options, $allowFailure = true); + $output = implode("\n", $output); + + for ($i = 2; $i != 6; ++$i) { + $this->assertContains("Retrying request, attempt number $i", $output); + } + + $this->assertNotContains("Retrying request, attempt number 6", $output); + + $this->assertContains("Max number of attempts reached, server is unreachable!", $output); + } + + private function simulateTrackerFailure() + { + $testingEnvironment = new \Piwik_TestingEnvironment(); + $testingEnvironment->configOverride = array( + 'Tracker' => array('bulk_requests_require_authentication' => 1) + ); + $testingEnvironment->save(); + } + public static function getOutputPrefix() { return 'ImportLogs'; } + + private function resetTestingEnvironmentChanges() + { + $testingEnvironment = new \Piwik_TestingEnvironment(); + $testingEnvironment->configOverride = null; + $testingEnvironment->save(); + } } ImportLogsTest::$fixture = new ManySitesImportedLogs(); diff --git a/tests/PHPUnit/System/TrackerResponseTest.php b/tests/PHPUnit/System/TrackerResponseTest.php index 7bd0f7aef78456beef09d7947d0e53a8d333a7bd..16ab4a4e0104ab15939211d2dd11272fb5902635 100755 --- a/tests/PHPUnit/System/TrackerResponseTest.php +++ b/tests/PHPUnit/System/TrackerResponseTest.php @@ -97,10 +97,10 @@ class TrackerResponseTest extends SystemTestCase public function test_response_ShouldReturnPiwikMessage_InCaseOfEmptyRequest() { $url = Fixture::getTrackerUrl(); - $response = file_get_contents($url); + $this->assertResponseCode(400, $url); $expected = "<a href='/'>Piwik</a> is a free/libre web <a href='http://piwik.org'>analytics</a> that lets you keep control of your data."; - $this->assertEquals($expected, $response); + $this->assertHttpResponseText($expected, $url); } } diff --git a/tests/PHPUnit/System/TrackerTest.php b/tests/PHPUnit/System/TrackerTest.php index 65b16f6feae42a88a1a607d601e78ccc8cf583b5..48d3a4e54d7a018366a4a330f03bed13a78fcfcc 100644 --- a/tests/PHPUnit/System/TrackerTest.php +++ b/tests/PHPUnit/System/TrackerTest.php @@ -9,7 +9,13 @@ namespace Piwik\Tests\System; use Piwik\Common; +use Piwik\Config; use Piwik\Db; +use Piwik\Option; +use Piwik\Scheduler\Schedule\Schedule; +use Piwik\Scheduler\Task; +use Piwik\Scheduler\Timetable; +use Piwik\SettingsPiwik; use Piwik\Tests\Framework\Fixture; use Piwik\Tests\Framework\TestCase\IntegrationTestCase; use Piwik\Tracker; @@ -20,10 +26,26 @@ use Piwik\Tracker; */ class TrackerTest extends IntegrationTestCase { + const TASKS_FINISHED_OPTION_NAME = "Tests.scheduledTasksFinished"; + const TASKS_STARTED_OPTION_NAME = "Tests.scheduledTasksStarted"; + public function setUp() { parent::setUp(); + Fixture::createWebsite('2014-02-04'); + + $testingEnvironment = new \Piwik_TestingEnvironment(); + $testingEnvironment->testCaseClass = null; + $testingEnvironment->addFailingScheduledTask = false; + $testingEnvironment->addScheduledTask = false; + $testingEnvironment->save(); + + Option::delete(self::TASKS_STARTED_OPTION_NAME); + Option::delete(self::TASKS_FINISHED_OPTION_NAME); + Option::delete(Timetable::TIMETABLE_OPTION_STRING); + + SettingsPiwik::overwritePiwikUrl(self::$fixture->getRootUrl() . "tests/PHPUnit/proxy"); } protected static function configureFixture($fixture) @@ -125,6 +147,59 @@ class TrackerTest extends IntegrationTestCase $this->assertEquals(0, count($this->getConversionItems())); } + public function test_scheduledTasks_CanBeRunThroughTracker_WithoutIncludingOutputInTrackerOutput() + { + $this->setScheduledTasksToRunInTracker(); + + $urlToTest = $this->getSimpleTrackingUrl(); + + $response = $this->sendTrackingRequestByCurl($urlToTest); + Fixture::checkResponse($response); + + $this->assertScheduledTasksWereRun(); + } + + public function test_scheduledTasks_CanBeRunThroughTracker_WithOutputIncluded_IfDebugQueryParamUsed() + { + $this->setScheduledTasksToRunInTracker(); + + $urlToTest = $this->getSimpleTrackingUrl() . '&debug=1'; + + $response = $this->sendTrackingRequestByCurl($urlToTest); + + $this->assertScheduledTasksWereRun(); + + $this->assertContains('Scheduled Tasks: Starting...', $response); + } + + public function getTypesOfErrorsForScheduledTasksTrackerFailureTest() + { + return array( + array(true), + array(false) + ); + } + + /** + * @dataProvider getTypesOfErrorsForScheduledTasksTrackerFailureTest + */ + public function test_scheduledTasks_DoNotFailTracking_WhenScheduledTaskFails($doFatalError) + { + $this->setScheduledTasksToRunInTracker(); + $this->addFailingScheduledTaskToTracker($doFatalError); + + $urlToTest = $this->getSimpleTrackingUrl(); + + $response = $this->sendTrackingRequestByCurl($urlToTest); + Fixture::checkResponse($response); + + if ($doFatalError) { + $this->assertScheduledTasksWereNotRun(); + } else { + $this->assertScheduledTasksWereRun(); + } + } + protected function issueBulkTrackingRequest($token_auth, $expectTrackingToSucceed) { $piwikHost = Fixture::getRootUrl() . 'tests/PHPUnit/proxy/piwik.php'; @@ -190,4 +265,100 @@ class TrackerTest extends IntegrationTestCase return "?idsite=1&idgoal=0&rec=1&url=" . urlencode('http://quellehorreur.com/movies') . "&ec_items=" . urlencode($ecItemsStr) . '&ec_id=myspecial-id-1234&revenue=16.99&ec_st=12.99&ec_tx=0&ec_sh=3'; } + + private function getSimpleTrackingUrl() + { + return "?idsite=1&rec=1&url=" . urlencode('http://quellehorreur.com/movies') . "&action_name=Movies"; + } + + private function setScheduledTasksToRunInTracker() + { + $testingEnvironment = new \Piwik_TestingEnvironment(); + $testingEnvironment->testCaseClass = 'Piwik\Tests\System\TrackerTest'; + $testingEnvironment->addScheduledTask = true; + $testingEnvironment->configOverride = array('Tracker' => array('scheduled_tasks_min_interval' => 1, 'debug_on_demand' => 1)); + $testingEnvironment->save(); + } + + private function addFailingScheduledTaskToTracker($doFatalError) + { + $testingEnvironment = new \Piwik_TestingEnvironment(); + $testingEnvironment->addFailingScheduledTask = true; + $testingEnvironment->scheduledTaskFailureShouldBeFatal = $doFatalError; + $testingEnvironment->save(); + } + + public function provideContainerConfig() + { + define('DEBUG_FORCE_SCHEDULED_TASKS', 1); + + $testingEnvironment = new \Piwik_TestingEnvironment(); + + $tasksToAdd = array(); + + if ($testingEnvironment->addFailingScheduledTask) { + if ($testingEnvironment->scheduledTaskFailureShouldBeFatal) { + $tasksToAdd[] = new Task($this, 'triggerFatalError', null, Schedule::factory('hourly')); + } else { + $tasksToAdd[] = new Task($this, 'throwScheduledTaskException', null, Schedule::factory('hourly')); + } + } + + if ($testingEnvironment->addScheduledTask) { + $tasksToAdd[] = new Task($this, 'markScheduledTaskExecutionFinished', null, Schedule::factory('hourly')); + } + + $result = array(); + if (!empty($tasksToAdd)) { + $initialTask = new Task($this, 'markCustomTaskExecuted', null, Schedule::factory('hourly')); + $tasksToAdd = array_merge(array($initialTask), $tasksToAdd); + + $mockTaskLoader = $this->getMock('Piwik\Scheduler\TaskLoader', array('loadTasks')); + $mockTaskLoader->expects($this->any())->method('loadTasks')->will($this->returnValue($tasksToAdd)); + $result['Piwik\Scheduler\TaskLoader'] = $mockTaskLoader; + } + return $result; + } + + public function triggerFatalError() + { + require 'thehowling'; + } + + public function throwScheduledTaskException() + { + throw new \Exception("triggered exception"); + } + + public function markScheduledTaskExecutionFinished() + { + Option::set(self::TASKS_FINISHED_OPTION_NAME, 1); + } + + public function markCustomTaskExecuted() + { + Option::set(self::TASKS_STARTED_OPTION_NAME, 1); + } + + private function assertScheduledTasksWereRun() + { + $this->assertCustomTasksWereStarted(); + + Option::clearCachedOption(self::TASKS_FINISHED_OPTION_NAME); + $this->assertEquals(1, Option::get(self::TASKS_FINISHED_OPTION_NAME)); + } + + private function assertScheduledTasksWereNotRun() + { + $this->assertCustomTasksWereStarted(); + + Option::clearCachedOption(self::TASKS_FINISHED_OPTION_NAME); + $this->assertEmpty(Option::get(self::TASKS_FINISHED_OPTION_NAME)); + } + + private function assertCustomTasksWereStarted() + { + Option::clearCachedOption(self::TASKS_STARTED_OPTION_NAME); + $this->assertEquals(1, Option::get(self::TASKS_STARTED_OPTION_NAME)); + } } \ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_ImportLogs__DevicesDetection.getBrowserEngines_month.xml b/tests/PHPUnit/System/expected/test_ImportLogs__DevicesDetection.getBrowserEngines_month.xml index 1fb125914df754adc2c4906c67bcd419e288f625..fc9865b7253efb025872ac57873a03a674645cbe 100644 --- a/tests/PHPUnit/System/expected/test_ImportLogs__DevicesDetection.getBrowserEngines_month.xml +++ b/tests/PHPUnit/System/expected/test_ImportLogs__DevicesDetection.getBrowserEngines_month.xml @@ -14,13 +14,13 @@ </row> <row> <label>Trident (IE)</label> - <nb_visits>7</nb_visits> - <nb_actions>8</nb_actions> + <nb_visits>8</nb_visits> + <nb_actions>9</nb_actions> <max_actions>2</max_actions> <sum_visit_length>242</sum_visit_length> - <bounce_count>6</bounce_count> - <nb_visits_converted>7</nb_visits_converted> - <sum_daily_nb_uniq_visitors>7</sum_daily_nb_uniq_visitors> + <bounce_count>7</bounce_count> + <nb_visits_converted>8</nb_visits_converted> + <sum_daily_nb_uniq_visitors>8</sum_daily_nb_uniq_visitors> <sum_daily_nb_users>0</sum_daily_nb_users> <segment>browserEngine==Trident</segment> </row> @@ -38,13 +38,13 @@ </row> <row> <label>Unknown</label> - <nb_visits>5</nb_visits> - <nb_actions>5</nb_actions> + <nb_visits>4</nb_visits> + <nb_actions>4</nb_actions> <max_actions>1</max_actions> <sum_visit_length>0</sum_visit_length> - <bounce_count>5</bounce_count> - <nb_visits_converted>2</nb_visits_converted> - <sum_daily_nb_uniq_visitors>5</sum_daily_nb_uniq_visitors> + <bounce_count>4</bounce_count> + <nb_visits_converted>1</nb_visits_converted> + <sum_daily_nb_uniq_visitors>4</sum_daily_nb_uniq_visitors> <sum_daily_nb_users>0</sum_daily_nb_users> <segment>browserEngine==</segment> </row> diff --git a/tests/PHPUnit/System/expected/test_ImportLogs__DevicesDetection.getBrowserFamilies_month.xml b/tests/PHPUnit/System/expected/test_ImportLogs__DevicesDetection.getBrowserFamilies_month.xml index 930e0f0aa71c5438cc2f39d0e4f8c34bb6bb086e..971cd5f0fb405227e1244a1e8f06712dd037b84d 100644 --- a/tests/PHPUnit/System/expected/test_ImportLogs__DevicesDetection.getBrowserFamilies_month.xml +++ b/tests/PHPUnit/System/expected/test_ImportLogs__DevicesDetection.getBrowserFamilies_month.xml @@ -14,13 +14,13 @@ </row> <row> <label>Internet Explorer</label> - <nb_visits>7</nb_visits> - <nb_actions>8</nb_actions> + <nb_visits>8</nb_visits> + <nb_actions>9</nb_actions> <max_actions>2</max_actions> <sum_visit_length>242</sum_visit_length> - <bounce_count>6</bounce_count> - <nb_visits_converted>7</nb_visits_converted> - <sum_daily_nb_uniq_visitors>7</sum_daily_nb_uniq_visitors> + <bounce_count>7</bounce_count> + <nb_visits_converted>8</nb_visits_converted> + <sum_daily_nb_uniq_visitors>8</sum_daily_nb_uniq_visitors> <sum_daily_nb_users>0</sum_daily_nb_users> <logo>plugins/DevicesDetection/images/browsers/IE.gif</logo> </row> @@ -50,13 +50,13 @@ </row> <row> <label>Unknown</label> - <nb_visits>5</nb_visits> - <nb_actions>5</nb_actions> + <nb_visits>4</nb_visits> + <nb_actions>4</nb_actions> <max_actions>1</max_actions> <sum_visit_length>0</sum_visit_length> - <bounce_count>5</bounce_count> - <nb_visits_converted>2</nb_visits_converted> - <sum_daily_nb_uniq_visitors>5</sum_daily_nb_uniq_visitors> + <bounce_count>4</bounce_count> + <nb_visits_converted>1</nb_visits_converted> + <sum_daily_nb_uniq_visitors>4</sum_daily_nb_uniq_visitors> <sum_daily_nb_users>0</sum_daily_nb_users> <logo>plugins/DevicesDetection/images/browsers/UNK.gif</logo> </row> diff --git a/tests/PHPUnit/System/expected/test_ImportLogs__DevicesDetection.getBrowserVersions_month.xml b/tests/PHPUnit/System/expected/test_ImportLogs__DevicesDetection.getBrowserVersions_month.xml index 2c687028a7925440747ce1ae063069a54e1f7bb0..d6572a8ba39d449e3dbfe520e6ccc27652b66a63 100644 --- a/tests/PHPUnit/System/expected/test_ImportLogs__DevicesDetection.getBrowserVersions_month.xml +++ b/tests/PHPUnit/System/expected/test_ImportLogs__DevicesDetection.getBrowserVersions_month.xml @@ -41,13 +41,13 @@ </row> <row> <label>Unknown</label> - <nb_visits>5</nb_visits> - <nb_actions>5</nb_actions> + <nb_visits>4</nb_visits> + <nb_actions>4</nb_actions> <max_actions>1</max_actions> <sum_visit_length>0</sum_visit_length> - <bounce_count>5</bounce_count> - <nb_visits_converted>2</nb_visits_converted> - <sum_daily_nb_uniq_visitors>5</sum_daily_nb_uniq_visitors> + <bounce_count>4</bounce_count> + <nb_visits_converted>1</nb_visits_converted> + <sum_daily_nb_uniq_visitors>4</sum_daily_nb_uniq_visitors> <sum_daily_nb_users>0</sum_daily_nb_users> <segment>browserCode==UNK;browserVersion==</segment> <logo>plugins/DevicesDetection/images/browsers/UNK.gif</logo> @@ -169,6 +169,19 @@ <segment>browserCode==CM;browserVersion==39.0</segment> <logo>plugins/DevicesDetection/images/browsers/CM.gif</logo> </row> + <row> + <label>Internet Explorer 5.0</label> + <nb_visits>1</nb_visits> + <nb_actions>1</nb_actions> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>1</bounce_count> + <nb_visits_converted>1</nb_visits_converted> + <sum_daily_nb_uniq_visitors>1</sum_daily_nb_uniq_visitors> + <sum_daily_nb_users>0</sum_daily_nb_users> + <segment>browserCode==IE;browserVersion==5.0</segment> + <logo>plugins/DevicesDetection/images/browsers/IE.gif</logo> + </row> <row> <label>Internet Explorer 6.0</label> <nb_visits>1</nb_visits> diff --git a/tests/PHPUnit/System/expected/test_ImportLogs__DevicesDetection.getBrowsers_month.xml b/tests/PHPUnit/System/expected/test_ImportLogs__DevicesDetection.getBrowsers_month.xml index 16aa9095e1b6b1a5c9a567ab6e140c0d53c1bcfd..6f3990403608c574b490864f64d9f09da338f182 100644 --- a/tests/PHPUnit/System/expected/test_ImportLogs__DevicesDetection.getBrowsers_month.xml +++ b/tests/PHPUnit/System/expected/test_ImportLogs__DevicesDetection.getBrowsers_month.xml @@ -15,13 +15,13 @@ </row> <row> <label>Internet Explorer</label> - <nb_visits>7</nb_visits> - <nb_actions>8</nb_actions> + <nb_visits>8</nb_visits> + <nb_actions>9</nb_actions> <max_actions>2</max_actions> <sum_visit_length>242</sum_visit_length> - <bounce_count>6</bounce_count> - <nb_visits_converted>7</nb_visits_converted> - <sum_daily_nb_uniq_visitors>7</sum_daily_nb_uniq_visitors> + <bounce_count>7</bounce_count> + <nb_visits_converted>8</nb_visits_converted> + <sum_daily_nb_uniq_visitors>8</sum_daily_nb_uniq_visitors> <sum_daily_nb_users>0</sum_daily_nb_users> <logo>plugins/DevicesDetection/images/browsers/IE.gif</logo> <segment>browserCode==IE</segment> @@ -54,13 +54,13 @@ </row> <row> <label>Unknown</label> - <nb_visits>5</nb_visits> - <nb_actions>5</nb_actions> + <nb_visits>4</nb_visits> + <nb_actions>4</nb_actions> <max_actions>1</max_actions> <sum_visit_length>0</sum_visit_length> - <bounce_count>5</bounce_count> - <nb_visits_converted>2</nb_visits_converted> - <sum_daily_nb_uniq_visitors>5</sum_daily_nb_uniq_visitors> + <bounce_count>4</bounce_count> + <nb_visits_converted>1</nb_visits_converted> + <sum_daily_nb_uniq_visitors>4</sum_daily_nb_uniq_visitors> <sum_daily_nb_users>0</sum_daily_nb_users> <logo>plugins/DevicesDetection/images/browsers/UNK.gif</logo> <segment>browserCode==UNK</segment> diff --git a/tests/PHPUnit/System/expected/test_ImportLogs__DevicesDetection.getType_month.xml b/tests/PHPUnit/System/expected/test_ImportLogs__DevicesDetection.getType_month.xml index 9daee7778d332fda4deb63cb29286396dcac3777..af1de4ce8e7a0eb92c4227d2c9726cde73410a06 100644 --- a/tests/PHPUnit/System/expected/test_ImportLogs__DevicesDetection.getType_month.xml +++ b/tests/PHPUnit/System/expected/test_ImportLogs__DevicesDetection.getType_month.xml @@ -62,6 +62,12 @@ <segment>deviceType==feature+phone</segment> <logo>plugins/DevicesDetection/images/screens/mobile.gif</logo> </row> + <row> + <label>Phablet</label> + <nb_visits>0</nb_visits> + <segment>deviceType==phablet</segment> + <logo>plugins/DevicesDetection/images/screens/unknown.gif</logo> + </row> <row> <label>Portable media player</label> <nb_visits>0</nb_visits> diff --git a/tests/PHPUnit/System/expected/test_ImportLogs__Live.getLastVisitsDetails_range.xml b/tests/PHPUnit/System/expected/test_ImportLogs__Live.getLastVisitsDetails_range.xml index 69b8825689200faed1a061a1baf7a5f4d3092716..2f4c1d0d3d416c739c4b1e6f22fd32012bcb368a 100644 --- a/tests/PHPUnit/System/expected/test_ImportLogs__Live.getLastVisitsDetails_range.xml +++ b/tests/PHPUnit/System/expected/test_ImportLogs__Live.getLastVisitsDetails_range.xml @@ -442,7 +442,7 @@ <customVariables> <row> <customVariablePageName1>HTTP-code</customVariablePageName1> - <customVariablePageValue1>200</customVariablePageValue1> + <customVariablePageValue1>501</customVariablePageValue1> </row> </customVariables> <icon /> @@ -830,7 +830,7 @@ <customVariables> <row> <customVariablePageName1>HTTP-code</customVariablePageName1> - <customVariablePageValue1>200</customVariablePageValue1> + <customVariablePageValue1>404</customVariablePageValue1> </row> </customVariables> <icon /> @@ -1203,7 +1203,7 @@ <customVariables> <row> <customVariablePageName1>HTTP-code</customVariablePageName1> - <customVariablePageValue1>200</customVariablePageValue1> + <customVariablePageValue1>403</customVariablePageValue1> </row> </customVariables> <generationTime>0.18s</generationTime> @@ -1233,7 +1233,7 @@ <customVariables> <row> <customVariablePageName1>HTTP-code</customVariablePageName1> - <customVariablePageValue1>200</customVariablePageValue1> + <customVariablePageValue1>500</customVariablePageValue1> </row> </customVariables> <generationTime>0.11s</generationTime> @@ -1964,13 +1964,13 @@ <operatingSystemIcon>plugins/DevicesDetection/images/os/MAC.gif</operatingSystemIcon> <operatingSystemCode>MAC</operatingSystemCode> <operatingSystemVersion /> - <browserFamily /> - <browserFamilyDescription>Unknown</browserFamilyDescription> - <browser>Unknown</browser> - <browserName>Unknown</browserName> - <browserIcon>plugins/DevicesDetection/images/browsers/UNK.gif</browserIcon> - <browserCode>UNK</browserCode> - <browserVersion /> + <browserFamily>Trident</browserFamily> + <browserFamilyDescription>Trident (IE)</browserFamilyDescription> + <browser>Internet Explorer 5.0</browser> + <browserName>Internet Explorer</browserName> + <browserIcon>plugins/DevicesDetection/images/browsers/IE.gif</browserIcon> + <browserCode>IE</browserCode> + <browserVersion>5.0</browserVersion> <events>0</events> <continent>Unknown</continent> <continentCode>unk</continentCode> diff --git a/tests/PHPUnit/System/expected/test_ImportLogs__Resolution.getConfiguration_month.xml b/tests/PHPUnit/System/expected/test_ImportLogs__Resolution.getConfiguration_month.xml index c1067505c8ff6564ba404a99921ebd15f4a27371..110eafe235319c4a5284ac3cfb146201e461c727 100644 --- a/tests/PHPUnit/System/expected/test_ImportLogs__Resolution.getConfiguration_month.xml +++ b/tests/PHPUnit/System/expected/test_ImportLogs__Resolution.getConfiguration_month.xml @@ -122,7 +122,7 @@ <sum_daily_nb_users>0</sum_daily_nb_users> </row> <row> - <label>Mac / Unknown / unknown</label> + <label>Mac / Internet Explorer / unknown</label> <nb_visits>1</nb_visits> <nb_actions>1</nb_actions> <max_actions>1</max_actions> diff --git a/tests/PHPUnit/System/expected/test_ImportLogs__UserSettings.getBrowserType_month.xml b/tests/PHPUnit/System/expected/test_ImportLogs__UserSettings.getBrowserType_month.xml index 1fb125914df754adc2c4906c67bcd419e288f625..fc9865b7253efb025872ac57873a03a674645cbe 100644 --- a/tests/PHPUnit/System/expected/test_ImportLogs__UserSettings.getBrowserType_month.xml +++ b/tests/PHPUnit/System/expected/test_ImportLogs__UserSettings.getBrowserType_month.xml @@ -14,13 +14,13 @@ </row> <row> <label>Trident (IE)</label> - <nb_visits>7</nb_visits> - <nb_actions>8</nb_actions> + <nb_visits>8</nb_visits> + <nb_actions>9</nb_actions> <max_actions>2</max_actions> <sum_visit_length>242</sum_visit_length> - <bounce_count>6</bounce_count> - <nb_visits_converted>7</nb_visits_converted> - <sum_daily_nb_uniq_visitors>7</sum_daily_nb_uniq_visitors> + <bounce_count>7</bounce_count> + <nb_visits_converted>8</nb_visits_converted> + <sum_daily_nb_uniq_visitors>8</sum_daily_nb_uniq_visitors> <sum_daily_nb_users>0</sum_daily_nb_users> <segment>browserEngine==Trident</segment> </row> @@ -38,13 +38,13 @@ </row> <row> <label>Unknown</label> - <nb_visits>5</nb_visits> - <nb_actions>5</nb_actions> + <nb_visits>4</nb_visits> + <nb_actions>4</nb_actions> <max_actions>1</max_actions> <sum_visit_length>0</sum_visit_length> - <bounce_count>5</bounce_count> - <nb_visits_converted>2</nb_visits_converted> - <sum_daily_nb_uniq_visitors>5</sum_daily_nb_uniq_visitors> + <bounce_count>4</bounce_count> + <nb_visits_converted>1</nb_visits_converted> + <sum_daily_nb_uniq_visitors>4</sum_daily_nb_uniq_visitors> <sum_daily_nb_users>0</sum_daily_nb_users> <segment>browserEngine==</segment> </row> diff --git a/tests/PHPUnit/System/expected/test_ImportLogs__UserSettings.getBrowserVersion_month.xml b/tests/PHPUnit/System/expected/test_ImportLogs__UserSettings.getBrowserVersion_month.xml index 2c687028a7925440747ce1ae063069a54e1f7bb0..d6572a8ba39d449e3dbfe520e6ccc27652b66a63 100644 --- a/tests/PHPUnit/System/expected/test_ImportLogs__UserSettings.getBrowserVersion_month.xml +++ b/tests/PHPUnit/System/expected/test_ImportLogs__UserSettings.getBrowserVersion_month.xml @@ -41,13 +41,13 @@ </row> <row> <label>Unknown</label> - <nb_visits>5</nb_visits> - <nb_actions>5</nb_actions> + <nb_visits>4</nb_visits> + <nb_actions>4</nb_actions> <max_actions>1</max_actions> <sum_visit_length>0</sum_visit_length> - <bounce_count>5</bounce_count> - <nb_visits_converted>2</nb_visits_converted> - <sum_daily_nb_uniq_visitors>5</sum_daily_nb_uniq_visitors> + <bounce_count>4</bounce_count> + <nb_visits_converted>1</nb_visits_converted> + <sum_daily_nb_uniq_visitors>4</sum_daily_nb_uniq_visitors> <sum_daily_nb_users>0</sum_daily_nb_users> <segment>browserCode==UNK;browserVersion==</segment> <logo>plugins/DevicesDetection/images/browsers/UNK.gif</logo> @@ -169,6 +169,19 @@ <segment>browserCode==CM;browserVersion==39.0</segment> <logo>plugins/DevicesDetection/images/browsers/CM.gif</logo> </row> + <row> + <label>Internet Explorer 5.0</label> + <nb_visits>1</nb_visits> + <nb_actions>1</nb_actions> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>1</bounce_count> + <nb_visits_converted>1</nb_visits_converted> + <sum_daily_nb_uniq_visitors>1</sum_daily_nb_uniq_visitors> + <sum_daily_nb_users>0</sum_daily_nb_users> + <segment>browserCode==IE;browserVersion==5.0</segment> + <logo>plugins/DevicesDetection/images/browsers/IE.gif</logo> + </row> <row> <label>Internet Explorer 6.0</label> <nb_visits>1</nb_visits> diff --git a/tests/PHPUnit/System/expected/test_ImportLogs__UserSettings.getBrowser_month.xml b/tests/PHPUnit/System/expected/test_ImportLogs__UserSettings.getBrowser_month.xml index 16aa9095e1b6b1a5c9a567ab6e140c0d53c1bcfd..6f3990403608c574b490864f64d9f09da338f182 100644 --- a/tests/PHPUnit/System/expected/test_ImportLogs__UserSettings.getBrowser_month.xml +++ b/tests/PHPUnit/System/expected/test_ImportLogs__UserSettings.getBrowser_month.xml @@ -15,13 +15,13 @@ </row> <row> <label>Internet Explorer</label> - <nb_visits>7</nb_visits> - <nb_actions>8</nb_actions> + <nb_visits>8</nb_visits> + <nb_actions>9</nb_actions> <max_actions>2</max_actions> <sum_visit_length>242</sum_visit_length> - <bounce_count>6</bounce_count> - <nb_visits_converted>7</nb_visits_converted> - <sum_daily_nb_uniq_visitors>7</sum_daily_nb_uniq_visitors> + <bounce_count>7</bounce_count> + <nb_visits_converted>8</nb_visits_converted> + <sum_daily_nb_uniq_visitors>8</sum_daily_nb_uniq_visitors> <sum_daily_nb_users>0</sum_daily_nb_users> <logo>plugins/DevicesDetection/images/browsers/IE.gif</logo> <segment>browserCode==IE</segment> @@ -54,13 +54,13 @@ </row> <row> <label>Unknown</label> - <nb_visits>5</nb_visits> - <nb_actions>5</nb_actions> + <nb_visits>4</nb_visits> + <nb_actions>4</nb_actions> <max_actions>1</max_actions> <sum_visit_length>0</sum_visit_length> - <bounce_count>5</bounce_count> - <nb_visits_converted>2</nb_visits_converted> - <sum_daily_nb_uniq_visitors>5</sum_daily_nb_uniq_visitors> + <bounce_count>4</bounce_count> + <nb_visits_converted>1</nb_visits_converted> + <sum_daily_nb_uniq_visitors>4</sum_daily_nb_uniq_visitors> <sum_daily_nb_users>0</sum_daily_nb_users> <logo>plugins/DevicesDetection/images/browsers/UNK.gif</logo> <segment>browserCode==UNK</segment> diff --git a/tests/PHPUnit/System/expected/test_ImportLogs__UserSettings.getConfiguration_month.xml b/tests/PHPUnit/System/expected/test_ImportLogs__UserSettings.getConfiguration_month.xml index c1067505c8ff6564ba404a99921ebd15f4a27371..110eafe235319c4a5284ac3cfb146201e461c727 100644 --- a/tests/PHPUnit/System/expected/test_ImportLogs__UserSettings.getConfiguration_month.xml +++ b/tests/PHPUnit/System/expected/test_ImportLogs__UserSettings.getConfiguration_month.xml @@ -122,7 +122,7 @@ <sum_daily_nb_users>0</sum_daily_nb_users> </row> <row> - <label>Mac / Unknown / unknown</label> + <label>Mac / Internet Explorer / unknown</label> <nb_visits>1</nb_visits> <nb_actions>1</nb_actions> <max_actions>1</max_actions> diff --git a/tests/PHPUnit/System/expected/test_ImportLogs__UserSettings.getMobileVsDesktop_month.xml b/tests/PHPUnit/System/expected/test_ImportLogs__UserSettings.getMobileVsDesktop_month.xml index 9daee7778d332fda4deb63cb29286396dcac3777..af1de4ce8e7a0eb92c4227d2c9726cde73410a06 100644 --- a/tests/PHPUnit/System/expected/test_ImportLogs__UserSettings.getMobileVsDesktop_month.xml +++ b/tests/PHPUnit/System/expected/test_ImportLogs__UserSettings.getMobileVsDesktop_month.xml @@ -62,6 +62,12 @@ <segment>deviceType==feature+phone</segment> <logo>plugins/DevicesDetection/images/screens/mobile.gif</logo> </row> + <row> + <label>Phablet</label> + <nb_visits>0</nb_visits> + <segment>deviceType==phablet</segment> + <logo>plugins/DevicesDetection/images/screens/unknown.gif</logo> + </row> <row> <label>Portable media player</label> <nb_visits>0</nb_visits> diff --git a/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits__DevicesDetection.getType_day.xml b/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits__DevicesDetection.getType_day.xml index 3f2929faffdd0b59f1c54d1153b728f35b8c7f80..745f9c609537f910d30bda2a47238bdf42573f0d 100644 --- a/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits__DevicesDetection.getType_day.xml +++ b/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits__DevicesDetection.getType_day.xml @@ -36,6 +36,12 @@ <segment>deviceType==feature+phone</segment> <logo>plugins/DevicesDetection/images/screens/mobile.gif</logo> </row> + <row> + <label>Phablet</label> + <nb_visits>0</nb_visits> + <segment>deviceType==phablet</segment> + <logo>plugins/DevicesDetection/images/screens/unknown.gif</logo> + </row> <row> <label>Portable media player</label> <nb_visits>0</nb_visits> diff --git a/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits__UserSettings.getMobileVsDesktop_day.xml b/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits__UserSettings.getMobileVsDesktop_day.xml index 3f2929faffdd0b59f1c54d1153b728f35b8c7f80..745f9c609537f910d30bda2a47238bdf42573f0d 100644 --- a/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits__UserSettings.getMobileVsDesktop_day.xml +++ b/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits__UserSettings.getMobileVsDesktop_day.xml @@ -36,6 +36,12 @@ <segment>deviceType==feature+phone</segment> <logo>plugins/DevicesDetection/images/screens/mobile.gif</logo> </row> + <row> + <label>Phablet</label> + <nb_visits>0</nb_visits> + <segment>deviceType==phablet</segment> + <logo>plugins/DevicesDetection/images/screens/unknown.gif</logo> + </row> <row> <label>Portable media player</label> <nb_visits>0</nb_visits> diff --git a/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits_withCookieSupport__DevicesDetection.getType_day.xml b/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits_withCookieSupport__DevicesDetection.getType_day.xml index 31bbd93f01fb0d3939d19099c7612ad8295b70c5..6b1fedb0c5a7622ea6d4a011888cca6f569a0c8f 100644 --- a/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits_withCookieSupport__DevicesDetection.getType_day.xml +++ b/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits_withCookieSupport__DevicesDetection.getType_day.xml @@ -37,6 +37,12 @@ <segment>deviceType==feature+phone</segment> <logo>plugins/DevicesDetection/images/screens/mobile.gif</logo> </row> + <row> + <label>Phablet</label> + <nb_visits>0</nb_visits> + <segment>deviceType==phablet</segment> + <logo>plugins/DevicesDetection/images/screens/unknown.gif</logo> + </row> <row> <label>Portable media player</label> <nb_visits>0</nb_visits> diff --git a/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits_withCookieSupport__UserSettings.getMobileVsDesktop_day.xml b/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits_withCookieSupport__UserSettings.getMobileVsDesktop_day.xml index 31bbd93f01fb0d3939d19099c7612ad8295b70c5..6b1fedb0c5a7622ea6d4a011888cca6f569a0c8f 100644 --- a/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits_withCookieSupport__UserSettings.getMobileVsDesktop_day.xml +++ b/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits_withCookieSupport__UserSettings.getMobileVsDesktop_day.xml @@ -37,6 +37,12 @@ <segment>deviceType==feature+phone</segment> <logo>plugins/DevicesDetection/images/screens/mobile.gif</logo> </row> + <row> + <label>Phablet</label> + <nb_visits>0</nb_visits> + <segment>deviceType==phablet</segment> + <logo>plugins/DevicesDetection/images/screens/unknown.gif</logo> + </row> <row> <label>Portable media player</label> <nb_visits>0</nb_visits> diff --git a/tests/PHPUnit/System/expected/test_apiGetReportMetadata__API.getSegmentsMetadata.xml b/tests/PHPUnit/System/expected/test_apiGetReportMetadata__API.getSegmentsMetadata.xml index add85693bbb308c173d7735681137fa77c3a3876..b7936fb1ab7717025238e500faa9f36dfd88723c 100644 --- a/tests/PHPUnit/System/expected/test_apiGetReportMetadata__API.getSegmentsMetadata.xml +++ b/tests/PHPUnit/System/expected/test_apiGetReportMetadata__API.getSegmentsMetadata.xml @@ -134,7 +134,7 @@ <category>Visit</category> <name>Device type</name> <segment>deviceType</segment> - <acceptedValues>desktop, smartphone, tablet, feature phone, console, tv, car browser, smart display, camera, portable media player</acceptedValues> + <acceptedValues>desktop, smartphone, tablet, feature phone, console, tv, car browser, smart display, camera, portable media player, phablet</acceptedValues> </row> <row> <type>dimension</type> diff --git a/tests/PHPUnit/TestingEnvironment.php b/tests/PHPUnit/TestingEnvironment.php index dc0b6adf774f6572260eea3f5a4af711ae8e1f5b..b7590eee17cc36155f16e6de8c56fc83b6818af1 100644 --- a/tests/PHPUnit/TestingEnvironment.php +++ b/tests/PHPUnit/TestingEnvironment.php @@ -168,6 +168,16 @@ class Piwik_TestingEnvironment } } + if ($testingEnvironment->testCaseClass) { + $testCaseClass = $testingEnvironment->testCaseClass; + if (class_exists($testCaseClass)) { + $testCase = new $testCaseClass(); + if (method_exists($testCase, 'provideContainerConfig')) { + $diConfig = array_merge($diConfig, $testCase->provideContainerConfig()); + } + } + } + if (!empty($diConfig)) { StaticContainer::addDefinitions($diConfig); } diff --git a/tests/PHPUnit/Unit/API/ResponseBuilderTest.php b/tests/PHPUnit/Unit/API/ResponseBuilderTest.php index d9e67698189121322f294070703323d51fa52bb7..14409c191380e9da5ac1340c62096ce9dfbd3004 100644 --- a/tests/PHPUnit/Unit/API/ResponseBuilderTest.php +++ b/tests/PHPUnit/Unit/API/ResponseBuilderTest.php @@ -10,6 +10,7 @@ namespace Piwik\Tests\Unit\API; use Exception; use Piwik\API\ResponseBuilder; +use Piwik\Config; use Piwik\DataTable; use Piwik\Plugin\Manager; @@ -184,91 +185,164 @@ class ResponseBuilderTest extends \PHPUnit_Framework_TestCase public function test_getResponse_shouldApplyFilterLimitOnIndexedArray() { - $input = range(0, 100); - - $builder = new ResponseBuilder('php', array('serialize' => 0, 'filter_limit' => 15)); - $response = $builder->getResponse($input); - - $this->assertEquals(range(0, 14), $response); + $input = range(0, 100); + $expected = range(0, 14); + $this->assertLimitedResponseEquals($expected, $input, $limit = 15, $offset = null); } public function test_getResponse_shouldReturnEmptyArrayOnIndexedArray_IfOffsetIsTooHigh() { $input = range(0, 100); - - $builder = new ResponseBuilder('php', array('serialize' => 0, 'filter_limit' => 15, 'filter_offset' => 200)); - $response = $builder->getResponse($input); - - $this->assertEquals(array(), $response); + $this->assertLimitedResponseEquals(array(), $input, $limit = 15, $offset = 200); } public function test_getResponse_shouldReturnAllOnIndexedArray_IfLimitIsTooHigh() { $input = range(0, 100); - - $builder = new ResponseBuilder('php', array('serialize' => 0, 'filter_limit' => 200)); - $response = $builder->getResponse($input); - - $this->assertEquals($input, $response); + $this->assertLimitedResponseEquals($input, $input, $limit = 200, $offset = null); } public function test_getResponse_shouldNotApplyFilterLimitOnIndexedArrayIfParamNotSet() { $input = range(0, 100); + $this->assertLimitedResponseEquals($input, $input, $limit = null, $offset = null); + } - $builder = new ResponseBuilder('php', array('serialize' => 0)); + public function test_getResponse_shouldApplyLimitOnIndexedArray_IfLimitIsDefaultFilterLimitValue() + { + $limit = Config::getInstance()->General['API_datatable_default_limit']; + $input = range(0, 2000); + $expected = range(0, $limit - 1); + $this->assertLimitedResponseEquals($expected, $input, $limit, $offset = 0); + } + + private function assertLimitedResponseEquals($expectedResponse, $input, $limit, $offset = 0) + { + $params = array('serialize' => 0); + + if (!is_null($limit)) { + $params['filter_limit'] = $limit; + } + + if (!is_null($offset)) { + $params['filter_offset'] = $offset; + } + + $builder = new ResponseBuilder('php', $params); $response = $builder->getResponse($input); - $this->assertEquals($input, $response); + $this->assertEquals($expectedResponse, $response); } - public function test_getResponse_shouldApplyFilterOffsetOnIndexedArray_IfFilterLimitIsGiven() + public function test_getResponse_shouldNotApplyLimit_IfLimitIsDefaultFilterLimitValueAndSetBySystem() { - $input = range(0, 100); + $input = range(0, 200); + $limit = Config::getInstance()->General['API_datatable_default_limit']; + + $builder = new ResponseBuilder('php', array( + 'serialize' => 0, + 'api_datatable_default_limit' => $limit, + 'filter_limit' => $limit, + 'filter_offset' => 0)); + $response = $builder->getResponse($input); + + $this->assertEquals(range(0, 200), $response); + } - $builder = new ResponseBuilder('php', array('serialize' => 0, 'filter_limit' => 15, 'filter_offset' => 30)); + public function test_getResponse_shouldApplyLimit_IfLimitIsSetBySystemButDifferentToDefaultLimit() + { + $input = range(0, 200); + $defaultLimit = Config::getInstance()->General['API_datatable_default_limit']; + $limit = $defaultLimit - 1; + + $builder = new ResponseBuilder('php', array( + 'serialize' => 0, + 'api_datatable_default_limit' => $defaultLimit, + 'filter_limit' => $limit, + 'filter_offset' => 0)); $response = $builder->getResponse($input); - $this->assertEquals(range(30, 44), $response); + $this->assertEquals(range(0, $limit - 1), $response); + } + + public function test_getResponse_shouldApplyFilterOffsetOnIndexedArray_IfFilterLimitIsGiven() + { + $input = range(0, 100); + $expected = range(30, 44); + $this->assertLimitedResponseEquals($expected, $input, $limit = 15, $offset = 30); } public function test_getResponse_shouldNotApplyFilterOffsetOnIndexedArray_IfNoFilterLimitIsSetButOffset() { $input = range(0, 100); - - $builder = new ResponseBuilder('php', array('serialize' => 0, 'filter_offset' => 30)); - $response = $builder->getResponse($input); - - $this->assertEquals($input, $response); + $this->assertLimitedResponseEquals($input, $input, $limit = null, $offset = 30); } public function test_getResponse_shouldReturnEmptyArrayOnIndexedArray_IfFilterLimitIsZero() { $input = range(0, 100); - - $builder = new ResponseBuilder('php', array('serialize' => 0, 'filter_limit' => 0, 'filter_offset' => 30)); - $response = $builder->getResponse($input); - - $this->assertEquals(array(), $response); + $this->assertLimitedResponseEquals($expected = array(), $input, $limit = 0, $offset = 30); } public function test_getResponse_shouldIgnoreFilterOffsetOnIndexedArray_IfFilterLimitIsMinusOne() { $input = range(0, 100); + $this->assertLimitedResponseEquals($input, $input, $limit = -1, $offset = 30); + } + + public function test_getResponse_shouldReturnAllOnIndexedArray_IfFilterLimitIsMinusOne() + { + $input = range(0, 100); + $this->assertLimitedResponseEquals($input, $input, $limit = -1, $offset = null); + } - $builder = new ResponseBuilder('php', array('serialize' => 0, 'filter_limit' => -1, 'filter_offset' => 30)); + public function test_getResponse_shouldApplyPattern_IfFilterColumnAndPatternIsGiven() + { + $input = array( + 0 => array('name' => 'google', 'url' => 'www.google.com'), + 1 => array('name' => 'ask', 'url' => 'www.ask.com'), + 2 => array('name' => 'piwik', 'url' => 'piwik.org'), + 3 => array('url' => 'nz.yahoo.com'), + 4 => array('name' => 'amazon', 'url' => 'amazon.com'), + 5 => array('url' => 'nz.piwik.org'), + ); + + $builder = new ResponseBuilder('php', array( + 'serialize' => 0, + 'filter_limit' => -1, + 'filter_column' => array('name', 'url'), + 'filter_pattern' => 'piwik' + )); $response = $builder->getResponse($input); - $this->assertEquals($input, $response); + $expected = array( + 2 => array('name' => 'piwik', 'url' => 'piwik.org'), + 5 => array('url' => 'nz.piwik.org'), + ); + $this->assertEquals($expected, $response); } - public function test_getResponse_shouldReturnAllOnIndexedArray_IfFilterLimitIsMinusOne() + public function test_getResponse_shouldBeAbleToApplyColumFilterAndLimitFilterOnIndexedAssociativeArray() { - $input = range(0, 100); + $input = array(); + for ($i = 0; $i < 10; $i++) { + $input[] = array('test' => 'two' . $i, 'test2' => 'three'); + } + + $limit = 3; - $builder = new ResponseBuilder('php', array('serialize' => 0, 'filter_limit' => -1)); + $builder = new ResponseBuilder('php', array( + 'serialize' => 0, + 'filter_limit' => $limit, + 'filter_offset' => 3, + 'showColumns' => 'test' + )); $response = $builder->getResponse($input); - $this->assertEquals($input, $response); + $this->assertEquals(array( + 0 => array('test' => 'two3'), + 1 => array('test' => 'two4'), + 2 => array('test' => 'two5'), + ), $response); } } diff --git a/tests/PHPUnit/Unit/Config/IniFileChainTest.php b/tests/PHPUnit/Unit/Config/IniFileChainTest.php index ca5e0797a84bc1ee7206001d00bd81b252934988..137b6c234dc6f68de6cb8e78a3016fa40b399026 100644 --- a/tests/PHPUnit/Unit/Config/IniFileChainTest.php +++ b/tests/PHPUnit/Unit/Config/IniFileChainTest.php @@ -244,6 +244,16 @@ class IniFileChainTest extends PHPUnit_Framework_TestCase $this->assertEquals(array('var1' => 'val"ue2', 'var3' => array('value3', 'value4')), $fileChain->getFrom($defaultSettingsPath, 'Section1')); } + public function test_getFrom_CorrectlyReturnsUnencodedValue() + { + $userSettingsPath = __DIR__ . '/test_files/special_values.ini.php'; + $fileChain = new IniFileChain(array(), $userSettingsPath); + + $this->assertEquals(array( + 'value1' => 'a"bc', 'value2' => array('<script>', '${@piwik(crash))}' + )), $fileChain->getFrom($userSettingsPath, 'Section')); + } + public function getTestDataForDumpTest() { return array( diff --git a/tests/PHPUnit/Unit/Config/test_files/special_values.ini.php b/tests/PHPUnit/Unit/Config/test_files/special_values.ini.php new file mode 100644 index 0000000000000000000000000000000000000000..4253ccbbe89ad780a3ea835d2ffa03c2414c32ef --- /dev/null +++ b/tests/PHPUnit/Unit/Config/test_files/special_values.ini.php @@ -0,0 +1,4 @@ +[Section] +value1 = "a"bc" +value2[] = "<script>" +value2[] = "${@piwik(crash))}" diff --git a/tests/PHPUnit/Unit/ConfigTest.php b/tests/PHPUnit/Unit/ConfigTest.php index 162d22a794adb564fec4512c088145b2edf8a62f..6ccd93a0fd30841f8af080e0f1427af2d7181479 100644 --- a/tests/PHPUnit/Unit/ConfigTest.php +++ b/tests/PHPUnit/Unit/ConfigTest.php @@ -397,4 +397,42 @@ class ConfigTest extends \PHPUnit_Framework_TestCase @unlink($configFile); } -} \ No newline at end of file + + public function testFromGlobalConfig() + { + $userFile = PIWIK_INCLUDE_PATH . '/tests/resources/Config/config.ini.php'; + $globalFile = PIWIK_INCLUDE_PATH . '/tests/resources/Config/global.ini.php'; + $commonFile = PIWIK_INCLUDE_PATH . '/tests/resources/Config/common.config.ini.php'; + + $config = new Config($globalFile, $userFile, $commonFile); + + $configCategory = $config->getFromGlobalConfig('Category'); + $this->assertEquals('value1', $configCategory['key1']); + $this->assertEquals('value2', $configCategory['key2']); + $this->assertEquals(array('key1' => 'value1', 'key2' => 'value2'), $configCategory); + } + + public function testFromCommonConfig() + { + $userFile = PIWIK_INCLUDE_PATH . '/tests/resources/Config/config.ini.php'; + $globalFile = PIWIK_INCLUDE_PATH . '/tests/resources/Config/global.ini.php'; + $commonFile = PIWIK_INCLUDE_PATH . '/tests/resources/Config/common.config.ini.php'; + + $config = new Config($globalFile, $userFile, $commonFile); + + $configCategory = $config->getFromCommonConfig('Category'); + $this->assertEquals(array('key2' => 'valueCommon', 'key3' => '${@piwik(crash))}'), $configCategory); + } + + public function testFromLocalConfig() + { + $userFile = PIWIK_INCLUDE_PATH . '/tests/resources/Config/config.ini.php'; + $globalFile = PIWIK_INCLUDE_PATH . '/tests/resources/Config/global.ini.php'; + $commonFile = PIWIK_INCLUDE_PATH . '/tests/resources/Config/common.config.ini.php'; + + $config = new Config($globalFile, $userFile, $commonFile); + + $configCategory = $config->getFromLocalConfig('Category'); + $this->assertEquals(array('key1' => 'value_overwritten'), $configCategory); + } +} diff --git a/tests/PHPUnit/Unit/DataTable/Filter/PatternTest.php b/tests/PHPUnit/Unit/DataTable/Filter/PatternTest.php index 5c97588a3c22fa1fa0f6a30953271c70b6681c99..fa9d3b60f2d3fd4c74d77c00eeb2d33fa7d34c19 100644 --- a/tests/PHPUnit/Unit/DataTable/Filter/PatternTest.php +++ b/tests/PHPUnit/Unit/DataTable/Filter/PatternTest.php @@ -13,8 +13,9 @@ use Piwik\DataTable\Row; /** * @group DataTableTest + * @group Core */ -class DataTable_Filter_PatternTest extends \PHPUnit_Framework_TestCase +class PatternTest extends \PHPUnit_Framework_TestCase { /** * Dataprovider for testFilterPattern @@ -22,23 +23,22 @@ class DataTable_Filter_PatternTest extends \PHPUnit_Framework_TestCase public function getTestData() { return array( - array(array('ask', array(1))), - array(array('oo', array(0, 3))), - array(array('^yah', array(3))), - array(array('\*', array(6))), - array(array('2/4', array(5))), - array(array('amazon|yahoo', array(3, 4))), + array('ask', array(1)), + array('oo', array(0, 3)), + array('^yah', array(3)), + array('\*', array(6)), + array('2/4', array(5)), + array('amazon|yahoo', array(3, 4)), ); } /** * Test to filter a column with a pattern * - * @group Core * _Pattern * @dataProvider getTestData */ - public function testFilterPattern($test) + public function testFilterPattern($pattern, $expectedRows) { $table = new DataTable; @@ -56,8 +56,6 @@ class DataTable_Filter_PatternTest extends \PHPUnit_Framework_TestCase $table->addRowsFromArray($rows); $rowIds = array_keys($rows); - $pattern = $test[0]; - $expectedRows = $test[1]; $rowToDelete = array_diff($rowIds, $expectedRows); $expectedtable = clone $table; $expectedtable->deleteRows($rowToDelete); @@ -65,4 +63,63 @@ class DataTable_Filter_PatternTest extends \PHPUnit_Framework_TestCase $filteredTable->filter('Pattern', array('label', $pattern)); $this->assertEquals($expectedtable->getRows(), $filteredTable->getRows()); } + + /** + * @dataProvider getTestData + */ + public function testFilterArrayPattern_OneColumn($pattern, $expectedRows) + { + $rows = array( + array('label' => 'google'), + array('label' => 'ask'), + array('label' => 'piwik'), + array('label' => 'yahoo'), + array('label' => 'amazon'), + array('label' => '2389752/47578949'), + array('label' => 'Q*(%&*("$&%*(&"$*")"))') + ); + + $filter = new DataTable\Filter\Pattern(new DataTable(), array('label'), $pattern); + $filteredRows = $filter->filterArray($rows); + + $this->assertSame($expectedRows, array_keys($filteredRows)); + } + + /** + * @dataProvider getTestDataTwoColumns + */ + public function testFilterArrayPattern_ShouldBeAbleToFilterByTwoColumns_IfMultipleColumnsAreGiven($pattern, $expectedRows) + { + $rows = array( + 0 => array('randomcolumn' => 'ask', 'name' => 'google', 'url' => 'www.google.com'), + 1 => array('randomcolumn' => 'goo', 'name' => 'ask', 'url' => 'www.ask.com'), + 2 => array('randomcolumn' => 'com', 'name' => 'piwik', 'url' => 'piwik.org'), + 3 => array('randomcolumn' => 'com', 'name' => 'yahoo', 'url' => 'nz.yahoo.com'), + 4 => array('randomcolumn' => 'nz1', 'name' => 'amazon', 'url' => 'amazon.com'), + 5 => array('randomcolumn' => 'com', 'url' => 'nz.piwik.org'), + 6 => array('randomcolumn' => 'com', 'name' => 'Piwik') + ); + + $filter = new DataTable\Filter\Pattern(new DataTable(), array('name', 'url'), $pattern); + $filteredRows = $filter->filterArray($rows); + + $this->assertSame($expectedRows, array_keys($filteredRows)); + } + + /** + * Dataprovider for testFilterPattern + */ + public function getTestDataTwoColumns() + { + return array( + array('ask', array(1)), + array('oo', array(0, 3)), + array('^yah', array(3)), + array('nz', array(3, 5)), + array('amazon|yahoo', array(3, 4)), + array('piwik', array(2, 5, 6)), + array('com', array(0, 1, 3, 4)), + array('org', array(2, 5)), + ); + } } diff --git a/tests/UI/expected-ui-screenshots b/tests/UI/expected-ui-screenshots index 8086d6b5babd5e20b0d6347e25b9605e22396f20..e1c7b5f9ade43f751883541898b82dc09ca830ec 160000 --- a/tests/UI/expected-ui-screenshots +++ b/tests/UI/expected-ui-screenshots @@ -1 +1 @@ -Subproject commit 8086d6b5babd5e20b0d6347e25b9605e22396f20 +Subproject commit e1c7b5f9ade43f751883541898b82dc09ca830ec diff --git a/tests/UI/specs/SitesManager_spec.js b/tests/UI/specs/SitesManager_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..cfb3af94e35dd944bf6debbf780761fd133f1228 --- /dev/null +++ b/tests/UI/specs/SitesManager_spec.js @@ -0,0 +1,82 @@ +/*! + * Piwik - free/libre analytics platform + * + * Site selector screenshot tests. + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +describe("SitesManager", function () { + this.timeout(0); + this.fixture = "Piwik\\Plugins\\SitesManager\\tests\\Fixtures\\ManySites"; + + var url = "?module=SitesManager&action=index&idSite=1&period=day&date=yesterday&showaddsite=false"; + + function assertScreenshotEquals(screenshotName, done, test) + { + expect.screenshot(screenshotName).to.be.captureSelector('#content', test, done); + } + + function loadNextPage(page) + { + page.click('.SitesManager .paging:first .next'); + } + + function loadPreviousPage(page) + { + page.click('.SitesManager .paging:first .prev'); + } + + function searchForText(page, textToAppendToSearchField) + { + page.sendKeys(".SitesManager .search:first input", textToAppendToSearchField); + page.click('.SitesManager .search:first .submit'); + page.wait(150); + } + + it("should load correctly and show page 0", function (done) { + assertScreenshotEquals("loaded", done, function (page) { + page.load(url); + page.evaluate(function () { + $('.ui-inline-help:contains(UTC time is)').hide(); + }); + }); + }); + + it("should show page 1 when clicking next", function (done) { + assertScreenshotEquals("page_1", done, function (page) { + loadNextPage(page); + }); + }); + + it("should show page 2 when clicking next", function (done) { + assertScreenshotEquals("page_2", done, function (page) { + loadNextPage(page); + }); + }); + + it("should show page 1 when clicking prev", function (done) { + assertScreenshotEquals("page_1_again", done, function (page) { + loadPreviousPage(page); + }); + }); + + it("should search for websites and reset page to 0", function (done) { + assertScreenshotEquals("search", done, function (page) { + searchForText(page, 'SiteTes'); + }); + }); + + it("should page within search result to page 1", function (done) { + assertScreenshotEquals("search_page_1", done, function (page) { + loadNextPage(page); + }); + }); + + it("should search for websites no result", function (done) { + assertScreenshotEquals("search_no_result", done, function (page) { + searchForText(page, 'RanDoMSearChTerm'); + }); + }); +}); \ No newline at end of file diff --git a/tests/resources/access-logs/fake_logs_replay.log b/tests/resources/access-logs/fake_logs_replay.log index 9f8db06c9d71563601eda894528548f3d0ec6b68..625cd75a417973ff0f622a7695ce985111bee177 100644 --- a/tests/resources/access-logs/fake_logs_replay.log +++ b/tests/resources/access-logs/fake_logs_replay.log @@ -9,20 +9,20 @@ #idsite=1 replay logs from demo (IP were changed) 84.194.72.21 - - [12/Mar/2014:01:34:15 +0100] "GET /piwik.php?action_name=Liberate%20Web%20Analytics%20-%20Analytics%20-%20Piwik&idsite=1&rec=1&r=045456&h=1&m=34&s=18&url=http%3A%2F%2Fpiwik.org%2F&urlref=http%3A%2F%2Fmusicforeveryoneradio.be%3A2222%2FCMD_PLUGINS%2Finstallatron%2Findex.raw&_id=e01d58157d66e023&_idts=1394670858&_idvc=1&_idn=1&_refts=1394670858&_viewts=1394670858&_ref=http%3A%2F%2Fmusicforeveryoneradio.be%3A2222%2FCMD_PLUGINS%2Finstallatron%2Findex.raw&pdf=1&qt=0&realp=0&wma=0&dir=0&fla=1&java=1&gears=0&ag=1&cookie=1&res=1920x1080&_cvar=%7B%221%22%3A%5B%22Domain%20landed%22%2C%22piwik.org%22%5D%7D>_ms=58 HTTP/1.1" 200 43 "http://piwik.org/" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 Safari/537.36" -93.80.130.163 - - [13/Mar/2014:01:32:23 +0100] "GET /piwik.php?action_name=Liberate%20Web%20Analytics%20-%20Analytics%20-%20Piwik&idsite=1&rec=1&r=293471&h=4&m=28&s=18&url=http%3A%2F%2Fpiwik.org%2F&urlref=http%3A%2F%2Fwww.google.ru%2Furl%3Fsa%3Dt%26rct%3Dj%26q%3D%26esrc%3Ds%26source%3Dweb%26cd%3D1%26ved%3D0CCoQFjAA%26url%3Dhttp%253A%252F%252Fpiwik.org%252F%26ei%3DffwgU57VGuf44QT3-oCQCw%26usg%3DAFQjCNF_MGJRqKPvaKuUokHtZ3VvNG9ALw%26bvm%3Dbv.62922401%2Cd.bGE%26cad%3Drjt&_id=f077de87fa56021e&_idts=1394670499&_idvc=1&_idn=1&_refts=1394670499&_viewts=1394670499&_ref=http%3A%2F%2Fwww.google.ru%2Furl%3Fsa%3Dt%26rct%3Dj%26q%3D%26esrc%3Ds%26source%3Dweb%26cd%3D1%26ved%3D0CCoQFjAA%26url%3Dhttp%253A%252F%252Fpiwik.org%252F%26ei%3DffwgU57VGuf44QT3-oCQCw%26usg%3DAFQjCNF_MGJRqKPvaKuUokHtZ3VvNG9ALw%26bvm%3Dbv.62922401%2Cd.bGE%26cad%3Drjt&pdf=0&qt=0&realp=0&wma=0&dir=0&fla=1&java=1&gears=0&ag=0&cookie=1&res=1920x1080&_cvar=%7B%221%22%3A%5B%22Domain%20landed%22%2C%22piwik.org%22%5D%7D>_ms=182 HTTP/1.1" 200 43 "http://piwik.org/" "Mozilla/5.0 (Windows NT 6.2; rv:27.0) Gecko/20100101 Firefox/27.0" +93.80.130.163 - - [13/Mar/2014:01:32:23 +0100] "GET /piwik.php?action_name=Liberate%20Web%20Analytics%20-%20Analytics%20-%20Piwik&idsite=1&rec=1&r=293471&h=4&m=28&s=18&url=http%3A%2F%2Fpiwik.org%2F&urlref=http%3A%2F%2Fwww.google.ru%2Furl%3Fsa%3Dt%26rct%3Dj%26q%3D%26esrc%3Ds%26source%3Dweb%26cd%3D1%26ved%3D0CCoQFjAA%26url%3Dhttp%253A%252F%252Fpiwik.org%252F%26ei%3DffwgU57VGuf44QT3-oCQCw%26usg%3DAFQjCNF_MGJRqKPvaKuUokHtZ3VvNG9ALw%26bvm%3Dbv.62922401%2Cd.bGE%26cad%3Drjt&_id=f077de87fa56021e&_idts=1394670499&_idvc=1&_idn=1&_refts=1394670499&_viewts=1394670499&_ref=http%3A%2F%2Fwww.google.ru%2Furl%3Fsa%3Dt%26rct%3Dj%26q%3D%26esrc%3Ds%26source%3Dweb%26cd%3D1%26ved%3D0CCoQFjAA%26url%3Dhttp%253A%252F%252Fpiwik.org%252F%26ei%3DffwgU57VGuf44QT3-oCQCw%26usg%3DAFQjCNF_MGJRqKPvaKuUokHtZ3VvNG9ALw%26bvm%3Dbv.62922401%2Cd.bGE%26cad%3Drjt&pdf=0&qt=0&realp=0&wma=0&dir=0&fla=1&java=1&gears=0&ag=0&cookie=1&res=1920x1080&_cvar=%7B%221%22%3A%5B%22Domain%20landed%22%2C%22piwik.org%22%5D%7D>_ms=182 HTTP/1.1" 403 43 "http://piwik.org/" "Mozilla/5.0 (Windows NT 6.2; rv:27.0) Gecko/20100101 Firefox/27.0" 176.41.226.154 - - [13/Mar/2014:01:32:24 +0100] "GET /piwik.php?action_name=Hello Installing%20Piwik%20-%20Analytics%20-%20Piwik&idsite=1&rec=1&r=144006&h=2&m=32&s=21&url=http%3A%2F%2Fpiwik.org%2Fdocs%2Finstallation%2F&_id=8b11942ef3b17467&_idts=1392074017&_idvc=5&_idn=0&_refts=1394670293&_viewts=1393965698&_ref=https%3A%2F%2Fwww.google.com.tr%2F&pdf=1&qt=0&realp=0&wma=0&dir=0&fla=1&java=1&gears=0&ag=1&cookie=1&res=1366x768&_cvar=%7B%221%22%3A%5B%22Domain%20landed%22%2C%22piwik.org%22%5D%7D>_ms=134 HTTP/1.1" 200 43 "http://piwik.org/docs/installation/" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 Safari/537.36" -93.80.130.163 - - [13/Mar/2014:01:32:25 +0100] "GET /piwik.php?action_name=Download%20-%20Analytics%20-%20Piwik&idsite=1&rec=1&r=147473&h=4&m=28&s=20&url=http%3A%2F%2Fpiwik.org%2Fdownload%2F&urlref=http%3A%2F%2Fpiwik.org%2F&_id=f077de87fa56021e&_idts=1394670499&_idvc=1&_idn=0&_refts=1394670499&_viewts=1394670499&_ref=http%3A%2F%2Fwww.google.ru%2Furl%3Fsa%3Dt%26rct%3Dj%26q%3D%26esrc%3Ds%26source%3Dweb%26cd%3D1%26ved%3D0CCoQFjAA%26url%3Dhttp%253A%252F%252Fpiwik.org%252F%26ei%3DffwgU57VGuf44QT3-oCQCw%26usg%3DAFQjCNF_MGJRqKPvaKuUokHtZ3VvNG9ALw%26bvm%3Dbv.62922401%2Cd.bGE%26cad%3Drjt&pdf=0&qt=0&realp=0&wma=0&dir=0&fla=1&java=1&gears=0&ag=0&cookie=1&res=1920x1080&_cvar=%7B%221%22%3A%5B%22Domain%20landed%22%2C%22piwik.org%22%5D%7D>_ms=113 HTTP/1.1" 200 43 "http://piwik.org/download/" "Mozilla/5.0 (Windows NT 6.2; rv:27.0) Gecko/20100101 Firefox/27.0" +93.80.130.163 - - [13/Mar/2014:01:32:25 +0100] "GET /piwik.php?action_name=Download%20-%20Analytics%20-%20Piwik&idsite=1&rec=1&r=147473&h=4&m=28&s=20&url=http%3A%2F%2Fpiwik.org%2Fdownload%2F&urlref=http%3A%2F%2Fpiwik.org%2F&_id=f077de87fa56021e&_idts=1394670499&_idvc=1&_idn=0&_refts=1394670499&_viewts=1394670499&_ref=http%3A%2F%2Fwww.google.ru%2Furl%3Fsa%3Dt%26rct%3Dj%26q%3D%26esrc%3Ds%26source%3Dweb%26cd%3D1%26ved%3D0CCoQFjAA%26url%3Dhttp%253A%252F%252Fpiwik.org%252F%26ei%3DffwgU57VGuf44QT3-oCQCw%26usg%3DAFQjCNF_MGJRqKPvaKuUokHtZ3VvNG9ALw%26bvm%3Dbv.62922401%2Cd.bGE%26cad%3Drjt&pdf=0&qt=0&realp=0&wma=0&dir=0&fla=1&java=1&gears=0&ag=0&cookie=1&res=1920x1080&_cvar=%7B%221%22%3A%5B%22Domain%20landed%22%2C%22piwik.org%22%5D%7D>_ms=113 HTTP/1.1" 500 43 "http://piwik.org/download/" "Mozilla/5.0 (Windows NT 6.2; rv:27.0) Gecko/20100101 Firefox/27.0" 93.80.130.163 - - [13/Mar/2014:01:32:32 +0100] "GET /piwik.php?download=http%3A%2F%2Fbuilds.piwik.org%2Flatest.zip&idsite=1&rec=1&r=533958&h=4&m=28&s=25&url=http%3A%2F%2Fpiwik.org%2Fdownload%2F&urlref=http%3A%2F%2Fpiwik.org%2F&_id=f077de87fa56021e&_idts=1394670499&_idvc=1&_idn=0&_refts=1394670499&_viewts=1394670499&_ref=http%3A%2F%2Fwww.google.ru%2Furl%3Fsa%3Dt%26rct%3Dj%26q%3D%26esrc%3Ds%26source%3Dweb%26cd%3D1%26ved%3D0CCoQFjAA%26url%3Dhttp%253A%252F%252Fpiwik.org%252F%26ei%3DffwgU57VGuf44QT3-oCQCw%26usg%3DAFQjCNF_MGJRqKPvaKuUokHtZ3VvNG9ALw%26bvm%3Dbv.62922401%2Cd.bGE%26cad%3Drjt&pdf=0&qt=0&realp=0&wma=0&dir=0&fla=1&java=1&gears=0&ag=0&cookie=1&res=1920x1080&_cvar=%7B%221%22%3A%5B%22Domain%20landed%22%2C%22piwik.org%22%5D%7D>_ms=113 HTTP/1.1" 200 43 "http://piwik.org/download/" "Mozilla/5.0 (Windows NT 6.2; rv:27.0) Gecko/20100101 Firefox/27.0" 188.107.238.9 - - [13/Mar/2014:01:33:11 +0100] "GET /piwik.php?action_name=Log%20Analytics%20-%20Analytics%20-%20Piwik&idsite=1&rec=1&r=993149&h=1&m=33&s=13&url=http%3A%2F%2Fpiwik.org%2Flog-analytics%2F&urlref=http%3A%2F%2Fforum.golem.de%2Fkommentare%2Fsecurity%2Furteil-zu-tracking-nutzer-muessen-piwik-analyse-widersprechen-koennen%2Fpiwik-log-analytics%2F80715%2C3669355%2C3669355%2Cread.html&_id=d6f0dcb66949b86b&_idts=1394570793&_idvc=5&_idn=1&_refts=1394270793&_viewts=1394370793&_ref=http%3A%2F%2Fforum.golem.de%2Fkommentare%2Fsecurity%2Furteil-zu-tracking-nutzer-muessen-piwik-analyse-widersprechen-koennen%2Fpiwik-log-analytics%2F80715%2C3669355%2C3669355%2Cread.html&pdf=1&qt=1&realp=0&wma=1&dir=0&fla=1&java=1&gears=0&ag=1&cookie=1&res=2560x1440&_cvar=%7B%221%22%3A%5B%22Domain%20landed%22%2C%22piwik.org%22%5D%7D>_ms=145 HTTP/1.1" 200 43 "http://piwik.org/log-analytics/" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:27.0) Gecko/20100101 Firefox/27.0" 206.190.75.8 - - [13/Mar/2014:01:33:27 +0100] "GET /piwik.php?action_name=Changelog%20Archive%20-%20Analytics%20-%20Piwik&idsite=1&rec=1&r=697117&h=17&m=33&s=27&url=http%3A%2F%2Fpiwik.org%2Fchangelog%2F&urlref=http%3A%2F%2Fpiwik.org%2Fwhat-is-piwik%2F&_id=e0eb35c4fae2aa0a&_idts=1379033580&_idvc=2&_idn=0&_refts=1394669945&_viewts=1379033693&_ref=http%3A%2F%2Ftrends.builtwith.com%2Fanalytics%2FPiwik-Web-Analytics&res=1536x864&_cvar=%7B%221%22%3A%5B%22Domain%20landed%22%2C%22piwik.org%22%5D%7D HTTP/1.1" 200 43 "http://piwik.org/changelog/" "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; .NET4.0C; InfoPath.2)" 80.136.160.65 - - [13/Mar/2014:01:33:33 +0100] "GET /piwik.php?action_name=Piwik%202.1%20%E2%80%94%20Massive%20Performance%20and%20Reliability%20Improvements%20-%20Analytics%20-%20Piwik&idsite=1&rec=1&r=542198&h=1&m=33&s=31&url=http%3A%2F%2Fpiwik.org%2Fblog%2F2014%2F03%2Fpiwik-2-1-massive-performance-reliability-improvements%2F&urlref=http%3A%2F%2Fberndjung.com%2Fpiwik%2Findex.php%3Fmodule%3DCoreHome%26action%3Dindex%26idSite%3D1%26period%3Dday%26date%3Dtoday&_id=fc96e596bd4c5d54&_idts=1394670812&_idvc=2&_idn=1&_refts=1394670812&_viewts=1394670812&_ref=http%3A%2F%2Fberndjung.com%2Fpiwik%2Findex.php%3Fmodule%3DCoreHome%26action%3Dindex%26idSite%3D1%26period%3Dday%26date%3Dtoday&pdf=0&qt=0&realp=0&wma=0&dir=0&fla=1&java=0&gears=0&ag=0&cookie=1&res=360x640&_cvar=%7B%221%22%3A%5B%22Domain%20landed%22%2C%22piwik.org%22%5D%7D>_ms=119 HTTP/1.1" 200 43 "http://piwik.org/blog/2014/03/piwik-2-1-massive-performance-reliability-improvements/" "Mozilla/5.0 (Android; Mobile; rv:27.0) Gecko/27.0 Firefox/27.0" -219.101.46.222 - - [13/Mar/2014:01:33:50 +0100] "GET /piwik.php?action_name=Log%20Analytics%20-%20Analytics%20-%20Piwik&idsite=1&rec=1&r=263291&h=9&m=33&s=50&url=https%3A%2F%2Fpiwik.org%2Flog-analytics%2F&urlref=https%3A%2F%2Fwww.google.co.jp%2Furl%3Fsa%3Dt%26rct%3Dj%26q%3D%26esrc%3Ds%26source%3Dweb%26cd%3D2%26ved%3D0CDAQFjAB%26url%3Dhttps%253A%252F%252Fpiwik.org%252Flog-analytics%252F%26ei%3DrogdU5OuK43pkgXZmYGICA%26usg%3DAFQjCNH4nR8bKYaliCj2egiJ0H_CX4sFJg%26bvm%3Dbv.62578216%2Cd.dGI&_id=15164ceeb5ddb76f&_idts=1394444478&_idvc=4&_idn=0&_refts=1394670831&_viewts=1394584228&_ref=https%3A%2F%2Fwww.google.co.jp%2Furl%3Fsa%3Dt%26rct%3Dj%26q%3D%26esrc%3Ds%26source%3Dweb%26cd%3D2%26ved%3D0CDAQFjAB%26url%3Dhttps%253A%252F%252Fpiwik.org%252Flog-analytics%252F%26ei%3DrogdU5OuK43pkgXZmYGICA%26usg%3DAFQjCNH4nR8bKYaliCj2egiJ0H_CX4sFJg%26bvm%3Dbv.62578216%2Cd.dGI&pdf=1&qt=1&realp=0&wma=0&dir=0&fla=0&java=1&gears=0&ag=0&cookie=1&res=2560x1440&_cvar=%7B%221%22%3A%5B%22Domain%20landed%22%2C%22piwik.org%22%5D%7D HTTP/1.1" 200 43 "https://piwik.org/log-analytics/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.74.9 (KHTML, like Gecko) Version/7.0.2 Safari/537.74.9" +219.101.46.222 - - [13/Mar/2014:01:33:50 +0100] "GET /piwik.php?action_name=Log%20Analytics%20-%20Analytics%20-%20Piwik&idsite=1&rec=1&r=263291&h=9&m=33&s=50&url=https%3A%2F%2Fpiwik.org%2Flog-analytics%2F&urlref=https%3A%2F%2Fwww.google.co.jp%2Furl%3Fsa%3Dt%26rct%3Dj%26q%3D%26esrc%3Ds%26source%3Dweb%26cd%3D2%26ved%3D0CDAQFjAB%26url%3Dhttps%253A%252F%252Fpiwik.org%252Flog-analytics%252F%26ei%3DrogdU5OuK43pkgXZmYGICA%26usg%3DAFQjCNH4nR8bKYaliCj2egiJ0H_CX4sFJg%26bvm%3Dbv.62578216%2Cd.dGI&_id=15164ceeb5ddb76f&_idts=1394444478&_idvc=4&_idn=0&_refts=1394670831&_viewts=1394584228&_ref=https%3A%2F%2Fwww.google.co.jp%2Furl%3Fsa%3Dt%26rct%3Dj%26q%3D%26esrc%3Ds%26source%3Dweb%26cd%3D2%26ved%3D0CDAQFjAB%26url%3Dhttps%253A%252F%252Fpiwik.org%252Flog-analytics%252F%26ei%3DrogdU5OuK43pkgXZmYGICA%26usg%3DAFQjCNH4nR8bKYaliCj2egiJ0H_CX4sFJg%26bvm%3Dbv.62578216%2Cd.dGI&pdf=1&qt=1&realp=0&wma=0&dir=0&fla=0&java=1&gears=0&ag=0&cookie=1&res=2560x1440&_cvar=%7B%221%22%3A%5B%22Domain%20landed%22%2C%22piwik.org%22%5D%7D HTTP/1.1" 404 43 "https://piwik.org/log-analytics/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.74.9 (KHTML, like Gecko) Version/7.0.2 Safari/537.74.9" 108.211.181.12 - - [13/Mar/2014:01:33:55 +0100] "GET /piwik.php?action_name=Integrate%20Piwik%20into%20your%20Rails%20Application%20-%20Analytics%20-%20Piwik&idsite=1&rec=1&r=006719&h=17&m=33&s=58&url=http%3A%2F%2Fpiwik.org%2Fblog%2F2012%2F10%2Fintegrate-piwik-into-your-rails-application%2F&urlref=https%3A%2F%2Fwww.google.com%2F&_id=3d9cad3fc284c272&_idts=1394670839&_idvc=1&_idn=1&_refts=1394670839&_viewts=1394670839&_ref=https%3A%2F%2Fwww.google.com%2F&pdf=1&qt=1&realp=0&wma=1&dir=0&fla=1&java=1&gears=0&ag=1&cookie=1&res=1360x768&_cvar=%7B%221%22%3A%5B%22Domain%20landed%22%2C%22piwik.org%22%5D%7D>_ms=391 HTTP/1.1" 200 43 "http://piwik.org/blog/2012/10/integrate-piwik-into-your-rails-application/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.149 Safari/537.36" # testing particular idvc, viewts, refts, etc. 50.244.17.130 - - [13/Mar/2014:01:33:59 +0100] "GET /piwik.php?action_name=Liberate%20Web%20Analytics%20-%20Analytics%20-%20Piwik&idsite=1&rec=1&r=658858&h=19&m=34&s=1&url=http%3A%2F%2Fpiwik.org%2F&urlref=http%3A%2F%2Fwww.google.com%2Furl%3Fsa%3Dt%26rct%3Dj%26q%3D%26esrc%3Ds%26source%3Dweb%26cd%3D1%26ved%3D0CCcQFjAA%26url%3Dhttp%253A%252F%252Fpiwik.org%252F%26ei%3D8vwgU8TYDZTI2wWTi4CQCA%26usg%3DAFQjCNF_MGJRqKPvaKuUokHtZ3VvNG9ALw%26bvm%3Dbv.62922401%2Cd.b2I&_id=90d89274a8c26d90&_idts=1393170841&_idvc=7&_idn=0&_refts=1394670841&_viewts=1394170841&_ref=http%3A%2F%2Fwww.google.com%2Furl%3Fsa%3Dt%26rct%3Dj%26q%3D%26esrc%3Ds%26source%3Dweb%26cd%3D1%26ved%3D0CCcQFjAA%26url%3Dhttp%253A%252F%252Fpiwik.org%252F%26ei%3D8vwgU8TYDZTI2wWTi4CQCA%26usg%3DAFQjCNF_MGJRqKPvaKuUokHtZ3VvNG9ALw%26bvm%3Dbv.62922401%2Cd.b2I&pdf=1&qt=1&realp=0&wma=0&dir=0&fla=1&java=1&gears=0&ag=0&cookie=1&res=2880x1800&_cvar=%7B%221%22%3A%5B%22Domain%20landed%22%2C%22piwik.org%22%5D%7D HTTP/1.1" 200 43 "http://piwik.org/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.74.9 (KHTML, like Gecko) Version/7.0.2 Safari/537.74.9" #this visitor visited the website yesterday 84.194.72.21 - - [13/Mar/2014:01:34:21 +0100] "GET /piwik.php?link=http%3A%2F%2Fdemo.piwik.org%2F&idsite=1&rec=1&forceLargeWindowLookBackForVisitor=1&r=578077&h=1&m=34&s=24&url=http%3A%2F%2Fpiwik.org%2F&urlref=http%3A%2F%2Fmusicforeveryoneradio.be%3A2222%2FCMD_PLUGINS%2Finstallatron%2Findex.raw&_id=e01d58157d66e023&_idts=1394670858&_idvc=1&_idn=0&_refts=1394670858&_viewts=1394670858&_ref=http%3A%2F%2Fmusicforeveryoneradio.be%3A2222%2FCMD_PLUGINS%2Finstallatron%2Findex.raw&pdf=1&qt=0&realp=0&wma=0&dir=0&fla=1&java=1&gears=0&ag=1&cookie=1&res=1920x1080&_cvar=%7B%221%22%3A%5B%22Domain%20landed%22%2C%22piwik.org%22%5D%7D>_ms=58 HTTP/1.1" 200 43 "http://piwik.org/" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 Safari/537.36" -50.244.17.130 - - [13/Mar/2014:01:34:31 +0100] "GET /piwik.php?action_name=What%20is%20Piwik%3F%20-%20Analytics%20-%20Piwik&idsite=1&rec=1&r=388822&h=19&m=34&s=33&url=http%3A%2F%2Fpiwik.org%2Fwhat-is-piwisk%2F&urlref=http%3A%2F%2Fpiwik.org%2F&_id=90d89274a8c26d90&_idts=1394170841&_idvc=1&_idn=0&_refts=1394670841&_viewts=1394670841&_ref=http%3A%2F%2Fwww.google.com%2Furl%3Fsa%3Dt%26rct%3Dj%26q%3D%26esrc%3Ds%26source%3Dweb%26cd%3D1%26ved%3D0CCcQFjAA%26url%3Dhttp%253A%252F%252Fpiwik.org%252F%26ei%3D8vwgU8TYDZTI2wWTi4CQCA%26usg%3DAFQjCNF_MGJRqKPvaKuUokHtZ3VvNG9ALw%26bvm%3Dbv.62922401%2Cd.b2I&pdf=1&qt=1&realp=0&wma=0&dir=0&fla=1&java=1&gears=0&ag=0&cookie=1&res=2880x1800&_cvar=%7B%221%22%3A%5B%22Domain%20landed%22%2C%22piwik.org%22%5D%7D HTTP/1.1" 200 43 "http://piwik.org/what-is-piwik/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.74.9 (KHTML, like Gecko) Version/7.0.2 Safari/537.74.9" +50.244.17.130 - - [13/Mar/2014:01:34:31 +0100] "GET /piwik.php?action_name=What%20is%20Piwik%3F%20-%20Analytics%20-%20Piwik&idsite=1&rec=1&r=388822&h=19&m=34&s=33&url=http%3A%2F%2Fpiwik.org%2Fwhat-is-piwisk%2F&urlref=http%3A%2F%2Fpiwik.org%2F&_id=90d89274a8c26d90&_idts=1394170841&_idvc=1&_idn=0&_refts=1394670841&_viewts=1394670841&_ref=http%3A%2F%2Fwww.google.com%2Furl%3Fsa%3Dt%26rct%3Dj%26q%3D%26esrc%3Ds%26source%3Dweb%26cd%3D1%26ved%3D0CCcQFjAA%26url%3Dhttp%253A%252F%252Fpiwik.org%252F%26ei%3D8vwgU8TYDZTI2wWTi4CQCA%26usg%3DAFQjCNF_MGJRqKPvaKuUokHtZ3VvNG9ALw%26bvm%3Dbv.62922401%2Cd.b2I&pdf=1&qt=1&realp=0&wma=0&dir=0&fla=1&java=1&gears=0&ag=0&cookie=1&res=2880x1800&_cvar=%7B%221%22%3A%5B%22Domain%20landed%22%2C%22piwik.org%22%5D%7D HTTP/1.1" 501 43 "http://piwik.org/what-is-piwik/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.74.9 (KHTML, like Gecko) Version/7.0.2 Safari/537.74.9" 193.159.20.129 - - [13/Mar/2014:01:34:42 +0100] "GET /piwik.php?action_name=demo.piwik.org%2FPiwik%20Forums%20-%20Piwik%20%E2%80%BA%20Web%20Analytics%20Reports&idsite=1&rec=1&r=546849&h=1&m=34&s=37&url=http%3A%2F%2Fdemo.piwik.org%2Findex.php%3Fmodule%3DCoreHome%26action%3Dindex%26date%3Dyesterday%26period%3Dday%26idSite%3D7%23%2Fmodule%3DLive%26action%3DindexVisitorLog%26date%3Dyesterday%26period%3Dday%26idSite%3D7&urlref=http%3A%2F%2Fdemo.piwik.org%2Findex.php%3Fmodule%3DMultiSites%26action%3Dindex%26date%3Dyesterday%26period%3Dday%26idSite%3D32&_id=d80e3396f1a4c2ac&_idts=1394665308&_idvc=1&_idn=0&_refts=0&_viewts=1394365308&pdf=1&qt=1&realp=0&wma=0&dir=0&fla=1&java=1&gears=0&ag=1&cookie=1&res=1920x1200&_cvar=%7B%221%22%3A%5B%22Domain%20landed%22%2C%22piwik.org%22%5D%2C%222%22%3A%5B%22Demo%20language%22%2C%22English%22%5D%7D>_ms=413 HTTP/1.1" 200 43 "http://demo.piwik.org/index.php?module=CoreHome&action=index&date=yesterday&period=day&idSite=7" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.45 Safari/537.36" 206.190.75.8 - - [13/Mar/2014:01:34:46 +0100] "GET /piwik.php?action_name=Contact%20the%20Piwik%20team%20-%20Analytics%20-%20Piwik&idsite=1&rec=1&r=456386&h=17&m=34&s=45&url=http%3A%2F%2Fpiwik.org%2Fcontact%2F&urlref=http%3A%2F%2Fpiwik.org%2Fchangelog%2F&_id=e0eb35c4fae2aa0a&_idts=1379033580&_idvc=2&_idn=0&_refts=1394169945&_viewts=1379033693&_ref=http%3A%2F%2Ftrends.builtwith.com%2Fanalytics%2FPiwik-Web-Analytics&res=1536x864&_cvar=%7B%221%22%3A%5B%22Domain%20landed%22%2C%22piwik.org%22%5D%7D HTTP/1.1" 200 43 "http://piwik.org/contact/" "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; .NET4.0C; InfoPath.2)" 174.97.139.63 - - [13/Mar/2014:01:34:53 +0100] "GET /piwik.php?action_name=Liberate%20Web%20Analytics%20-%20Analytics%20-%20Piwik&idsite=1&rec=1&r=022131&h=20&m=34&s=49&url=http%3A%2F%2Fpiwik.org%2F&urlref=http%3A%2F%2Fblog.comperiosearch.com%2Fblog%2F2014%2F02%2F05%2Fdynamic-search-ranking-using-elasticsearch-neo4j-and-piwik%2F&_id=d98b53d7fcf5a082&_idts=1394670890&_idvc=77&_idn=1&_refts=1394670890&_viewts=1394670890&_ref=http%3A%2F%2Fblog.comperiosearch.com%2Fblog%2F2014%2F02%2F05%2Fdynamic-search-ranking-using-elasticsearch-neo4j-and-piwik%2F&pdf=1&qt=0&realp=0&wma=0&dir=0&fla=1&java=1&gears=0&ag=0&cookie=1&res=1366x768&_cvar=%7B%221%22%3A%5B%22Domain%20landed%22%2C%22piwik.org%22%5D%7D>_ms=292 HTTP/1.1" 200 43 "http://piwik.org/" "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 Safari/537.36"