/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, "