` in the root .htaccess applies to every * directory below it, so the log rule covers wp-content/debug.log too. */ class CoreFileRules { const MARKER = 'GemSecurity Core Files'; /** @return string[] */ public static function rules() { $deny = [ ' ', ' Require all denied', ' ', ' ', ' Order allow,deny', ' Deny from all', ' ', ]; return array_merge( [ '' ], $deny, [ '' ], [ '' ], $deny, [ '' ], [ '', ' RewriteEngine On', ' RewriteRule ^wp-admin/includes/ - [F,L]', '', ] ); } /** * Write or remove the block. * * @param bool $enabled * @param string|null $file Defaults to the root .htaccess; overridable for tests. * @return bool Whether the file now matches $enabled. */ public static function sync( $enabled, $file = null ) { $file = $file ?: \GemSecurity\Paths::root() . '.htaccess'; if ( ! $enabled && ! file_exists( $file ) ) { return true; } $writable = file_exists( $file ) ? wp_is_writable( $file ) : wp_is_writable( dirname( $file ) ); if ( ! $writable ) { return false; } if ( ! $enabled ) { return self::remove_block( $file, self::MARKER ); } if ( ! function_exists( 'insert_with_markers' ) ) { require_once ABSPATH . 'wp-admin/includes/misc.php'; } return (bool) insert_with_markers( $file, self::MARKER, self::rules() ); } /** * Remove a marked block entirely. insert_with_markers() with no lines * leaves the BEGIN/END comments behind. * * @param string $file * @param string $marker * @return bool */ public static function remove_block( $file, $marker ) { $body = (string) file_get_contents( $file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- local file. $rest = preg_replace( '/\n?# BEGIN ' . preg_quote( $marker, '/' ) . '\n.*?# END ' . preg_quote( $marker, '/' ) . '\n?/s', "\n", $body ); if ( null === $rest || $rest === $body ) { return true; // Nothing of ours in there. } return false !== file_put_contents( $file, ltrim( $rest, "\n" ) ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- .htaccess edit, same as insert_with_markers(). } /** * Whether the block is in place. * * @param string|null $file * @return bool */ public static function installed( $file = null ) { $file = $file ?: \GemSecurity\Paths::root() . '.htaccess'; return is_readable( $file ) && false !== strpos( (string) file_get_contents( $file ), '# BEGIN ' . self::MARKER ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- local file. } }