, so a server missing a module ignores * the rule instead of returning a 500 error. The block is clearly marked and is * the only part of .htaccess this plugin ever edits. */ class Htaccess { const BEGIN = '# BEGIN Cache Shark'; const END = '# END Cache Shark'; public static function path(): string { return ABSPATH . '.htaccess'; } /** True on Apache and LiteSpeed (WordPress sets $is_apache for both). */ public static function is_supported(): bool { global $is_apache; return ! empty( $is_apache ); } /** The marked block, ready to write. */ public static function block(): string { $rules = array( self::BEGIN, '# Rules between BEGIN and END are generated by Cache Shark. Do not edit by hand.', '', ' AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript', ' AddOutputFilterByType DEFLATE application/javascript application/x-javascript application/json', ' AddOutputFilterByType DEFLATE application/xml application/xhtml+xml application/rss+xml application/atom+xml', ' AddOutputFilterByType DEFLATE image/svg+xml font/ttf font/otf application/vnd.ms-fontobject', '', '', ' ExpiresActive On', ' ExpiresByType image/jpeg "access plus 1 year"', ' ExpiresByType image/png "access plus 1 year"', ' ExpiresByType image/gif "access plus 1 year"', ' ExpiresByType image/webp "access plus 1 year"', ' ExpiresByType image/avif "access plus 1 year"', ' ExpiresByType image/svg+xml "access plus 1 year"', ' ExpiresByType image/x-icon "access plus 1 year"', ' ExpiresByType font/woff2 "access plus 1 year"', ' ExpiresByType font/woff "access plus 1 year"', ' ExpiresByType font/ttf "access plus 1 year"', ' ExpiresByType font/otf "access plus 1 year"', ' ExpiresByType text/css "access plus 1 month"', ' ExpiresByType text/javascript "access plus 1 month"', ' ExpiresByType application/javascript "access plus 1 month"', '', self::END, ); return implode( "\n", $rules ) . "\n"; } /** Pure: return $src with our block (re)placed at the top. */ public static function with_block( string $src, string $block ): string { $src = self::without_block( $src ); return $block . ( '' === trim( $src ) ? '' : "\n" . $src ); } /** Pure: return $src with our block removed. */ public static function without_block( string $src ): string { $pattern = '/^' . preg_quote( self::BEGIN, '/' ) . '.*?^' . preg_quote( self::END, '/' ) . '[ \t]*\R?\R?/ms'; return (string) preg_replace( $pattern, '', $src ); } public static function insert(): bool { if ( ! self::is_supported() ) { return false; } $path = self::path(); $src = file_exists( $path ) ? (string) file_get_contents( $path ) : ''; // phpcs:ignore WordPress.WP.AlternativeFunctions return self::write( $path, self::with_block( $src, self::block() ) ); } public static function remove(): bool { $path = self::path(); if ( ! file_exists( $path ) ) { return true; } $src = (string) file_get_contents( $path ); // phpcs:ignore WordPress.WP.AlternativeFunctions if ( false === strpos( $src, self::BEGIN ) ) { return true; } $new = self::without_block( $src ); if ( '' === trim( $new ) ) { return Fs::delete( $path ); // The file held only our block. } return self::write( $path, $new ); } /** Atomic write that keeps the existing permissions. */ private static function write( string $path, string $contents ): bool { $exists = file_exists( $path ); if ( $exists ? ! is_writable( $path ) : ! is_writable( dirname( $path ) ) ) { // phpcs:ignore WordPress.WP.AlternativeFunctions return false; } if ( $exists && ! is_writable( dirname( $path ) ) ) { // phpcs:ignore WordPress.WP.AlternativeFunctions return false; // rename() needs a writable directory. } $tmp = $path . '.vc-tmp'; if ( false === file_put_contents( $tmp, $contents, LOCK_EX ) ) { // phpcs:ignore WordPress.WP.AlternativeFunctions return false; } Fs::chmod( $tmp, $exists ? ( fileperms( $path ) & 0777 ) : 0644 ); if ( ! Fs::move( $tmp, $path ) ) { Fs::delete( $tmp ); return false; } return true; } }