Source: classes/Htaccess.php

<?php
/**
 * Htaccress rules
 *
 * @package PoweredCache
 */

namespace PoweredCache;

use function PoweredCache\Utils\get_cache_dir;
use function PoweredCache\Utils\mobile_browsers;
use function PoweredCache\Utils\mobile_prefixes;
use function PoweredCache\Utils\permalink_structure_has_trailingslash;

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly.
}

// phpcs:disable Generic.Strings.UnnecessaryStringConcat.Found

/**
 * Class Htaccess
 */
class Htaccess {
	/**
	 * Plugin settings
	 *
	 * @var $settings
	 */
	private $settings;


	/**
	 * placeholder
	 *
	 * @since 2.5
	 */
	public function __construct() {
		$this->settings = \PoweredCache\Utils\get_settings();
	}

	/**
	 * Return an instance of the current class
	 *
	 * @return Htaccess
	 */
	public static function factory() {
		static $instance = false;

		if ( ! $instance ) {
			$instance = new self();
		}

		return $instance;
	}

	/**
	 * Get htaccess rules
	 *
	 * @return string
	 */
	public function htaccess_rules() {
		$rules = '';
		/**
		 * Filters base htaccess rules
		 *
		 * @hook   powered_cache_pre_htaccess
		 *
		 * @param  {string} empty htaccesss rules by default
		 *
		 * @return {boolean} New value.
		 *
		 * @since  1.1
		 */
		$rules .= apply_filters( 'powered_cache_pre_htaccess', '' );

		$rules .= '# BEGIN POWERED CACHE' . PHP_EOL;

		$rules .= $this->browser_cache_rules();
		$rules .= $this->cors_rules();
		$rules .= $this->gzip_rules();
		$rules .= $this->etag_rules();
		$rules .= $this->cache_control_rules();
		$rules .= $this->file_optimizer_rewrite_rules();
		$rules .= $this->rewrite_rules();

		/**
		 * Filters post htaccess rules
		 *
		 * @hook   powered_cache_after_htaccess
		 *
		 * @param  {string} empty htaccesss rules by default
		 *
		 * @return {boolean} New value.
		 *
		 * @since  2.0
		 */
		$rules .= apply_filters( 'powered_cache_after_htaccess', '' );

		$rules .= '# END POWERED CACHE' . PHP_EOL;

		return $rules;
	}


	/**
	 * Browser cache rules
	 *
	 * @return string
	 */
	public function browser_cache_rules() {
		$rules = '';
		/**
		 * Filters whether doing the configuration for browser cache or not
		 *
		 * @hook   powered_cache_browser_cache
		 *
		 * @param  {boolean} true for creating .htaccess rules for browser cache
		 *
		 * @return {boolean} New value.
		 *
		 * @since  1.1
		 */
		if ( apply_filters( 'powered_cache_browser_cache', true ) ) {

			$mime_types = [
				'text/css',
				'application/javascript',
				'image/jpeg',
				'image/gif',
				'image/png',
				'image/bmp',
				'image/tiff',
				'image/webp',
				'image/heic',
				'image/svg+xml',
				'font/ttf',
				'font/woff',
				'font/woff2',
				'font/otf',
				'application/vnd.ms-fontobject',
				'image/x-icon',
				'application/atom+xml',
				'application/rss+xml',
			];

			// set expire time
			$rules .= '<IfModule mod_expires.c>' . PHP_EOL;
			$rules .= '    ExpiresActive On' . PHP_EOL;
			$rules .= '    ExpiresByType  text/html                       "access plus 0 seconds"' . PHP_EOL;
			$rules .= '    ExpiresByType  text/richtext                   "access plus 0 seconds"' . PHP_EOL;
			$rules .= '    ExpiresByType  text/plain                      "access plus 0 seconds"' . PHP_EOL;
			$rules .= '    ExpiresByType  text/xsd                        "access plus 0 seconds"' . PHP_EOL;
			$rules .= '    ExpiresByType  text/xsl                        "access plus 0 seconds"' . PHP_EOL;
			$rules .= '    ExpiresByType  text/xml                        "access plus 0 seconds"' . PHP_EOL;
			$rules .= '    ExpiresByType  application/xml                 "access plus 0 seconds"' . PHP_EOL;
			$rules .= '    ExpiresByType  application/json                "access plus 0 seconds"' . PHP_EOL;
			$rules .= '    ExpiresByType  text/cache-manifest             "access plus 0 seconds"' . PHP_EOL;

			foreach ( $mime_types as $mime_type ) {
				$rules .= sprintf( '    ExpiresByType  %s  "%s"', str_pad( $mime_type, 30, ' ' ), $this->get_browser_cache_lifespan( $mime_type ) ) . PHP_EOL;
			}

			$rules .= '</IfModule>' . PHP_EOL;
		}

		return $rules;
	}

	/**
	 * Cors rules - when CDN integration activated
	 *
	 * @return string
	 */
	public function cors_rules() {
		$rules = '';
		/**
		 * Filters whether add CORS configuration or not
		 *
		 * @hook   powered_cache_htaccess_add_cors
		 *
		 * @param  {boolean} true for creating .htaccess rules for CORS
		 *
		 * @return {boolean} New value.
		 *
		 * @since  2.5
		 */
		if ( apply_filters( 'powered_cache_htaccess_add_cors', $this->settings['enable_cdn'] ) ) {
			/**
			 * Add CORS configuration
			 *
			 * @link  https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image
			 * @since 2.1
			 */
			$rules .= '<IfModule mod_setenvif.c>' . PHP_EOL;
			$rules .= '  <IfModule mod_headers.c>' . PHP_EOL;
			$rules .= '    <FilesMatch "\.(avifs?|bmp|cur|gif|ico|jpe?g|jxl|a?png|svgz?|webp)$">' . PHP_EOL;
			$rules .= '      SetEnvIf Origin ":" IS_CORS' . PHP_EOL;
			$rules .= '      Header set Access-Control-Allow-Origin "*" env=IS_CORS' . PHP_EOL;
			$rules .= '    </FilesMatch>' . PHP_EOL;
			$rules .= '  </IfModule>' . PHP_EOL;
			$rules .= '</IfModule>' . PHP_EOL . PHP_EOL;

			// configure fonts
			$rules .= '<FilesMatch "\.(ttf|ttc|otf|eot|woff|woff2|font.css)$">' . PHP_EOL;
			$rules .= '  <IfModule mod_headers.c>' . PHP_EOL;
			$rules .= '    Header set Access-Control-Allow-Origin "*"' . PHP_EOL;
			$rules .= '  </IfModule>' . PHP_EOL;
			$rules .= '</FilesMatch>' . PHP_EOL . PHP_EOL;
		}

		return $rules;
	}

	/**
	 * Gzip rules
	 *
	 * @return string
	 */
	public function gzip_rules() {
		$rules = '';

		$rules .= '<IfModule filter_module>' . PHP_EOL;
		$rules .= '  <IfModule version.c>' . PHP_EOL;
		$rules .= '    <IfVersion >= 2.4>' . PHP_EOL;
		$rules .= '      FilterDeclare   COMPRESS' . PHP_EOL;
		$rules .= '      FilterProvider  COMPRESS  DEFLATE "%{CONTENT_TYPE} = \'application/atom+xml\'"' . PHP_EOL;
		$rules .= '      FilterProvider  COMPRESS  DEFLATE "%{CONTENT_TYPE} = \'application/javascript\'"' . PHP_EOL;
		$rules .= '      FilterProvider  COMPRESS  DEFLATE "%{CONTENT_TYPE} = \'application/json\'"' . PHP_EOL;
		$rules .= '      FilterProvider  COMPRESS  DEFLATE "%{CONTENT_TYPE} = \'application/ld+json\'"' . PHP_EOL;
		$rules .= '      FilterProvider  COMPRESS  DEFLATE "%{CONTENT_TYPE} = \'application/manifest+json\'"' . PHP_EOL;
		$rules .= '      FilterProvider  COMPRESS  DEFLATE "%{CONTENT_TYPE} = \'application/rss+xml\'"' . PHP_EOL;
		$rules .= '      FilterProvider  COMPRESS  DEFLATE "%{CONTENT_TYPE} = \'application/vnd.ms-fontobject\'"' . PHP_EOL;
		$rules .= '      FilterProvider  COMPRESS  DEFLATE "%{CONTENT_TYPE} = \'application/xhtml+xml\'"' . PHP_EOL;
		$rules .= '      FilterProvider  COMPRESS  DEFLATE "%{CONTENT_TYPE} = \'application/xml\'"' . PHP_EOL;
		$rules .= '      FilterProvider  COMPRESS  DEFLATE "%{CONTENT_TYPE} = \'font/opentype\'"' . PHP_EOL;
		$rules .= '      FilterProvider  COMPRESS  DEFLATE "%{CONTENT_TYPE} = \'image/svg+xml\'"' . PHP_EOL;
		$rules .= '      FilterProvider  COMPRESS  DEFLATE "%{CONTENT_TYPE} = \'image/x-icon\'"' . PHP_EOL;
		$rules .= '      FilterProvider  COMPRESS  DEFLATE "%{CONTENT_TYPE} = \'text/html\'"' . PHP_EOL;
		$rules .= '      FilterProvider  COMPRESS  DEFLATE "%{CONTENT_TYPE} = \'text/plain\'"' . PHP_EOL;
		$rules .= '      FilterProvider  COMPRESS  DEFLATE "%{CONTENT_TYPE} = \'text/x-component\'"' . PHP_EOL;
		$rules .= '      FilterProvider  COMPRESS  DEFLATE "%{CONTENT_TYPE} = \'text/xml\'"' . PHP_EOL;
		$rules .= '      FilterChain     COMPRESS' . PHP_EOL;
		$rules .= '      FilterProtocol  COMPRESS  DEFLATE change=yes;byteranges=no' . PHP_EOL;
		$rules .= '    </IfVersion>' . PHP_EOL;
		$rules .= '  </IfModule>' . PHP_EOL;
		$rules .= '</IfModule>' . PHP_EOL;

		$rules .= '<IfModule mod_deflate.c>' . PHP_EOL;
		$rules .= '  SetOutputFilter DEFLATE' . PHP_EOL;
		$rules .= '  <IfModule mod_setenvif.c>' . PHP_EOL;
		$rules .= '    <IfModule mod_headers.c>' . PHP_EOL;
		$rules .= '      SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding' . PHP_EOL;
		$rules .= '      RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding' . PHP_EOL;
		$rules .= '    </IfModule>' . PHP_EOL;
		$rules .= '  </IfModule>' . PHP_EOL;
		$rules .= '  <IfModule mod_filter.c>' . PHP_EOL;
		$rules .= '    AddOutputFilterByType DEFLATE application/atom+xml \
		                          application/javascript \
		                          application/json \
		                          application/ld+json \
		                          application/manifest+json \
		                          application/rss+xml \
		                          application/vnd.ms-fontobject \
		                          application/x-font-ttf \
		                          application/xhtml+xml \
		                          application/xml \
		                          font/opentype \
		                          image/svg+xml \
		                          image/x-icon \
		                          text/html \
		                          text/plain \
		                          text/css \
		                          text/x-component \
		                          text/xml' . PHP_EOL;
		$rules .= '  </IfModule>' . PHP_EOL;
		$rules .= '  <IfModule mod_headers.c>' . PHP_EOL;
		$rules .= '    Header append Vary: Accept-Encoding' . PHP_EOL;
		$rules .= '  </IfModule>' . PHP_EOL;
		$rules .= '</IfModule>' . PHP_EOL . PHP_EOL;

		return $rules;
	}

	/**
	 * Remove ETag rules
	 *
	 * @link  https://htaccessbook.com/disable-etags/
	 * @return string
	 */
	public function etag_rules() {
		// remove etag
		$rules  = '# Remove ETag' . PHP_EOL;
		$rules  = '<IfModule mod_headers.c>' . PHP_EOL;
		$rules .= 'Header unset ETag' . PHP_EOL;
		$rules .= '</IfModule>' . PHP_EOL;
		$rules .= 'FileETag None' . PHP_EOL . PHP_EOL;

		return $rules;
	}

	/**
	 * Rewrite rules
	 *
	 * @return string
	 */
	public function rewrite_rules() {
		$rules = '';
		/**
		 * Filters whether doing the configuration for htaccess rewrite cache or not
		 *
		 * @hook   powered_cache_mod_rewrite
		 *
		 * @param  {boolean} true for creating .htaccess rewrite rules.
		 *
		 * @return {boolean} New value.
		 *
		 * @since  2.0
		 */
		if ( apply_filters( 'powered_cache_mod_rewrite', true ) ) { // rewrite
			// add gzip type for .html.gz format
			if ( $this->is_gzip_enabled() ) {
				$rules .= '<IfModule mod_mime.c>' . PHP_EOL;
				$rules .= '    AddType text/html .html.gz' . PHP_EOL;
				$rules .= '    AddEncoding gzip .gz' . PHP_EOL;
				$rules .= '</IfModule>' . PHP_EOL;
				$rules .= '<IfModule mod_setenvif.c>' . PHP_EOL;
				$rules .= '    SetEnvIfNoCase Request_URI \.html.gz$ no-gzip' . PHP_EOL;
				$rules .= '</IfModule>' . PHP_EOL;
			}

			$env_powered_cache_ua  = '';
			$env_powered_cache_ssl = '';
			$env_powered_cache_enc = '';

			$rewrite_base = wp_parse_url( home_url() );
			$rewrite_base = isset( $rewrite_base['path'] ) ? trailingslashit( $rewrite_base['path'] ) : '/';

			$rules .= '<IfModule mod_rewrite.c>' . PHP_EOL;
			$rules .= '    RewriteEngine On' . PHP_EOL;
			$rules .= '    RewriteBase ' . $rewrite_base . PHP_EOL;
			$rules .= '    AddDefaultCharset UTF-8 ' . PHP_EOL;

			if ( true === $this->settings['cache_mobile'] && true === $this->settings['cache_mobile_separate_file'] ) {
				$mobile_browsers = addcslashes( implode( '|', preg_split( '/[\s*,\s*]*,+[\s*,\s*]*/', mobile_browsers() ) ), ' ' );
				$mobile_prefixes = addcslashes( implode( '|', preg_split( '/[\s*,\s*]*,+[\s*,\s*]*/', mobile_prefixes() ) ), ' ' );
				// mobile env set
				$rules               .= '    RewriteCond %{HTTP_USER_AGENT} (' . $mobile_browsers . ') [NC]' . PHP_EOL;
				$rules               .= '    RewriteRule .* - [E=PC_UA:-mobile]' . PHP_EOL;
				$rules               .= '    RewriteCond %{HTTP_USER_AGENT} ^(' . $mobile_prefixes . ') [NC]' . PHP_EOL;
				$rules               .= '    RewriteRule .* - [E=PC_UA:-mobile]' . PHP_EOL;
				$env_powered_cache_ua = '%{ENV:PC_UA}';
			}

			$rules                .= '    RewriteCond %{HTTPS} on [OR]' . PHP_EOL;
			$rules                .= '    RewriteCond %{SERVER_PORT} ^443$ [OR]' . PHP_EOL;
			$rules                .= '    RewriteCond %{HTTP:X-Forwarded-Proto} https' . PHP_EOL;
			$rules                .= '    RewriteRule .* - [E=PC_SSL:-https]' . PHP_EOL;
			$env_powered_cache_ssl = '%{ENV:PC_SSL}';

			if ( $this->is_gzip_enabled() ) {
				$rules                .= '    RewriteCond %{HTTP:Accept-Encoding} gzip' . PHP_EOL;
				$rules                .= '    RewriteRule .* - [E=PC_ENC:.gz]' . PHP_EOL;
				$env_powered_cache_enc = '%{ENV:PC_ENC}';
			}

			$rules .= '    RewriteCond %{REQUEST_METHOD} !=POST' . PHP_EOL;
			$rules .= '    RewriteCond %{QUERY_STRING} =""' . PHP_EOL;

			if ( permalink_structure_has_trailingslash() ) {
				$rules .= '    RewriteCond %{REQUEST_URI} !^.*[^/]$' . PHP_EOL;
				$rules .= '    RewriteCond %{REQUEST_URI} !^.*//.*$' . PHP_EOL;
			}

			// Get root base
			$site_root = wp_parse_url( site_url() );
			$site_root = isset( $site_root['path'] ) ? trailingslashit( $site_root['path'] ) : '';

			// reject user agent
			$rejected_user_agents = (array) AdvancedCache::get_rejected_user_agents();
			if ( ! empty( $rejected_user_agents ) ) {
				$rules .= '    RewriteCond %{HTTP_USER_AGENT} !^(' . implode( '|', $rejected_user_agents ) . ').* [NC]' . PHP_EOL;
			}

			// rejected cookies
			$rejected_cookies = AdvancedCache::get_rejected_cookies();
			if ( ! empty( $rejected_cookies ) ) {
				$rules .= '    RewriteCond %{HTTP:Cookie} !(' . implode( '|', $rejected_cookies ) . ') [NC]' . PHP_EOL;
			}

			// dont cache fbexternal
			$rules .= '    RewriteCond %{HTTP_USER_AGENT} !^(facebookexternalhit).* [NC]' . PHP_EOL;

			$cache_location = get_cache_dir();
			$cache_location = untrailingslashit( $cache_location ) . '/powered-cache/';
			if ( strpos( ABSPATH, $cache_location ) === false ) {
				$cache_path = str_replace( $_SERVER['DOCUMENT_ROOT'], '', $cache_location ); // phpcs:ignore
			} else {
				$cache_path = $site_root . str_replace( ABSPATH, '', $cache_location );
			}

			/**
			 * Filters whether running on 1and1_hosting or not
			 *
			 * @hook   powered_cache_maybe_1and1_hosting
			 *
			 * @param  {boolean} $status true if  /kunden/homepage directory exists
			 *
			 * @return {boolean} New value.
			 *
			 * @since  1.1
			 */
			if ( apply_filters( 'powered_cache_maybe_1and1_hosting', ( 0 === strpos( $_SERVER['DOCUMENT_ROOT'], '/kunden/homepage/' ) ) ) ) { // phpcs:ignore
				$rules .= '    RewriteCond "' . str_replace( '/kunden/homepage/', '/', $cache_location ) . '%{HTTP_HOST}' . '%{REQUEST_URI}/index' . $env_powered_cache_ssl . $env_powered_cache_ua . '.html' . $env_powered_cache_enc . '" -f' . PHP_EOL;
			} else {
				$rules .= '    RewriteCond "%{DOCUMENT_ROOT}/' . ltrim( $cache_path, '/' ) . '%{HTTP_HOST}' . '%{REQUEST_URI}/index' . $env_powered_cache_ssl . $env_powered_cache_ua . '.html' . $env_powered_cache_enc . '" -f' . PHP_EOL;
			}
			$rules .= '    RewriteRule .* "' . $cache_path . '%{HTTP_HOST}' . '%{REQUEST_URI}/index' . $env_powered_cache_ssl . $env_powered_cache_ua . '.html' . $env_powered_cache_enc . '" [L]' . PHP_EOL;

			if ( $this->is_gzip_enabled() ) {
				$rules .= '    # prevent mod_deflate double gzip' . PHP_EOL;
				$rules .= '    RewriteRule \.html\.gz$ - [T=text/html,E=no-gzip:1]' . PHP_EOL;
			}

			$rules .= '</IfModule>' . PHP_EOL;
		}

		return $rules;
	}

	/**
	 * Apache allows both format like A2592000 => "access plus 1 month"
	 * A => access, M => Modified
	 *
	 * @param string $mime_type Mimetype
	 *
	 * @return string cache lifespan for apache
	 * @see   http://httpd.apache.org/docs/current/mod/mod_expires.html
	 * @since 2.0
	 */
	public function get_browser_cache_lifespan( $mime_type ) {
		switch ( $mime_type ) {
			case 'text/css':
			case 'application/javascript':
				/**
				 * Filters TTL for CSS/JS files.
				 *
				 * @hook   powered_cache_browser_cache_assets_lifespan
				 *
				 * @param  {string} $expiry_time .htaccess lifespan
				 *
				 * @return {string} New value.
				 *
				 * @since  1.1
				 */
				$expiry_time = apply_filters( 'powered_cache_browser_cache_assets_lifespan', 'access plus 1 year' );
				break;
			case 'image/jpeg':
			case 'image/gif':
			case 'image/png':
			case 'image/bmp':
			case 'image/tiff':
			case 'image/webp':
			case 'image/heic':
				$expiry_time = 'access plus 6 months';
				break;
			case 'image/x-icon':
				$expiry_time = 'access plus 1 week'; // favicon
				break;
			case 'image/svg+xml':
			case 'font/ttf':
			case 'font/woff':
			case 'font/woff2':
			case 'font/otf':
			case 'application/vnd.ms-fontobject':
				$expiry_time = 'access plus 4 month';
				break;
			case 'application/atom+xml':
			case 'application/rss+xml':
				$expiry_time = 'access plus 1 hour';
				break;
			default:
				/**
				 * Filters default TTL for browser cache lifespan.
				 *
				 * @hook   powered_cache_browser_cache_default_lifespan
				 *
				 * @param  {string} $expiry_time .htaccess lifespan
				 *
				 * @return {string} New value.
				 *
				 * @since  1.1
				 */
				$expiry_time = apply_filters( 'powered_cache_browser_cache_default_lifespan', 'access plus 1 month' );
		}

		/**
		 * Filters TTL for browser cache lifespan.
		 *
		 * @hook   powered_cache_browser_cache_lifespan
		 *
		 * @param  {string} $expiry_time .htaccess lifespan
		 * @param  {string} $mime_type mime type
		 *
		 * @return {string} New value.
		 *
		 * @since  2.0
		 */
		$expiry_time = apply_filters( 'powered_cache_browser_cache_lifespan', $expiry_time, $mime_type );

		return $expiry_time;
	}


	/**
	 * Check if the gzip option enabled
	 *
	 * @return bool
	 * @since 2.5
	 */
	public function is_gzip_enabled() {
		/**
		 * Filters whether gzip enabled or not for the htaccess rules
		 *
		 * @hook   powered_cache_htaccess_enable_gzip_compression
		 *
		 * @param  {boolean} $status true if gzip option enabled on settings page
		 *
		 * @return {boolean} New value.
		 *
		 * @since  2.5
		 */
		return function_exists( 'gzencode' ) && apply_filters( 'powered_cache_htaccess_enable_gzip_compression', $this->settings['gzip_compression'] );
	}


	/**
	 * Cache-Control rules
	 *
	 * @return string
	 */
	public function cache_control_rules() {
		$rules  = '<FilesMatch "\.(html|htm|html\.gz|rtf|rtx|txt|xsd|xsl|xml)$">' . PHP_EOL;
		$rules .= '  <IfModule mod_headers.c>' . PHP_EOL;
		$rules .= '    Header set X-Powered-By "Powered Cache"' . PHP_EOL;
		$rules .= '    Header unset Pragma' . PHP_EOL;
		$rules .= '    Header append Cache-Control "public"' . PHP_EOL;
		$rules .= '  </IfModule>' . PHP_EOL;
		$rules .= '</FilesMatch>' . PHP_EOL;

		/**
		 * Filters cache control rules
		 *
		 * @hook   powered_cache_htaccess_cache_control_rules
		 *
		 * @param  {string} $rules cache control
		 *
		 * @return {string} New value.
		 *
		 * @since  2.5
		 */
		$rules = apply_filters( 'powered_cache_htaccess_cache_control_rules', $rules );

		return $rules;
	}


	/**
	 * Rewrite rules for file optimizer
	 *
	 * @return string|void
	 * @since  3.3
	 *         https://example.com/wp-content/plugins/powered-cache/includes/file-optimizer.php??
	 *         to
	 *         https://example.com/_static/??
	 * @see    https://docs.poweredcache.com/rewrite-file-optimizer/
	 */
	public function file_optimizer_rewrite_rules() {
		if ( ! $this->settings['rewrite_file_optimizer'] ) {
			return;
		}

		$file_optimizer_path = \PoweredCache\Optimizer\Helper::get_file_optimizer_relative_path();

		$rules  = '';
		$rules .= '<IfModule mod_rewrite.c>' . PHP_EOL;
		$rules .= '    # Enable rewrite engine' . PHP_EOL;
		$rules .= '    RewriteEngine On' . PHP_EOL;
		$rules .= '    # Redirect all requests starting with /_static/' . PHP_EOL;
		$rules .= '    RewriteRule ^_static/.* ' . $file_optimizer_path . ' [L]' . PHP_EOL;
		$rules .= '</IfModule>' . PHP_EOL;

		return $rules;
	}

}