path_selection_cookie();
}
function admin_menu() {
$page_slug = add_media_page(
__( 'Add From Server', 'add-from-server' ),
__( 'Add From Server', 'add-from-server' ),
'upload_files',
'add-from-server',
[ $this, 'menu_page' ]
);
add_action( 'load-' . $page_slug, function() {
wp_enqueue_style( 'add-from-server' );
wp_enqueue_script( 'add-from-server' );
} );
}
function add_upload_link( $links ) {
if ( current_user_can( 'upload_files' ) ) {
array_unshift( $links, '' . __( 'Import Files', 'add-from-server' ) . '' );
}
return $links;
}
function menu_page() {
// Handle any imports
$this->handle_imports();
echo '
';
echo '
' . __( 'Add From Server', 'add-from-server' ) . '
';
$this->outdated_options_notice();
$this->main_content();
$this->language_notice();
echo '';
}
function get_root() {
// Lock users to either
// a) The 'ADD_FROM_SERVER' constant.
// b) Their home directory.
// c) The parent directory of the current install or wp-content directory.
if ( defined( 'ADD_FROM_SERVER' ) ) {
$root = ADD_FROM_SERVER;
} elseif ( str_starts_with( __FILE__, '/home/' ) ) {
$root = implode( '/', array_slice( explode( '/', __FILE__ ), 0, 3 ) );
} else {
if ( str_starts_with( WP_CONTENT_DIR, ABSPATH ) ) {
$root = dirname( ABSPATH );
} else {
$root = dirname( WP_CONTENT_DIR );
}
}
// Precautions. The user is using the folder placeholder code. Abort for lower-privledge users.
if (
str_contains( get_option( 'frmsvr_root', '%' ), '%' )
&&
! defined( 'ADD_FROM_SERVER' )
&&
! current_user_can( 'unfiltered_html' )
) {
$root = false;
}
return $root;
}
function path_selection_cookie() {
if ( isset( $_REQUEST['path'] ) && current_user_can( 'upload_files' ) ) {
$_COOKIE[ COOKIE ] = $_REQUEST['path'];
$parts = parse_url( admin_url(), PHP_URL_HOST );
setcookie(
COOKIE,
wp_unslash( $_COOKIE[ COOKIE ] ),
time() + 30 * DAY_IN_SECONDS,
parse_url( admin_url(), PHP_URL_PATH ),
parse_url( admin_url(), PHP_URL_HOST ),
'https' === parse_url( admin_url(), PHP_URL_SCHEME ),
true
);
}
}
// Handle the imports
function handle_imports() {
if ( !empty( $_POST['files'] ) ) {
check_admin_referer( 'afs_import' );
$files = wp_unslash( $_POST['files'] );
$root = $this->get_root();
if ( ! $root ) {
return false;
}
flush();
wp_ob_end_flush_all();
foreach ( (array)$files as $file ) {
$filename = trailingslashit( $root ) . ltrim( $file, '/' );
if ( $filename !== realpath( $filename ) ) {
continue;
}
$id = $this->handle_import_file( $filename );
if ( is_wp_error( $id ) ) {
echo '' . sprintf( __( '%s was not imported due to an error: %s', 'add-from-server' ), esc_html( basename( $file ) ), $id->get_error_message() ) . '
';
} else {
echo '' . sprintf( __( '%s has been added to Media library', 'add-from-server' ), esc_html( basename( $file ) ) ) . '
';
}
flush();
wp_ob_end_flush_all();
}
}
}
// Handle an individual file import.
function handle_import_file( $file ) {
set_time_limit( 0 );
$file = wp_normalize_path( $file );
// Initially, Base it on the -current- time.
$time = time();
// A writable uploads dir will pass this test. Again, there's no point overriding this one.
if ( ! ( ( $uploads = wp_upload_dir( $time ) ) && false === $uploads['error'] ) ) {
return new WP_Error( 'upload_error', $uploads['error'] );
}
$wp_filetype = wp_check_filetype( $file, null );
$type = $wp_filetype['type'];
$ext = $wp_filetype['ext'];
if ( ( !$type || !$ext ) && !current_user_can( 'unfiltered_upload' ) ) {
return new WP_Error( 'wrong_file_type', __( 'Sorry, this file type is not permitted for security reasons.', 'add-from-server' ) );
}
// Is the file already in the uploads folder?
if ( preg_match( '|^' . preg_quote( wp_normalize_path( $uploads['basedir'] ), '|' ) . '(.*)$|i', $file, $mat ) ) {
$filename = basename( $file );
$new_file = $file;
$url = $uploads['baseurl'] . $mat[1];
$attachment = get_posts( array( 'post_type' => 'attachment', 'meta_key' => '_wp_attached_file', 'meta_value' => ltrim( $mat[1], '/' ) ) );
if ( !empty($attachment) ) {
return new WP_Error( 'file_exists', __( 'Sorry, that file already exists in the WordPress media library.', 'add-from-server' ) );
}
$time = filemtime( $file ) ?: time();
// Ok, Its in the uploads folder, But NOT in WordPress's media library.
if ( preg_match( '|^/?(?P(?P\d{4})/(?P\d{2}))|', dirname( $mat[1] ), $datemat ) ) {
// The file date and the folder it's in are mismatched. Set it to the date of the folder.
if ( date( 'Y/m', $time ) !== $datemat['Ym'] ) {
$time = mktime( 0, 0, 0, $datemat['month'], 1, $datemat['year'] );
}
}
// A new time has been found! Get the new uploads folder:
// A writable uploads dir will pass this test. Again, there's no point overriding this one.
if ( !(($uploads = wp_upload_dir( $time )) && false === $uploads['error']) ) {
return new WP_Error( 'upload_error', $uploads['error'] );
}
$url = $uploads['baseurl'] . $mat[1];
} else {
$filename = wp_unique_filename( $uploads['path'], basename( $file ) );
// copy the file to the uploads dir
$new_file = $uploads['path'] . '/' . $filename;
if ( false === @copy( $file, $new_file ) )
return new WP_Error( 'upload_error', sprintf( __( 'The selected file could not be copied to %s.', 'add-from-server' ), $uploads['path'] ) );
// Set correct file permissions
$stat = stat( dirname( $new_file ) );
$perms = $stat['mode'] & 0000666;
@ chmod( $new_file, $perms );
// Compute the URL
$url = $uploads['url'] . '/' . $filename;
}
// Apply upload filters
$return = apply_filters( 'wp_handle_upload', array( 'file' => $new_file, 'url' => $url, 'type' => $type ) );
$new_file = $return['file'];
$url = $return['url'];
$type = $return['type'];
$title = preg_replace( '!\.[^.]+$!', '', basename( $file ) );
$content = $excerpt = '';
if ( preg_match( '#^audio#', $type ) ) {
$meta = wp_read_audio_metadata( $new_file );
if ( ! empty( $meta['title'] ) ) {
$title = $meta['title'];
}
if ( ! empty( $title ) ) {
if ( ! empty( $meta['album'] ) && ! empty( $meta['artist'] ) ) {
/* translators: 1: audio track title, 2: album title, 3: artist name */
$content .= sprintf( __( '"%1$s" from %2$s by %3$s.', 'add-from-server' ), $title, $meta['album'], $meta['artist'] );
} elseif ( ! empty( $meta['album'] ) ) {
/* translators: 1: audio track title, 2: album title */
$content .= sprintf( __( '"%1$s" from %2$s.', 'add-from-server' ), $title, $meta['album'] );
} elseif ( ! empty( $meta['artist'] ) ) {
/* translators: 1: audio track title, 2: artist name */
$content .= sprintf( __( '"%1$s" by %2$s.', 'add-from-server' ), $title, $meta['artist'] );
} else {
$content .= sprintf( __( '"%s".', 'add-from-server' ), $title );
}
} elseif ( ! empty( $meta['album'] ) ) {
if ( ! empty( $meta['artist'] ) ) {
/* translators: 1: audio album title, 2: artist name */
$content .= sprintf( __( '%1$s by %2$s.', 'add-from-server' ), $meta['album'], $meta['artist'] );
} else {
$content .= $meta['album'] . '.';
}
} elseif ( ! empty( $meta['artist'] ) ) {
$content .= $meta['artist'] . '.';
}
if ( ! empty( $meta['year'] ) )
$content .= ' ' . sprintf( __( 'Released: %d.' ), $meta['year'] );
if ( ! empty( $meta['track_number'] ) ) {
$track_number = explode( '/', $meta['track_number'] );
if ( isset( $track_number[1] ) )
$content .= ' ' . sprintf( __( 'Track %1$s of %2$s.', 'add-from-server' ), number_format_i18n( $track_number[0] ), number_format_i18n( $track_number[1] ) );
else
$content .= ' ' . sprintf( __( 'Track %1$s.', 'add-from-server' ), number_format_i18n( $track_number[0] ) );
}
if ( ! empty( $meta['genre'] ) )
$content .= ' ' . sprintf( __( 'Genre: %s.', 'add-from-server' ), $meta['genre'] );
// Use image exif/iptc data for title and caption defaults if possible.
} elseif ( 0 === strpos( $type, 'image/' ) && $image_meta = @wp_read_image_metadata( $new_file ) ) {
if ( trim( $image_meta['title'] ) && ! is_numeric( sanitize_title( $image_meta['title'] ) ) ) {
$title = $image_meta['title'];
}
if ( trim( $image_meta['caption'] ) ) {
$excerpt = $image_meta['caption'];
}
}
// Construct the attachment array
$attachment = [
'post_mime_type' => $type,
'guid' => $url,
'post_parent' => 0,
'post_title' => $title,
'post_name' => $title,
'post_content' => $content,
'post_excerpt' => $excerpt,
'post_date' => gmdate( 'Y-m-d H:i:s', $time ),
'post_date_gmt' => gmdate( 'Y-m-d H:i:s', $time ),
];
$attachment = apply_filters( 'afs-import_details', $attachment, $file, 0, 'current' );
// Save the data
$id = wp_insert_attachment( $attachment, $new_file, 0 );
if ( !is_wp_error( $id ) ) {
$data = wp_generate_attachment_metadata( $id, $new_file );
wp_update_attachment_metadata( $id, $data );
}
return $id;
}
protected function get_default_dir() {
$root = $this->get_root();
if ( str_starts_with( WP_CONTENT_DIR, $root ) ) {
return WP_CONTENT_DIR;
}
return $root;
}
// Create the content for the page
function main_content() {
$url = admin_url( 'upload.php?page=add-from-server' );
$root = $this->get_root();
if ( ! $root ) {
return; // Intervention required.
}
$cwd = $this->get_default_dir();
if ( ! empty( $_COOKIE[ COOKIE ] ) ) {
$cwd = realpath( trailingslashit( $root ) . wp_unslash( $_COOKIE[ COOKIE ] ) );
}
// Validate it.
if ( ! str_starts_with( $cwd, $root ) ) {
$cwd = $root;
}
$cwd_relative = substr( $cwd, strlen( $root ) );
// Make a list of the directories the user can enter.
$dirparts = [];
$dirparts[] = '' . esc_html( trailingslashit( $root ) ) . ' ';
$dir_path = '';
foreach ( array_filter( explode( '/', $cwd_relative ) ) as $dir ) {
$dir_path .= '/' . $dir;
$dirparts[] = '' . esc_html( $dir ?: basename( $root ) ) . '/ ';
}
$dirparts = implode( '', $dirparts );
// Sort alphabetically correctly..
$sort_by_text = function( $a, $b ) {
return strtolower( $a['text'] ) <=> strtolower( $b['text'] );
};
// Get a list of files to show.
$nodes = glob( rtrim( $cwd, '/' ) . '/*' ) ?: [];
$directories = array_flip( array_filter( $nodes, function( $node ) {
return is_dir( $node );
} ) );
$get_import_root = function( $path ) use ( &$get_import_root ) {
if ( ! is_readable( $path ) ) {
return false;
}
$files = glob( $path . '/*' );
if ( ! $files ) {
return false;
}
$has_files = false;
foreach ( $files as $i => $file ) {
if ( is_file( $file ) ) {
$has_files = true;
break;
} else {
if ( $get_import_root( $file ) ) {
$has_files = true;
break;
} else {
unset( $files[ $i ] );
}
}
}
if ( ! $has_files ) {
return false;
}
// Rekey the array incase anything was removed.
$files = array_values( $files );
if ( 1 === count( $files ) && is_dir( $files[0] ) ) {
return $get_import_root( $files[0] );
}
return $path;
};
$get_root_relative_path = function( $path ) use( $root ) {
$root_offset = strlen( $root );
if ( '/' !== $root ) {
$root_offset += 1;
}
return substr( $path, $root_offset );
};
array_walk( $directories, function( &$data, $path ) use( $root, $cwd_relative, $get_import_root, $get_root_relative_path ) {
$import_root = $get_import_root( $path );
if ( ! $import_root ) {
// Unreadable, etc.
$data = false;
return;
}
$data = [
'text' => substr(
$get_root_relative_path( $import_root ),
strlen( $cwd_relative )
) . '/',
'path' => $get_root_relative_path( $import_root )
];
$data['text'] = ltrim( $data['text'], '/' );
} );
$directories = array_filter( $directories );
// Sort the directories case insensitively.
uasort( $directories, $sort_by_text );
// Prefix the parent directory.
if ( str_starts_with( dirname( $cwd ), $root ) && dirname( $cwd ) != $cwd ) {
$directories = array_merge(
[
dirname( $cwd ) => [
'text' => __( 'Parent Folder', 'add-from-server' ),
'path' => $get_root_relative_path( dirname( $cwd ) ) ?: '/',
]
],
$directories
);
}
$files = array_flip( array_filter( $nodes, function( $node ) {
return is_file( $node );
} ) );
array_walk( $files, function( &$data, $path ) use( $root, $get_root_relative_path ) {
$importable = ( false !== wp_check_filetype( $path )['type'] || current_user_can( 'unfiltered_upload' ) );
$readable = is_readable( $path );
$data = [
'text' => basename( $path ),
'file' => $get_root_relative_path( $path ),
'importable' => $importable,
'readable' => $readable,
'error' => (
! $importable ? 'doesnt-meet-guidelines' : (
! $readable ? 'unreadable' : false
)
),
];
} );
// Sort case insensitively.
uasort( $files, $sort_by_text );
?>
translate.WordPress.org and suggest translations for any languages which you know.
Thanks! Dion.';
/* translators: %1$s = The Locale (de_DE, en_US, fr_FR, he_IL, etc). %2$s = The translate.wordpress.org link to the plugin overview */
$message = __( 'Hi there!
I notice you use WordPress in a Language other than English (US), Did you know you can translate WordPress Plugins into your native language as well?
If you\'d like to help out with translating this plugin into %1$s you can head over to translate.WordPress.org and suggest translations for any languages which you know.
Thanks! Dion.', 'add-from-server' );
$locale = get_locale();
if ( function_exists( 'get_user_locale' ) ) {
$locale = get_user_locale();
}
// Don't display the message for English (Any) or what we'll assume to be fully translated localised builds.
if ( 'en_' === substr( $locale, 0, 3 ) || ( $message != $message_english && ! $force ) ) {
return false;
}
$translate_url = 'https://translate.wordpress.org/projects/wp-plugins/add-from-server/stable';
echo '' . sprintf( nl2br( $message ), get_locale(), $translate_url ) . '
';
}
function outdated_options_notice() {
$old_root = get_option( 'frmsvr_root', '' );
if (
$old_root
&&
str_contains( $old_root, '%' )
&&
! defined( 'ADD_FROM_SERVER' )
) {
printf(
'',
'You previously used the "Root Directory" option with a placeholder, such as "%username% or "%role%".
' .
'Unfortunately this feature is no longer supported. As a result, Add From Server has been disabled for users who have restricted upload privledges.
' .
'To make this warning go away, empty the "frmsvr_root" option on options.php.'
);
}
if ( $old_root && ! str_starts_with( $old_root, $this->get_root() ) ) {
printf(
'',
'Warning: Root Directory changed. You previously used ' . esc_html( $old_root ) . ' as your "Root Directory", ' .
'this has been changed to ' . esc_html( $this->get_root() ) . '.
' .
'To restore your previous settings, add the following line to your wp-config.php file:
' .
'define( "ADD_FROM_SERVER", "' . $old_root . '" );
' .
'To make this warning go away, empty the "frmsvr_root" option on options.php.'
);
}
}
}