From 32e37d1cac068c5e4cbcceefb189f112357cd1fd Mon Sep 17 00:00:00 2001 From: Lukas Winkler Date: Wed, 31 Mar 2021 23:38:26 +0200 Subject: [PATCH] add icon, notification and a few translatable strings --- Diagnostic/CurlVersionCheck.php | 2 +- Diagnostic/DatabaseVersionCheck.php | 31 ++-- Diagnostic/ExampleCheck.php | 2 +- ...zipMatomoJsCheck.php => MatomoJsCheck.php} | 22 +-- Diagnostic/OpensslVersionCheck.php | 2 +- Diagnostic/PhpIniCheck.php | 16 ++- Diagnostic/PhpUserCheck.php | 2 +- Diagnostic/PhpVersionCheck.php | 35 +++-- Diagnostic/URLCheck.php | 135 ++++++++++++++++++ DiagnosticsExtended.php | 32 +++++ config/config.php | 5 +- lang/en.json | 29 ++++ plugin.json | 23 ++- 13 files changed, 283 insertions(+), 53 deletions(-) rename Diagnostic/{GzipMatomoJsCheck.php => MatomoJsCheck.php} (79%) create mode 100644 Diagnostic/URLCheck.php create mode 100644 lang/en.json diff --git a/Diagnostic/CurlVersionCheck.php b/Diagnostic/CurlVersionCheck.php index 3526eee..1ddb5ca 100644 --- a/Diagnostic/CurlVersionCheck.php +++ b/Diagnostic/CurlVersionCheck.php @@ -36,7 +36,7 @@ class CurlVersionCheck implements Diagnostic public function __construct(LoggerInterface $logger, \Matomo\Cache\Lazy $lazyCache) { $this->logger = $logger; - $this->label = "curl version check"; + $this->label = "🧪 curl version check"; $this->lazyCache = $lazyCache; } diff --git a/Diagnostic/DatabaseVersionCheck.php b/Diagnostic/DatabaseVersionCheck.php index 3094bc7..65bd143 100644 --- a/Diagnostic/DatabaseVersionCheck.php +++ b/Diagnostic/DatabaseVersionCheck.php @@ -11,6 +11,7 @@ namespace Piwik\Plugins\DiagnosticsExtended\Diagnostic; use Piwik\Date; use Piwik\Db; use Piwik\Http; +use Piwik\Piwik; use Piwik\Plugins\Diagnostics\Diagnostic\Diagnostic; use Piwik\Plugins\Diagnostics\Diagnostic\DiagnosticResult; use Piwik\Plugins\Diagnostics\Diagnostic\DiagnosticResultItem; @@ -37,7 +38,7 @@ class DatabaseVersionCheck implements Diagnostic public function __construct(LoggerInterface $logger, \Matomo\Cache\Lazy $lazyCache) { $this->logger = $logger; - $this->label = "Database version check"; + $this->label = "🧪 " . Piwik::translate("DiagnosticsExtended_DatabaseVersionCheckLabel"); $this->lazyCache = $lazyCache; } @@ -78,29 +79,37 @@ class DatabaseVersionCheck implements Diagnostic if (version_compare($currentVersion, $latestVersion, ">=")) { $results->addItem(new DiagnosticResultItem( DiagnosticResult::STATUS_OK, - "You are using the latest version of MariaDB " . $minorVersion + Piwik::translate("DiagnosticsExtended_PhpVersionCheckLatestVersion", + [$minorVersion]) )); } else { $results->addItem(new DiagnosticResultItem( DiagnosticResult::STATUS_WARNING, - "There is a newer MariaDB patch version ($latestVersion) available (you are using $version/$currentVersion). - You should update to it as soon as possible - (unless the distributor of your MariaDB binary is backporting security patches)." + Piwik::translate("DiagnosticsExtended_DatabaseVersionCheckMariaDBOutdated", [ + $latestVersion, $version, $currentVersion + ]) + . " " + . Piwik::translate("DiagnosticsExtended_BackportingDisclaimerMariaDB") + . "." )); } + $formattedDate = (Date::factory($versionInfo["eol"]))->getLocalized(Date::DATE_FORMAT_LONG); if (new \DateTime() > new \DateTime($versionInfo["eol"])) { $results->addItem(new DiagnosticResultItem( DiagnosticResult::STATUS_WARNING, - "Your MariaDB version ($currentVersion) does not recieve security support by the MariaDB - team anymore. You should update to a newer version - (unless the distributor of your PHP binary is backporting security patches)." + Piwik::translate("DiagnosticsExtended_DatabaseVersionCheckMariaDBEol", [ + $currentVersion, $formattedDate + ]) + . " " + . Piwik::translate("DiagnosticsExtended_BackportingDisclaimerMariaDB") + . "." )); } else { - $formattedDate = (Date::factory($versionInfo["eol"]))->getLocalized(Date::DATE_FORMAT_LONG); $results->addItem(new DiagnosticResultItem( DiagnosticResult::STATUS_OK, - "Your MariaDB version ($minorVersion) receives security support by the MariaDB - team until $formattedDate." + Piwik::translate("DiagnosticsExtended_DatabaseVersionCheckMariaDBNotEol", [ + $minorVersion, $formattedDate + ]) )); } return [$results]; diff --git a/Diagnostic/ExampleCheck.php b/Diagnostic/ExampleCheck.php index 1d847f3..741a9b3 100644 --- a/Diagnostic/ExampleCheck.php +++ b/Diagnostic/ExampleCheck.php @@ -27,7 +27,7 @@ class ExampleCheck implements Diagnostic public function execute() { - $result = new DiagnosticResult("label"); + $result = new DiagnosticResult("🧪 label"); $result->addItem(new DiagnosticResultItem(DiagnosticResult::STATUS_ERROR, "error")); $result->addItem(new DiagnosticResultItem(DiagnosticResult::STATUS_WARNING, "warning")); $result->addItem(new DiagnosticResultItem(DiagnosticResult::STATUS_OK, "okay")); diff --git a/Diagnostic/GzipMatomoJsCheck.php b/Diagnostic/MatomoJsCheck.php similarity index 79% rename from Diagnostic/GzipMatomoJsCheck.php rename to Diagnostic/MatomoJsCheck.php index c0ddd7c..e1a0909 100644 --- a/Diagnostic/GzipMatomoJsCheck.php +++ b/Diagnostic/MatomoJsCheck.php @@ -9,6 +9,7 @@ namespace Piwik\Plugins\DiagnosticsExtended\Diagnostic; use Piwik\Http; +use Piwik\Piwik; use Piwik\Plugins\Diagnostics\Diagnostic\Diagnostic; use Piwik\Plugins\Diagnostics\Diagnostic\DiagnosticResult; use Piwik\Plugins\Diagnostics\Diagnostic\DiagnosticResultItem; @@ -16,7 +17,7 @@ use Piwik\SettingsPiwik; use Piwik\Tracker\TrackerCodeGenerator; use Psr\Log\LoggerInterface; -class GzipMatomoJsCheck implements Diagnostic +class MatomoJsCheck implements Diagnostic { /** * @var LoggerInterface @@ -37,7 +38,7 @@ class GzipMatomoJsCheck implements Diagnostic public function __construct(LoggerInterface $logger) { $this->logger = $logger; - $this->label = "matomo.js gzip"; + $this->label = "🧪 " . "matomo.js"; # no need to make it translatable } @@ -62,18 +63,23 @@ class GzipMatomoJsCheck implements Diagnostic $headers = $response["headers"]; $data = $response["data"]; if ($status != 200 || strpos($data, "c80d50af7d3db9be66a4d0a86db0286e4fd33292") === false) { - return [DiagnosticResult::singleResult( - $this->label, + $result = new DiagnosticResult($this->label); + $result->addItem(new DiagnosticResultItem( DiagnosticResult::STATUS_INFORMATIONAL, - "It seems like matomo.js can't be fetched properly" - )]; + Piwik::translate("DiagnosticsExtended_MatomoJSCheckFailed") + )); + $result->setLongErrorMessage(Piwik::translate("DiagnosticsExtended_MatomoJSCheckFailedCurlTip", [ + "curl -v $checkURL" + ])); + return [$result]; } $results = new DiagnosticResult($this->label); $contentType = $headers["content-type"]; if ($contentType !== "application/javascript") { $results->addItem(new DiagnosticResultItem( DiagnosticResult::STATUS_WARNING, - "matomo.js should be delivered with an 'application/javascript' Content-Type. You are using '$contentType'." + Piwik::translate("DiagnosticsExtended_MatomoJSCheckMIMEError", + [$contentType]) )); } @@ -81,7 +87,7 @@ class GzipMatomoJsCheck implements Diagnostic if ($contentEncoding === "gzip") { $results->addItem(new DiagnosticResultItem( DiagnosticResult::STATUS_OK, - "matomo.js is delivered gzipped." + Piwik::translate("DiagnosticsExtended_MatomoJSCheckGzipped") )); } else { diff --git a/Diagnostic/OpensslVersionCheck.php b/Diagnostic/OpensslVersionCheck.php index 9da8cf7..7930eac 100644 --- a/Diagnostic/OpensslVersionCheck.php +++ b/Diagnostic/OpensslVersionCheck.php @@ -33,7 +33,7 @@ class OpensslVersionCheck implements Diagnostic public function __construct(LoggerInterface $logger) { $this->logger = $logger; - $this->label = "OpenSSL version check"; + $this->label = "🧪 OpenSSL version check"; } /** diff --git a/Diagnostic/PhpIniCheck.php b/Diagnostic/PhpIniCheck.php index 0ea6b28..b95cc73 100644 --- a/Diagnostic/PhpIniCheck.php +++ b/Diagnostic/PhpIniCheck.php @@ -8,6 +8,7 @@ namespace Piwik\Plugins\DiagnosticsExtended\Diagnostic; +use Piwik\Piwik; use Piwik\Plugins\Diagnostics\Diagnostic\Diagnostic; use Piwik\Plugins\Diagnostics\Diagnostic\DiagnosticResult; use Piwik\Plugins\Diagnostics\Diagnostic\DiagnosticResultItem; @@ -24,29 +25,38 @@ class PhpIniCheck implements Diagnostic * @var IniSetting[] */ private $iniSettings; + /** + * @var string + */ + private $label; public function __construct(array $iniSettings, LoggerInterface $logger) { $this->logger = $logger; $this->iniSettings = $iniSettings; + $this->label = "🧪 " . Piwik::translate("DiagnosticsExtended_PhpIniCheckLabel"); } public function execute() { - $result = new DiagnosticResult("php.ini checks"); + $result = new DiagnosticResult($this->label); foreach ($this->iniSettings as $setting) { $key = $setting::$key; if ($this->booleanIni($key) === $setting::$targetValue) { $item = new DiagnosticResultItem( DiagnosticResult::STATUS_OK, - $setting::$targetValue ? "$key is enabled" : "$key is disabled" + $setting::$targetValue + ? Piwik::translate("DiagnosticsExtended_PhpIniCheckIsEnabled", [$key]) + : Piwik::translate("DiagnosticsExtended_PhpIniCheckIsDisabled", [$key]) ); } else { $status = $setting::$severe ? DiagnosticResult::STATUS_ERROR : DiagnosticResult::STATUS_WARNING; $item = new DiagnosticResultItem( $status, - $setting::$targetValue ? "$key should be enabled" : "$key should be disabled" + $setting::$targetValue + ? Piwik::translate("DiagnosticsExtended_PhpIniCheckShouldBeEnabled", [$key]) + : Piwik::translate("DiagnosticsExtended_PhpIniCheckShouldBeDisabled", [$key]) ); } $result->addItem($item); diff --git a/Diagnostic/PhpUserCheck.php b/Diagnostic/PhpUserCheck.php index fb01cc2..ddcdc76 100644 --- a/Diagnostic/PhpUserCheck.php +++ b/Diagnostic/PhpUserCheck.php @@ -35,7 +35,7 @@ class PhpUserCheck implements Diagnostic } if (posix_getuid() === 0) { return [DiagnosticResult::singleResult( - "php running as root", + "🧪 php running as root", DiagnosticResult::STATUS_WARNING, "PHP seems to be running as root. Unless you are using Matomo inside a docker container you should check your setup." diff --git a/Diagnostic/PhpVersionCheck.php b/Diagnostic/PhpVersionCheck.php index 24fd919..84788b6 100644 --- a/Diagnostic/PhpVersionCheck.php +++ b/Diagnostic/PhpVersionCheck.php @@ -10,6 +10,7 @@ namespace Piwik\Plugins\DiagnosticsExtended\Diagnostic; use Piwik\Date; use Piwik\Http; +use Piwik\Piwik; use Piwik\Plugins\Diagnostics\Diagnostic\Diagnostic; use Piwik\Plugins\Diagnostics\Diagnostic\DiagnosticResult; use Piwik\Plugins\Diagnostics\Diagnostic\DiagnosticResultItem; @@ -36,7 +37,7 @@ class PhpVersionCheck implements Diagnostic public function __construct(LoggerInterface $logger, \Matomo\Cache\Lazy $lazyCache) { $this->logger = $logger; - $this->label = "php version check"; + $this->label = "🧪 " . Piwik::translate("DiagnosticsExtended_PhpVersionCheckLabel"); $this->lazyCache = $lazyCache; } @@ -81,35 +82,45 @@ class PhpVersionCheck implements Diagnostic if (version_compare($currentVersion, $latestVersion, ">=")) { $results->addItem(new DiagnosticResultItem( DiagnosticResult::STATUS_OK, - "You are using the latest version of PHP " . $minorVersion + Piwik::translate("DiagnosticsExtended_PhpVersionCheckLatestVersion", [$minorVersion]) )); } else { $results->addItem(new DiagnosticResultItem( DiagnosticResult::STATUS_WARNING, - "There is a newer PHP patch version ($latestVersion) available (you are using $currentVersion). - You should update to it as soon as possible - (unless the distributor of your PHP binary is backporting security patches)." + Piwik::translate("DiagnosticsExtended_PhpVersionCheckOutdated", [ + $latestVersion, $currentVersion + ]) + . " " + . Piwik::translate("DiagnosticsExtended_BackportingDisclaimerPHP") + . "." )); } if (empty($this->eolDates[$minorVersion])) { $results->addItem(new DiagnosticResultItem( DiagnosticResult::STATUS_INFORMATIONAL, - "No information is know about your PHP version ($currentVersion)." + Piwik::translate("DiagnosticsExtended_PhpVersionCheckNoInformation", [ + $latestVersion, $currentVersion + ]) )); } elseif (new \DateTime() > new \DateTime($this->eolDates[$minorVersion])) { + $formattedDate = (Date::factory($this->eolDates[$minorVersion]))->getLocalized(Date::DATE_FORMAT_LONG); $results->addItem(new DiagnosticResultItem( DiagnosticResult::STATUS_WARNING, - "Your PHP version ($currentVersion) does not recieve security support by the PHP - team anymore. You should update to a newer version - (unless the distributor of your PHP binary is backporting security patches)." + Piwik::translate("DiagnosticsExtended_PhpVersionCheckEol", [ + $currentVersion, $formattedDate + ]) + . " " + . Piwik::translate("DiagnosticsExtended_BackportingDisclaimerPHP") + . "." )); } else { $formattedDate = (Date::factory($this->eolDates[$minorVersion]))->getLocalized(Date::DATE_FORMAT_LONG); $results->addItem(new DiagnosticResultItem( DiagnosticResult::STATUS_OK, - "Your PHP version ($minorVersion) receives security support by the PHP - team until $formattedDate." + Piwik::translate("DiagnosticsExtended_PhpVersionCheckEol", [ + $minorVersion, $formattedDate + ]) )); } } catch (\Exception $e) { @@ -124,7 +135,7 @@ class PhpVersionCheck implements Diagnostic return DiagnosticResult::singleResult( $this->label, DiagnosticResult::STATUS_INFORMATIONAL, - "Matomo could not check if your PHP version is up-to-date" + Piwik::translate("DiagnosticsExtended_PhpVersionCheckNotWorking") ); } } diff --git a/Diagnostic/URLCheck.php b/Diagnostic/URLCheck.php new file mode 100644 index 0000000..f7988cd --- /dev/null +++ b/Diagnostic/URLCheck.php @@ -0,0 +1,135 @@ +logger = $logger; + $this->matomoURL = SettingsPiwik::getPiwikUrl(); + $this->criticalIssue = false; + } + + public function execute() + { + //TODO: don't check if running in development mode + $result = new DiagnosticResult("Files that should not be public"); + $result->addItem($this->checkConfigIni()); + $result->addItem($this->checkRequestNotAllowed( + ".git/info/exclude", + "Lines that start" + )); + $result->addItem($this->checkRequestNotAllowed( + "tmp/cache/token.php", + "?php exit" + )); + $result->addItem($this->checkRequestNotAllowed( + "cache/tracker/matomocache_general.php", + "unserialize" + )); + + if ($this->criticalIssue) { + $result->setLongErrorMessage( + "Please check if your webserver processes the .htaccess files + generated by Matomo properly. If you are using Nginx, please take a look at the + + official matomo-nginx config for reference.
+ Otherwise attackers might be able to read sensitive data." + ); + } + return array($result); + } + + /** + * @return DiagnosticResultItem + */ + protected function checkConfigIni() + { + $relativeUrl = "config/config.ini.php"; + list($status, $headers, $data) = $this->makeHTTPReququest($relativeUrl); + if ($this->contains($data, "salt")) { + return $this->isPublicError($relativeUrl, true); + } + if ($this->contains($data, ";")) { + return new DiagnosticResultItem( + DiagnosticResult::STATUS_WARNING, + "$relativeUrl seems to be semi-public. " . + "While attackers can't read the config now, the file is publicly accessible and if for whatever reason your webserver " . + "stops executing PHP files, everyone can read your MySQL credentials and more" . + "Please check your webserver config." + ); + } + } + + protected function checkRequestNotAllowed($relativeUrl, $content, $critical = true): DiagnosticResultItem + { + list($status, $headers, $data) = $this->makeHTTPReququest($relativeUrl); +// var_dump($data); + if (strpos($data, $content) !== false) { + return $this->isPublicError($relativeUrl, $critical); + } + + return new DiagnosticResultItem(DiagnosticResult::STATUS_OK, "$relativeUrl doesn't seem to be publically reachable"); + } + + protected function isPublicError($relativeUrl, $critical): DiagnosticResultItem + { + if ($critical) { + $this->criticalIssue = true; + } + return new DiagnosticResultItem( + $critical ? DiagnosticResult::STATUS_ERROR : DiagnosticResult::STATUS_WARNING, + "$relativeUrl should never be public. Please check your webserver config." + ); + } + + protected function makeHTTPReququest($relativeUrl) + { + $response = Http::sendHttpRequest($this->matomoURL . $relativeUrl, self::SOCKET_TIMEOUT, $userAgent = null, + $destinationPath = null, + $followDepth = 0, + $acceptLanguage = false, + $byteRange = false, + $getExtendedInfo = true); + $status = $response["status"]; + $headers = $response["headers"]; + $data = $response["data"]; + return [$status, $headers, $data]; + } + + protected function contains(string $haystack, string $needle): bool + { + return strpos($haystack, $needle) !== false; + } + + +} diff --git a/DiagnosticsExtended.php b/DiagnosticsExtended.php index 378b33e..c10e478 100644 --- a/DiagnosticsExtended.php +++ b/DiagnosticsExtended.php @@ -8,6 +8,38 @@ namespace Piwik\Plugins\DiagnosticsExtended; +use Piwik\Notification; +use Piwik\Piwik; + class DiagnosticsExtended extends \Piwik\Plugin { + public function registerEvents() + { + return [ + 'Request.dispatch' => "addNotification" + ]; + } + + public function addNotification(&$module, &$action, &$parameters) + { + if ($module == "Installation" && $action == "systemCheckPage") { + $id = 'DiagnosticsExtended_Help'; + + $notification = new Notification(Piwik::translate("DiagnosticsExtended_NotificationText", + [ + '', + '', + '', + '' + ] + )); + $notification->raw = true; + $notification->title = Piwik::translate('DiagnosticsExtended_NotificationTitle'); + $notification->context = Notification::CONTEXT_INFO; + \Piwik\Notification\Manager::notify($id, $notification); + } + } + + + } diff --git a/config/config.php b/config/config.php index 26c8bc9..00edfa2 100644 --- a/config/config.php +++ b/config/config.php @@ -10,14 +10,15 @@ return [ DI\get('diagnosticsExtended.inisettings') ), 'diagnostics.optional' => DI\add([ - DI\get('\Piwik\Plugins\DiagnosticsExtended\Diagnostic\ExampleCheck'), +// DI\get('\Piwik\Plugins\DiagnosticsExtended\Diagnostic\ExampleCheck'), DI\get('\Piwik\Plugins\DiagnosticsExtended\Diagnostic\PhpIniCheck'), DI\get('\Piwik\Plugins\DiagnosticsExtended\Diagnostic\PhpVersionCheck'), DI\get('\Piwik\Plugins\DiagnosticsExtended\Diagnostic\DatabaseVersionCheck'), - DI\get('\Piwik\Plugins\DiagnosticsExtended\Diagnostic\GzipMatomoJsCheck'), + DI\get('\Piwik\Plugins\DiagnosticsExtended\Diagnostic\MatomoJsCheck'), DI\get('\Piwik\Plugins\DiagnosticsExtended\Diagnostic\CurlVersionCheck'), DI\get('\Piwik\Plugins\DiagnosticsExtended\Diagnostic\OpensslVersionCheck'), DI\get('\Piwik\Plugins\DiagnosticsExtended\Diagnostic\PhpUserCheck'), + DI\get('\Piwik\Plugins\DiagnosticsExtended\Diagnostic\URLCheck'), ]), ]; diff --git a/lang/en.json b/lang/en.json new file mode 100644 index 0000000..bd83a84 --- /dev/null +++ b/lang/en.json @@ -0,0 +1,29 @@ +{ + "DiagnosticsExtended": { + "BackportingDisclaimerMariaDB": "(unless the distributor of your MariaDB binary is backporting security patches)", + "BackportingDisclaimerPHP": "(unless the distributor of your PHP binary is backporting security patches)", + "DatabaseVersionCheckLabel": "Database version", + "MatomoJSCheckFailed": "It seems like matomo.js can't be fetched properly.", + "MatomoJSCheckGzipped": "matomo.js is delivered gzipped.", + "MatomoJSCheckMIMEError": "matomo.js should be delivered with an 'application/javascript' Content-Type. You are using '%s'.", + "MatomoJSCheckFailedCurlTip": "try running %s on your server and see if it is able to fetch the file successfully", + "DatabaseVersionCheckMariaDBEol": "Your MariaDB version (%1$s) does not receive security support by the MariaDB team anymore (since %2$s). You should update to a newer version", + "DatabaseVersionCheckMariaDBNotEol": "Your MariaDB version (%1$s) receives security support by the MariaDB team until %2$s.", + "DatabaseVersionCheckMariaDBLatestVersion": "You are using the latest version of MariaDB %s.", + "DatabaseVersionCheckMariaDBOutdated": "There is a newer MariaDB patch version (%1$s) available (you are using %2$s/%3$s). You should update to it as soon as possible", + "NotificationText": "You have enabled the DiagnosticsExtended plugin. It adds a few more experimental system checks (marked with 🧪) to this page that might help you find issues with your Matomo instance. There might still be a few false positives and false negatives, so if you notice something strange, please report it to the %1$sforum%2$s or %3$screate a GitHub issue%4$s.", + "NotificationTitle": "About DiagnosticsExtended", + "PhpIniCheckIsDisabled": "%s is enabled", + "PhpIniCheckIsEnabled": "%s is enabled", + "PhpIniCheckLabel": "php.ini options", + "PhpIniCheckShouldBeDisabled": "%s should be enabled", + "PhpIniCheckShouldBeEnabled": "%s should be enabled", + "PhpVersionCheckEol": "Your PHP version (%1$s) does not receive security support by the PHP team anymore (since %2$s). You should update to a newer version", + "PhpVersionCheckLabel": "PHP version", + "PhpVersionCheckLatestVersion": "You are using the latest version of PHP %s", + "PhpVersionCheckNoInformation": "No information is know about your PHP version (%s)", + "PhpVersionCheckNotEol": "Your PHP version (%1$s) receives security support by the PHP team until %2$s.", + "PhpVersionCheckNotWorking": "Matomo could not check if your PHP version is up-to-date", + "PhpVersionCheckOutdated": "There is a newer PHP patch version (%1$s) available (you are using %2$s). You should update to it as soon as possible" + } +} diff --git a/plugin.json b/plugin.json index 2a9e11e..addcf9d 100644 --- a/plugin.json +++ b/plugin.json @@ -8,22 +8,19 @@ }, "authors": [ { - "name": "Matomo", - "email": "", - "homepage": "" + "name": "Lukas Winkler", + "email": "lukas@matomo.org", + "homepage": "https://lw1.at" } ], "support": { - "email": "", - "issues": "", - "forum": "", - "irc": "", - "wiki": "", - "source": "", - "docs": "", - "rss": "" + "email": "lukas@matomo.org", + "issues": "https://github.com/Findus23/plugin-DiagnosticsExtended/issues", + "forum": "https://forum.matomo.org", + "source": "https://github.com/Findus23/plugin-DiagnosticsExtended" }, - "homepage": "", + "homepage": "https://lw1.at", "license": "GPL v3+", - "keywords": [] + "keywords": [ + ] }