diff --git a/system/Security/CheckPhpIni.php b/system/Security/CheckPhpIni.php new file mode 100644 index 000000000000..282102434fae --- /dev/null +++ b/system/Security/CheckPhpIni.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Security; + +use CodeIgniter\CLI\CLI; +use CodeIgniter\View\Table; + +/** + * Checks php.ini settings + * + * @used-by \CodeIgniter\Commands\Utilities\PhpIniCheck + * @see \CodeIgniter\Security\CheckPhpIniTest + */ +class CheckPhpIni +{ + /** + * @param bool $isCli Set false if you run via Web + * + * @return string|void HTML string or void in CLI + */ + public static function run(bool $isCli = true) + { + $output = static::checkIni(); + + $thead = ['Directive', 'Global', 'Current', 'Recommended', 'Remark']; + $tbody = []; + + // CLI + if ($isCli) { + self::outputForCli($output, $thead, $tbody); + + return; + } + + // Web + return self::outputForWeb($output, $thead, $tbody); + } + + private static function outputForCli(array $output, array $thead, array $tbody): void + { + foreach ($output as $directive => $values) { + $current = $values['current']; + $notRecommended = false; + + if ($values['recommended'] !== '') { + if ($values['recommended'] !== $values['current']) { + $notRecommended = true; + } + + $current = $notRecommended + ? CLI::color($values['current'] === '' ? 'n/a' : $values['current'], 'red') + : $values['current']; + } + + $directive = $notRecommended ? CLI::color($directive, 'red') : $directive; + $tbody[] = [ + $directive, $values['global'], $current, $values['recommended'], $values['remark'], + ]; + } + + CLI::table($tbody, $thead); + } + + private static function outputForWeb(array $output, array $thead, array $tbody): string + { + foreach ($output as $directive => $values) { + $current = $values['current']; + $notRecommended = false; + + if ($values['recommended'] !== '') { + if ($values['recommended'] !== $values['current']) { + $notRecommended = true; + } + + if ($values['current'] === '') { + $current = 'n/a'; + } + + $current = $notRecommended + ? '' . $current . '' + : $current; + } + + $directive = $notRecommended + ? '' . $directive . '' + : $directive; + $tbody[] = [ + $directive, $values['global'], $current, $values['recommended'], $values['remark'], + ]; + } + + $table = new Table(); + $template = [ + 'table_open' => '', + ]; + $table->setTemplate($template); + + $table->setHeading($thead); + + return '
' . $table->generate($tbody) . '
'; + } + + /** + * @internal Used for testing purposes only. + * @testTag + */ + public static function checkIni(): array + { + $items = [ + 'error_reporting' => ['recommended' => '5111'], + 'display_errors' => ['recommended' => '0'], + 'display_startup_errors' => ['recommended' => '0'], + 'log_errors' => [], + 'error_log' => [], + 'default_charset' => ['recommended' => 'UTF-8'], + 'memory_limit' => ['remark' => '> post_max_size'], + 'post_max_size' => ['remark' => '> upload_max_filesize'], + 'upload_max_filesize' => ['remark' => '< post_max_size'], + 'request_order' => ['recommended' => 'GP'], + 'variables_order' => ['recommended' => 'GPCS'], + 'date.timezone' => ['recommended' => 'UTC'], + 'mbstring.language' => ['recommended' => 'neutral'], + ]; + + $output = []; + $ini = ini_get_all(); + + foreach ($items as $key => $values) { + $output[$key] = [ + 'global' => $ini[$key]['global_value'], + 'current' => $ini[$key]['local_value'], + 'recommended' => $values['recommended'] ?? '', + 'remark' => $values['remark'] ?? '', + ]; + } + + // [directive => [current_value, recommended_value]] + return $output; + } +} diff --git a/tests/system/Security/CheckPhpIniTest.php b/tests/system/Security/CheckPhpIniTest.php new file mode 100644 index 000000000000..847f9fd82897 --- /dev/null +++ b/tests/system/Security/CheckPhpIniTest.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Security; + +use CodeIgniter\CLI\CLI; +use CodeIgniter\Test\CIUnitTestCase; +use CodeIgniter\Test\Mock\MockInputOutput; + +/** + * @internal + * + * @group Others + */ +final class CheckPhpIniTest extends CIUnitTestCase +{ + public function testCheckIni() + { + $output = CheckPhpIni::checkIni(); + + $expected = [ + 'global' => '', + 'current' => '1', + 'recommended' => '0', + 'remark' => '', + ]; + $this->assertSame($expected, $output['display_errors']); + } + + public function testRunCli() + { + // Set MockInputOutput to CLI. + $io = new MockInputOutput(); + CLI::setInputOutput($io); + + CheckPhpIni::run(true); + + // Get the whole output string. + $output = $io->getOutput(); + + $this->assertStringContainsString('display_errors', $output); + + // Remove MockInputOutput. + CLI::resetInputOutput(); + } + + public function testRunWeb() + { + $output = CheckPhpIni::run(false); + + $this->assertStringContainsString('display_errors', $output); + } +}