Inserts an array of strings into a file (.htaccess), placing it between BEGIN and END markers.
Description
Replaces existing marked info. Retains surrounding data. Creates file if none exists.
Parameters
$filename
stringrequired- Filename to alter.
$marker
stringrequired- The marker to alter.
$insertion
array|stringrequired- The new content to insert.
Source
function insert_with_markers( $filename, $marker, $insertion ) {
if ( ! file_exists( $filename ) ) {
if ( ! is_writable( dirname( $filename ) ) ) {
return false;
}
if ( ! touch( $filename ) ) {
return false;
}
// Make sure the file is created with a minimum set of permissions.
$perms = fileperms( $filename );
if ( $perms ) {
chmod( $filename, $perms | 0644 );
}
} elseif ( ! is_writable( $filename ) ) {
return false;
}
if ( ! is_array( $insertion ) ) {
$insertion = explode( "\n", $insertion );
}
$switched_locale = switch_to_locale( get_locale() );
$instructions = sprintf(
/* translators: 1: Marker. */
__(
'The directives (lines) between "BEGIN %1$s" and "END %1$s" are
dynamically generated, and should only be modified via WordPress filters.
Any changes to the directives between these markers will be overwritten.'
),
$marker
);
$instructions = explode( "\n", $instructions );
foreach ( $instructions as $line => $text ) {
$instructions[ $line ] = '# ' . $text;
}
/**
* Filters the inline instructions inserted before the dynamically generated content.
*
* @since 5.3.0
*
* @param string[] $instructions Array of lines with inline instructions.
* @param string $marker The marker being inserted.
*/
$instructions = apply_filters( 'insert_with_markers_inline_instructions', $instructions, $marker );
if ( $switched_locale ) {
restore_previous_locale();
}
$insertion = array_merge( $instructions, $insertion );
$start_marker = "# BEGIN {$marker}";
$end_marker = "# END {$marker}";
$fp = fopen( $filename, 'r+' );
if ( ! $fp ) {
return false;
}
// Attempt to get a lock. If the filesystem supports locking, this will block until the lock is acquired.
flock( $fp, LOCK_EX );
$lines = array();
while ( ! feof( $fp ) ) {
$lines[] = rtrim( fgets( $fp ), "\r\n" );
}
// Split out the existing file into the preceding lines, and those that appear after the marker.
$pre_lines = array();
$post_lines = array();
$existing_lines = array();
$found_marker = false;
$found_end_marker = false;
foreach ( $lines as $line ) {
if ( ! $found_marker && str_contains( $line, $start_marker ) ) {
$found_marker = true;
continue;
} elseif ( ! $found_end_marker && str_contains( $line, $end_marker ) ) {
$found_end_marker = true;
continue;
}
if ( ! $found_marker ) {
$pre_lines[] = $line;
} elseif ( $found_marker && $found_end_marker ) {
$post_lines[] = $line;
} else {
$existing_lines[] = $line;
}
}
// Check to see if there was a change.
if ( $existing_lines === $insertion ) {
flock( $fp, LOCK_UN );
fclose( $fp );
return true;
}
// Generate the new file data.
$new_file_data = implode(
"\n",
array_merge(
$pre_lines,
array( $start_marker ),
$insertion,
array( $end_marker ),
$post_lines
)
);
// Write to the start of the file, and truncate it to that length.
fseek( $fp, 0 );
$bytes = fwrite( $fp, $new_file_data );
if ( $bytes ) {
ftruncate( $fp, ftell( $fp ) );
}
fflush( $fp );
flock( $fp, LOCK_UN );
fclose( $fp );
return (bool) $bytes;
}
Hooks
- apply_filters( ‘insert_with_markers_inline_instructions’,
string[] $instructions ,string $marker ) Filters the inline instructions inserted before the dynamically generated content.
Changelog
Version | Description |
---|---|
1.5.0 | Introduced. |
I don’t think this is classed as a bug but worth noting.
When splitting out the htaccess file, the code searches for:
$start_marker = "# BEGIN {$marker}";
and will find the first instance of it in the file, regardless of where it is and even if this is in a commented line before the actual WordPress section.For example, if I add the code suggested by Wordfence to solve an issue with Wordfence scans and updates and LiteSpeed, and I want to add a comment to explain what this code does and where it needs to go (ie before the WordPress section), I may add:
The
# BEGIN WordPress
in this comment is found first by insert_with_markers and the new WordPress section is appended to the next line, overwriting the Wordfence noabort code.The workaround is not to have
# BEGIN WordPress
in a comment before the WordPress section, but ideally the function would be altered so that it only matches # BEGIN WordPress at the start of a line.