wp-config.php are disabled via a filter.', 'better-wp-security' ) );
}
return self::remove( $file_path, $patterns );
}
/**
* Write the supplied modification to the wp-config.php file.
*
* @since 1.15.0
* @access protected
*
* @param string $modification The modification to add to the wp-config.php file.
* @param bool $clear_existing_modifications Optional. Whether or not existing modifications should be removed
* first. Defaults to true.
* @return bool|WP_Error Boolean true on success or a WP_Error object otherwise.
*/
protected static function write_wp_config( $modification, $clear_existing_modifications = true ) {
$file_path = self::get_wp_config_file_path();
if ( empty( $file_path ) ) {
return new WP_Error( 'itsec-lib-config-file-wp-config-file-updates-disabled', __( 'Updates to wp-config.php
are disabled via a filter.', 'better-wp-security' ) );
}
return self::update( $file_path, 'wp-config', $modification, $clear_existing_modifications );
}
/**
* Returns the contents of the file.
*
* @since 1.15.0
* @access protected
*
* @param string $file Config file to read.
* @return string|WP_Error The contents of the file, an empty string if the file does not exist, or a WP_Error object on error.
*/
protected static function get_file_contents( $file ) {
if ( ! ITSEC_Lib_File::exists( $file ) ) {
return '';
}
$contents = ITSEC_Lib_File::read( $file );
if ( is_wp_error( $contents ) ) {
return new WP_Error( 'itsec-lib-config-file-cannot-read-file', sprintf( __( 'Unable to read %1$s due to the following error: %2$s', 'better-wp-security' ), $file, $contents->get_error_message() ) );
}
return $contents;
}
/**
* Returns the contents of the file with the iThemes Security modifications removed.
*
* @since 1.15.0
* @access protected
*
* @param string $file Config file to read.
* @param string $type The type of config file. Valid options are apache, nginx, and wp-config.
* @return string|WP_Error The contents of the file with modifications removed, an empty string if the file does not exist, or a WP_Error object on error.
*/
protected static function get_file_contents_without_modification( $file, $type ) {
$contents = self::get_file_contents( $file );
if ( is_wp_error( $contents ) ) {
return $contents;
}
// Contents of just whitespace are treated as empty.
if ( preg_match( '/^\s+$/', $contents ) ) {
return '';
}
$format_version = 0;
// Attempt to retrieve config file details from the contents.
if ( preg_match( '/iThemes\s+Security\s+Config\s+Details:\s+([^\s]+)/', $contents, $match ) ) {
$details = explode( ':', $match[1] );
if ( isset( $details[0] ) && ( (string) intval( $details[0] ) === $details[0] ) ) {
$format_version = intval( $details[0] );
}
}
$placeholder = self::get_placeholder();
// Ensure that the generated placeholder can be uniquely identified in the contents.
while ( false !== strpos( $contents, $placeholder ) ) {
$placeholder = self::get_placeholder();
}
// Create a set of regex patterns to identify existing iThemes Security modifications.
$comment_delimiter = self::get_comment_delimiter( $type );
$quoted_comment_delimiter = preg_quote( $comment_delimiter, '/' );
$line_ending = self::get_line_ending( $contents );
$patterns = array(
array(
'begin' => "$quoted_comment_delimiter+\s*BEGIN\s+iThemes\s+Security",
'end' => "$quoted_comment_delimiter+\s*END\s+iThemes\s+Security",
),
array(
'begin' => "$quoted_comment_delimiter+\s*BEGIN\s+Better\s+WP\s+Security",
'end' => "$quoted_comment_delimiter+\s*END\s+Better\s+WP\s+Security",
),
);
// Remove matched content.
foreach ( $patterns as $pattern ) {
$contents = preg_replace( "/\s*{$pattern['begin']}.+?{$pattern['end']}[^\r\n]*\s*/is", "$line_ending$placeholder", $contents );
}
if ( 'wp-config' === $type ) {
// Special treatment for wp-config.php config data.
// If the format is old or could not be detected, assume that cleanup of old modifications is required.
if ( version_compare( $format_version, self::FORMAT_VERSION, '<' ) ) {
// This code is clumsy, but it's the only way to remove the modifications given that the start and end
// were not indicated in older versions.
$contents = preg_replace( '/(<\?(?:php)?)?.+BWPS_FILECHECK.+/', "$1$line_ending$placeholder", $contents );
$contents = preg_replace( '/(<\?(?:php)?)?.+BWPS_AWAY_MODE.+/', "$1$line_ending$placeholder", $contents );
if ( preg_match( '|//\s*The entry below were created by iThemes Security to disable the file editor|', $contents ) ) {
$contents = preg_replace( '%(<\?(?:php)?)?.*//\s*The entry below were created by iThemes Security to disable the file editor.*(?:\r\n|\r|\n)%', "$1$line_ending$placeholder", $contents );
$contents = preg_replace( '/(<\?(?:php)?)?.+DISALLOW_FILE_EDIT.+(?:\r\r\n|\r\n|\r|\n)/', "$1$line_ending$placeholder", $contents );
}
if ( preg_match( '|//\s*The entries below were created by iThemes Security to enforce SSL|', $contents ) ) {
$contents = preg_replace( '%(<\?(?:php)?)?.*//\s*The entries below were created by iThemes Security to enforce SSL.*(?:\r\n|\r|\n)%', "$1$line_ending$placeholder", $contents );
$contents = preg_replace( '/(<\?(?:php)?)?.+FORCE_SSL_LOGIN.+(?:\r\r\n|\r\n|\r|\n)/', "$1$line_ending$placeholder", $contents );
$contents = preg_replace( '/(<\?(?:php)?)?.+FORCE_SSL_ADMIN.+(?:\r\r\n|\r\n|\r|\n)/', "$1$line_ending$placeholder", $contents );
}
}
}
// Remove adjacent placeholders.
$contents = preg_replace( "/$placeholder(?:\s*$placeholder)+/", $placeholder, $contents );
// Remove whitespace from around the placeholders.
$contents = preg_replace( "/\s*$placeholder\s*/", $placeholder, $contents );
// Placeholders at the beginning or end of the contents do not need to have newlines added.
$contents = preg_replace( "/^$placeholder|$placeholder$/", '', $contents );
// Remaining placeholders are replaced with two newlines to leave a gap between sections of remaining contents.
$contents = preg_replace( "/$placeholder/", "$line_ending$line_ending", $contents );
// Fix potentially damaged Windows-style newlines for W3 Total Cache modifications
$translated_w3tc_comment = __( 'Added by W3 Total Cache', 'w3-total-cache' );
if ( preg_match_all( '/[^\r\n]+(?:W3 Total Cache|' . preg_quote( $translated_w3tc_comment, '/' ) . ').*?(?:\r\n|\r|\n)/', $contents, $matches ) ) {
foreach ( $matches[0] as $match ) {
$new_line = rtrim( $match ) . "\r\n";
if ( $new_line !== $match ) {
$contents = str_replace( $match, $new_line, $contents );
}
}
}
return $contents;
}
/**
* Update modifications in the supplied configuration file.
*
* If a blank $contents argument is supplied, all modifications will be removed.
*
* @since 1.15.0
* @access protected
*
* @param string $file Config file to update.
* @param string $type The type of config file. Valid options are apache, nginx, and
* wp-config.
* @param string $modification The contents to add or update the file with. If an empty string is
* supplied, all iThemes Security modifications will be removed.
* @param bool $clear_existing_modifications Optional. Whether or not existing modifications should be removed
* first. Defaults to true.
* @return bool|WP_Error Boolean true on success or a WP_Error object otherwise.
*/
protected static function update( $file, $type, $modification, $clear_existing_modifications = true ) {
// Check to make sure that the settings give permission to write files.
if ( ! ITSEC_Files::can_write_to_files() ) {
$display_file = str_replace( '\\', '/', $file );
$abspath = str_replace( '\\', '/', ABSPATH );
$display_file = preg_replace( '/^' . preg_quote( $abspath, '/' ) . '/', '', $display_file );
$display_file = ltrim( $display_file, '/' );
return new WP_Error( 'itsec-config-file-update-writes-files-disabled', sprintf( __( 'The "Write to Files" setting is disabled. Manual configuration for the %s
file can be found on the Security > Settings page in the Advanced section.', 'better-wp-security' ), $display_file ) );
}
if ( $clear_existing_modifications ) {
$contents = self::get_file_contents_without_modification( $file, $type );
} else {
$contents = self::get_file_contents( $file );
}
if ( is_wp_error( $contents ) ) {
return $contents;
}
$modification = ltrim( $modification, "\x0B\r\n\0" );
$modification = rtrim( $modification, " \t\x0B\r\n\0" );
if ( empty( $modification ) ) {
// If there isn't a new modification, write the content without any modification and return the result.
if ( empty( $contents ) ) {
$contents = PHP_EOL;
}
return ITSEC_Lib_File::write( $file, $contents );
}
$placeholder = self::get_placeholder();
// Ensure that the generated placeholder can be uniquely identified in the contents.
while ( false !== strpos( $contents, $placeholder ) ) {
$placeholder = self::get_placeholder();
}
if ( 'wp-config' === $type ) {
// Put the placeholder at the beginning of the file, after the $contents";
}
} else {
// Apache and nginx server config files.
$contents = "$placeholder$contents";
}
// Pad away from existing sections when adding iThemes Security modifications.
$line_ending = self::get_line_ending( $contents );
while ( ! preg_match( "/(?:^|(?:(? $replacement ) {
$contents = preg_replace( $pattern, $replacement, $contents, -1, $count );
$total += $count;
}
// Write the new contents to the file and return the results.
return ITSEC_Lib_File::write( $file, $contents );
}
/**
* Get the appropriate comment delimiter for a specific type of config.
*
* @since 1.15.0
* @access protected
*
* @param string $type The type of config that the comment will be used for.
* @return string The comment delimiter.
*/
protected static function get_comment_delimiter( $type ) {
if ( 'wp-config' === $type ) {
$delimiter = '//';
} else {
$delimiter = '#';
}
$delimiter = apply_filters( 'itsec_filter_server_config_file_comment_delimiter', $delimiter, $type );
return $delimiter;
}
/**
* Internal class function to get the primary line ending found in the contents.
*
* @since 1.15.0
* @access protected
*
* @param string $contents The contents to count line endings in.
* @return string The line ending.
*/
protected static function get_line_ending( $contents ) {
if ( empty( $contents ) ) {
return PHP_EOL;
}
$count["\n"] = preg_match_all( "/(? '.htaccess',
'litespeed' => '.htaccess',
'nginx' => 'nginx.conf',
);
if ( isset( $defaults[$server] ) ) {
$name = $defaults[$server];
} else {
$name = false;
}
return apply_filters( 'itsec_filter_default_server_config_file_name', $name, $server );
}
/**
* Get full file path to the site's wp-config.php file.
*
* Customize the returned value with the itsec_filter_wp_config_file_path filter. Filter the value to a blank string
* ("") in order to disable modifications to this file.
*
* @since 1.15.0
*
* @return string Full path to the wp-config.php file or a blank string if modifications for the file are disabled.
*/
public static function get_wp_config_file_path() {
if ( file_exists( ABSPATH . 'wp-config.php') ) {
$path = ABSPATH . 'wp-config.php';
} else if ( file_exists( dirname( ABSPATH ) . '/wp-config.php' ) && ! file_exists( dirname( ABSPATH ) . '/wp-settings.php' ) ) {
$path = dirname( ABSPATH ) . '/wp-config.php';
} else {
$path = ABSPATH . 'wp-config.php';
}
return apply_filters( 'itsec_filter_wp_config_file_path', $path );
}
}
require_once( dirname( __FILE__ ) . '/class-itsec-lib-utility.php' );
require_once( dirname( __FILE__ ) . '/class-itsec-lib-file.php' );