diff --git a/config/global.ini.php b/config/global.ini.php
index 0ac565592471cc2f5b21e72c67dfb686e67f9bc3..526f8b3e243bb7de1d6cba7d4adeef141f08f6b0 100644
--- a/config/global.ini.php
+++ b/config/global.ini.php
@@ -720,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/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/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/Updates/2.13.0-b3.php b/core/Updates/2.13.0-b3.php
new file mode 100644
index 0000000000000000000000000000000000000000..db519eea45719c5d49348d72671849b89fce5993
--- /dev/null
+++ b/core/Updates/2.13.0-b3.php
@@ -0,0 +1,26 @@
+<?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\Updates;
+
+class Updates_2_13_0_b3 extends Updates
+{
+    public function doUpdate()
+    {
+        $pluginManager = \Piwik\Plugin\Manager::getInstance();
+
+        try {
+            $pluginManager->activatePlugin('Diagnostics');
+        } catch(\Exception $e) {
+        }
+    }
+}
diff --git a/core/Version.php b/core/Version.php
index b5921a9617b514bcf6214db7a9f819286226fc5f..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-b2';
+    const VERSION = '2.13.0-b3';
 
     public function isStableVersion($version)
     {
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 0a69ab1b9dd737d9deced49d78e7e1f17a2122a6..d058a1ed4d98ec974861651d811dfc0611ae6fe7 100644
--- a/plugins/CoreUpdater/config/config.php
+++ b/plugins/CoreUpdater/config/config.php
@@ -3,4 +3,8 @@
 return array(
     'Piwik\Plugins\CoreUpdater\Updater' => DI\object()
         ->constructorParameter('tmpPath', DI\get('path.tmp')),
+
+    'diagnostics.optional' => DI\add(array(
+        DI\link('Piwik\Plugins\CoreUpdater\Diagnostic\HttpsUpdateCheck'),
+    )),
 );
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..417aee17103f38f9d3a61c51ba81e6cbdb15f196
--- /dev/null
+++ b/plugins/Diagnostics/Diagnostic/PhpExtensionsCheck.php
@@ -0,0 +1,88 @@
+<?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();
+
+        foreach ($requiredExtensions as $extension) {
+            if (! in_array($extension, $this->getPhpExtensionsLoaded())) {
+                $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 getPhpExtensionsLoaded()
+    {
+        return @get_loaded_extensions();
+    }
+
+    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..6288d33cbdf816e807ac27faec5a9c0efca841e8
--- /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 (! $this->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
+     */
+    private 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..ebbb74c933cc52fe7d0dd6030067e5de3846c18c
--- /dev/null
+++ b/plugins/Diagnostics/Diagnostic/RecommendedExtensionsCheck.php
@@ -0,0 +1,72 @@
+<?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);
+
+        foreach ($this->getRecommendedExtensions() as $extension) {
+            if (! in_array($extension, $this->getPhpExtensionsLoaded())) {
+                $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]);
+    }
+
+    private function getPhpExtensionsLoaded()
+    {
+        return @get_loaded_extensions();
+    }
+}
diff --git a/plugins/Diagnostics/Diagnostic/RecommendedFunctionsCheck.php b/plugins/Diagnostics/Diagnostic/RecommendedFunctionsCheck.php
new file mode 100644
index 0000000000000000000000000000000000000000..1db14ade8c83ccb5eb5e257a7c17df8df217c79b
--- /dev/null
+++ b/plugins/Diagnostics/Diagnostic/RecommendedFunctionsCheck.php
@@ -0,0 +1,99 @@
+<?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 (! $this->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]);
+    }
+
+    /**
+     * Tests if a function exists. Also handles the case where a function is disabled via Suhosin.
+     *
+     * @param string $function
+     * @return bool
+     */
+    private 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;
+    }
+}
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..5a291ce4bbf5a8a395f03af952dc8bf570ec12be
--- /dev/null
+++ b/plugins/Diagnostics/Diagnostic/WriteAccessCheck.php
@@ -0,0 +1,87 @@
+<?php
+
+namespace Piwik\Plugins\Diagnostics\Diagnostic;
+
+use Piwik\Container\StaticContainer;
+use Piwik\DbHelper;
+use Piwik\Filechecks;
+use Piwik\Translation\Translator;
+
+/**
+ * Check the permissions for some directories.
+ */
+class WriteAccessCheck implements Diagnostic
+{
+    /**
+     * @var Translator
+     */
+    private $translator;
+
+    public function __construct(Translator $translator)
+    {
+        $this->translator = $translator;
+    }
+
+    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()
+    {
+        // TODO dependency injection
+        $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;
+    }
+}
diff --git a/plugins/Diagnostics/DiagnosticReport.php b/plugins/Diagnostics/DiagnosticReport.php
new file mode 100644
index 0000000000000000000000000000000000000000..3fc3ae7e75066ffaf58f034649e633cfad938f85
--- /dev/null
+++ b/plugins/Diagnostics/DiagnosticReport.php
@@ -0,0 +1,115 @@
+<?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|null
+     */
+    private $errorCount;
+
+    /**
+     * @var int|null
+     */
+    private $warningCount;
+
+    /**
+     * @param DiagnosticResult[] $mandatoryDiagnosticResults
+     * @param DiagnosticResult[] $optionalDiagnosticResults
+     */
+    public function __construct(array $mandatoryDiagnosticResults, array $optionalDiagnosticResults)
+    {
+        $this->mandatoryDiagnosticResults = $mandatoryDiagnosticResults;
+        $this->optionalDiagnosticResults = $optionalDiagnosticResults;
+    }
+
+    /**
+     * @return bool
+     */
+    public function hasErrors()
+    {
+        return $this->getErrorCount() > 0;
+    }
+
+    /**
+     * @return bool
+     */
+    public function hasWarnings()
+    {
+        return $this->getWarningCount() > 0;
+    }
+
+    /**
+     * @return int
+     */
+    public function getErrorCount()
+    {
+        if ($this->errorCount === null) {
+            $this->errorCount = 0;
+            foreach ($this->getAllResults() as $result) {
+                if ($result->getStatus() === DiagnosticResult::STATUS_ERROR) {
+                    $this->errorCount++;
+                }
+            }
+        }
+
+        return $this->errorCount;
+    }
+
+    /**
+     * @return int
+     */
+    public function getWarningCount()
+    {
+        if ($this->warningCount === null) {
+            $this->warningCount = 0;
+            foreach ($this->getAllResults() as $result) {
+                if ($result->getStatus() === DiagnosticResult::STATUS_WARNING) {
+                    $this->warningCount++;
+                }
+            }
+        }
+
+        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;
+    }
+}
diff --git a/plugins/Diagnostics/DiagnosticService.php b/plugins/Diagnostics/DiagnosticService.php
new file mode 100644
index 0000000000000000000000000000000000000000..3251f42d3bf343b6e98784fe6afc41654c7d2a73
--- /dev/null
+++ b/plugins/Diagnostics/DiagnosticService.php
@@ -0,0 +1,60 @@
+<?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
+     */
+    public function __construct(array $mandatoryDiagnostics, array $optionalDiagnostics)
+    {
+        $this->mandatoryDiagnostics = $mandatoryDiagnostics;
+        $this->optionalDiagnostics = $optionalDiagnostics;
+    }
+
+    /**
+     * @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;
+    }
+}
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/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..c4375dd648ed3089f688956009c0e7bbb089a2ee
--- /dev/null
+++ b/plugins/Diagnostics/Test/Unit/DiagnosticServiceTest.php
@@ -0,0 +1,39 @@
+<?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\DiagnosticWithWarning;
+
+class DiagnosticServiceTest extends \PHPUnit_Framework_TestCase
+{
+    public function test_runDiagnostics()
+    {
+        $mandatoryDiagnostics = array(
+            new DiagnosticWithError(),
+        );
+        $optionalDiagnostics = array(
+            new DiagnosticWithWarning(),
+        );
+
+        $service = new DiagnosticService($mandatoryDiagnostics, $optionalDiagnostics);
+
+        $report = $service->runDiagnostics();
+
+        $results = $report->getAllResults();
+
+        $this->assertCount(2, $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());
+    }
+}
diff --git a/plugins/Diagnostics/config/config.php b/plugins/Diagnostics/config/config.php
new file mode 100644
index 0000000000000000000000000000000000000000..1ec0898a55f7f79b8185828c30b80c0e963236e8
--- /dev/null
+++ b/plugins/Diagnostics/config/config.php
@@ -0,0 +1,34 @@
+<?php
+
+return array(
+    // Diagnostics for everything that is required for Piwik to run
+    'diagnostics.required' => array(
+        DI\link('Piwik\Plugins\Diagnostics\Diagnostic\PhpVersionCheck'),
+        DI\link('Piwik\Plugins\Diagnostics\Diagnostic\DbAdapterCheck'),
+        DI\link('Piwik\Plugins\Diagnostics\Diagnostic\PhpExtensionsCheck'),
+        DI\link('Piwik\Plugins\Diagnostics\Diagnostic\PhpFunctionsCheck'),
+        DI\link('Piwik\Plugins\Diagnostics\Diagnostic\PhpSettingsCheck'),
+        DI\link('Piwik\Plugins\Diagnostics\Diagnostic\WriteAccessCheck'),
+    ),
+    // Diagnostics for recommended features
+    'diagnostics.optional' => array(
+        DI\link('Piwik\Plugins\Diagnostics\Diagnostic\FileIntegrityCheck'),
+        DI\link('Piwik\Plugins\Diagnostics\Diagnostic\TrackerCheck'),
+        DI\link('Piwik\Plugins\Diagnostics\Diagnostic\MemoryLimitCheck'),
+        DI\link('Piwik\Plugins\Diagnostics\Diagnostic\TimezoneCheck'),
+        DI\link('Piwik\Plugins\Diagnostics\Diagnostic\HttpClientCheck'),
+        DI\link('Piwik\Plugins\Diagnostics\Diagnostic\PageSpeedCheck'),
+        DI\link('Piwik\Plugins\Diagnostics\Diagnostic\GdExtensionCheck'),
+        DI\link('Piwik\Plugins\Diagnostics\Diagnostic\RecommendedExtensionsCheck'),
+        DI\link('Piwik\Plugins\Diagnostics\Diagnostic\RecommendedFunctionsCheck'),
+        DI\link('Piwik\Plugins\Diagnostics\Diagnostic\NfsDiskCheck'),
+        DI\link('Piwik\Plugins\Diagnostics\Diagnostic\CronArchivingCheck'),
+        DI\link('Piwik\Plugins\Diagnostics\Diagnostic\LoadDataInfileCheck'),
+    ),
+
+    'Piwik\Plugins\Diagnostics\DiagnosticService' => DI\object()
+        ->constructor(DI\link('diagnostics.required'), DI\link('diagnostics.optional')),
+
+    'Piwik\Plugins\Diagnostics\Diagnostic\MemoryLimitCheck' => DI\object()
+        ->constructorParameter('minimumMemoryLimit', DI\link('ini.General.minimum_memory_limit')),
+);
diff --git a/plugins/Installation/Controller.php b/plugins/Installation/Controller.php
index cdce51d111a41dbe4e0069aa9dee8d0c25d7ac86..c533ac07fca75ae607146a5aea2e85a3122546a9 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,11 @@ class Controller extends \Piwik\Plugin\ControllerAdmin
             __FUNCTION__
         );
 
-        $view->duringInstall = true;
+        /** @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 +440,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 +458,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 +627,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 +714,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 }} &gt;= {{ 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/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..89c61fc9797a70782acb77cb0de8a19b28370268
--- /dev/null
+++ b/plugins/UserCountry/config/config.php
@@ -0,0 +1,7 @@
+<?php
+
+return array(
+    'diagnostics.optional' => DI\add(array(
+        DI\link('Piwik\Plugins\UserCountry\Diagnostic\GeolocationDiagnostic'),
+    )),
+);