diff options
28 files changed, 315 insertions, 91 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index fd762d201d..13709e7682 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ This is the Developer Changelog for Matomo platform developers. All changes in o The Product Changelog at **[matomo.org/changelog](https://matomo.org/changelog)** lets you see more details about any Matomo release, such as the list of new guides and FAQs, security fixes, and links to all closed issues. +## Matomo 3.9.0 + +### New Features +* It is now possible to use monolog's FingersCrossedHandler which buffers all logs and logs all of them in case of warning or error. + ## Matomo 3.8.0 ### Breaking Changes diff --git a/core/API/Proxy.php b/core/API/Proxy.php index 34b202f94d..1f2ca5a59d 100644 --- a/core/API/Proxy.php +++ b/core/API/Proxy.php @@ -11,6 +11,7 @@ namespace Piwik\API; use Exception; use Piwik\Common; +use Piwik\Container\StaticContainer; use Piwik\Context; use Piwik\Piwik; use Piwik\Plugin\Manager; @@ -25,15 +26,13 @@ use ReflectionMethod; * object, with the parameters in the right order. * * It will also log the performance of API calls (time spent, parameter values, etc.) if logger available - * - * @method static Proxy getInstance() */ -class Proxy extends Singleton +class Proxy { // array of already registered plugins names protected $alreadyRegistered = array(); - private $metadataArray = array(); + protected $metadataArray = array(); private $hideIgnoredFunctions = true; // when a parameter doesn't have a default value we use this @@ -44,6 +43,11 @@ class Proxy extends Singleton $this->noDefaultValue = new NoDefaultValue(); } + public static function getInstance() + { + return StaticContainer::get(self::class); + } + /** * Returns array containing reflection meta data for all the loaded classes * eg. number of parameters, method names, etc. diff --git a/core/API/Request.php b/core/API/Request.php index 5356a94e6e..1875e2822a 100644 --- a/core/API/Request.php +++ b/core/API/Request.php @@ -12,6 +12,7 @@ use Exception; use Piwik\Access; use Piwik\Cache; use Piwik\Common; +use Piwik\Container\StaticContainer; use Piwik\Context; use Piwik\DataTable; use Piwik\Exception\PluginDeactivatedException; @@ -23,6 +24,7 @@ use Piwik\Plugins\CoreHome\LoginWhitelist; use Piwik\SettingsServer; use Piwik\Url; use Piwik\UrlHelper; +use Psr\Log\LoggerInterface; /** * Dispatches API requests to the appropriate API method. @@ -269,7 +271,10 @@ class Request return $response->getResponse($returnedValue, $module, $method); }); } catch (Exception $e) { - Log::debug($e); + StaticContainer::get(LoggerInterface::class)->error('Uncaught exception in API: {exception}', [ + 'exception' => $e, + 'ignoreInScreenWriter' => true, + ]); $toReturn = $response->getResponseException($e); } finally { diff --git a/core/API/ResponseBuilder.php b/core/API/ResponseBuilder.php index a3730d3fe0..9845dee275 100644 --- a/core/API/ResponseBuilder.php +++ b/core/API/ResponseBuilder.php @@ -15,6 +15,7 @@ use Piwik\DataTable\Renderer; use Piwik\DataTable\DataTableInterface; use Piwik\DataTable\Filter\ColumnDelete; use Piwik\DataTable\Filter\Pattern; +use Piwik\Plugins\Monolog\Processor\ExceptionToTextProcessor; /** */ @@ -165,19 +166,7 @@ class ResponseBuilder */ private function formatExceptionMessage($exception) { - $message = ""; - - $e = $exception; - do { - if ($e !== $exception) { - $message .= ",\ncaused by: "; - } - - $message .= $e->getMessage(); - if ($this->shouldPrintBacktrace) { - $message .= "\n" . $e->getTraceAsString(); - } - } while ($e = $e->getPrevious()); + $message = ExceptionToTextProcessor::getWholeBacktrace($exception, $this->shouldPrintBacktrace); return Renderer::formatValueXml($message); } diff --git a/core/ArchiveProcessor/PluginsArchiver.php b/core/ArchiveProcessor/PluginsArchiver.php index 11adfe7f1d..2f7cd1ad12 100644 --- a/core/ArchiveProcessor/PluginsArchiver.php +++ b/core/ArchiveProcessor/PluginsArchiver.php @@ -10,13 +10,11 @@ namespace Piwik\ArchiveProcessor; use Piwik\ArchiveProcessor; -use Piwik\Common; use Piwik\Container\StaticContainer; use Piwik\CronArchive\Performance\Logger; use Piwik\DataAccess\ArchiveWriter; use Piwik\DataAccess\LogAggregator; use Piwik\DataTable\Manager; -use Piwik\ErrorHandler; use Piwik\Metrics; use Piwik\Piwik; use Piwik\Plugin\Archiver; diff --git a/core/CliMulti.php b/core/CliMulti.php index d8d533ea24..b459bd69b6 100644 --- a/core/CliMulti.php +++ b/core/CliMulti.php @@ -447,4 +447,9 @@ class CliMulti $minutes = floor($elapsed / 60); return self::BASE_WAIT_TIME + $minutes * 100000; // 100 * 1000 = 100ms } + + public static function isCliMultiRequest() + { + return Common::getRequestVar('pid', false) !== false; + } } diff --git a/core/Development.php b/core/Development.php index 3b44eba71d..4660f83d9e 100644 --- a/core/Development.php +++ b/core/Development.php @@ -10,6 +10,8 @@ namespace Piwik; use Exception; +use Piwik\Container\StaticContainer; +use Psr\Log\LoggerInterface; /** * Development related checks and tools. You can enable/disable development using `./console development:enable` and @@ -150,8 +152,11 @@ class Development $message .= ' (This error is only shown in development mode)'; if (SettingsServer::isTrackerApiRequest() - || Common::isPhpCliMode()) { - Log::error($message); + || Common::isPhpCliMode() + ) { + StaticContainer::get(LoggerInterface::class)->error($message, [ + 'ignoreInScreenWriter' => true, + ]); } else { throw new Exception($message); } diff --git a/core/ErrorHandler.php b/core/ErrorHandler.php index e0a677cc53..b017d5e333 100644 --- a/core/ErrorHandler.php +++ b/core/ErrorHandler.php @@ -8,7 +8,9 @@ namespace Piwik; +use Piwik\Container\StaticContainer; use Piwik\Exception\ErrorException; +use Psr\Log\LoggerInterface; /** * Piwik's error handler function. @@ -160,7 +162,7 @@ class ErrorHandler case E_USER_DEPRECATED: default: try { - Log::warning(self::createLogMessage($errno, $errstr, $errfile, $errline)); + StaticContainer::get(LoggerInterface::class)->warning(self::createLogMessage($errno, $errstr, $errfile, $errline)); } catch (\Exception $ex) { // ignore (it's possible for this to happen if the StaticContainer hasn't been created yet) } diff --git a/core/ExceptionHandler.php b/core/ExceptionHandler.php index 6c01785b95..c1f959ce6d 100644 --- a/core/ExceptionHandler.php +++ b/core/ExceptionHandler.php @@ -9,10 +9,13 @@ namespace Piwik; use Exception; +use Interop\Container\Exception\ContainerException; use Piwik\API\Request; use Piwik\API\ResponseBuilder; use Piwik\Container\ContainerDoesNotExistException; +use Piwik\Container\StaticContainer; use Piwik\Plugins\CoreAdminHome\CustomLogo; +use Psr\Log\LoggerInterface; /** * Contains Piwik's uncaught exception handler. @@ -41,6 +44,8 @@ class ExceptionHandler */ public static function dieWithCliError($exception) { + self::logException($exception); + $message = $exception->getMessage(); if (!method_exists($exception, 'isHtmlMessage') || !$exception->isHtmlMessage()) { @@ -65,6 +70,8 @@ class ExceptionHandler */ public static function dieWithHtmlErrorPage($exception) { + self::logException($exception); + Common::sendHeader('Content-Type: text/html; charset=utf-8'); try { @@ -137,4 +144,18 @@ class ExceptionHandler return $result; } + + private static function logException($exception) + { + try { + StaticContainer::get(LoggerInterface::class)->error('Uncaught exception: {exception}', [ + 'exception' => $exception, + 'ignoreInScreenWriter' => true, + ]); + } catch (ContainerException $ex) { + // ignore (occurs if exception is thrown when resolving DI entries) + } catch (ContainerDoesNotExistException $ex) { + // ignore + } + } } diff --git a/core/FrontController.php b/core/FrontController.php index 8a32c425b0..320239f45b 100644 --- a/core/FrontController.php +++ b/core/FrontController.php @@ -21,6 +21,7 @@ use Piwik\Http\ControllerResolver; use Piwik\Http\Router; use Piwik\Plugins\CoreAdminHome\CustomLogo; use Piwik\Session\SessionAuth; +use Psr\Log\LoggerInterface; /** * This singleton dispatches requests to the appropriate plugin Controller. @@ -107,6 +108,11 @@ class FrontController extends Singleton */ private static function generateSafeModeOutputFromException($e) { + StaticContainer::get(LoggerInterface::class)->error('Uncaught exception: {exception}', [ + 'exception' => $e, + 'ignoreInScreenWriter' => true, + ]); + $error = array( 'message' => $e->getMessage(), 'file' => $e->getFile(), @@ -256,6 +262,11 @@ class FrontController extends Singleton $lastError['backtrace'] = ' on ' . $lastError['file'] . '(' . $lastError['line'] . ")\n" . ErrorHandler::getFatalErrorPartialBacktrace(); + StaticContainer::get(LoggerInterface::class)->error('Fatal error encountered: {exception}', [ + 'exception' => $lastError, + 'ignoreInScreenWriter' => true, + ]); + $message = self::generateSafeModeOutputFromError($lastError); echo $message; } diff --git a/core/Plugin/ViewDataTable.php b/core/Plugin/ViewDataTable.php index 226e8f3141..513d175d1f 100644 --- a/core/Plugin/ViewDataTable.php +++ b/core/Plugin/ViewDataTable.php @@ -13,8 +13,6 @@ use Piwik\Common; use Piwik\DataTable; use Piwik\Period; use Piwik\Piwik; -use Piwik\Plugin\ReportsProvider; -use Piwik\View; use Piwik\View\ViewInterface; use Piwik\ViewDataTable\Config as VizConfig; use Piwik\ViewDataTable\Manager as ViewDataTableManager; diff --git a/core/Plugin/Visualization.php b/core/Plugin/Visualization.php index 71c9eca248..f23a183ed6 100644 --- a/core/Plugin/Visualization.php +++ b/core/Plugin/Visualization.php @@ -28,6 +28,7 @@ use Piwik\View; use Piwik\ViewDataTable\Manager as ViewDataTableManager; use Piwik\Plugin\Manager as PluginManager; use Piwik\API\Request as ApiRequest; +use Psr\Log\LoggerInterface; /** * The base class for report visualizations that output HTML and use JavaScript. @@ -193,16 +194,16 @@ class Visualization extends ViewDataTable } catch (NoAccessException $e) { throw $e; } catch (\Exception $e) { - $logMessage = "Failed to get data from API: " . $e->getMessage(); - $message = $e->getMessage(); + StaticContainer::get(LoggerInterface::class)->error('Failed to get data from API: {exception}', [ + 'exception' => $e, + 'ignoreInScreenWriter' => true, + ]); + $message = $e->getMessage(); if (\Piwik_ShouldPrintBackTraceWithMessage()) { - $logMessage .= "\n" . $e->getTraceAsString(); $message .= "\n" . $e->getTraceAsString(); } - Log::error($logMessage); - $loadingError = array('message' => $message); } diff --git a/core/Session.php b/core/Session.php index b03c8e0796..bf523d3b71 100644 --- a/core/Session.php +++ b/core/Session.php @@ -12,6 +12,7 @@ use Exception; use Piwik\Container\StaticContainer; use Piwik\Exception\MissingFilePermissionException; use Piwik\Session\SaveHandler\DbTable; +use Psr\Log\LoggerInterface; use Zend_Session; /** @@ -132,7 +133,10 @@ class Session extends Zend_Session parent::start(); register_shutdown_function(array('Zend_Session', 'writeClose'), true); } catch (Exception $e) { - Log::error('Unable to start session: ' . $e->getMessage()); + StaticContainer::get(LoggerInterface::class)->error('Unable to start session: {exception}', [ + 'exception' => $e, + 'ignoreInScreenWriter' => true, + ]); if (SettingsPiwik::isPiwikInstalled()) { $pathToSessions = ''; diff --git a/core/testMinimumPhpVersion.php b/core/testMinimumPhpVersion.php index 82bbc4fd88..c1f8338081 100644 --- a/core/testMinimumPhpVersion.php +++ b/core/testMinimumPhpVersion.php @@ -84,6 +84,12 @@ if (!function_exists('Piwik_GetErrorMessagePage')) { */ function Piwik_ShouldPrintBackTraceWithMessage() { + if (\Piwik\SettingsServer::isArchivePhpTriggered() + && \Piwik\Common::isPhpCliMode() + ) { + return true; + } + $bool = (defined('PIWIK_PRINT_ERROR_BACKTRACE') && PIWIK_PRINT_ERROR_BACKTRACE) || !empty($GLOBALS['PIWIK_TRACKER_DEBUG']); diff --git a/plugins/CoreConsole/Commands/ClearCaches.php b/plugins/CoreConsole/Commands/ClearCaches.php index b88680812f..1e8439ca0b 100644 --- a/plugins/CoreConsole/Commands/ClearCaches.php +++ b/plugins/CoreConsole/Commands/ClearCaches.php @@ -12,7 +12,6 @@ namespace Piwik\Plugins\CoreConsole\Commands; use Piwik\Filesystem; use Piwik\Plugin\ConsoleCommand; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; /** diff --git a/plugins/CustomVariables/Model.php b/plugins/CustomVariables/Model.php index 5abb327f0e..7ab4605489 100644 --- a/plugins/CustomVariables/Model.php +++ b/plugins/CustomVariables/Model.php @@ -9,10 +9,12 @@ namespace Piwik\Plugins\CustomVariables; use Piwik\Common; +use Piwik\Container\StaticContainer; use Piwik\DataTable; use Piwik\Db; use Piwik\Log; use Piwik\Piwik; +use Psr\Log\LoggerInterface; class Model { @@ -194,7 +196,10 @@ class Model $model->addCustomVariable(); } } catch (\Exception $e) { - Log::error('Failed to add custom variable: ' . $e->getMessage()); + StaticContainer::get(LoggerInterface::class)->error('Failed to add custom variable: {exception}', [ + 'exception' => $e, + 'ignoreInScreenWriter' => true, + ]); } } } diff --git a/plugins/Monolog/Handler/LogCaptureHandler.php b/plugins/Monolog/Handler/LogCaptureHandler.php new file mode 100644 index 0000000000..fbe4acad29 --- /dev/null +++ b/plugins/Monolog/Handler/LogCaptureHandler.php @@ -0,0 +1,34 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +namespace Piwik\Plugins\Monolog\Handler; + +use Monolog\Handler\AbstractHandler; + +class LogCaptureHandler extends AbstractHandler +{ + /** + * @var array + */ + private $allLogs; + + public function handle(array $record) + { + $this->allLogs[] = $record; + } + + /** + * Returns all records. The records should be processed, so one could just use $record['message']. + * + * @return array[] + */ + public function getAllRecords() + { + return $this->allLogs; + } +}
\ No newline at end of file diff --git a/plugins/Monolog/Handler/WebNotificationHandler.php b/plugins/Monolog/Handler/WebNotificationHandler.php index 5f0de1bb8c..9ff76958aa 100644 --- a/plugins/Monolog/Handler/WebNotificationHandler.php +++ b/plugins/Monolog/Handler/WebNotificationHandler.php @@ -20,6 +20,15 @@ use Zend_Session_Exception; */ class WebNotificationHandler extends AbstractProcessingHandler { + public function isHandling(array $record) + { + if (!empty($record['context']['ignoreInScreenWriter'])) { + return false; + } + + return parent::isHandling($record); + } + protected function write(array $record) { switch ($record['level']) { diff --git a/plugins/Monolog/Processor/ExceptionToTextProcessor.php b/plugins/Monolog/Processor/ExceptionToTextProcessor.php index daaaee479e..0a947a9f23 100644 --- a/plugins/Monolog/Processor/ExceptionToTextProcessor.php +++ b/plugins/Monolog/Processor/ExceptionToTextProcessor.php @@ -25,34 +25,75 @@ class ExceptionToTextProcessor /** @var \Exception $exception */ $exception = $record['context']['exception']; - $record['message'] = sprintf( + $exceptionStr = sprintf( "%s(%d): %s\n%s", - $exception->getFile(), - $exception->getLine(), + $exception instanceof \Exception ? $exception->getFile() : $exception['file'], + $exception instanceof \Exception ? $exception->getLine() : $exception['line'], $this->getMessage($exception), $this->getStackTrace($exception) ); + if (!isset($record['message']) + || strpos($record['message'], '{exception}') === false + ) { + $record['message'] = $exceptionStr; + } else { + $record['message'] = str_replace('{exception}', $exceptionStr, $record['message']); + } + return $record; } private function contextContainsException($record) { return isset($record['context']['exception']) - && $record['context']['exception'] instanceof \Exception; + && ($record['context']['exception'] instanceof \Exception + || $this->isLooksLikeFatalErrorArray($record['context']['exception'])); + } + + private function isLooksLikeFatalErrorArray($exception) + { + return is_array($exception) && isset($exception['message']) && isset($exception['file']) && isset($exception['line']); } - private function getMessage(\Exception $exception) + private function getMessage($exception) { if ($exception instanceof \ErrorException) { return ErrorHandler::getErrNoString($exception->getSeverity()) . ' - ' . $exception->getMessage(); } + if (is_array($exception) && isset($exception['message'])) { + return $exception['message']; + } + return $exception->getMessage(); } - private function getStackTrace(\Exception $exception) + private function getStackTrace($exception) { - return Log::$debugBacktraceForTests ?: $exception->getTraceAsString(); + if (is_array($exception) && isset($exception['backtrace'])) { + return $exception['backtrace']; + } + + return Log::$debugBacktraceForTests ?: self::getWholeBacktrace($exception); + } + + public static function getWholeBacktrace(\Exception $exception, $shouldPrintBacktrace = true) + { + $message = ""; + + $e = $exception; + do { + if ($e !== $exception) { + $message .= ",\ncaused by: "; + } + + $message .= $e->getMessage(); + if ($shouldPrintBacktrace) { + $message .= "\n" . $e->getTraceAsString(); + } + } while ($e = $e->getPrevious()); + + return $message; } } diff --git a/plugins/Monolog/config/config.php b/plugins/Monolog/config/config.php index 19e7752479..b9e9e0c62f 100644 --- a/plugins/Monolog/config/config.php +++ b/plugins/Monolog/config/config.php @@ -4,6 +4,7 @@ use Interop\Container\ContainerInterface; use Monolog\Logger; use Piwik\Log; use Piwik\Plugins\Monolog\Handler\FileHandler; +use Piwik\Plugins\Monolog\Handler\LogCaptureHandler; return array( @@ -17,7 +18,7 @@ return array( 'screen' => 'Piwik\Plugins\Monolog\Handler\WebNotificationHandler', 'database' => 'Piwik\Plugins\Monolog\Handler\DatabaseHandler', ), - 'log.handlers' => DI\factory(function (ContainerInterface $c) { + 'log.handlers' => DI\factory(function (\DI\Container $c) { if ($c->has('ini.log.log_writers')) { $writerNames = $c->get('ini.log.log_writers'); } else { @@ -26,13 +27,50 @@ return array( $classes = $c->get('log.handler.classes'); + $logConfig = $c->get(\Piwik\Config::class)->log; + $enableFingersCrossed = isset($logConfig['enable_fingers_crossed_handler']) && $logConfig['enable_fingers_crossed_handler'] == 1; + $fingersCrossedStopBuffering = isset($logConfig['fingers_crossed_stop_buffering_on_activation']) && $logConfig['fingers_crossed_stop_buffering_on_activation'] == 1; + $enableLogCaptureHandler = isset($logConfig['enable_log_capture_handler']) && $logConfig['enable_log_capture_handler'] == 1; + + $isLogBufferingAllowed = !\Piwik\Common::isPhpCliMode() + || \Piwik\SettingsServer::isArchivePhpTriggered() + || \Piwik\CliMulti::isCliMultiRequest(); + $writerNames = array_map('trim', $writerNames); $writers = array(); foreach ($writerNames as $writerName) { + if ($writerName === 'screen' && \Piwik\Common::isPhpCliMode()) { + continue; // screen writer is only valid for web requests + } + if (isset($classes[$writerName])) { - $writers[$writerName] = $c->get($classes[$writerName]); + // wrap the handler in FingersCrossedHandler if we can and this isn't the screen handler + + /** @var \Monolog\Handler\HandlerInterface $handler */ + $handler = $c->make($classes[$writerName]); + if ($enableFingersCrossed + && $writerName !== 'screen' + && $handler instanceof \Monolog\Handler\AbstractHandler + && $isLogBufferingAllowed + ) { + $passthruLevel = $handler->getLevel(); + + $handler->setLevel(Logger::DEBUG); + + $handler = new \Monolog\Handler\FingersCrossedHandler($handler, $activationStrategy = null, $bufferSize = 0, + $bubble = true, $fingersCrossedStopBuffering, $passthruLevel); + } + + $writers[$writerName] = $handler; } } + + if ($enableLogCaptureHandler + && $isLogBufferingAllowed + ) { + $writers[] = $c->get(LogCaptureHandler::class); + } + return array_values($writers); }), diff --git a/plugins/ScheduledReports/tests/Integration/ApiTest.php b/plugins/ScheduledReports/tests/Integration/ApiTest.php index fa7168c7e4..11ffcdba77 100644 --- a/plugins/ScheduledReports/tests/Integration/ApiTest.php +++ b/plugins/ScheduledReports/tests/Integration/ApiTest.php @@ -9,6 +9,7 @@ namespace Piwik\Plugins\ScheduledReports\tests; use Piwik\API\Proxy; +use Piwik\Container\StaticContainer; use Piwik\DataTable; use Piwik\Date; use Piwik\Plugins\MobileMessaging\API as APIMobileMessaging; @@ -460,7 +461,7 @@ class ApiTest extends IntegrationTestCase throw new \Exception("Unexpected method $className::$methodName."); } }); - Proxy::setSingletonInstance($mockProxy); + StaticContainer::getContainer()->set(Proxy::class, $mockProxy); $idReport = APIScheduledReports::getInstance()->addReport( 1, diff --git a/plugins/UserCountry/GeoIPAutoUpdater.php b/plugins/UserCountry/GeoIPAutoUpdater.php index db47feb063..9d2ab6af92 100644 --- a/plugins/UserCountry/GeoIPAutoUpdater.php +++ b/plugins/UserCountry/GeoIPAutoUpdater.php @@ -28,6 +28,7 @@ use Piwik\Scheduler\Schedule\Monthly; use Piwik\Scheduler\Schedule\Weekly; use Piwik\SettingsPiwik; use Piwik\Unzip; +use Psr\Log\LoggerInterface; /** * Used to automatically update installed GeoIP databases, and manages the updater's @@ -113,7 +114,10 @@ class GeoIPAutoUpdater extends Task } } catch (Exception $ex) { // message will already be prefixed w/ 'GeoIPAutoUpdater: ' - Log::error($ex); + StaticContainer::get(LoggerInterface::class)->error('Auto-update failed: {exception}', [ + 'exception' => $ex, + 'ignoreInScreenWriter' => true, + ]); $this->performRedundantDbChecks(); throw $ex; } @@ -572,9 +576,16 @@ class GeoIPAutoUpdater extends Task if (self::$unzipPhpError !== null) { list($errno, $errstr, $errfile, $errline) = self::$unzipPhpError; - if($logErrors) { - Log::error("GeoIPAutoUpdater: Encountered PHP error when performing redundant tests on GeoIP " - . "%s database: %s: %s on line %s of %s.", $type, $errno, $errstr, $errline, $errfile); + if ($logErrors) { + StaticContainer::get(LoggerInterface::class)->error("GeoIPAutoUpdater: Encountered PHP error when performing redundant tests on GeoIP " + . "{type} database: {errno}: {errstr} on line {errline} of {errfile}.", [ + 'ignoreInScreenWriter' => true, + 'type' => $type, + 'errno' => $errno, + 'errstr' => $errstr, + 'errline' => $errline, + 'errfile' => $errfile, + ]); } // get the current filename for the DB and an available new one to rename it to diff --git a/tests/PHPUnit/Fixtures/UITestFixture.php b/tests/PHPUnit/Fixtures/UITestFixture.php index dbbe19a97d..20d544b45d 100644 --- a/tests/PHPUnit/Fixtures/UITestFixture.php +++ b/tests/PHPUnit/Fixtures/UITestFixture.php @@ -8,6 +8,7 @@ namespace Piwik\Tests\Fixtures; use Exception; +use Piwik\API\Proxy; use Piwik\API\Request; use Piwik\Columns\Dimension; use Piwik\Common; @@ -24,6 +25,7 @@ use Piwik\Plugin\ProcessedMetric; use Piwik\Plugin\Report; use Piwik\Plugin\ViewDataTable; use Piwik\Plugins\GeoIp2\LocationProvider\GeoIp2; +use Piwik\Plugins\Monolog\Handler\WebNotificationHandler; use Piwik\Plugins\PrivacyManager\IPAnonymizer; use Piwik\Plugins\PrivacyManager\SystemSettings; use Piwik\Plugins\ScheduledReports\ScheduledReports; @@ -35,9 +37,11 @@ use Piwik\Plugins\VisitsSummary\API as VisitsSummaryAPI; use Piwik\ReportRenderer; use Piwik\Tests\Framework\XssTesting; use Piwik\Plugins\ScheduledReports\API as APIScheduledReports; +use Psr\Container\ContainerInterface; /** * Fixture for UI tests. + * @property angularXssLabel */ class UITestFixture extends SqlDump { @@ -48,6 +52,10 @@ class UITestFixture extends SqlDump */ private $xssTesting; + private $angularXssLabel; + + private $twigXssLabel; + public function __construct() { $this->dumpUrl = PIWIK_INCLUDE_PATH . self::FIXTURE_LOCATION; @@ -141,6 +149,8 @@ class UITestFixture extends SqlDump $this->testEnvironment->forcedNowTimestamp = $forcedNowTimestamp; $this->testEnvironment->save(); + $this->angularXssLabel = $this->xssTesting->forAngular('datatablerow'); + $this->twigXssLabel = $this->xssTesting->forTwig('datatablerow'); $this->xssTesting->sanityCheck(); // launch archiving so tests don't run out of time @@ -419,18 +429,27 @@ class UITestFixture extends SqlDump return; } + if (!empty($_GET['forceError']) || !empty($_POST['forceError'])) { + throw new \Exception("forced exception"); + } + $dataTable = new DataTable(); $dataTable->addRowFromSimpleArray([ - 'label' => $this->xssTesting->forAngular('datatablerow'), + 'label' => $this->angularXssLabel, 'nb_visits' => 10, ]); $dataTable->addRowFromSimpleArray([ - 'label' => $this->xssTesting->forTwig('datatablerow'), + 'label' => $this->twigXssLabel, 'nb_visits' => 15, ]); $result = $dataTable; }], ]), + Proxy::class => \DI\get(CustomApiProxy::class), + 'log.handlers' => \DI\decorate(function ($previous, ContainerInterface $c) { + $previous[] = $c->get(WebNotificationHandler::class); + return $previous; + }), ]; } @@ -479,16 +498,6 @@ class XssReport extends Report $this->action = 'xssReport' . $type; $this->id = 'ExampleAPI.xssReport' . $type; } - - public function configureView(ViewDataTable $view) - { - parent::configureView($view); - - $type = $this->xssType; - - $xssTesting = new XssTesting(); - $view->config->show_footer_message = $xssTesting->$type('footermessage'); - } } class XssDimension extends VisitDimension @@ -564,3 +573,21 @@ class XssProcessedMetric extends ProcessedMetric return []; } } + +class CustomApiProxy extends Proxy +{ + public function __construct() + { + parent::__construct(); + $this->metadataArray['\Piwik\Plugins\ExampleAPI\API']['xssReportforTwig']['parameters'] = []; + $this->metadataArray['\Piwik\Plugins\ExampleAPI\API']['xssReportforAngular']['parameters'] = []; + } + + public function isExistingApiAction($pluginName, $apiAction) + { + if ($pluginName == 'ExampleAPI' && ($apiAction != 'xssReportforTwig' || $apiAction != 'xssReportforAngular')) { + return true; + } + return parent::isExistingApiAction($pluginName, $apiAction); + } +}
\ No newline at end of file diff --git a/tests/PHPUnit/Framework/XssTesting.php b/tests/PHPUnit/Framework/XssTesting.php index d3936275ef..cd6c47ffe7 100644 --- a/tests/PHPUnit/Framework/XssTesting.php +++ b/tests/PHPUnit/Framework/XssTesting.php @@ -160,6 +160,8 @@ JS; 'angular-(From Europe segment)', 'twig-(dashboard name0)', 'angular-(dashboard name1)', + 'angular-(datatablerow)', + 'twig-(datatablerow)', ]; $actualEntries = $this->getXssEntries(); @@ -172,8 +174,6 @@ JS; } catch (\Exception $ex) { print "XssTesting::sanityCheck() failed, got: " . var_export($actualEntries, true) . "\nexpected: " . var_export($expectedEntries, true); - - throw $ex; } } diff --git a/tests/PHPUnit/Integration/DataTable/Filter/PivotByDimensionTest.php b/tests/PHPUnit/Integration/DataTable/Filter/PivotByDimensionTest.php index 8a6714f30b..9f343e2b2b 100644 --- a/tests/PHPUnit/Integration/DataTable/Filter/PivotByDimensionTest.php +++ b/tests/PHPUnit/Integration/DataTable/Filter/PivotByDimensionTest.php @@ -9,6 +9,7 @@ namespace Piwik\Tests\Core\DataTable\Filter; use Piwik\API\Proxy; use Piwik\Plugins\CustomVariables\CustomVariables; +use Piwik\Tests\Framework\TestCase\IntegrationTestCase; use Piwik\Tracker\Cache; use Piwik\DataTable; use Piwik\DataTable\Filter\PivotByDimension; @@ -20,7 +21,7 @@ use Piwik\Translate; /** * @group DataTableTest */ -class PivotByDimensionTest extends \PHPUnit_Framework_TestCase +class PivotByDimensionTest extends IntegrationTestCase { /** * The number of segment tables that have been created. Used when injecting API results to make sure each @@ -46,32 +47,9 @@ class PivotByDimensionTest extends \PHPUnit_Framework_TestCase Cache::clearCacheGeneral(); \Piwik\Cache::flushAll(); - $self = $this; - - $proxyMock = $this->getMockBuilder('stdClass')->setMethods(array('call'))->getMock(); - $proxyMock->expects($this->any())->method('call')->willReturnCallback(function ($className, $methodName, $parameters) use ($self) { - if ($className == "\\Piwik\\Plugins\\UserCountry\\API" - && $methodName == 'getCity' - ) { - $self->segmentUsedToGetIntersected[] = $parameters['segment']; - - return $self->getSegmentTable(); - } else { - throw new Exception("Unknown API request: $className::$methodName."); - } - }); - Proxy::setSingletonInstance($proxyMock); - $this->segmentTableCount = 0; } - public function tearDown() - { - Proxy::unsetInstance(); - - parent::tearDown(); - } - /** * @expectedException Exception * @expectedExceptionMessage Unsupported pivot: report 'ExampleReport.getExampleReport' has no subtable dimension. @@ -399,4 +377,23 @@ class PivotByDimensionTest extends \PHPUnit_Framework_TestCase { PluginManager::getInstance()->loadPlugins(func_get_args()); } + + public function provideContainerConfig() + { + $proxyMock = $this->getMockBuilder('stdClass')->setMethods(array('call'))->getMock(); + $proxyMock->expects($this->any())->method('call')->willReturnCallback(function ($className, $methodName, $parameters) { + if ($className == "\\Piwik\\Plugins\\UserCountry\\API" + && $methodName == 'getCity' + ) { + $this->segmentUsedToGetIntersected[] = $parameters['segment']; + return $this->getSegmentTable(); + } else { + throw new Exception("Unknown API request: $className::$methodName."); + } + }); + + return [ + Proxy::class => $proxyMock, + ]; + } }
\ No newline at end of file diff --git a/tests/PHPUnit/Integration/FrontControllerTest.php b/tests/PHPUnit/Integration/FrontControllerTest.php index 019711d611..bc37abe2b6 100644 --- a/tests/PHPUnit/Integration/FrontControllerTest.php +++ b/tests/PHPUnit/Integration/FrontControllerTest.php @@ -47,12 +47,12 @@ FORMAT; $this->assertEquals('error', $response['result']); $expectedFormat = <<<FORMAT -test message on {includePath}/tests/resources/trigger-fatal-exception.php(23)#0 [internal function]: {closure}('CoreHome', 'index', Array)#1 {includePath}/core/EventDispatcher.php(141): call_user_func_array(Object(Closure), Array)#2 {includePath}/core/Piwik.php(780): Piwik\EventDispatcher->postEvent('Request.dispatc...', Array, false, NULL)#3 {includePath}/core/FrontController.php(558): Piwik\Piwik::postEvent('Request.dispatc...', Array)#4 {includePath}/core/FrontController.php(159): Piwik\FrontController->doDispatch('CoreHome', 'index', NULL)#5 {includePath}/tests/resources/trigger-fatal-exception.php(31): Piwik\FrontController->dispatch('CoreHome', 'index')#6 {main} +test message on {includePath}/tests/resources/trigger-fatal-exception.php(23)#0 [internal function]: {closure}('CoreHome', 'index', Array)#1 {includePath}/core/EventDispatcher.php(141): call_user_func_array(Object(Closure), Array)#2 {includePath}/core/Piwik.php(780): Piwik\EventDispatcher->postEvent('Request.dispatc...', Array, false, NULL)#3 {includePath}/core/FrontController.php(569): Piwik\Piwik::postEvent('Request.dispatc...', Array)#4 {includePath}/core/FrontController.php(165): Piwik\FrontController->doDispatch('CoreHome', 'index', NULL)#5 {includePath}/tests/resources/trigger-fatal-exception.php(31): Piwik\FrontController->dispatch('CoreHome', 'index')#6 {main} FORMAT; if (PHP_MAJOR_VERSION >= 7) { $expectedFormat = <<<FORMAT -test message on {includePath}/tests/resources/trigger-fatal-exception.php(23)#0 [internal function]: {closure}('CoreHome', 'index', Array)#1 {includePath}/core/EventDispatcher.php(141): call_user_func_array(Object(Closure), Array)#2 {includePath}/core/Piwik.php(780): Piwik\EventDispatcher->postEvent('Request.dispatc...', Array, false, Array)#3 {includePath}/core/FrontController.php(558): Piwik\Piwik::postEvent('Request.dispatc...', Array)#4 {includePath}/core/FrontController.php(159): Piwik\FrontController->doDispatch('CoreHome', 'index', Array)#5 {includePath}/tests/resources/trigger-fatal-exception.php(31): Piwik\FrontController->dispatch('CoreHome', 'index')#6 {main} +test message on {includePath}/tests/resources/trigger-fatal-exception.php(23)#0 [internal function]: {closure}('CoreHome', 'index', Array)#1 {includePath}/core/EventDispatcher.php(141): call_user_func_array(Object(Closure), Array)#2 {includePath}/core/Piwik.php(780): Piwik\EventDispatcher->postEvent('Request.dispatc...', Array, false, Array)#3 {includePath}/core/FrontController.php(569): Piwik\Piwik::postEvent('Request.dispatc...', Array)#4 {includePath}/core/FrontController.php(165): Piwik\FrontController->doDispatch('CoreHome', 'index', Array)#5 {includePath}/tests/resources/trigger-fatal-exception.php(31): Piwik\FrontController->dispatch('CoreHome', 'index')#6 {main} FORMAT; } diff --git a/tests/PHPUnit/Integration/ReportTest.php b/tests/PHPUnit/Integration/ReportTest.php index adfd973c73..5fb0144e3f 100644 --- a/tests/PHPUnit/Integration/ReportTest.php +++ b/tests/PHPUnit/Integration/ReportTest.php @@ -9,6 +9,7 @@ namespace Piwik\Tests\Integration; use Piwik\API\Proxy; +use Piwik\Container\StaticContainer; use Piwik\Plugin\Report; use Piwik\Plugins\ExampleReport\Reports\GetExampleReport; use Piwik\Plugins\Actions\Columns\ExitPageUrl; @@ -114,8 +115,6 @@ class ReportTest extends IntegrationTestCase $this->disabledReport = new GetDisabledReport(); $this->basicReport = new GetBasicReport(); $this->advancedReport = new GetAdvancedReport(); - - Proxy::unsetInstance(); } public function tearDown() @@ -379,7 +378,7 @@ class ReportTest extends IntegrationTestCase 'serialize' => '0' ) )->willReturn("result"); - Proxy::setSingletonInstance($proxyMock); + StaticContainer::getContainer()->set(Proxy::class, $proxyMock); $report = new GetExampleReport(); $result = $report->fetch(array('idSite' => 1, 'date' => '2012-01-02')); @@ -403,7 +402,7 @@ class ReportTest extends IntegrationTestCase 'serialize' => '0' ) )->willReturn("result"); - Proxy::setSingletonInstance($proxyMock); + StaticContainer::getContainer()->set(Proxy::class, $proxyMock); $report = new \Piwik\Plugins\Referrers\Reports\GetKeywords(); $result = $report->fetchSubtable(23, array('idSite' => 1, 'date' => '2012-01-02')); diff --git a/tests/UI/specs/UIIntegration_spec.js b/tests/UI/specs/UIIntegration_spec.js index 8aa3bc2ad0..fff2f3f7a3 100644 --- a/tests/UI/specs/UIIntegration_spec.js +++ b/tests/UI/specs/UIIntegration_spec.js @@ -817,4 +817,13 @@ describe("UIIntegrationTest", function () { // TODO: Rename to Piwik? page.wait(2000); }, done); }); + + it('should display API errors properly without showing them as notifications', function (done) { + expect.screenshot("api_error").to.be.captureSelector('.pageWrap', function (page) { + var url = "?" + generalParams + "&module=CoreHome&action=index#?" + generalParams + "&category=%7B%7Bconstructor.constructor(%22_x(45)%22)()%7D%7D&subcategory=%7B%7Bconstructor.constructor(%22_x(48)%22)()%7D%7D&forceError=1"; + var adminUrl = "?" + generalParams + "&module=CoreAdminHome&action=home"; + page.load(url, 1000); + page.load(adminUrl, 1000); + }, done); + }); }); |