--- a/wp-members/includes/admin/admin.php
+++ b/wp-members/includes/admin/admin.php
@@ -40,6 +40,8 @@
global $wpmem;
+ add_filter( 'wpmem_admin_tabs', array( 'WP_Members_Admin_Tab_About', 'add_tab' ), 99 );
+
if ( $wpmem->captcha ) {
add_filter( 'wpmem_admin_tabs', array( 'WP_Members_Admin_Tab_Captcha', 'add_tab' ) );
add_action( 'wpmem_admin_do_tab', array( 'WP_Members_Admin_Tab_Captcha', 'do_tab' ), 1, 1 );
@@ -47,7 +49,18 @@
if ( $wpmem->dropins ) {
add_filter( 'wpmem_admin_tabs', array( 'WP_Members_Admin_Tab_Dropins', 'add_tab' ) );
add_action( 'wpmem_admin_do_tab', array( 'WP_Members_Admin_Tab_Dropins', 'do_tab' ), 1, 1 );
- } ?>
+ }
+
+ // @todo Adds tab for updating filesystem if /wpmembers/user_files/ exists.
+ $uploads = wp_upload_dir();
+ $deprecated_folder = trailingslashit( $uploads['basedir'] ) . 'wpmembers/user_files';
+ if ( is_dir( $deprecated_folder ) ) {
+ include_once $wpmem->path . 'includes/admin/tabs/class-wp-members-admin-tab-filesystem-upgrade.php';
+ add_filter( 'wpmem_admin_tabs', array( 'WP_Members_Admin_Filesystem_Upgrade', 'add_tab' ), 98 );
+ add_action( 'wpmem_admin_do_tab', array( 'WP_Members_Admin_Filesystem_Upgrade', 'do_tab' ), 98 );
+ }
+
+ ?>
<div class="wrap">
<?php
--- a/wp-members/includes/admin/class-wp-members-admin-api.php
+++ b/wp-members/includes/admin/class-wp-members-admin-api.php
@@ -159,7 +159,7 @@
add_action( 'wpmem_admin_do_tab', array( 'WP_Members_Admin_Tab_Dialogs', 'do_tab' ), 10 );
add_action( 'wpmem_admin_do_tab', array( 'WP_Members_Admin_Tab_Emails', 'do_tab' ), 15 );
add_action( 'wpmem_admin_do_tab', array( 'WP_Members_Admin_Tab_Shortcodes', 'do_tab' ), 16 );
- add_action( 'wpmem_admin_do_tab', array( 'WP_Members_Admin_Tab_About', 'do_tab' ), 17 );
+ add_action( 'wpmem_admin_do_tab', array( 'WP_Members_Admin_Tab_About', 'do_tab' ), 99 );
// If user has a role that cannot edit users, set profile actions for non-admins.
@@ -223,6 +223,9 @@
add_action( 'current_screen', array( $this, 'check_user_folders_for_index' ) );
+ // Check for any upgrade notices.
+ add_filter( 'wpmem_admin_notices', array( $this, 'check_for_upgrade_notices' ) );
+
} // End of load_hooks()
/**
@@ -244,10 +247,18 @@
*/
function do_admin_notices() {
global $wpmem;
+ /**
+ * Filter admin notices.
+ *
+ * @since 3.5.5
+ *
+ * @param array $wpmem->admin_notices Array of admin notices to display.
+ */
+ $wpmem->admin_notices = apply_filters( 'wpmem_admin_notices', $wpmem->admin_notices );
if ( $wpmem->admin_notices ) {
foreach ( $wpmem->admin_notices as $key => $value ) {
- echo '<div class="notice notice-' . $value['type'] . ' is-dismissible">
- <p><strong>' . $value['notice'] . '</strong></p>
+ echo '<div class="notice notice-' . esc_attr( $value['type'] ) . ' is-dismissible">
+ <p><strong>' . wp_kses_post( $value['notice'] ) . '</strong></p>
</div>';
}
@@ -392,7 +403,6 @@
'dialogs' => esc_html__( 'Dialogs', 'wp-members' ),
'emails' => esc_html__( 'Emails', 'wp-members' ),
'shortcodes' => esc_html__( 'Shortcodes', 'wp-members' ),
- 'about' => esc_html__( 'About WP-Members', 'wp-members' )
);
}
@@ -699,7 +709,7 @@
$upload_vars = wp_upload_dir( null, false );
$wpmem_base_dir = trailingslashit( trailingslashit( $upload_vars['basedir'] ) . wpmem_get_upload_base() );
- $wpmem_user_files_dir = $wpmem_base_dir . 'user_files/';
+ $wpmem_user_files_dir = $wpmem_base_dir . trailingslashit( wpmem_get_file_dir_hash() );
if ( file_exists( $wpmem_user_files_dir ) ) {
// If there is a user file dir, check/self-heal htaccess/index files.
@@ -722,6 +732,35 @@
}
}
+ function check_for_upgrade_notices( $notices ) {
+
+ // Check for deprecated folder system.
+ $uploads = wp_upload_dir();
+ $deprecated_folder = trailingslashit( $uploads['basedir'] ) . 'wpmembers/user_files';
+ if ( is_dir( $deprecated_folder ) ) {
+ $notice_dismissed = get_option( 'wpmem_dismiss_filesystem_upgrade_notice' );
+ if ( ! $notice_dismissed ) {
+ $notices['deprecated_foldersystem'] = array(
+ 'notice' => __( 'The /wpmembers/user_files/ folder is deprecated. Please <a href="' . esc_url( trailingslashit( admin_url() ) . 'options-general.php?page=wpmem-settings&tab=filesystem-upgrade' ) . '">go to the settings page</a> to either upgrade or permanently remove this message.', 'wp-members' ),
+ 'type' => 'warning',
+ );
+ }
+ if ( 'update_filesystem' == wpmem_get( 'wpmem_admin_a' ) ) {
+ if ( false == wpmem_get( 'wpmem_dismiss_filesystem_upgrade_notice' ) ) {
+ $notices['deprecated_foldersystem'] = array(
+ 'notice' => __( 'The /wpmembers/user_files/ folder is deprecated. Please <a href="' . esc_url( trailingslashit( admin_url() ) . 'options-general.php?page=wpmem-settings&tab=filesystem-upgrade' ) . '">go to the settings page</a> to either upgrade or permanently remove this message.', 'wp-members' ),
+ 'type' => 'warning',
+ );
+ } elseif ( 1 == wpmem_get( 'wpmem_dismiss_filesystem_upgrade_notice' ) ) {
+ unset( $notices['deprecated_foldersystem'] );
+ }
+ }
+ }
+
+ // Return any notices.
+ return $notices;
+ }
+
} // End of WP_Members_Admin_API class.
// End of file.
No newline at end of file
--- a/wp-members/includes/admin/tabs/class-wp-members-admin-tab-about.php
+++ b/wp-members/includes/admin/tabs/class-wp-members-admin-tab-about.php
@@ -22,6 +22,17 @@
class WP_Members_Admin_Tab_About {
/**
+ * Adds the About tab.
+ *
+ * @param array $tabs The existing admin tabs.
+ * @return array The modified admin tabs.
+ */
+ static function add_tab( $tabs ) {
+ $tabs['about'] = esc_html__( 'About WP-Members', 'wp-members' );
+ return $tabs;
+ }
+
+ /**
* Creates the About tab.
*
* @since 3.1.1
--- a/wp-members/includes/admin/tabs/class-wp-members-admin-tab-filesystem-upgrade.php
+++ b/wp-members/includes/admin/tabs/class-wp-members-admin-tab-filesystem-upgrade.php
@@ -0,0 +1,192 @@
+<?php
+/**
+ * WP-Members Admin functions
+ *
+ * Functions to upgrade the filesystem.
+ *
+ * This file is part of the WP-Members plugin by Chad Butler
+ * You can find out more about this plugin at https://rocketgeek.com
+ * Copyright (c) 2006-2025 Chad Butler
+ * WP-Members(tm) is a trademark of butlerblog.com
+ *
+ * @package WP-Members
+ * @author Chad Butler
+ * @copyright 2006-2025
+ */
+
+// Exit if accessed directly.
+if ( ! defined( 'ABSPATH' ) ) {
+ exit();
+}
+
+class WP_Members_Admin_Filesystem_Upgrade {
+
+ /**
+ * Creates the tab.
+ *
+ * @param string $tab The admin tab being displayed.
+ * @return string|bool The tab html, otherwise false.
+ */
+ static function do_tab( $tab ) {
+ if ( $tab == 'filesystem-upgrade' || ! $tab ) {
+ // Render the tab.
+ return self::build_settings();
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Adds the tab.
+ *
+ * @param array $tabs The array of tabs for the admin panel.
+ * @return array The updated array of tabs for the admin panel.
+ */
+ public static function add_tab( $tabs ) {
+ return array_merge( $tabs, array( 'filesystem-upgrade' => esc_html__( 'Filesystem', 'wp-members' ) ) );
+ }
+
+ /**
+ * Builds the dialogs panel.
+ *
+ * @since 2.2.2
+ * @since 3.3.0 Ported from wpmem_a_build_dialogs().
+ *
+ * @global object $wpmem
+ */
+ static function build_settings() {
+
+ global $wpmem;
+ require_once $wpmem->path . 'includes/class-wp-members-filesystem.php';
+ $wpmem->filesystem = New WP_Members_Update_Filesystem_Class();
+
+ // Check how many files to move.
+ $files_to_move = $wpmem->filesystem->get_file_list();
+ $num_to_move = count( $files_to_move );
+ if ( isset( $_POST['wpmem_dismiss_filesystem_upgrade_notice'] ) && 1 == $_POST['wpmem_dismiss_filesystem_upgrade_notice'] ) {
+ update_option( 'wpmem_dismiss_filesystem_upgrade_notice', intval( wpmem_get( 'wpmem_dismiss_filesystem_upgrade_notice', 0, 'post' ) ), false );
+ } else {
+ if ( isset( $_POST['wpmem_admin_a'] ) && 'update_filesystem' == $_POST['wpmem_admin_a'] && ! isset( $_POST['wpmem_dismiss_filesystem_upgrade_notice'] ) ) {
+ delete_option( 'wpmem_dismiss_filesystem_upgrade_notice' );
+ }
+ }
+ if ( isset( $_POST['update-filesystem-confirm'] ) && 'move' == $_POST['update-filesystem-confirm'] ) {
+ $wpmem->filesystem->update_filesystem();
+ $wpmem->filesystem->set_move_complete( true );
+ update_option( 'wpmem_upgrade_filesystem_move_complete', 1, false );
+ }
+ ?>
+ <div class="wrap">
+ <form name="update-filesystem-form" id="update-filesystem-form" method="post" action="<?php echo esc_url( wpmem_admin_form_post_url() ); ?>">
+ <?php wp_nonce_field( 'wpmem-upgrade-filesystem' ); ?>
+ <h2><?php esc_html_e( 'Upgrade the WP-Members filesystem', 'wp-members' ); ?></h2>
+ <?php
+
+ if ( ! empty( $wpmem->filesystem->get_errors() ) ) { ?>
+ <p>
+ File moves were attempted. The table below displays user ID folders that were not moved.
+ This may be all existing files or only some. You should verify by checking
+ the filesystem directly.
+ </p>
+ <p>
+ Once you have confirmed your moves and you are satisfied that things are correct,
+ you will need to delete the original directory and files.<br />
+ <a href="<?php echo trailingslashit( admin_url() ) . '/options-general.php?page=wpmem-settings&tab=filesystem-upgrade'; ?>">Continue to the initial screen
+ and select the delete option</a>.
+ </p>
+ <table class="widefat fixed" cellspacing="0">
+ <tr>
+ <th id="user_id" style="width:68px">User ID</th>
+ <th id="error">Error</th>
+ </tr>
+ <?php foreach ( $wpmem->filesystem->get_errors() as $user_id => $error ) {
+ echo '<tr><td>' . $user_id . '</td><td>' . $error . '</td></tr>';
+ } ?>
+ </table>
+ <?php } elseif ( $wpmem->filesystem->is_move_complete() ) { ?>
+ <p>Filesystem was updated.</p>
+ <?php
+ // Get an updated count.
+ $files_to_move = $wpmem->filesystem->get_file_list();
+ $num_to_move = count( $files_to_move );
+ if ( $num_to_move > 0 ) {
+ echo '<p>There are ' . $num_to_move . ' files that were not moved. You may run step 1 again to
+ attempt to move these, or you may need to move them manually.';
+ } ?>
+ <p>If you wish to proceed to step 2 to delete the original directories and files you may do so below.</p>
+ <h3>Step 2: Delete old filesystem</h3>
+ <input type="radio" id="delete" name="update-filesystem-confirm" value="delete" /> Delete the old filesystem.<br>
+ <?php submit_button(); ?>
+ <?php } elseif ( isset( $_POST['update-filesystem-confirm'] ) && 'delete' == $_POST['update-filesystem-confirm'] ) {
+ // Clean up (delete) old dir.
+ $rmdir = $wpmem->filesystem->delete_directory( trailingslashit( $wpmem->filesystem->basedir ) . 'wpmembers/user_files' );
+ if ( $rmdir ) {
+ echo '<p>Deletion was successful.</p>';
+ delete_option( 'wpmem_upgrade_filesystem_move_complete' );
+ delete_option( 'wpmem_dismiss_filesystem_upgrade_notice' );
+ } else {
+ echo '<p>Deletion was not successful. You may need to check directory permissions and/or delete the folder manually</p>';
+ } ?>
+ <p><a href="<?php echo esc_url( admin_url() . '/options-general.php?page=wpmem-settings&tab=options' ); ?>">Return to main plugin page</a></p>
+ <?php } else { ?>
+ <div style="width:500px;">
+ <p>
+ Your current configuration indicates that there are uploaded files within the WP-Members
+ filesystem in a deprecated configuration. You have the option to move these files. However,
+ this cannot be reversed or undone.
+ </p>
+ <p>
+ The upgrade process is two steps:
+ <ol>
+ <li>Move the current files to a new structure.</li>
+ <li>Delete the old files and directory.</li>
+ </ol>
+ </p>
+ <p>
+ This process will move uploaded user files and delete the
+ previous directories. It cannot be undone. <strong>Please
+ make sure you have backed up the database and the filesystem
+ before running these actions</strong>.
+ </p>
+ <p>
+ There is a more thorough explanation of this process
+ <a href="https://rocketgeek.com/release-announcements/wp-members-3-5-4-5-release-notes/">here</a>.
+ Alternative to using this admin panel process, there is a
+ <a href="https://rocketgeek.com/plugins/wp-members/docs/plugin-settings/fields/upgrade-the-wp-members-uploaded-files-using-wp-cli/">WP-CLI process</a>
+ for doing this upgrade.
+ </p>
+ <p>
+ If you chose to either not proceed with the move or do it at
+ a later time, you can permanently disable the admin notice by
+ checking the box below.<br />
+ <?php
+ $dismiss_upgrade_notice = get_option( 'wpmem_dismiss_filesystem_upgrade_notice' );
+ $checked = ( $dismiss_upgrade_notice || isset( $_POST['wpmem_dismiss_filesystem_upgrade_notice'] ) ) ? true : false ?>
+ <input type="checkbox" name="wpmem_dismiss_filesystem_upgrade_notice" value="1" <?php checked( 1, $checked ); ?> /> Permanently dismiss the admin notice.
+ </p>
+ </div>
+ <h3>Step 1: Move the filesystem</h3>
+ <input type="radio" id="move" name="update-filesystem-confirm" value="move" /> Move the current filesystem.<br>
+ <p>
+ There are <?php echo $num_to_move; ?> files to move.<br/>
+ </p>
+ <?php
+ $step_1_done = get_option( 'wpmem_upgrade_filesystem_move_complete' );
+ if ( $step_1_done ) { ?>
+ <h3>Step 2: Delete old filesystem</h3>
+ <input type="radio" id="delete" name="update-filesystem-confirm" value="delete" /> Delete the old filesystem.<br>
+ <p>
+ Make sure you have run step 1 above first.<br/>
+ Make sure step 1 indicates there are no files to move or
+ you are satisfied that all files have been move to new directories.
+ </p>
+ <?php } ?>
+ <input type="hidden" name="wpmem_admin_a" value="update_filesystem" />
+ <?php submit_button();
+ } ?>
+ </form>
+ </div><!-- #post-box -->
+ <?php
+ }
+}
+// End of file.
No newline at end of file
--- a/wp-members/includes/api/api-users.php
+++ b/wp-members/includes/api/api-users.php
@@ -1280,6 +1280,6 @@
*/
function wpmem_get_user_count_by_role( $role ) {
$users = count_users();
- return ( 'all' == $role ) ? $users['avail_roles'][ $role ] : $users['total_users'];
+ return ( 'all' == $role ) ? $users['total_users'] : $users['avail_roles'][ $role ];
}
// End of file.
No newline at end of file
--- a/wp-members/includes/api/api-utilities.php
+++ b/wp-members/includes/api/api-utilities.php
@@ -336,13 +336,7 @@
*/
function wpmem_get_upload_base() {
global $wpmem;
- if ( isset( $wpmem->upload_base ) ) {
- return $wpmem->upload_base;
- } else {
- /** This filter is defined in class-wp-members-forms.php */
- $args = apply_filters( 'wpmem_user_upload_dir', array( 'wpmem_dir' => "wpmembers" ) );
- return $args['wpmem_dir'];
- }
+ return ( isset( $wpmem->upload_base ) ) ? $wpmem->upload_base : "wpmembers";
}
/**
@@ -361,7 +355,7 @@
$upload_vars = wp_upload_dir( null, false );
$base_dir = $upload_vars['basedir'];
$wpmem_base_dir = trailingslashit( trailingslashit( $base_dir ) . wpmem_get_upload_base() );
- $wpmem_user_files_dir = $wpmem_base_dir . 'user_files/';
+ $wpmem_user_files_dir = $wpmem_base_dir . trailingslashit( wpmem_get_file_dir_hash() );
return array(
'upload_vars' => $upload_vars,
'base_dir' => $base_dir,
@@ -371,6 +365,95 @@
}
/**
+ * Creates a randomized file name.
+ *
+ * @since 3.5.5
+ *
+ * @param string $filename
+ * @return string $key.$ext
+ */
+function wpmem_hash_file_name( $filename ) {
+ // How long a random string do we want?
+ $hash_len = 36;
+
+ // Get the file extension.
+ $ext = pathinfo( $filename, PATHINFO_EXTENSION );
+
+ if ( preg_match( '/^[a-f0-9]{'. $hash_len . '}-.*/', $filename ) ) {
+ $filename = substr( $filename, $hash_len + 1 );
+ }
+
+ $key = sha1( random_bytes(32) );
+ $key = substr( $key, 0, $hash_len );
+
+ $filename = "$key.$ext";
+ /**
+ * Filter the hashed file name.
+ *
+ * @since 3.5.5
+ *
+ * @param string $filename
+ * @param int $hash_len
+ * @param string $key
+ * @param string $ext
+ */
+ return apply_filters( 'wpmem_hashed_file_name', $filename, $hash_len, $key, $ext );
+}
+
+/**
+ * Gets or creates a user directory hash.
+ *
+ * @since 3.5.5
+ *
+ * @param int $user_id
+ * @return string $user_dir_hash
+ */
+function wpmem_get_user_dir_hash( $user_id ) {
+ $hash_len = 36;
+ $user_dir_hash = get_user_meta( $user_id, 'wpmem_user_dir_hash', true );
+ if ( ! $user_dir_hash ) {
+ $uid_len = strlen( $user_id );
+ $user_dir_hash = $user_id . wp_generate_password( ( $hash_len-$uid_len ), false, false );
+ update_user_meta( $user_id, 'wpmem_user_dir_hash', $user_dir_hash );
+ }
+ /**
+ * Filter the user directory hash.
+ *
+ * @since 3.5.5
+ *
+ * @param string $user_dir_hash
+ * @param int $hash_len
+ * @param int $user_id
+ */
+ return apply_filters( 'wpmem_user_dir_hash', $user_dir_hash, $hash_len, $user_id );
+}
+
+/**
+ * Gets or creates the file directory hash.
+ *
+ * @since 3.5.5
+ *
+ * @return string $dir_hash
+ */
+function wpmem_get_file_dir_hash() {
+ $hash_len = 36;
+ $dir_hash = get_option( 'wpmem_file_dir_hash' );
+ if ( ! $dir_hash ) {
+ $dir_hash = wp_generate_password( $hash_len, false, false );
+ update_option( 'wpmem_file_dir_hash', $dir_hash );
+ }
+ /**
+ * Filter the file directory hash.
+ *
+ * @since 3.5.5
+ *
+ * @param string $dir_hash
+ * @param int $hash_len
+ */
+ return apply_filters( 'wpmem_file_dir_hash', 'user_files_' . $dir_hash, $hash_len );
+}
+
+/**
* Reads a csv file to a keyed array.
*
* @since 3.5.4
--- a/wp-members/includes/class-wp-members-api.php
+++ b/wp-members/includes/class-wp-members-api.php
@@ -14,6 +14,8 @@
class WP_Members_API {
+ public $file_user_id; // A container for the uploaded file user ID.
+
/**
* Plugin initialization function.
*
@@ -233,5 +235,117 @@
public function is_field_in_wp_users( $meta ) {
return ( in_array( $meta, $this->get_wp_users_fields() ) ) ? true : false;
}
+
+ /**
+ * Uploads file from the user.
+ *
+ * @since 3.1.0
+ * @since 3.5.5 Moved to API class.
+ *
+ * @param array $file
+ * @param int $user_id
+ * @return int|bool
+ */
+ function do_file_upload( $file = array(), $user_id = false ) {
+
+ // Filter the upload directory.
+ add_filter( 'upload_dir', array( &$this, 'file_upload_dir' ) );
+ add_filter( 'sanitize_file_name', 'wpmem_hash_file_name' );
+
+ // Set up user ID for use in upload process.
+ $this->file_user_id = ( $user_id ) ? $user_id : 0;
+
+ // Get WordPress file upload processing scripts.
+ require_once( ABSPATH . 'wp-admin/includes/file.php' );
+ require_once( ABSPATH . 'wp-admin/includes/media.php' );
+
+ $file_return = wp_handle_upload( $file, array( 'test_form' => false ) );
+
+ remove_filter( 'sanitize_file_name', 'wpmem_hash_file_name' );
+
+ if ( isset( $file_return['error'] ) || isset( $file_return['upload_error_handler'] ) ) {
+ return false;
+ } else {
+
+ $attachment = array(
+ 'post_mime_type' => $file_return['type'],
+ 'post_title' => preg_replace( '/.[^.]+$/', '', basename( $file['name'] ) ),
+ 'post_content' => '',
+ 'post_status' => 'inherit',
+ 'guid' => $file_return['url'],
+ 'post_author' => ( $user_id ) ? $user_id : '',
+ );
+
+ $attachment_id = wp_insert_attachment( $attachment, $file_return['file'] );
+
+ require_once( ABSPATH . 'wp-admin/includes/image.php' );
+ $attachment_data = wp_generate_attachment_metadata( $attachment_id, $file_return['file'] );
+ wp_update_attachment_metadata( $attachment_id, $attachment_data );
+
+ if ( 0 < intval( $attachment_id ) ) {
+ // Returns an array with file information.
+ return $attachment_id;
+ }
+ }
+ return false;
+ } // End upload_file()
+
+ /**
+ * Sets the file upload directory.
+ *
+ * This is a filter function for upload_dir.
+ *
+ * @link https://codex.wordpress.org/Plugin_API/Filter_Reference/upload_dir
+ *
+ * @since 3.1.0
+ * @since 3.5.5 Moved to API class.
+ * @since 3.5.5 Updated to add a randomized hash to the user directories.
+ *
+ * @param array $param {
+ * The directory information for upload.
+ *
+ * @type string $path
+ * @type string $url
+ * @type string $subdir
+ * @type string $basedir
+ * @type string $baseurl
+ * @type string $error
+ * }
+ * @return array $param
+ */
+ function file_upload_dir( $param ) {
+
+ $user_id = ( isset( $this->file_user_id ) ) ? $this->file_user_id : null;
+
+ $file_dir = wpmem_get_file_dir_hash();
+ $user_dir = wpmem_get_user_dir_hash( $user_id );
+
+ $args = array(
+ 'user_id' => $user_id,
+ 'wpmem_dir' => wpmem_get_upload_base(),
+ 'user_dir' => trailingslashit( $file_dir ) . $user_dir,
+ 'basedir' => $param['basedir'],
+ 'baseurl' => $param['baseurl'],
+ 'file_hash' => $file_dir,
+ 'user_hash' => $user_dir,
+ );
+
+ /**
+ * Filter the user directory elements.
+ *
+ * @since 3.1.0
+ * @since 3.5.5 Added base vals and hashes to args.
+ *
+ * @param array $args
+ */
+ $args = apply_filters( 'wpmem_user_upload_dir', $args );
+
+ $param['subdir'] = '/' . $args['wpmem_dir'] . '/' . $args['user_dir'];
+ $param['path'] = $args['basedir'] . '/' . $args['wpmem_dir'] . '/' . $args['user_dir'];
+ $param['url'] = $args['baseurl'] . '/' . $args['wpmem_dir'] . '/' . $args['user_dir'];
+
+ return $param;
+ }
+
} // End of WP_Members_Utilties class.
No newline at end of file
--- a/wp-members/includes/class-wp-members-filesystem.php
+++ b/wp-members/includes/class-wp-members-filesystem.php
@@ -0,0 +1,256 @@
+<?php
+// Exit if accessed directly.
+if ( ! defined( 'ABSPATH' ) ) {
+ exit();
+}
+
+class WP_Members_Update_Filesystem_Class {
+
+ public $uploads = array();
+ public $basedir;
+ public $baseurl;
+ protected $errors = array();
+ protected $move_complete = false;
+
+ public function __construct() {
+ $this->uploads = wp_upload_dir();
+ $this->basedir = $this->uploads['basedir'];
+ $this->baseurl = $this->uploads['baseurl'];
+ }
+
+ public function is_move_complete() {
+ return $this->move_complete;
+ }
+
+ public function set_move_complete( $val ) {
+ $this->move_complete = $val;
+ }
+
+ public function get_errors() {
+ return $this->errors;
+ }
+
+ public function get_file_list() {
+ global $wpdb;
+ $search_str = '%wpmembers/user_files/%';
+ /**
+ * Filter the file list string.
+ *
+ * @since 3.5.5
+ */
+ $search_str = apply_filters( 'wpmem_get_file_list_search_str', $search_str );
+ //return $wpdb->get_results( 'SELECT ID, post_author, post_title, guid FROM ' . $wpdb->posts . ' WHERE post_type = "attachment" AND guid LIKE "' . $search_str . '";' );
+ return $wpdb->get_results( 'SELECT
+ u1.ID,
+ u1.post_author,
+ u1.post_title,
+ u1.guid,
+ m1.meta_value AS wp_attached_file
+ FROM ' . $wpdb->posts . ' u1
+ JOIN ' . $wpdb->postmeta . ' m1 ON (m1.post_id = u1.id AND m1.meta_key = "_wp_attached_file")
+ WHERE m1.meta_value like "%wpmembers/user_files/%";'
+ );
+ }
+
+ public function update_filesystem() {
+
+ // Check user capability to prevent being used by a non-permissioned user.
+ /**
+ * Filter the required capability.
+ *
+ * @since 3.5.5
+ */
+ $has_req_caps = apply_filters( 'wpmem_update_filesystem_caps', 'delete_site' );
+
+ if ( ! $has_req_caps ) {
+ return false;
+ }
+
+ $results = $this->get_file_list();
+
+ // If there are results, they need to move.
+ if ( $results ) {
+
+ $new_dir_location = trailingslashit( '/wpmembers/' . wpmem_get_file_dir_hash() );
+ // If new_dir_location does not exist, create it.
+ if ( ! is_dir( $this->basedir . $new_dir_location ) ) {
+ mkdir( $this->basedir . $new_dir_location, 0755, true );
+ // Add indexes and htaccess
+ wpmem_create_file( array(
+ 'path' => $this->basedir . $new_dir_location,
+ 'name' => 'index.php',
+ 'contents' => "<?php // Silence is golden."
+ ) );
+ wpmem_create_file( array(
+ 'path' => $this->basedir . $new_dir_location,
+ 'name' => '.htaccess',
+ 'contents' => "Options -Indexes"
+ ) );
+ }
+
+ // Set up progress bar for WP-CLI.
+ if ( defined( 'WP_CLI' ) && WP_CLI ) {
+ $count = count( $results );
+ $progress = WP_CLIUtilsmake_progress_bar( 'Moving uploaded user files.', $count );
+ }
+
+ foreach ( $results as $result ) {
+
+ $user_id = $result->post_author;
+ $guid = $result->guid;
+
+ // User ID dir
+ $user_dir_hash = wpmem_get_user_dir_hash( $user_id );
+
+ // File name
+ $filename = wpmem_hash_file_name( basename( get_attached_file( $result->ID ) ) );
+
+ // Move location.
+ $new_file_path = $this->basedir . $new_dir_location . trailingslashit( $user_dir_hash ) . $filename;
+
+ $do_move = $this->move_attachment_file( $result->ID, $new_file_path );
+
+ if ( is_wp_error( $do_move ) ) {
+ $this->errors[ $user_id ] = $do_move->get_error_message();
+ }
+
+ if ( defined( 'WP_CLI' ) && WP_CLI ) {
+ $progress->tick();
+ }
+ }
+
+ if ( defined( 'WP_CLI' ) && WP_CLI ) {
+ $progress->finish();
+ }
+ }
+ }
+
+ // this one from grok.
+ /**
+ * Move/rename a media attachment file and update WordPress metadata.
+ *
+ * @param int $attachment_id The ID of the attachment post.
+ * @param string $new_file_path Absolute path to the new location (including filename).
+ *
+ * @return bool|WP_Error True on success, WP_Error on failure.
+ */
+ public function move_attachment_file( $attachment_id, $new_file_path ) {
+
+ // Check user capability to prevent being used by a non-permissioned user.
+ /**
+ * Filter the required capability.
+ *
+ * @since 3.5.5
+ */
+ $has_req_caps = apply_filters( 'wpmem_update_filesystem_caps', 'delete_site' );
+
+ if ( ! $has_req_caps ) {
+ return false;
+ }
+
+ // Verify it's a valid attachment
+ if ( get_post_type( $attachment_id ) !== 'attachment' ) {
+ return new WP_Error( 'invalid_attachment', 'Invalid attachment ID.' );
+ }
+
+ // Get current absolute file path
+ $old_file_path = get_attached_file( $attachment_id );
+ if ( ! file_exists( $old_file_path ) ) {
+ $error = new WP_Error( 'file_missing', 'Original file not found.' );
+ if ( is_wp_error( $error ) ) {
+ // Try if it's http
+ $old_file_path = str_replace( trailingslashit( $this->baseurl ), '', $old_file_path );
+ if ( ! file_exists( $old_file_path ) ) {
+ // Still an error.
+ return $error;
+ }
+ }
+ }
+
+ // Ensure destination directory exists
+ $new_dir = dirname( $new_file_path );
+ if ( ! file_exists( $new_dir ) ) {
+ if ( ! wp_mkdir_p( $new_dir ) ) {
+ return new WP_Error( 'dir_create_failed', 'Could not create destination directory.' );
+ }
+ }
+
+ // Move the original file
+ if ( ! rename( $old_file_path, $new_file_path ) ) {
+ return new WP_Error( 'move_failed', 'Failed to move the main file.' );
+ }
+
+ // Update the main attached file path (relative to uploads dir)
+ $new_relative_path = str_replace( trailingslashit( $this->basedir ), '', $new_file_path );
+ update_attached_file( $attachment_id, $new_relative_path );
+
+ // For images: regenerate metadata (updates paths for all thumbnail sizes)
+ // This also moves the thumbnail files if needed (rename handles it via metadata update)
+ if ( wp_attachment_is_image( $attachment_id ) ) {
+ $new_metadata = wp_generate_attachment_metadata( $attachment_id, $new_file_path );
+ wp_update_attachment_metadata( $attachment_id, $new_metadata );
+ }
+
+ // Optional: Update the GUID (rarely needed, but can help in some cases)
+ wp_update_post( array( 'ID' => $attachment_id, 'guid' => trailingslashit( $this->basedir ) . $new_relative_path ) );
+
+ wpmem_create_file( array(
+ 'path' => $new_dir,
+ 'name' => 'index.php',
+ 'contents' => "<?php // Silence is golden."
+ ) );
+
+ return true;
+ }
+
+ /**
+ * Deletes directories using rmdir even if they are not empty.
+ *
+ * @see https://wpbitz.com/code-snippets/delete-directory-using-rmdir-in-php-even-if-the-directory-is-not-empty/
+ *
+ * @param string $dir
+ * @return boolean
+ */
+ public function delete_directory( $dir ) {
+
+ // Check user capability to prevent being used by a non-permissioned user.
+ /**
+ * Filter the required capability.
+ *
+ * @since 3.5.5
+ */
+ $has_req_caps = apply_filters( 'wpmem_update_filesystem_caps', 'delete_site' );
+
+ if ( ! $has_req_caps ) {
+ return false;
+ }
+
+ // Check if $dir is a directory, return false if it's not.
+ if ( ! is_dir( $dir ) ) {
+ return false;
+ }
+
+ // Get all files and folders in the directory.
+ $items = scandir( $dir );
+
+ // Loop through items in the directory.
+ foreach ( $items as $item ) {
+ // Skip special entries "." (current directory) and ".." (parent directory)
+ if ( $item == '.' || $item == '..' ) {
+ continue;
+ }
+ // Build the full path of the current item.
+ $path = $dir . DIRECTORY_SEPARATOR . $item;
+
+ // If the item is a directory, call this function recursively.
+ if ( is_dir( $path ) ) {
+ $this->delete_directory( $path );
+ } else { // If the item is a file, delete it.
+ unlink( $path );
+ }
+ }
+
+ // Remove the main directory.
+ return rmdir( $dir );
+ }
+}
No newline at end of file
--- a/wp-members/includes/class-wp-members-forms.php
+++ b/wp-members/includes/class-wp-members-forms.php
@@ -15,7 +15,6 @@
class WP_Members_Forms {
public $reg_form_showing = false;
- public $file_user_id; // A container for the uploaded file user ID.
/**
* Plugin initialization function.
@@ -530,104 +529,6 @@
return $label;
}
-
- /**
- * Uploads file from the user.
- *
- * @since 3.1.0
- *
- * @param array $file
- * @param int $user_id
- * @return int|bool
- */
- function do_file_upload( $file = array(), $user_id = false ) {
-
- // Filter the upload directory.
- add_filter( 'upload_dir', array( &$this, 'file_upload_dir' ) );
-
- // Set up user ID for use in upload process.
- $this->file_user_id = ( $user_id ) ? $user_id : 0;
-
- // Get WordPress file upload processing scripts.
- require_once( ABSPATH . 'wp-admin/includes/file.php' );
- require_once( ABSPATH . 'wp-admin/includes/media.php' );
-
- $file_return = wp_handle_upload( $file, array( 'test_form' => false ) );
-
- if ( isset( $file_return['error'] ) || isset( $file_return['upload_error_handler'] ) ) {
- return false;
- } else {
-
- $attachment = array(
- 'post_mime_type' => $file_return['type'],
- 'post_title' => preg_replace( '/.[^.]+$/', '', basename( $file_return['file'] ) ),
- 'post_content' => '',
- 'post_status' => 'inherit',
- 'guid' => $file_return['url'],
- 'post_author' => ( $user_id ) ? $user_id : '',
- );
-
- $attachment_id = wp_insert_attachment( $attachment, $file_return['url'] );
-
- require_once( ABSPATH . 'wp-admin/includes/image.php' );
- $attachment_data = wp_generate_attachment_metadata( $attachment_id, $file_return['file'] );
- wp_update_attachment_metadata( $attachment_id, $attachment_data );
-
- if ( 0 < intval( $attachment_id ) ) {
- // Returns an array with file information.
- return $attachment_id;
- }
- }
-
- return false;
- } // End upload_file()
-
- /**
- * Sets the file upload directory.
- *
- * This is a filter function for upload_dir.
- *
- * @link https://codex.wordpress.org/Plugin_API/Filter_Reference/upload_dir
- *
- * @since 3.1.0
- *
- * @param array $param {
- * The directory information for upload.
- *
- * @type string $path
- * @type string $url
- * @type string $subdir
- * @type string $basedir
- * @type string $baseurl
- * @type string $error
- * }
- * @return array $param
- */
- function file_upload_dir( $param ) {
-
- $user_id = ( isset( $this->file_user_id ) ) ? $this->file_user_id : null;
-
- $args = array(
- 'user_id' => $user_id,
- 'wpmem_dir' => wpmem_get_upload_base(),
- 'user_dir' => 'user_files/' . $user_id,
- );
-
- /**
- * Filter the user directory elements.
- *
- * @since 3.1.0
- *
- * @param array $args
- */
- $args = apply_filters( 'wpmem_user_upload_dir', $args );
-
- $param['subdir'] = '/' . $args['wpmem_dir'] . '/' . $args['user_dir'];
- $param['path'] = $param['basedir'] . '/' . $args['wpmem_dir'] . '/' . $args['user_dir'];
- $param['url'] = $param['baseurl'] . '/' . $args['wpmem_dir'] . '/' . $args['user_dir'];
-
- return $param;
- }
/**
* Login Form Builder.
--- a/wp-members/includes/class-wp-members-user-profile.php
+++ b/wp-members/includes/class-wp-members-user-profile.php
@@ -145,14 +145,15 @@
$empty_file = '<span class="description">' . esc_html__( 'None' ) . '</span>';
if ( 'file' == $field['type'] ) {
$attachment_url = wp_get_attachment_url( $val );
- $input = ( $attachment_url ) ? '<a href="' . esc_url( $attachment_url ) . '">' . esc_url_raw( $attachment_url ) . '</a>' : $empty_file;
+ $attachment_path = get_attached_file( $val );
+ $input = ( $attachment_url ) ? '<a href="' . esc_url( $attachment_url ) . '">' . esc_html( basename( $attachment_path ) ) . '</a>' : $empty_file;
} else {
$attachment_url = wp_get_attachment_image( $val, 'medium' );
if ( 'admin' == $display ) {
$edit_url = admin_url( 'upload.php?item=' . $val );
- $input = ( $attachment_url ) ? '<a href="' . esc_url( $edit_url ) . '">' . esc_url_raw( $attachment_url ) . '</a>' : $empty_file;
+ $input = ( $attachment_url ) ? '<a href="' . esc_url( $edit_url ) . '">' . wp_kses_post( $attachment_url ) . '</a>' : $empty_file;
} else {
- $input = ( $attachment_url ) ? esc_url_raw( $attachment_url ) : $empty_file;
+ $input = ( $attachment_url ) ? wp_kses_post( $attachment_url ) : $empty_file;
}
}
$input.= '<br />' . wpmem_get_text( 'profile_upload' ) . '<br />';
--- a/wp-members/includes/class-wp-members-user.php
+++ b/wp-members/includes/class-wp-members-user.php
@@ -913,7 +913,7 @@
if ( ( 'file' == $field['type'] || 'image' == $field['type'] ) && isset( $_FILES[ $meta_key ] ) && is_array( $_FILES[ $meta_key ] ) ) {
if ( ! empty( $_FILES[ $meta_key ]['name'] ) ) {
// Upload the file and save it as an attachment.
- $file_post_id = $wpmem->forms->do_file_upload( $_FILES[ $meta_key ], $user_id );
+ $file_post_id = $wpmem->api->do_file_upload( $_FILES[ $meta_key ], $user_id );
// Save the attachment ID as user meta.
update_user_meta( $user_id, $meta_key, $file_post_id );
// Add attachment ID to post data array.
--- a/wp-members/includes/class-wp-members.php
+++ b/wp-members/includes/class-wp-members.php
@@ -478,6 +478,15 @@
public $admin;
/**
+ * Handle the filesystem.
+ *
+ * @since 3.5.5
+ * @access public
+ * @var object
+ */
+ public $filesystem;
+
+ /**
* Objects for premium extensions.
*
* @access public
--- a/wp-members/includes/cli/class-wp-members-cli-filesystem.php
+++ b/wp-members/includes/cli/class-wp-members-cli-filesystem.php
@@ -0,0 +1,125 @@
+<?php
+if ( defined( 'WP_CLI' ) && WP_CLI ) {
+
+ class WP_Members_CLI_Filesystem_Upgrade {
+
+ function __construct() {
+ // Need the admin api for some CLI commands.
+ global $wpmem;
+ require_once $wpmem->path . 'includes/admin/api.php';
+ require_once $wpmem->path . 'includes/class-wp-members-filesystem.php';
+ $wpmem->filesystem = new WP_Members_Update_Filesystem_Class;
+ }
+
+ /**
+ * List files that need to be moved.
+ *
+ * ## OPTIONS
+ *
+ * [--page=<page>]
+ * : Current page of results to display. Default is 1.
+ *
+ * [--per_page=<per_page>]
+ * : Number of results to display per page. Default is 20.
+ *
+ * ## EXAMPLES
+ *
+ * wp mem fs-upgrade list --page=1 --per_page=10
+ */
+ public function list( $args, $assoc_args ) {
+ global $wpmem;
+ $files_to_move = $wpmem->filesystem->get_file_list();
+ if ( empty( $files_to_move ) ) {
+ WP_CLI::line( WP_CLI::colorize( '%gThere are no files to move.%n' ) );
+ } else {
+
+ $current_page = WP_CLIUtilsget_flag_value( $assoc_args, 'page', 1 );
+ $per_page = WP_CLIUtilsget_flag_value( $assoc_args, 'per_page', 20 );
+
+ $query_args = array(
+ 'number' => intval( $per_page ),
+ 'offset' => intval( $current_page )
+ );
+
+ $total_items = count( $files_to_move );
+ $total_pages = ceil( $total_items/$per_page );
+
+ $offset = ($current_page-1) * $per_page;
+
+ // paginate with array_slice.
+ $paged_files_to_move = array_slice( $files_to_move, $offset, $per_page );
+
+ // Indicate paginated results.
+ WP_CLI::line( 'Page ' . $current_page . ' of ' . $total_pages . ' (' . $total_items . ' records, ' . $per_page . ' per page)' );
+
+ foreach( $paged_files_to_move as $file ) {
+ $user = get_userdata( $file->post_author );
+ $list[] = array(
+ 'user id' => $file->post_author,
+ 'user_email' => $user->user_email,
+ 'file id' => $file->ID,
+ 'path (in wp-content/uploads)' => wpmem_get_sub_str( '/wpmembers/user_files', get_attached_file( $file->ID ) )
+ );
+ }
+ $formatter = new WP_CLIFormatter( $assoc_args, array( 'user id', 'user_email', 'file id', 'path (in wp-content/uploads)' ) );
+ $formatter->display_items( $list );
+
+ }
+ }
+
+ /**
+ * Move files to new directory structure.
+ *
+ * ## EXAMPLES
+ *
+ * wp mem fs-upgrade move
+ */
+ public function move( $args, $assoc_args ) {
+
+ WP_CLI::line( 'This will move all files in the WP-Members uploads directory to a new directory structure.' );
+ WP_CLI::line( 'Files are not deleted but the attachment data is updated in the database.' );
+ WP_CLI::line( 'Make sure you have backed up your database.' );
+ WP_CLI::confirm( 'Are you sure you want to continue?' );
+
+ global $wpmem;
+ $wpmem->filesystem->update_filesystem();
+ if ( ! empty( $wpmem->filesystem->get_errors() ) ) {
+ WP_CLI::error( 'There were errors' );
+ foreach ( $wpmem->filesystem->get_errors() as $user_id => $error ) {
+ $user = get_userdata( $user_id );
+ $list[] = array(
+ 'user_id' => $user_id,
+ 'user_email' => $user->user_email,
+ 'error' => $error
+ );
+ }
+ WP_CLI::line( 'Attempts to move uploads for the following user IDs returned an error:' );
+ $formatter = new WP_CLIFormatter( $assoc_args, array( 'user_id', 'user_email', 'error' ) );
+ $formatter->display_items( $list );
+ } else {
+ WP_CLI::line( WP_CLI::colorize( '%gNo errors during move.%n' ) );
+ WP_CLI::line( 'Run <wp mem fs-upgrade list> to confirm all files were moved.' );
+ WP_CLI::line( 'Run <wp mem fs-upgrade delete> to remove the old files and directories.' );
+ }
+ }
+
+ /**
+ * Delete old uploads directory.
+ *
+ * ## EXAMPLES
+ *
+ * wp mem fs-upgrade delete
+ */
+ public function delete( $args, $assoc_args ) {
+ global $wpmem;
+ WP_CLI::confirm( 'This will delete all files in uploads/wpmembers/user_files/ and cannot be undone. Are you sure?' );
+ $rmdir = $wpmem->filesystem->delete_directory( trailingslashit( $wpmem->filesystem->basedir ) . 'wpmembers/user_files/' );
+ if ( $rmdir ) {
+ WP_CLI::success( 'Deletion of old directory successful.' );
+ } else {
+ WP_CLI::error( 'Deletion processing returned an error. You may need to check directory permissions and/or delete the folder manually' );
+ }
+ }
+ }
+}
+WP_CLI::add_command( 'mem fs-upgrade', 'WP_Members_CLI_Filesystem_Upgrade' );
No newline at end of file
--- a/wp-members/includes/cli/class-wp-members-cli.php
+++ b/wp-members/includes/cli/class-wp-members-cli.php
@@ -141,6 +141,7 @@
WP_CLI::add_command( 'mem', 'WP_Members_CLI' );
// Load all subcommands
+ require_once 'class-wp-members-cli-filesystem.php';
require_once 'class-wp-members-cli-import.php';
require_once 'class-wp-members-cli-memberships.php';
require_once 'class-wp-members-cli-settings.php';
--- a/wp-members/includes/install.php
+++ b/wp-members/includes/install.php
@@ -611,7 +611,7 @@
$upload_vars = wpmem_upload_dir();
- $users_to_check = get_users( array( 'fields'=>'ID' ));
+ //$users_to_check = get_users( array( 'fields'=>'ID' ));
if ( file_exists( $upload_vars['wpmem_user_files_dir'] ) ) {
@@ -629,6 +629,9 @@
) );
}
+ // User files are being moved in bulk and this will be handled in the move.
+ // Left the above to create the new dir and setup.
+ /*
if ( file_exists( $upload_vars['wpmem_user_files_dir'] ) ) {
// Loop through users to update user dirs.
@@ -643,6 +646,7 @@
}
}
}
+ */
}
function wpmem_update_autoload_options() {
@@ -714,7 +718,7 @@
global $wpmem;
$show_release_notes = true;
- $release_notes_link = "https://rocketgeek.com/release-announcements/wp-members-3-5-4-2/";
+ $release_notes_link = "https://rocketgeek.com/release-announcements/wp-members-3-5-4-5/";
if ( 'new_install' == $wpmem->install_state ) {
$notice_heading = __( 'Thank you for installing WP-Members, the original WordPress membership plugin.', 'wp-members' );
--- a/wp-members/includes/vendor/rocketgeek-utilities/includes/strings.php
+++ b/wp-members/includes/vendor/rocketgeek-utilities/includes/strings.php
@@ -98,10 +98,10 @@
} else {
if ( 'before' == $position ) {
$new = ( substr( $haystack, 0, $pos ) );
- $new = ( $keep_needle ) ? $string . $needle : $new;
+ $new = ( $keep_needle ) ? $needle . $new : $new;
} elseif ( 'after' == $position ) {
$new = ( substr( $haystack, $pos+strlen( $needle ) ) );
- $new = ( $keep_needle ) ? $needle . $string : $new;
+ $new = ( $keep_needle ) ? $needle . $new : $new;
} elseif ( 'split' == $position ) {
$before = ( substr( $haystack, 0, $pos ) );
$after = ( substr( $haystack, $pos+strlen( $needle ) ) );
--- a/wp-members/wp-members.php
+++ b/wp-members/wp-members.php
@@ -3,7 +3,7 @@
Plugin Name: WP-Members
Plugin URI: https://rocketgeek.com
Description: WP access restriction and user registration. For more information on plugin features, refer to <a href="https://rocketgeek.com/plugins/wp-members/docs/">the online Users Guide</a>. A <a href="https://rocketgeek.com/plugins/wp-members/quick-start-guide/">Quick Start Guide</a> is also available. WP-Members(tm) is a trademark of butlerblog.com.
-Version: 3.5.4.4
+Version: 3.5.4.5
Author: Chad Butler
Author URI: https://butlerblog.com/
Text Domain: wp-members
@@ -58,7 +58,7 @@
}
// Initialize constants.
-define( 'WPMEM_VERSION', '3.5.4.4' );
+define( 'WPMEM_VERSION', '3.5.4.5' );
define( 'WPMEM_DB_VERSION', '2.4.2' );
define( 'WPMEM_PATH', plugin_dir_path( __FILE__ ) ); // @todo Fairly certain this is obsolete.