MrApplejuice / wp-autologin-links

Development branch of the WordPress Autologin-Links plugin
https://www.wordpress.org/plugins/autologin-links/
21 stars 5 forks source link

Shortcode for generating new link #34

Open tcmo6 opened 1 year ago

tcmo6 commented 1 year ago

Hello!

I have a feature request for this wonderful plugin:

I need to be able to insert the fields from the profile on another page. I use a b2b plugin that allows me to have some subaccounts. But the issue is that i can not give access to backend to another uses, this need to happen from front end.

The easiest way could be to just move those fields using a shortcode on a specific page.

Is this something doable? we can talk about payment for this if you are interested.

Thank you!

MrApplejuice commented 1 year ago

Hello tcmo6.

I cam not quite sure that I understand what you need... "the fields" -> do you mean the "generate autologin link" fields? Or do you want to create some sort of deep-link like:

www.example.com/deep/link/to/page?autologin_code=ABC

Thus inserting the autologin link in the URL, so that users can immediately login to that deep page? That last thing is already supported.

Maybe some mockups or screenshots would help me understand what you want?

Kind regards, Paul K.

tcmo6 commented 1 year ago

Hello Paul!

Thank you for your reply! What i mean is i want to display the functionality of generating a new link for a user in a different location(front end). For example we have a plugin B2B King(but could be whatever else plugin that allows front end user setting/pages) and on the custom front end page of an user, we need to insert the option to generate/delete the autolink plugin.

For easier understanding please see the below images.

(1) here we have the default view in profile settings. We need this to be able to move it in each of the subbacounts at (2), more exactly anywere in (3).

Currently there is no option to display this anywhere by in location (1) - user profile. I believe a shortcode or something could allow this plugin to display the content on frontend location like (3) - woocommerce account pages. The user seeing (3) has administrative right over the subaccounts (1) 1 (2) 2 (3)3

Thank you so much for your time and dedication with this plugin!

Regards,

V.

MrApplejuice commented 1 year ago

Dear @tcmo6

Thank you so much for your time and dedication with this plugin!

I think I do not have earned that right now ... this plugin is quite a dormant project right now. You can probably see this from my response times...

Anyhow, I see what you are meaning, I think. You are building the fronted you are showing here yourself, is that correct? In that case, you would probably want some API-endpoints to expose the "generate new code" function, is that correct?

Or would you want a full widget (the shortcode you mention) so that you can embed that on a Wordpress-page, for example?

The thing that I do not quite grasp right now is how much of the screenshots (2) and (3) are custom-built. I assume that those are somehow Wordpress-pages that a user would visit, and the user would be shielded from the actual wordpress-admin interface?

tcmo6 commented 1 year ago

Hello @MrApplejuice,

Thank you for your reply! Indeed, i am building a front end for the user who will have subaccounts. Going trough the code of this plugin, i observ that the only place where the generate/regenerate autologin link is on user account. This does not allow me to display those fields on a list of subaccounts of an user. The most I achieved was just displaying the link with no option to regenerate the link or delete it.

The complete scenario is this:

I am the administrator of an website where i allow hotels to provide breakfast for their customers:

The account that needs to have access to the front end of the subaccoutns is a hotel and the subaccounts are the rooms. Giving access to the rooms need to happen by regenerating the login, as the occupant of the room to not be able to login when he is no longer a client. The client of the room by having this access is able to order breakfast specifically as he wants for his room as long as he stays in the hotel.

Every subaccount will only have access to a woocommerce product page where it will select his options.

The issue is that no matter what i tried i could not display on front end, in the list of subaccounts the autologin fields to generate/regenerate/delete options.

I am trying to keep the hotels far away from WordPress backend.

I believe a widget would work too, but i can not imagine how this could be working on a page where there are the subaccounts listed.

Screenshot 2 and 3 are full custom built(b2b king plugin), and yes, those are front end pages to shield user that have access to them from WordPress-admin interface.

In screenshot 2 where there are 2 subaccounts we could add for each the autologin options. The issue to add them here is that we need a page where we could save the data, as i do not think the save autologin options are "ajaxified".

Thank you for your time and i hope you have a great day!

Cheers, Tcmo6

MrApplejuice commented 1 year ago

Hey back!

Okay, I think I get most of what you want. You sort-of want unprivileged access to certain "generate new login link" functions for certain other accounts.

I do not quite understand what a "subaccount" is. Is that a wordpress-user that has a special role assigned? Or that you somehow link yourself to other accounts? Anyway, a "subaccount" seems to have a proper associated wordpress-users, otherwise you would not be able to generate a autologin-link, so this does not seem to be a problem


Anyway, the "unprivileged" access to the generate-new-login-link feature is certainly not a feature for the plugin as-is. The plugin was designed to make a somwhat unsafe feature as safe as possible, and because it allows to bypass the login mechanism of wordpress, the feature only really is exposed to administrators.

I am not sure if you bought the paid addon at some point (see https://www.pkgsoftware.eu/doc/autologin-addon/v1.0/ for details), but there is a function in there that allows you to easily create a login-link for a particular user from PHP. Do not buy the addon only for this function, however, that particular function is super trivial, and I do not think there is much else in there for your. Here is the function that you can use to generate a login-link for a given user, identified by the user's id:

Source code for `pkg_autologin_addon_create_new_code_for_user` function ~~~~~.php /** * This function creates a new autologin-link for a given user. If the * user already has an autologin-link, the old autologin-link gets deleted * and a replaced with the new. * * @return boolean * Returns true when the update was successful. */ function pkg_autologin_addon_create_new_code_for_user($user_id) { $user = new WP_User((int) $user_id); if (!$user->exists()) { return false; } $new_code = pkg_autologin_generate_code(); if (add_user_meta($user->ID, PKG_AUTOLOGIN_USER_META_KEY, $new_code, True) === false) { if (update_user_meta($user->ID, PKG_AUTOLOGIN_USER_META_KEY, $new_code) === false) { return false; } } return true; } ~~~~~

If you are familiar with PHP and wordpress you should be able to use this function to create something that works to your liking.

We can certainly also talk about me doing the required work for you against some payment, but availability from my side would be the biggest constraint for me here, so I am not quite certain if I can provide a timely service for you...

tcmo6 commented 1 year ago

Hello @MrApplejuice,

Thank you for your time regarding this matter! I need that i could generate new login from other place than the user edit screen. Given the fact that there will be some users that will generate this from the frontend, i need to keep them out of wordpress admin area.

A subaccount in this situation is the following: 1st I have some users that are store customers on my websites(some hotels). These Hotels are providing the breakfast trough my website to their guests(subaccounts). Each subaccount represents a hotel's room. Hotel room 01 is the subaccount of Hotel 1. This means that all invoicing(we are talking about a woocommerce site) from Room 01 will go trough Hotel 1 account and details. The Hotel 1 will have like 20-30 subaccounts (users in wordpress user menus). Each day the Hotel can change the access to the room 01(the subaccount), making a different access to that subaccount. The Hotel needs this to restrict access to the room renter if they leave the hotel. This way, having always a room protected by a new autologin link, the hotel does not have problems with fraudulent accessing the order page and putting an order by a guest not staying with the hotel.

The hotel only has access to a front end page, where it can see al orders from all rooms, accepting them or not. Also here, the hotel, could have an option to change anytime a auto login link for any subaccount(room) in the subaccounts list. Currently this is not possible, as i said earlier, the autologin link generating/deleting only works in user menu on the backend.

I was not aware of the extension for bulk generating, and after going into documentation, i believe could be very helpful. In my testing a few weeks ago, i was able to display the autologin link on front end. With this plugin i could generate all links at once. and every day the Hotel should have the new links ready on front end, without the option to regenerate them there. Is there any chance to ad a cron job to this plugin to make them auto regenerate at a specific time? Is there a hook name that could be used for this?

Thank you so much for your time and help!

MrApplejuice commented 1 year ago

Dear tcmo6,

Yeah, ok I have a quite good picture now of what you attempt to do. I think your general the design is sound.

If you are interested in buying the addon plugin you can contact me via email on paulkgerke@pkgsoftware.eu. However, I do not think that you are looking for the functions of the addon, because the addon is more focused in providing advanced UI tools, and you are really looking into integrating the autologin functions with you custom site. I do not think that the goals quite align there...

I already sent you the the source code for the most handy function to get the integration done in https://github.com/MrApplejuice/wp-autologin-links/issues/34#issuecomment-1575243990. What is still missing is a authorization check:

That authorization goes far beyond what I can provide out-of-the-box...

Is there any chance to ad a cron job to this plugin to make them auto regenerate at a specific time?

Well, it is certainly possible but not readily implemented. The closest thing I imagined I would roll out at some point that resembles this feature were "timed login links" - login links that are only useable in a certain date range. However, this is a bit of a hassle to get right, so I shelved this feature as well.

For your specific issue: If you can figure out for which user-ids you want to refresh the links, you can call the pkg_autologin_addon_create_new_code_for_user function I sent you from with wp_schedule_event, for example, to set new login links.

tcmo6 commented 1 year ago

Hi @MrApplejuice,

I battled with trying to editing the plugin but failed to achieve what i was needed. I ended up in writing a new one with Chat GPT's help. :(

Here are some images how it looks based on my needs:

User menu: user-menu

Front end: front-end

Also the code, just for lols, as this is for a specific case only:

`function regenerate_unique_login_key($user_id) { $login_key = md5(uniqid(wp_rand(), true)); $generated_times = get_user_meta($user_id, 'unique_login_key_generated_times', true); $generated_keys = get_user_meta($user_id, 'unique_login_generated_keys', true); $generated_by = get_user_meta($user_id, 'unique_login_generated_by', true);

array_unshift($generated_times, time());
array_unshift($generated_keys, $login_key);
array_unshift($generated_by, get_current_user_id());

// Keep only the last 5 generated times, keys, and generated by
$generated_times = array_slice($generated_times, 0, 5);
$generated_keys = array_slice($generated_keys, 0, 5);
$generated_by = array_slice($generated_by, 0, 5);

update_user_meta($user_id, 'unique_login_key', $login_key);
update_user_meta($user_id, 'unique_login_key_generated_times', $generated_times);
update_user_meta($user_id, 'unique_login_generated_keys', $generated_keys);
update_user_meta($user_id, 'unique_login_generated_by', $generated_by);

}

// Generate unique login link function generate_unique_login_link($user_id, $login_key) { $login_link = add_query_arg(array('key' => rawurlencode($login_key)), home_url()); return $login_link; }

// Auto-login user on unique login link function auto_login_on_unique_login_link() { if (isset($_GET['key'])) { $login_key = $_GET['key']; $users = get_users(array( 'meta_key' => 'unique_login_key', 'meta_value' => $login_key, 'number' => 1, ));

    if (!empty($users)) {
        $user = $users[0];
        $user_id = $user->ID;

        $user_link = get_user_meta( $user_id, 'user_link', true ); // Retrieve the user_link meta value for the user

        // Log in the user
        wp_set_auth_cookie($user_id);
        wp_set_current_user($user_id);

         // Redirect the user after login
        wp_redirect($user_link);
        exit;
    }
}

} add_action('init', 'auto_login_on_unique_login_link');

// Generate and update unique login key for user

// Regenerate unique login link via AJAX function ajax_regenerate_unique_login_link() { if (!isset($_POST['regenerate_login_link_nonce']) || !wp_verify_nonce($_POST['regenerate_login_link_nonce'], 'regenerate_unique_login_link')) { wp_send_json_error('Invalid nonce'); }

$user_id = isset($_POST['user_id']) ? intval($_POST['user_id']) : 0;

if ($user_id <= 0) {
    wp_send_json_error('Invalid user ID');
}

$login_key = regenerate_unique_login_key($user_id);
$login_link = generate_unique_login_link($user_id, $login_key);

wp_send_json_success($login_link);

} add_action('wp_ajax_regenerate_unique_login_link', 'ajax_regenerate_unique_login_link');

// Create a submenu page under the Users menu function unique_login_key_submenu_page() { add_submenu_page( 'users.php', 'Regenerate Unique Login Keys', 'Regenerate Unique Login Keys', 'manage_options', 'regenerate_unique_login_keys', 'unique_login_key_regenerate_keys_page' ); } add_action('admin_menu', 'unique_login_key_submenu_page');

// Display the regenerate keys page function unique_login_key_regenerate_keys_page() { if (!current_user_can('manage_options')) { return; }

if (isset($_POST['regenerate_all_keys'])) {
    // Regenerate keys for all users
    $users = get_users();
    foreach ($users as $user) {
        regenerate_unique_login_key($user->ID);
    }
    echo '<div class="notice notice-success"><p>All unique login keys have been regenerated.</p></div>';
}

if (isset($_POST['regenerate_selected_keys'])) {
    // Regenerate keys for selected users
    $selected_users = isset($_POST['selected_users']) ? $_POST['selected_users'] : array();
    foreach ($selected_users as $user_id) {
        regenerate_unique_login_key($user_id);
    }
    echo '<div class="notice notice-success"><p>The unique login keys have been regenerated for the selected users.</p></div>';
}

?>
<div class="wrap">
    <h1>Regenerate Unique Login Keys</h1>
    <p>This page allows you to regenerate the unique login keys for users and set auto-regeneration options.</p>

    <form method="post">
        <input type="text" id="username-search" placeholder="Search by username">
        <input type="text" id="email-search" placeholder="Search by email"><br><br>
        <button id="reset-search">Reset</button>
        <table class="wp-list-table widefat fixed striped">
            <thead>
                <tr>
                    <th>1. <?php _e('Select', 'text-domain'); ?></th>
                    <th>2. <?php _e('Username', 'text-domain'); ?></th>
                    <th>4. <?php _e('Email', 'text-domain'); ?></th>
                    <th>5. <?php _e('Unique Login Key', 'text-domain'); ?></th>
                    <th>6. <?php _e('Last Generated Time', 'text-domain'); ?></th>
                    <th>7. <?php _e('Last Generated Key', 'text-domain'); ?></th>
                    <th>8. <?php _e('Generated By', 'text-domain'); ?></th>
                    <th>9. <?php _e('Actions', 'text-domain'); ?></th>
                </tr>
            </thead>
            <tbody>
                <?php
                $users = get_users();
                foreach ($users as $user) {
                    $user_id = $user->ID;
                    $login_key = get_user_meta($user_id, 'unique_login_key', true);
                    $generated_times = get_user_meta($user_id, 'unique_login_key_generated_times', true);
                    $generated_keys = get_user_meta($user_id, 'unique_login_generated_keys', true);
                    $generated_by = get_user_meta($user_id, 'unique_login_generated_by', true);
                    ?>
                    <tr class="user-row">
                        <td><input type="checkbox" name="selected_users[]" value="<?php echo $user_id; ?>"></td>
                        <td class="username"><?php echo $user->user_login; ?></td>
                        <td class="email"><?php echo $user->user_email; ?></td>
                        <td><?php echo $login_key; ?></td>
                        <td>
                            <?php
                            foreach ($generated_times as $generated_time) {
                                echo date('Y-m-d H:i:s', $generated_time) . '<br>';
                            }
                            ?>
                        </td>
                        <td>
                            <?php
                            foreach ($generated_keys as $generated_key) {
                                echo $generated_key . '<br>';
                            }
                            ?>
                        </td>
                    <td>
                        <?php
                        foreach ($generated_by as $generated_by_user_id) {
                            $generated_by_user = get_userdata($generated_by_user_id);
                            echo $generated_by_user ? $generated_by_user->user_login : '';
                            echo '<br>';
                        }
                        ?>
                    </td>
                        <td>
                            <button type="button" class="copy-login-link" data-key="<?php echo $login_key; ?>">Copy Link</button>
                            <button type="button" class="regenerate-login-link" data-user-id="<?php echo $user_id; ?>">Regenerate</button>
                        </td>
                    </tr>
                <?php
                }
                ?>
            </tbody>
        </table>
        <br>
        <input type="submit" name="regenerate_selected_keys" class="button button-primary" value="Regenerate Selected Keys">
        <input type="submit" name="regenerate_all_keys" class="button" value="Regenerate All Keys">
    </form>
</div>

<script>
    //search functionality script
    document.addEventListener('DOMContentLoaded', function() {
        var usernameInput = document.getElementById('username-search');
        var emailInput = document.getElementById('email-search');
        var resetButton = document.getElementById('reset-search');

        var userRows = document.querySelectorAll('.user-row');

        usernameInput.addEventListener('input', filterUserRows);
        emailInput.addEventListener('input', filterUserRows);
        resetButton.addEventListener('click', resetSearch);

        function filterUserRows() {
            var usernameTerm = usernameInput.value.trim().toLowerCase();
            var emailTerm = emailInput.value.trim().toLowerCase();

            userRows.forEach(function(row) {
                var username = row.querySelector('.username').textContent.trim().toLowerCase();
                var email = row.querySelector('.email').textContent.trim().toLowerCase();

                var matchUsername = username.includes(usernameTerm);
                var matchEmail = email.includes(emailTerm);

                if (matchUsername && matchEmail) {
                    row.style.display = 'table-row';
                } else {
                    row.style.display = 'none';
                }
            });
        }

function resetSearch() { usernameInput.value = ''; emailInput.value = '';

userRows.forEach(function(row) {
    row.style.display = 'table-row';
});

filterUserRows();

} });

<script>
    // JavaScript code for copying login links and regenerating keys
    document.addEventListener('DOMContentLoaded', function() {
        var copyButtons = document.querySelectorAll('.copy-login-link');
        copyButtons.forEach(function(button) {
            button.addEventListener('click', function() {
                var key = this.getAttribute('data-key');
                var link = '<?php echo home_url('/'); ?>?key=' + key;
                copyToClipboard(link);
            });
        });

        var regenerateButtons = document.querySelectorAll('.regenerate-login-link');
        regenerateButtons.forEach(function(button) {
            button.addEventListener('click', function() {
                var userId = this.getAttribute('data-user-id');
                regenerateLoginKey(userId);
            });
        });
    });

    // Function to copy login link to the clipboard
    function copyToClipboard(text) {
        var textarea = document.createElement('textarea');
        textarea.value = text;
        textarea.setAttribute('readonly', '');
        textarea.style.position = 'absolute';
        textarea.style.left = '-9999px';
        document.body.appendChild(textarea);
        textarea.select();
        document.execCommand('copy');
        document.body.removeChild(textarea);
        alert('Login link has been copied to the clipboard.');
    }

    // Function to regenerate login key using AJAX
    function regenerateLoginKey(userId) {
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function() {
            if (xhr.readyState === 4 && xhr.status === 200) {
                var response = JSON.parse(xhr.responseText);
                if (response.success) {
                    alert('Unique login key has been regenerated for the selected user.');
                    location.reload();
                } else {
                    alert('An error occurred while regenerating the unique login key.');
                }
            }
        };
        xhr.open('POST', '<?php echo admin_url('admin-ajax.php'); ?>');
        xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
        xhr.send('action=regenerate_unique_login_key_ajax&user_id=' + userId);
    }
</script>
<?php

}

function regenerate_unique_login_key_ajax() { if (!current_user_can('manage_options')) { wp_send_json_error('Permission denied'); }

$user_id = isset($_POST['user_id']) ? intval($_POST['user_id']) : 0;
if ($user_id > 0) {
    regenerate_unique_login_key($user_id);
    wp_send_json_success();
} else {
    wp_send_json_error('Invalid user ID');
}

} add_action('wp_ajax_regenerate_unique_login_key_ajax', 'regenerate_unique_login_key_ajax');

function regenerate_unique_login_link_callback() { // Verify the nonce if ( ! isset( $_POST['regenerate_login_link_nonce'] ) || ! wp_verify_nonce( $_POST['regenerate_login_link_nonce'], 'regenerate_unique_loginlink' . $_POST['user_id'] ) ) { wp_send_json_error( 'Invalid nonce' ); }

$user_id = $_POST['user_id'];

// Regenerate the login key and login link
$login_key = regenerate_unique_login_key( $user_id );
$login_link = generate_unique_login_link( $user_id, $login_key );

// Update the login link value in the database
update_user_meta( $user_id, 'unique_login_key', $login_key );

wp_send_json_success( $login_link );

} add_action( 'wp_ajax_regenerate_unique_login_link', 'regenerate_unique_login_link_callback' ); add_action( 'wp_ajax_nopriv_regenerate_unique_login_link', 'regenerate_unique_login_link_callback' );

add_action('wp_ajax_regenerate_unique_login_link', 'regenerate_unique_login_link_ajax_callback'); function regenerate_unique_login_link_ajax_callback() { // Verify the security nonce $nonce = $_POST['regenerate_login_link_nonce']; $user_id = $_POST['user_id'];

if (!wp_verify_nonce($nonce, 'regenerate_unique_login_link_' . $user_id)) {
    wp_send_json_error('Invalid nonce');
}

// Regenerate the login key
$new_login_key = regenerate_unique_login_key($user_id);

// Generate the new login link
$new_login_link = generate_unique_login_link($user_id, $new_login_key);

// Update the login key in user meta
update_user_meta($user_id, 'unique_login_key', $new_login_key);

wp_send_json_success($new_login_link);

} ///email test

//moving from plugin file(b2bking) function generate_login_link_and_display($subaccount, $username, $name, $last_name, $job_title, $phone, $email) { // Generate and display unique login link $login_key = get_user_meta($subaccount, 'unique_login_key', true);

if (empty($login_key)) {
    $login_key = regenerate_unique_login_key($subaccount);
}
$login_link = home_url('?key=' . rawurlencode($login_key));

// Get the existing room capacity value for the user
$room_capacity = get_user_meta($subaccount, 'room_capacity', true);

// HTML code for displaying user details and login link
ob_start();

?>

<div>
    <tr>
        <td><?php echo $username; ?></td>
        <td><?php echo $name; ?></td>
        <td><?php echo $last_name; ?></td>
        <td><?php echo $job_title; ?></td>
        <td><?php echo $phone; ?></td>
        <td><?php echo $email; ?></td>
        <td>
            <input type="text" value="<?php echo esc_attr($login_link); ?>" readonly="readonly" class="regular-text" id="login-link-<?php echo $subaccount; ?>">
            <button class="button copy-link" onclick="copyUniqueLoginLink(event, <?php echo $subaccount; ?>)">Copy Link</button>
            <button class="button regenerate-link" data-userid="<?php echo $subaccount; ?>">Regenerate Link</button>
            <input type="number" class="room-capacity-input" value="<?php echo esc_attr($room_capacity); ?>">
            <button class="button save-room-capacity" data-userid="<?php echo $subaccount; ?>">Save</button>
            <?php
            $generated_times = get_user_meta($subaccount, 'unique_login_key_generated_times', true);
            if (!empty($generated_times)) {
                $last_generated_time = reset($generated_times);
                $expiry_time = date('Y-m-d H:i:s', strtotime('+3 hours', $last_generated_time));
                echo '<span class="last-generated-time">Last Regenerated: ' . $expiry_time . '</span>';
            }
            ?>  
        </td>

    </tr>
</div>
<?php
$output = ob_get_clean();

echo $output;

}

// AJAX handler to save the room capacity value add_action('wp_ajax_save_room_capacity', 'save_room_capacity'); function save_room_capacity() { // Check nonce for security $nonce = $_POST['save_room_capacity_nonce']; if (!wp_verify_nonce($nonce, 'save_room_capacity')) { wp_send_json_error('Invalid nonce'); }

// Get the user ID and room capacity from the AJAX request
$user_id = $_POST['user_id'];
$room_capacity = $_POST['room_capacity'];

// Update the room capacity value for the user
update_user_meta($user_id, 'room_capacity', $room_capacity);

wp_send_json_success();

}

// Define the action hook function generate_login_link_for_subaccount($subaccount) { $user = get_user_by('ID', $subaccount);

// If user does not exist, delete the user and continue to the next foreach item
if ($user === false) {
    $user_subaccounts_list = str_replace(',' . $subaccount, '', $user_subaccounts_list);
    update_user_meta($user_id, 'b2bking_subaccounts_list', sanitize_text_field($user_subaccounts_list));
    return;
}

$username = $user->user_login;
$name = get_user_meta($subaccount, 'first_name', true);
$last_name = get_user_meta($subaccount, 'last_name', true);
$job_title = get_user_meta($subaccount, 'b2bking_account_job_title', true);
$phone = get_user_meta($subaccount, 'b2bking_account_phone', true);
$email = $user->user_email;

generate_login_link_and_display($subaccount, $username, $name, $last_name, $job_title, $phone, $email);

}

add_action('generate_login_link_for_subaccount', 'generate_login_link_for_subaccount', 10, 1);`

Cheers mate, and thank you for your time and ideas.

Regards, V.