diff --git a/NEWS.md b/NEWS.md index 8393da3f66..3c39d5e565 100644 --- a/NEWS.md +++ b/NEWS.md @@ -98,14 +98,15 @@ - RSS spout now prefers the feed logo to website favicon. ([#1152](https://github.com/fossar/selfoss/pull/1152)) - RSS spout now tries to use favicon from the feed domain when there is no logo or home page favicon. ([#1152](https://github.com/fossar/selfoss/pull/1152)) - Setting `DEBUG` to `1` in `src/common.php` no longer logs HTTP bodies, only headers. Set it to `2` if you need the bodies as well. ([#1152](https://github.com/fossar/selfoss/pull/1152)) -- PHP startup errors are now logged, instead of having F3 crash with Error 500 ([#1195](https://github.com/fossar/selfoss/pull/1195)) +- The debugging level (previously set by modifying `src/common.php`) can be changed in the `config.ini` using `debug` key. ([#1261](https://github.com/fossar/selfoss/pull/1261)) - In order to support offline mode, we moved much of the UI to the browser. ([#1150](https://github.com/fossar/selfoss/pull/1150), [#1184](https://github.com/fossar/selfoss/pull/1184), [#1215](https://github.com/fossar/selfoss/pull/1215), [#1216](https://github.com/fossar/selfoss/pull/1216)) - We carried out a significant internal refactoring ([#1164](https://github.com/fossar/selfoss/pull/1164), [#1190](https://github.com/fossar/selfoss/pull/1190)) - Removed Instapaper spout since it has been broken since its acquisition. Sources using it were migrated to “RSS Feed (with content extraction)”. ([#1245](https://github.com/fossar/selfoss/pull/1245)) - Placeholders are now used for images before they are loaded to avoid content jumping around ([#1204](https://github.com/fossar/selfoss/pull/1204)) - Search button is now always on the screen, avoiding the need to scroll to top to be able to use it. ([#1231](https://github.com/fossar/selfoss/issues/1231)) - Button for opening articles, tags, sources and filters in the sidebar, as well as the source and tag links in articles are now real links, allowing to open them in a new tab by middle-clicking them. ([#1216](https://github.com/fossar/selfoss/issues/1216), [#695](https://github.com/fossar/selfoss/issues/695)) -- Configuration is no longer managed by F3 framework. ([#1261](https://github.com/fossar/selfoss/pull/1261)) +- [F3 framework](https://fatfreeframework.com) is no longer used. So long… ([#1261](https://github.com/fossar/selfoss/pull/1261), [#1295](https://github.com/fossar/selfoss/pull/1295), [#1296](https://github.com/fossar/selfoss/pull/1296)) +- [Tracy](https://tracy.nette.org/) is now used for error handling, resulting in much nicer error messages. ([#1296](https://github.com/fossar/selfoss/pull/1296)) ## 2.18 – 2018-03-05 diff --git a/README.md b/README.md index cc466a9fac..19c58edcfa 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,6 @@ Very special thanks to all contributors of pull requests here on [GitHub](https: Special thanks to the great programmers of these libraries used by selfoss: -* [FatFree PHP Framework](https://fatfreeframework.com/) * [SimplePie](http://simplepie.org/) * [jQuery](https://jquery.com/) * [WideImage](http://wideimage.sourceforge.net/) @@ -100,6 +99,7 @@ Special thanks to the great programmers of these libraries used by selfoss: * [Spectrum Colorpicker](https://github.com/bgrins/spectrum) * [Graby](https://github.com/j0k3r/graby) * [FullTextRSS filters](http://help.fivefilters.org/customer/portal/articles/223153-site-patterns) +* [Tracy](https://tracy.nette.org/) Icon made by http://blackbooze.com/ diff --git a/composer.json b/composer.json index 53ddac4044..827ca90f0a 100644 --- a/composer.json +++ b/composer.json @@ -5,7 +5,6 @@ "require": { "php": ">= 5.6", "ext-gd": "*", - "bcosca/fatfree-core": "^3.7.0", "bramus/router": "^1.6", "danielstjules/stringy": "^3.1", "fossar/guzzle-transcoder": "^0.2.0", @@ -23,6 +22,7 @@ "php-http/guzzle6-adapter": "^1.0", "simplepie/simplepie": "^1.3", "smottt/wideimage": "^1.1", + "tracy/tracy": "^2.5", "violet/streaming-json-encoder": "^1.1", "willwashburn/phpamo": "^1.0" }, diff --git a/composer.lock b/composer.lock index 9c8285d5c2..8f04a0962f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,43 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c6028fdec0e065363013d1461e6009bd", + "content-hash": "5ae67f7bd862e293545823d8a36fb58f", "packages": [ - { - "name": "bcosca/fatfree-core", - "version": "3.7.3", - "source": { - "type": "git", - "url": "https://github.com/bcosca/fatfree-core.git", - "reference": "3e23ae05384b2f830e99c5888b94118819ed948b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/bcosca/fatfree-core/zipball/3e23ae05384b2f830e99c5888b94118819ed948b", - "reference": "3e23ae05384b2f830e99c5888b94118819ed948b", - "shasum": "" - }, - "require": { - "php": ">=5.4" - }, - "type": "library", - "autoload": { - "classmap": [ - "." - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "GPL-3.0" - ], - "description": "A powerful yet easy-to-use PHP micro-framework designed to help you build dynamic and robust Web applications - fast!", - "homepage": "http://fatfreeframework.com/", - "support": { - "issues": "https://github.com/bcosca/fatfree-core/issues", - "source": "https://github.com/bcosca/fatfree-core/tree/3.7.3" - }, - "time": "2020-12-13T12:49:39+00:00" - }, { "name": "bramus/router", "version": "1.6.1", @@ -2795,6 +2760,76 @@ ], "time": "2020-10-23T09:01:57+00:00" }, + { + "name": "tracy/tracy", + "version": "v2.5.9", + "source": { + "type": "git", + "url": "https://github.com/nette/tracy.git", + "reference": "cd9b889371cfbffe17e5b1a19e0a11de1ad31c8f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/tracy/zipball/cd9b889371cfbffe17e5b1a19e0a11de1ad31c8f", + "reference": "cd9b889371cfbffe17e5b1a19e0a11de1ad31c8f", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-session": "*", + "php": ">=5.4.4" + }, + "require-dev": { + "nette/di": "~2.3 || ~3.0.0", + "nette/tester": "~1.7 || ~2.0", + "nette/utils": "~2.3" + }, + "suggest": { + "https://nette.org/donate": "Please support Tracy via a donation" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev" + } + }, + "autoload": { + "classmap": [ + "src" + ], + "files": [ + "src/shortcuts.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "😎 Tracy: the addictive tool to ease debugging PHP code for cool developers. Friendly design, logging, profiler, advanced features like debugging AJAX calls or CLI support. You will love it.", + "homepage": "https://tracy.nette.org", + "keywords": [ + "Xdebug", + "debug", + "debugger", + "nette", + "profiler" + ], + "support": { + "issues": "https://github.com/nette/tracy/issues", + "source": "https://github.com/nette/tracy/tree/v2.5.9" + }, + "time": "2020-05-17T09:25:46+00:00" + }, { "name": "true/punycode", "version": "v2.1.1", diff --git a/docs/content/docs/administration/options.md b/docs/content/docs/administration/options.md index 10ba9b6169..5e378ad6d5 100644 --- a/docs/content/docs/administration/options.md +++ b/docs/content/docs/administration/options.md @@ -59,6 +59,16 @@ Port for database connections. By default `3306` will be used for MySQL and `543 A UNIX domain socket used for connecting to the MySQL database server. Usually, you want to use `db_host=localhost`, which should use the default socket path (typically `/run/mysqld/mysqld.sock` for MySQL or `/run/postgresql` for PostgreSQL) but if you need to specify a different location, you can. This is orthogonal to `db_host` option. +### `debug` +
+ +Level of detail for diagnostics. Enable this for debug traces if you encounter selfoss internal errors or are developing selfoss. + +* `0` (default) – Debugging is disabled, warnings will only be [logged](#logger-level). +* `1` – Debugging is enabled, any error or warning will terminate the application and cause debugging information to be printed into the web browser. +* `2` – Same as `1` but additional information (HTTP requests) will be logged when fetching feed content. +
+ ### `logger_destination`
diff --git a/src/common.php b/src/common.php index d9db78c785..61bc148e9d 100644 --- a/src/common.php +++ b/src/common.php @@ -3,42 +3,40 @@ use Dice\Dice; use helpers\Configuration; use helpers\DatabaseConnection; +use function helpers\sendError; use Monolog\Formatter\LineFormatter; use Monolog\Handler\ErrorLogHandler; use Monolog\Handler\NullHandler; use Monolog\Handler\StreamHandler; use Monolog\Logger; +use Tracy\Debugger; require __DIR__ . '/constants.php'; +require_once __DIR__ . '/helpers/responses.php'; $autoloader = @include BASEDIR . '/vendor/autoload.php'; // we will show custom error if ($autoloader === false) { + header('Content-type: text/plain'); echo 'The PHP dependencies are missing. Did you run `composer install` in the selfoss directory?'; exit; } -$startup_error = error_get_last(); - -// F3 crashes when there were PHP startups error even though -// they might not affect the program (e.g. unable to load an extension). -// It also sets its own error_reporting value and uses the previous one -// as a signal to disable the initialization failure check. -error_reporting(0); - -$f3 = Base::instance(); - -// Disable deprecation warnings. -// Dice uses ReflectionParameter::getClass(), which is deprecated in PHP 8 -// but we have not set an error handler yet because it needs a Logger instantiated by Dice. -error_reporting(E_ALL & ~E_DEPRECATED); - -$f3->set('AUTOLOAD', false); -$f3->set('BASEDIR', BASEDIR); +// Catch any errors and hopefully log them. +Debugger::$errorTemplate = __DIR__ . '/error.500.phtml'; +Debugger::enable(Debugger::PRODUCTION); $configuration = new Configuration(__DIR__ . '/../config.ini', $_ENV); -$f3->set('DEBUG', $configuration->debug); -$f3->set('cache', $configuration->cache); +if ($configuration->debug !== 0) { + // Enable strict mode to loudly fail on any error or warning. + // We ignore deprecation warnings because Dice uses deprecated + // ReflectionParameter::getClass(), which we cannot do anything about. + Debugger::$strictMode = E_ALL & ~E_DEPRECATED; + // Switch to development mode so that traces are displayed. + Debugger::enable(Debugger::DEVELOPMENT); + // Dispatch will not run in production mode preventing bar from loading. + Debugger::dispatch(); +} $dice = new Dice(); @@ -83,8 +81,7 @@ $dice->addRule(daos\TagsInterface::class, $shared); if ($configuration->isChanged('dbSocket') && $configuration->isChanged('dbHost')) { - echo 'You cannot set both `db_socket` and `db_host` options.' . PHP_EOL; - exit; + sendError('You cannot set both `db_socket` and `db_host` options.' . PHP_EOL); } // Database connection @@ -231,8 +228,7 @@ function($pattern, $text) { } elseif ($logger_destination === 'error_log') { $handler = new ErrorLogHandler(ErrorLogHandler::OPERATING_SYSTEM, $configuration->loggerLevel); } else { - echo 'The `logger_destination` option needs to be either `error_log` or a file path prefixed by `file:`.'; - exit; + sendError('The `logger_destination` option needs to be either `error_log` or a file path prefixed by `file:`.'); } $formatter = new LineFormatter(null, null, true, true); @@ -241,42 +237,5 @@ function($pattern, $text) { } $log->pushHandler($handler); -if (isset($startup_error)) { - $log->warn('PHP likely encountered a startup error: ', [$startup_error]); -} - -// init error handling -$f3->set('ONERROR', - function(Base $f3) use ($configuration, $log, $handler) { - $exception = $f3->get('EXCEPTION'); - - try { - if ($exception) { - $log->error($exception->getMessage(), ['exception' => $exception]); - } else { - $log->error($f3->get('ERROR.text')); - } - - if ($configuration->debug !== 0) { - echo 'An error occurred' . ': '; - echo $f3->get('ERROR.text') . "\n"; - echo $f3->get('ERROR.trace'); - } else { - if ($handler instanceof StreamHandler) { - echo 'An error occured, please check the log file “' . $handler->getUrl() . '”.' . PHP_EOL; - } elseif ($handler instanceof ErrorLogHandler) { - echo 'An error occured, please check your system logs.' . PHP_EOL; - } else { - echo 'An error occurred' . PHP_EOL; - } - } - } catch (Exception $e) { - echo 'Unable to write logs.' . PHP_EOL; - echo $e->getMessage() . PHP_EOL; - } - } -); - -if ($configuration->debug !== 0) { - ini_set('display_errors', '0'); -} +// Try to log errors encountered by error handler. +Debugger::setLogger($dice->create(helpers\TracyLogger::class)); diff --git a/src/controllers/Index.php b/src/controllers/Index.php index dc91868721..f2188f6d36 100644 --- a/src/controllers/Index.php +++ b/src/controllers/Index.php @@ -63,11 +63,13 @@ public function home() { $home = BASEDIR . '/public/index.html'; if (!file_exists($home)) { http_response_code(500); + header('Content-type: text/plain'); echo 'Please build the assets using `npm run build` or obtain a pre-built packages from https://selfoss.aditu.de.'; exit; } // show as full html page + header('Content-type: text/html'); readfile($home); return; diff --git a/src/controllers/Opml/Export.php b/src/controllers/Opml/Export.php index 27bd6b28f8..d359c209d5 100644 --- a/src/controllers/Opml/Export.php +++ b/src/controllers/Opml/Export.php @@ -94,6 +94,11 @@ private function writeSource(array $source) { public function export() { $this->authentication->needsLoggedIn(); + // save content as file and suggest file name + $exportName = 'selfoss-subscriptions-' . date('YmdHis') . '.xml'; + header('Content-Disposition: attachment; filename="' . $exportName . '"'); + header('Content-Type: text/xml; charset=UTF-8'); + $this->logger->debug('start OPML export'); $this->writer->openMemory(); $this->writer->setIndent(true); @@ -166,10 +171,6 @@ public function export() { $this->writer->endDocument(); $this->logger->debug('finished OPML export'); - // save content as file and suggest file name - $exportName = 'selfoss-subscriptions-' . date('YmdHis') . '.xml'; - header('Content-Disposition: attachment; filename="' . $exportName . '"'); - header('Content-Type: text/xml; charset=UTF-8'); echo $this->writer->outputMemory(); } } diff --git a/src/controllers/Opml/ImportPage.php b/src/controllers/Opml/ImportPage.php index 11ec42152e..2c2d7b6adb 100644 --- a/src/controllers/Opml/ImportPage.php +++ b/src/controllers/Opml/ImportPage.php @@ -25,6 +25,7 @@ public function __construct(Authentication $authentication) { */ public function show() { $this->authentication->needsLoggedIn(); + header('Content-type: text/html'); readfile(BASEDIR . '/public/opml.html'); } } diff --git a/src/controllers/Sources/Update.php b/src/controllers/Sources/Update.php index 6ff65c7ee8..5526072dd4 100644 --- a/src/controllers/Sources/Update.php +++ b/src/controllers/Sources/Update.php @@ -27,6 +27,8 @@ public function __construct(Authentication $authentication, ContentLoader $conte * @return void */ public function updateAll() { + header('Content-type: text/plain'); + // only allow access for localhost and loggedin users if (!$this->authentication->allowedToUpdate()) { exit('unallowed access'); @@ -47,6 +49,8 @@ public function updateAll() { * @return void */ public function update($id) { + header('Content-type: text/plain'); + // only allow access for localhost and authenticated users if (!$this->authentication->allowedToUpdate()) { exit('unallowed access'); diff --git a/src/controllers/Sources/Write.php b/src/controllers/Sources/Write.php index 20714a7a57..dee16a47db 100644 --- a/src/controllers/Sources/Write.php +++ b/src/controllers/Sources/Write.php @@ -109,7 +109,7 @@ public function write($id = null) { $validation = $this->sourcesDao->validate($title, $spout, $data); if ($validation !== true) { - $this->view->error(json_encode($validation)); + $this->view->jsonError($validation); } // add/edit source diff --git a/src/error.500.phtml b/src/error.500.phtml new file mode 100644 index 0000000000..31d967633c --- /dev/null +++ b/src/error.500.phtml @@ -0,0 +1,50 @@ + + + + + +selfoss failed + + + +
+
+

selfoss failed

+ +

We’re sorry! selfoss encountered an unexpected error and was unable to complete your request.

+ +

Please contact your administrator or try again later.

+ +

If you are the administrator, + + please check the logs or add + + selfoss is unable to log the error, make sure the data/logs directory is writeable (if applicable), or try adding + + debug=1 to your selfoss instance configuration.

+ +

error 500 |

+
+
+ + diff --git a/src/helpers/Authentication.php b/src/helpers/Authentication.php index 87dbc7ec2a..368794525a 100644 --- a/src/helpers/Authentication.php +++ b/src/helpers/Authentication.php @@ -172,6 +172,7 @@ public function needsLoggedIn() { */ public function forbidden() { header('HTTP/1.0 403 Forbidden'); + header('Content-type: text/plain'); echo 'Access forbidden!'; exit; } diff --git a/src/helpers/Configuration.php b/src/helpers/Configuration.php index 31ab8cfb24..11f7ad4953 100644 --- a/src/helpers/Configuration.php +++ b/src/helpers/Configuration.php @@ -24,7 +24,7 @@ class Configuration { // Internal but overridable values. - /** @var int debugging level @internal */ + /** @var int debugging level */ public $debug = 0; /** @var string @internal */ diff --git a/src/helpers/TracyLogger.php b/src/helpers/TracyLogger.php new file mode 100644 index 0000000000..6aa6c734fc --- /dev/null +++ b/src/helpers/TracyLogger.php @@ -0,0 +1,52 @@ + Monolog\Logger::DEBUG, + self::INFO => Monolog\Logger::INFO, + self::WARNING => Monolog\Logger::WARNING, + self::ERROR => Monolog\Logger::ERROR, + self::EXCEPTION => Monolog\Logger::CRITICAL, + self::CRITICAL => Monolog\Logger::CRITICAL, + ]; + + /** @var Monolog\Logger */ + protected $monolog; + + public function __construct(Monolog\Logger $monolog) { + $this->monolog = $monolog; + } + + public function log($message, $priority = self::INFO) { + $context = [ + 'at' => Helpers::getSource(), + ]; + + if ($message instanceof Throwable || $message instanceof Exception) { + $context['exception'] = $message; + $message = ''; + } + + $this->monolog->addRecord( + self::PRIORITY_MAP[$priority] ?: Monolog\Logger::ERROR, + $message, + $context + ); + } +} diff --git a/src/helpers/View.php b/src/helpers/View.php index a9c37085b6..f63fc6b9e0 100644 --- a/src/helpers/View.php +++ b/src/helpers/View.php @@ -99,8 +99,7 @@ public function isAjax() { * @return void */ public function error($message) { - header('HTTP/1.0 400 Bad Request'); - exit($message); + sendError($message); } /** @@ -111,8 +110,9 @@ public function error($message) { * @return void */ public function jsonError($data) { + header('HTTP/1.0 400 Bad Request'); header('Content-type: application/json'); - $this->error(json_encode($data)); + exit(json_encode($data)); } /** diff --git a/src/helpers/responses.php b/src/helpers/responses.php new file mode 100644 index 0000000000..b5bbc7bcb9 --- /dev/null +++ b/src/helpers/responses.php @@ -0,0 +1,16 @@ +