/Y/m/ instead of the standard public year/month root. $extra lets callers keep drafts and final-submission files in separate sub-trees under the same protected root (both covered by the one .htaccess at the root). */
public static function filter_upload_dir( array $dirs, $extra = '' ) {
$prefix = '/' . self::SUBDIR . ( $extra ? '/' . trim( $extra, '/' ) : '' );
$dirs['subdir'] = $prefix . $dirs['subdir'];
$dirs['path'] = $dirs['basedir'] . $dirs['subdir'];
$dirs['url'] = $dirs['baseurl'] . $dirs['subdir'];
return $dirs;
}
/** Writes the deny-all .htaccess (Apache 2.2 + 2.4 syntax, so it works either way) and a blank index.php once, at the protected root — idempotent, cheap to call on every upload since it no-ops after the first. */
public static function ensure_protected_root() {
$root = trailingslashit( wp_upload_dir()['basedir'] ) . self::SUBDIR;
if ( ! file_exists( $root ) ) {
wp_mkdir_p( $root );
}
$htaccess = $root . '/.htaccess';
if ( ! file_exists( $htaccess ) ) {
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- one-time, plugin-controlled path, not user input.
@file_put_contents( $htaccess, "\n\tRequire all denied\n\n\n\tOrder deny,allow\n\tDeny from all\n\n" );
}
$index = $root . '/index.php';
if ( ! file_exists( $index ) ) {
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- one-time, plugin-controlled path, not user input.
@file_put_contents( $index, "