User Role Membership (URM) – Snippet Bundle
Author: Black & Copilot
License: Free to use, modify, and enhance
📘 Overview
User Role Membership (URM) is a modular snippet system for WordPress that enables administrators to assign user roles with time duration and point cost — without using complex plugins or unnecessary features. URM integrates seamlessly with existing tools such as:
- User Role Editor (for permission control)
- Gamipress (for point logic and gamification)
- PeepSo (for community building)
- WordPress native roles and admin structure
This bundle contains 6 cleanly separated snippets:
- Main Router Menu
Handles tab rendering and routing across all URM modules. - Control Panel
Creates new roles with time-based duration and point cost. - Process Monitoring
Logs actions, displays activity, allows export and cleanup. - User List
Displays users with active roles, enables search, revoke, and CSV export. - User Logic
Core backend protections and permission guards. - Instructions
Full documentation, philosophy, and practical usage guide.
🔒 Security & Integration
URM avoids injecting protection into every snippet line by using centralized guards:
urm_global_guard()— blocks unauthorized accessurm_guard_admin_view()— limits access to sensitive tabs- All actions require administrator privileges (
manage_options) - Cron-safe, shortcode-safe, and compatible with security plugins like Wordfence
External plugins are used for:
- Role capability assignment (User Role Editor, PeepSo)
- Point logic and payment (Gamipress & add-ons)
- Frontend visibility control (Restrict Content)
🚀 How to Use
➤ Admins create roles via Control Panel
➤ Time, points, and names are defined per role
➤ Shortcodes are automatically generated and placed on frontend pages
➤ URM logs all changes and monitors the process
➤ Admins review active roles via User List tab
➤ CSV export and cleanup options included
➤ Integration with Restrict Content ensures points are properly deducted
➤ Users are prevented from stacking roles and can contact admin for revocation
👤 Philosophy
URM was born from real site needs — where monetization, gamification, and member privilege all had to co-exist without friction.
No plugin offered the exact solution, so this system was built manually — clean, lean, and powerful.
Points earned through activity or donation allow users to unlock roles. Admins retain full control.
Roles don’t define capabilities — existing plugins handle that. URM handles duration and logic.
📦 Credits
Built with:
- WordPress admin APIs
- Love for modular clarity
- Practical site experience
- ✨ Co-composed by Black & Copilot
Copilot reviewed, refined and supported every step — a true AI companion and technical editor.
URM – Main Router Menu
This script registers the main admin menu for the User Role Membership plugin.
It handles tab rendering, navigation, and routing to individual module views.
Tabs: Dashboard, Process Log, User List, Instructions
<?php
/**
* URM – Main Router Menu
*
* This script registers the main admin menu for the User Role Membership plugin.
* It handles tab rendering, navigation, and routing to individual module views.
* Tabs: Dashboard, Process Log, User List, Instructions
*
* Author: Black & Copilot
*/
// // 🔧 URM – Main router menu
function create_ur_membership_menu() {
add_menu_page(
'User Role Membership', // Page title
'UR Membership', // Name in the sidebar (shortend to fit the sidebar without wrapping)
'manage_options', // Access rights
'ur-membership', // Slug
'render_ur_membership_page', // Callback function to render content
'dashicons-admin-users', // Icon
6 // Position in the menu
);
}
add_action('admin_menu', 'create_ur_membership_menu');
function render_ur_membership_page() {
urm_guard_router_integrity();
$active_tab = $_GET['tab'] ?? 'dashboard';
echo '<div class="wrap">';
echo '<h1 style="margin-bottom: 20px;">User Role Membership</h1>';
// 🧭 Tabs with icons
$tabs = [
'dashboard' => ['label' => 'Control panel', 'icon' => '⚙️'],
'log' => ['label' => 'Process monitoring', 'icon' => '📊'],
'users' => ['label' => 'User list', 'icon' => '👥'],
'help' => ['label' => 'Instructions', 'icon' => '📋']
];
// 🔷 Tab navigation
echo '<div style="display: flex; gap: 0px; margin-bottom: 0;">';
foreach ($tabs as $slug => $tab) {
$is_active = ($active_tab === $slug);
$style = 'padding: 10px 16px; border-radius: 8px 8px 0 0; text-decoration: none; background: ' . ($is_active ? '#f9f9f9' : '#e4e4e4') . '; color: black; border: 1px solid #ccc; border-bottom: none; outline: none; box-shadow: none;';
echo '<a href="?page=ur-membership&tab=' . $slug . '" style="' . $style . '">';
echo '<span style="margin-right: 6px;">' . $tab['icon'] . '</span>' . esc_html($tab['label']);
echo '</a>';
}
echo '</div>';
// ⬇️ Visual tab support bar - creates the ilusion of tabs ancored in the panel
echo '<div style="height: 0px; background: #ccc; margin-top: -1px;"></div>';
// 📦 Content container styled for clarity and separation
echo '<div style="background: #f9f9f9; border: 1px solid #ccc; border-radius: 0 8px 8px 8px; padding: 20px; margin-bottom: 30px;">';
switch ($active_tab) {
case 'log':
render_ur_process_log_tab();
break;
case 'users':
render_ur_user_list_tab();
break;
case 'help':
render_ur_help_tab();
break;
case 'dashboard':
default:
render_ur_dashboard_tab();
break;
}
// ➕ Plugin footer signature
echo '<div style="text-align: center; margin-top: 40px; font-size: 14px; color: #999;">';
echo '<i>Made with 💖 User Role Membership 3.0</i>';
echo '</div>';
// Close the main .wrap div
echo '</div>';
}
URM – Control Panel
Handles creation, deletion, and management of user roles and their duration.
Integrates shortcodes, cron scheduling, process logging and JSON storage.
Intended for use with URM system backend.
<?php
/**
* URM – Control Panel
*
* Handles creation, deletion, and management of user roles and their duration.
* Integrates shortcodes, cron scheduling, process logging and JSON storage.
* Intended for use with URM system backend.
*
* Author: Black & Copilot
*/
// 🛠 URM - Control panel
function render_ur_dashboard_tab() {
?>
<div style="display: flex; gap: 20px;">
<!-- Left box: Add user role -->
<div style="flex: 1; background: #fff; border: 1px solid #ccc; border-radius: 0px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.05);">
<!-- Frame header -->
<div style="background: #f7f9fa; padding: 10px 16px; border-bottom: 1px solid #ccc; font-weight: bold; font-size: 16px;">
🎭 Add user role
</div>
<!-- Content of the frame -->
<div style="padding: 20px;">
<form method="post" action="">
<label for="role_name" style="font-weight: bold;">Add role:</label><br>
<input type="text" name="add_role_name" placeholder="Enter the role name" style="width: 200px; margin-bottom: 10px;">
<button type="submit" name="add_role" style="padding: 7px 10px; background: #198754; color: white; border: none; border-radius: 4px; cursor: pointer;">✨ Add</button>
<p style="color: black; font-size: 14px; margin-top: 10px;">
By adding a user role, you create a new role in WordPress. These roles will appear in the role menus in all plugins used for role manipulation. In those additions, you can determine which options you want to assign to newly created roles. This tool is only used for creating new roles and follows certain rules. When you create a role with multiple words, you must register it as one word in this way, for example: 'User_1_month'. Don't worry, your role will be displayed through a short code and messages in this way: 'User 1 month'.
</p>
</form>
</div>
</div>
<!-- Right frame: Delete user role -->
<div style="flex: 1; background: #fff; border: 1px solid #ccc; border-radius: 0px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.05);">
<!-- Frame header -->
<div style="background: #f7f9fa; padding: 10px 16px; border-bottom: 1px solid #ccc; font-weight: bold; font-size: 16px;">
🎭 Delete user role
</div>
<!-- Content of the frame -->
<div style="padding: 20px;">
<form method="post" action="">
<label for="role_name" style="font-weight: bold;">Delete role:</label><br>
<input type="text" name="delete_role_name" placeholder="Enter the role name" style="width: 200px; margin-bottom: 10px;">
<button type="submit" name="delete_role" style="padding: 7px 10px; background: #dc3545; color: white; border: none; border-radius: 4px; cursor: pointer;">🗑️ Delete</button>
<p style="color: black; font-size: 14px; margin-top: 10px;">
By entering the role name and clicking the 'Delete' button, the role will be removed from WordPress roles. You will no longer be able to use that role and its shortcodes.
All users who use this role will be left without a role. Make sure to transfer users to a new role before deletion, and only then delete the role when you are sure it will not cause any problems.
<b>Important!</b> Input is case-sensitive, so if you do not comply with this, the role will not be deleted. You also need to enter the correct role name as one word, e.g.: 'User_1_month'.
</p>
</form>
</div>
</div>
</div>
<!-- Left box: Add role duration -->
<div style="display: flex; gap: 20px; margin-top: 20px;">
<div style="flex: 1; background: #fff; border: 1px solid #ccc; border-radius: 0px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.05);">
<!-- Frame header -->
<div style="background: #f7f9fa; padding: 10px 16px; border-bottom: 1px solid #ccc; font-weight: bold; font-size: 16px;">
⏱️ Add role duration
</div>
<!-- Content of the frame -->
<div style="padding: 20px;">
<form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
<input type="hidden" name="action" value="add_role_duration">
<div style="display: flex; flex-wrap: wrap; gap: 20px;">
<!-- Role name -->
<div style="flex: 1 1 100px;">
<label for="role_name" style="font-weight: bold;">Role name:</label><br>
<input type="text" name="role_name" placeholder="Enter the role name" id="role_name" style="width: 100%;" required>
</div>
<!-- Duration of the role -->
<div style="flex: 1 1 100px;">
<label for="role_duration" style="font-weight: bold;">Duration of the role:</label><br>
<select name="role_duration" id="role_duration" style="width: 100%;">
<option value="10_minutes">Test (10 minutes)</option>
<option value="1_month">1 Month</option>
<option value="3_months">3 Months</option>
<option value="6_months">6 Months</option>
<option value="1_year">1 Year</option>
</select>
</div>
<!-- Amount of points -->
<div style="flex: 1 1 100px;">
<label for="points_amount" style="font-weight: bold;">Amount of points:</label><br>
<input type="number" name="points_amount" id="points_amount" value="1" min="1" style="width: 100%;" required>
</div>
<!-- Type of points -->
<div style="flex: 1 1 100px;">
<label for="points_type" style="font-weight: bold;">Type of points:</label><br>
<select name="points_type" id="points_type" style="width: 100%;" required>
<option value="first">First</option>
<option value="second">Second</option>
<option value="third">Third</option>
</select>
</div>
</div>
<!-- Button -->
<div style="margin-top: 25px; text-align: center;">
<button type="submit" name="add_role_duration" style="padding: 7px 10px; background: #0d6efd; color: white; border: none; border-radius: 4px; cursor: pointer;">🧾 Create shortcode in the table</button>
</div>
</form>
<p style="color: black; font-size: 14px; margin-top: 10px;">
Enter the name of the newly created role as you created it, for example: 'User_1_month', determine its duration, set how many points are needed to obtain the role and what type of points.
By clicking the 'Create shortcode in the table' button, the entered values will be saved to the table. Place the obtained shortcode on the page where you want the 'Change Role' button to appear.
The duration of the role 'Test (10 minutes)' is only for testing to see if cron correctly registers hooks.
</p>
</div>
</div>
<!-- Right frame: Guide to shortcodes -->
<div style="flex: 1; background: #fff; border: 1px solid #ccc; border-radius: 0px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.05);">
<!-- Header -->
<div style="background: #f7f9fa; padding: 10px 16px; border-bottom: 1px solid #ccc; font-weight: bold; font-size: 16px;">
🖇️ Guide to Shortcodes
</div>
<!-- Content -->
<div style="padding: 20px;">
<p style="color: black; font-size: 14px;">
If you want to display the current role that the user is using, set this shortcode <b>❌ Morate biti prijavljeni da biste vidjeli svoju ulogu.</b> anywhere on the frontend page where you decided to display the 'Change Role' button. This short code will also display the change of role.
</p>
<p style="color: black; font-size: 14px;">
If you want to display messages about errors, warnings, and success, you can display them through a shortcode <b></b> anywhere on your frontend page where you have placed the above shortcode.
</p>
<p style="color: black; font-size: 14px;">If you want to display the 'User List' table on the frontend, create a new page and use the shortcode <b>⛔ Ova stranica je dostupna samo administratorima. </b> which will be displayed only for Administrators, while others will see the message: '⛔ This page is only available to administrators.'. If you want to protect the page from being displayed in the menu, you can use the If Menu plugin.</p>
</div>
</div>
</div>
<!-- Frame for the table -->
<div style="margin-top: 20px; background: #fff; border: 1px solid #ccc; border-radius: 0px; box-shadow: 0 1px 3px rgba(0,0,0,0.05); overflow: hidden;">
<div style="background: #f7f9fa; padding: 10px 16px; border-bottom: 1px solid #ccc; font-weight: bold; font-size: 16px;">
📋 Role name, role duration, amount of points, type of points and role shortcode
</div>
<div style="padding: 20px;">
<style>
.urm-log-table th, .urm-log-table td {
padding: 6px 6px 6px 12px; /* ⬅️ added 12px of left padding */
border: 1px solid #ccc;
font-size: 14px;
vertical-align: middle;
word-break: break-word;
}
.urm-log-table tr:nth-child(even) {
background-color: #f9f9f9;
}
.urm-log-table tr:nth-child(odd) {
background-color: #ffffff;
}
</style>
<table class="urm-log-table" style="width: 100%; border-collapse: collapse; margin-top: 0px;">
<thead>
<tr>
<th>🆔 ID</th>
<th>🎭 Role name</th>
<th>⏱️ Role duration</th>
<th>🪙 Amount of points</th>
<th>🧿 Type of points</th>
<th>🖇️ Role shortcode</th>
<th>🗑️ Delete</th>
</tr>
</thead>
<tbody>
<?php
$json_file = WP_CONTENT_DIR . '/plugins/user-role-membership/roles.json';
$roles_data = json_decode(file_get_contents($json_file), true);
if (!empty($roles_data['roles'])) {
foreach ($roles_data['roles'] as $role_name => $entries) {
foreach ($entries as $entry) {
if (isset($entry['duration'])) {
echo "<tr>
<td>" . esc_html($entry['id']) . "</td>
<td>" . esc_html($role_name) . "</td>
<td>" . esc_html($entry['duration']) . "</td>
<td>" . (!empty($entry['points_amount']) ? esc_html($entry['points_amount']) : '') . "</td>
<td>" . (!empty($entry['points_type']) ? esc_html(ucfirst($entry['points_type'])) : '') . "</td>
<td>⛔ Morate biti prijavljeni da biste vidjeli svoju ulogu.) . "']</td>
<td>
<form method='post' action='" . esc_url(admin_url('admin-post.php')) . "'>
<input type='hidden' name='action' value='delete_role_entry'>
<input type='hidden' name='delete_role_name' value='" . esc_attr($role_name) . "'>
<input type='hidden' name='delete_role_id' value='" . esc_attr($entry['id']) . "'>
<center><button type='submit' style='padding: 5px 10px; background: #dc3545; color: white; border: none; border-radius: 4px; cursor: pointer;'>🗑️ Delete</button></center>
</form>
</td>
</tr>";
}
}
}
} else {
echo "<tr><td colspan='7'><center>Role not found.</center></td></tr>";
}
?>
</tbody>
</table>
<!-- Button to reset the table -->
<div style="text-align: right; margin-top: 20px;">
<form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" onsubmit="return confirm('⚠️ Are you sure you want to delete all roles from the table? This action is irreversible!')">
<input type="hidden" name="action" value="reset_role_entries">
<button type="submit" style="padding: 6px 16px; background: #dc3545; color: white; border: none; border-radius: 4px; font-weight: bold; cursor: pointer;">
🧹 Reset the table
</button>
</form>
</div>
<?php
// 👇 View all backups
$backup_dir = WP_CONTENT_DIR . '/plugins/user-role-membership/backups/';
$backup_url = content_url('/plugins/user-role-membership/backups/');
$backups = glob($backup_dir . 'roles_backup_*.json');
if (!empty($backups)) {
echo '<div style="margin-top: 40px; border-top: 1px solid #ccc; padding-top: 20px;">';
echo '<h3>🔁 Restore from backup</h3>';
echo '<p style="font-size: 14px; color: gray;">⚠️ By clicking on "Restore" you change the current <code>roles.json</code> with data from the selected backup.</p>';
echo '<ul style="list-style: none; padding-left: 0;">';
foreach ($backups as $file) {
$filename = basename($file);
$timestamp = str_replace(['roles_backup_', '.json'], '', $filename);
$formatted = date('d.m.Y. H:i:s', DateTime::createFromFormat('Ymd_His', $timestamp)->getTimestamp());
echo '<li style="margin-bottom: 15px; padding: 10px 15px; background: #f9f9f9; border-radius: 8px; display: flex; justify-content: space-between; align-items: center;">
<span>📁 <strong>' . esc_html($formatted) . '</strong></span>
<div style="display: flex; gap: 8px;">
<!-- Restore backup -->
<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" onsubmit="return confirm(\'⚠️ Are you sure you want to restore this backup?\')">
<input type="hidden" name="action" value="restore_role_backup">
<input type="hidden" name="filename" value="' . esc_attr($filename) . '">
<button type="submit" style="padding: 5px 10px; background: #198754; color: white; border: none; border-radius: 4px; cursor: pointer;">🔄 Restore</button>
</form>
<!-- Download backup -->
<a href="' . esc_url($backup_url . $filename) . '" download style="padding: 4px 10px; background: #0d6efd; color: white; border-radius: 4px; text-decoration: none;">⬇️ Download</a>
<!-- Delete backup -->
<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" onsubmit="return confirm(\'⚠️ Are you sure you want to delete this backup?\')">
<input type="hidden" name="action" value="delete_role_backup">
<input type="hidden" name="filename" value="' . esc_attr($filename) . '">
<button type="submit" style="padding: 5px 10px; background: #dc3545; color: white; border: none; border-radius: 4px; cursor: pointer;">🗑️ Delete</button>
</form>
</div>
</li>';
}
echo '</ul></div>';
}
?>
<?php
}
function handle_user_roles() {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') return;
$protected_roles = ['administrator', 'editor', 'author', 'contributor', 'subscriber'];
if (isset($_POST['add_role'])) {
$role_name = sanitize_text_field($_POST['add_role_name']);
if (!empty($role_name) && !in_array($role_name, $protected_roles)) {
add_role($role_name, ucfirst($role_name), array('read' => true));
echo '<div class="notice notice-success is-dismissible"><p>The role has been successfully added.</p></div>';
} else {
echo '<div class="notice notice-error is-dismissible"><p>❌ You cannot change the default WordPress roles.</p></div>';
}
}
if (isset($_POST['delete_role'])) {
$role_name = sanitize_text_field($_POST['delete_role_name']);
if (!empty($role_name) && !in_array($role_name, $protected_roles)) {
remove_role($role_name);
echo '<div class="notice notice-success is-dismissible"><p>The role has been successfully deleted.</p></div>';
} else {
echo '<div class="notice notice-error is-dismissible"><p>❌ You cannot delete default WordPress roles.</p></div>';
}
}
}
add_action('admin_init', 'handle_user_roles');
function create_json_folder_and_file() {
$folder_path = WP_CONTENT_DIR . '/plugins/user-role-membership';
$json_file = $folder_path . '/roles.json';
// Check and create a folder if it does not exist
if (!file_exists($folder_path)) {
wp_mkdir_p($folder_path);
}
// Check and create a `.json` file if it does not exist
if (!file_exists($json_file)) {
$default_data = [
'roles' => []
];
file_put_contents($json_file, json_encode($default_data, JSON_PRETTY_PRINT));
}
}
add_action('admin_init', 'create_json_folder_and_file');
function add_role_duration_to_json() {
if (!isset($_POST['role_name']) || !isset($_POST['role_duration'])) return;
$role_name = sanitize_text_field($_POST['role_name']);
$role_duration = sanitize_text_field($_POST['role_duration']);
$points_amount = intval($_POST['points_amount'] ?? 1);
$points_type = sanitize_text_field($_POST['points_type'] ?? '');
// Check default WordPress roles
$protected_roles = ['administrator', 'editor', 'author', 'contributor', 'subscriber'];
if (in_array($role_name, $protected_roles)) {
return;
}
$json_file = WP_CONTENT_DIR . '/plugins/user-role-membership/roles.json';
// If it doesn't exist, we create a JSON file.
if (!file_exists($json_file)) {
file_put_contents($json_file, json_encode(['roles' => []], JSON_PRETTY_PRINT));
}
// We are retrieving existing data.
$roles_data = json_decode(file_get_contents($json_file), true);
if (!isset($roles_data['roles'][$role_name])) {
$roles_data['roles'][$role_name] = [];
}
// Generating an ID number
// We are counting the total number of all previous entries.
$all_entries_count = 0;
foreach ($roles_data['roles'] as $entries_per_role) {
$all_entries_count += count($entries_per_role);
}
$new_id = $all_entries_count + 1;
// Generating a short code role
$role_shortcode = "⛔ Morate biti prijavljeni da biste vidjeli svoju ulogu.";
set_process_message("ℹ️ Adding a new entry: Role = $role_name, Duration = $role_duration, ID = $new_id, Shortcode = $role_shortcode", 'info');
// Adding duration in JSON with full recording
$roles_data['roles'][$role_name][] = [
'id' => $new_id,
'duration' => str_replace('_', ' ', $role_duration),
'shortcode' => $role_shortcode,
'points_amount' => $points_amount,
'points_type' => $points_type
];
file_put_contents($json_file, json_encode($roles_data, JSON_PRETTY_PRINT));
set_process_message("ℹ️ Successfully saved to roles.json", 'info');
// Refreshing the page
wp_redirect(admin_url('admin.php?page=ur-membership'));
exit;
}
add_action('admin_post_add_role_duration', 'add_role_duration_to_json');
function delete_role_entry_from_json() {
if (!isset($_POST['delete_role_name']) || !isset($_POST['delete_role_id'])) return;
$role_name = sanitize_text_field($_POST['delete_role_name']);
$role_id = intval($_POST['delete_role_id']);
$json_file = WP_CONTENT_DIR . '/plugins/user-role-membership/roles.json';
$roles_data = json_decode(file_get_contents($json_file), true);
if (!isset($roles_data['roles'][$role_name])) {
return;
}
// We filter the records and remove the one that matches the ID.
$roles_data['roles'][$role_name] = array_values(array_filter($roles_data['roles'][$role_name], function ($entry) use ($role_id) {
return $entry['id'] !== $role_id;
}));
// Update `roles.json`
file_put_contents($json_file, json_encode($roles_data, JSON_PRETTY_PRINT));
set_process_message("ℹ️ Update roles.json is successful", 'info');
// Refreshing the page after deletion
wp_redirect(admin_url('admin.php?page=ur-membership'));
exit;
}
add_action('admin_post_delete_role_entry', 'delete_role_entry_from_json');
function user_has_active_membership_role($user_id, $default_roles = ['subscriber']) {
$user = get_userdata($user_id);
$roles = (array) $user->roles;
foreach ($roles as $role) {
if (!in_array($role, $default_roles)) {
return true; // It has some additional, active role.
}
}
return false; // It only has the default (e.g. subscriber)
}
function display_role_change_message() {
if (!is_user_logged_in()) {
$icon = '❌';
$color = 'red';
$background = '#f8d7da';
$text = '⛔ You must be logged in to see your role.';
} else {
$user = wp_get_current_user();
$raw_role = !empty($user->roles) ? $user->roles[0] : '';
$display_role = $raw_role ? format_role_name($raw_role) : 'No role assigned';
$text = 'Your current role is: <strong>' . esc_html($display_role) . '</strong>';
$icon = '✅';
$color = '#198754';
$background = '#d1e7dd';
}
return '<div style="
background: ' . $background . ';
border-left: 5px solid ' . $color . ';
color: ' . $color . ';
padding: 10px;
margin: 20px 0;
border-radius: 10px;
font-size: 17px;
">' . $icon . ' ' . $text . '</div>';
}
add_shortcode('message_role_change', 'display_role_change_message');
function display_role_shortcode($atts) {
$atts = shortcode_atts([
'role' => '',
'duration' => ''
], $atts);
if (!is_user_logged_in()) {
return '<div style="
background: #f8d7da;
border-left: 5px solid red;
color: red;
padding: 10px;
margin: 20px 0;
border-radius: 10px;
font-size: 17px;
">⛔ You must be logged in to see your role.</div>';
}
$user = wp_get_current_user();
$current_role = !empty($user->roles) ? ucfirst($user->roles[0]) : '';
$json_file = WP_CONTENT_DIR . '/plugins/user-role-membership/roles.json';
$roles_data = json_decode(file_get_contents($json_file), true);
if (!isset($roles_data['roles'][$atts['role']])) {
return '<div style="
background: #f8d7da;
border-left: 5px solid red;
color: red;
padding: 10px;
margin: 20px 0;
border-radius: 10px;
font-size: 17px;
">⛔ The role was not found in the database.</div>';
}
$role_data = array_filter($roles_data['roles'][$atts['role']], function ($entry) use ($atts) {
return $entry['duration'] === str_replace('_', ' ', $atts['duration']);
});
$entry = reset($role_data); // This must immediately follow the filter operation
if (empty($role_data)) {
return '<div style="
background: #f8d7da;
border-left: 5px solid red;
color: red;
padding: 10px;
margin: 20px 0;
border-radius: 10px;
font-size: 17px;
">⛔ Duration not found for this role.</div>';
}
ob_start();
?>
<center>
<div style="border: 1px; padding: 10px; background: transparent;">
<strong>Role:</strong> <?php echo esc_html(format_role_name($atts['role'])); ?><br>
<strong>Duration:</strong> <?php echo esc_html($atts['duration']); ?><br>
<strong>Price:</strong> <?php echo esc_html($entry['points_amount']) . ' ' . ucfirst($entry['points_type']); ?><br>
<form method="post">
<input type="hidden" name="selected_role" value="<?php echo esc_attr($atts['role']); ?>">
<input type="hidden" name="selected_duration" value="<?php echo esc_attr($atts['duration']); ?>">
<button type="submit" name="change_role" style="margin-top: 10px;">Change role</button>
</form>
</div>
</center>
<?php
return ob_get_clean();
}
add_shortcode('shortcode_role', 'display_role_shortcode');
function process_switch_role() {
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['change_role']) && is_user_logged_in()) {
$user_id = get_current_user_id();
// ⛔ Check: the user already has an active time-limited role
if (user_has_active_membership_role($user_id)) {
set_process_message('⛔ You have already selected a role. You must wait to expire before a new activation.', 'warning');
wp_redirect(wp_get_referer() ?: home_url('/membership'));
exit;
}
// ➕ Input and basic check
$new_role = sanitize_text_field($_POST['selected_role'] ?? '');
$duration = sanitize_text_field($_POST['selected_duration'] ?? '');
// 🎯 Normalization of spaces in underscores (e.g. "3 months" ➝ "3_months")
$duration = str_replace(' ', '_', $duration);
// 🛡️ Check for empty values
if (empty($new_role) || empty($duration)) {
set_process_message('❌ Error: Missing data for role change.', 'error');
wp_redirect(wp_get_referer() ?: home_url('/membership'));
exit;
}
// ✅ Allowed duration intervals
$valid_durations = ['1_month', '3_months', '6_months', '1_year'];
if (!in_array($duration, $valid_durations, true)) {
set_process_message('⛔ Invalid role duration. Please try again.', 'error');
wp_redirect(wp_get_referer() ?: home_url('/membership'));
exit;
}
// 🎭 Assignment of roles
$user = new WP_User($user_id);
$user->set_role($new_role);
set_process_message("ℹ️ The role has been changed to: " . esc_html(format_role_name($new_role)) . ".", 'info');
// 🧠 Storage of temporal data
$start = time();
$end = match ($duration) {
'1_month' => strtotime('+1 month', $start),
'3_months' => strtotime('+3 months', $start),
'6_months' => strtotime('+6 months', $start),
'1_year' => strtotime('+1 year', $start)
};
save_ur_user_role_history($user_id, $new_role, format_duration_label($duration), $start, $end);
// ⏱️ Cron for role reset
schedule_role_reset($user_id, str_replace('_', ' ', $duration)); // because your map uses spaces
wp_redirect($_SERVER['REQUEST_URI']);
exit;
}
}
add_action('init', 'process_switch_role');
function schedule_role_reset($user_id, $duration) {
$duration_mapping = [
'10 minutes' => 600,
'1 month' => 2592000,
'3 months' => 7776000,
'6 months' => 15552000,
'1 year' => 31536000
];
if (!isset($duration_mapping[$duration])) {
set_process_message("⛔ Invalid duration: $duration");
return;
}
// 🧽 Remove the old cron if it exists
$existing = wp_get_scheduled_event('reset_user_role', [$user_id]);
if ($existing) {
wp_unschedule_event($existing->timestamp, 'reset_user_role', [$user_id]);
set_process_message("🧹 Removed previous cron for user ID $user_id", 'info');
}
$reset_time = time() + $duration_mapping[$duration];
$label = $duration;
set_process_message("ℹ️ Planned role reset for user ID $user_id for $label.", 'info');
wp_schedule_single_event($reset_time, 'reset_user_role', [$user_id]);
}
function reset_user_role($user_id) {
if (!$user_id || !get_userdata($user_id)) {
set_process_message("⛔ reset_user_role invited without a valid user (ID: " . ($user_id ?? 'null') . ").", 'error');
return;
}
$user = new WP_User($user_id);
$default_role = get_option('default_role', 'subscriber');
// 🧠 Log in start
set_process_message("♻️ Initiating a role reset for user ID $user_id → returning to: $default_role", 'info');
// 🚀 Reset
$user->set_role($default_role);
// 🧽 Try removing the scheduled cron
$event = wp_get_scheduled_event('reset_user_role', [$user_id]);
if ($event) {
wp_unschedule_event($event->timestamp, 'reset_user_role', [$user_id]);
set_process_message("🧹 Cron task removed after execution for user ID: $user_id", 'info');
} else {
set_process_message("ℹ️ No more active cron after execution for user ID: $user_id", 'info');
}
}
add_action('reset_user_role', 'reset_user_role', 10, 1);
function set_process_message($message, $type = 'info') {
if (!is_user_logged_in()) return;
$user_id = get_current_user_id();
// ➕ Log in globally
add_to_ur_process_log($message, $type);
// ➕ Message for frontend user
update_user_meta($user_id, 'ur_process_message', json_encode([
'message' => $message,
'type' => $type
], JSON_UNESCAPED_UNICODE));
}
function display_process_log() {
if (!is_user_logged_in()) return '';
$user_id = get_current_user_id();
$raw = get_user_meta($user_id, 'ur_process_message', true);
if (empty($raw)) return '';
delete_user_meta($user_id, 'ur_process_message');
$data = json_decode($raw, true);
$type = $data['type'] ?? 'info';
$message = esc_html($data['message'] ?? '');
// Style according to the type of message
switch ($type) {
case 'error':
$icon = '❌';
$color = 'red';
$background = '#f8d7da';
break;
case 'warning':
$icon = '⚠️';
$color = '#664d03';
$background = '#fff3cd';
break;
default: // info
$icon = '✅';
$color = '#0f5132';
$background = '#d1e7dd';
break;
}
return '<div style="
background: ' . $background . ';
border-left: 5px solid ' . $color . ';
color: ' . $color . ';
padding: 10px;
margin: 20px 0;
border-radius: 10px;
font-size: 17px;
">' . $icon . ' ' . $message . '</div>';
}
add_shortcode('process_log', 'display_process_log');
add_action('admin_post_reset_role_entries', 'handle_reset_role_entries');
function handle_reset_role_entries() {
if (!current_user_can('manage_options')) {
wp_die('You do not have permission for this action.');
}
$json_file = WP_CONTENT_DIR . '/plugins/user-role-membership/roles.json';
// Backup before deletion
if (file_exists($json_file)) {
$backup_dir = WP_CONTENT_DIR . '/plugins/user-role-membership/backups/';
if (!file_exists($backup_dir)) {
mkdir($backup_dir, 0755, true);
}
$timestamp = date('Ymd_His');
$backup_file = $backup_dir . 'roles_backup_' . $timestamp . '.json';
copy($json_file, $backup_file);
}
// Reset JSON
$empty_data = ['roles' => []];
file_put_contents($json_file, json_encode($empty_data, JSON_PRETTY_PRINT));
// Message and redirect
set_process_message('🧹 All roles have been successfully reset. Backup saved.', 'warning');
wp_redirect(wp_get_referer());
exit;
}
add_action('admin_post_delete_role_backup', 'handle_delete_role_backup');
function handle_delete_role_backup() {
if (!current_user_can('manage_options')) {
wp_die('❌ You do not have permission for this action.');
}
$filename = sanitize_file_name($_POST['filename'] ?? '');
$file = WP_CONTENT_DIR . '/plugins/user-role-membership/backups/' . $filename;
if (!file_exists($file)) {
set_process_message('❌ The backup file does not exist.', 'error');
wp_redirect(wp_get_referer());
exit;
}
unlink($file);
set_process_message('🗑️ The backup has been successfully deleted.', 'warning');
wp_redirect(wp_get_referer());
exit;
}
function handle_restore_role_backup() {
$filename = sanitize_file_name($_POST['filename'] ?? '');
$backup_file = WP_CONTENT_DIR . '/plugins/user-role-membership/backups/' . $filename;
$target_file = WP_CONTENT_DIR . '/plugins/user-role-membership/roles.json';
if (file_exists($backup_file)) {
copy($backup_file, $target_file);
set_process_message('🔄 The backup has been successfully restored.', 'info');
}
wp_redirect(wp_get_referer());
exit;
}
add_action('admin_post_restore_role_backup', 'handle_restore_role_backup');
function format_role_name($role_slug) {
return ucfirst(str_replace('_', ' ', $role_slug));
}
function format_duration_label($duration) {
return match ($duration) {
'1_month' => '1 month',
'3_months' => '3 months',
'6_months' => '6 months',
'1_year' => '1 year',
};
}
URM – Process Monitoring
Displays an administrative log of actions taken through the URM system.
Includes paginated viewing, export to CSV, and deletion functionality.
Log entries include timestamp, message, type (error, warning, info), and user ID.
<?php
/**
* URM – Process Monitoring
*
* Displays an administrative log of actions taken through the URM system.
* Includes paginated viewing, export to CSV, and deletion functionality.
* Log entries include timestamp, message, type (error, warning, info), and user ID.
*
* Author: Black & Copilot
*/
// 🔍 URM – Process Monitoring (admin log display)
function render_ur_process_log_tab() {
urm_guard_admin_view();
echo '<div style="margin-top: 0px; background: #fff; border: 1px solid #ccc; border-radius: 0px; box-shadow: 0 1px 3px rgba(0,0,0,0.05); overflow: hidden;">';
// 👑 Frame header
echo '<div style="background: #f7f9fa; padding: 10px 16px; border-bottom: 1px solid #ccc; font-weight: bold; font-size: 16px;">📊 Process monitoring</div>';
// 📦 Internal content
echo '<div style="padding: 0px 20px 20px 20px;">';
$log = get_option('ur_process_log', []);
if (empty($log)) {
echo '<p style="color: gray;">📭 Currently, there are no logged messages.</p>';
return;
}
// ✨ The admin can set the number of rows per page (or use the default value)
$per_page = isset($_GET['per_page']) ? intval($_GET['per_page']) : 15;
$per_page = ($per_page > 0 && $per_page <= 500) ? $per_page : 15;
// ➕ Total number of entries per pages
$total_items = count($log);
$total_pages = ceil($total_items / $per_page);
$current_page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
//✂️ Cut the part of the log that is displayed on the current page.
$log = array_reverse($log); // latest first
$offset = ($current_page - 1) * $per_page;
$log_slice = array_slice($log, $offset, $per_page);
// 🧾 Table
echo '<table class="widefat fixed striped" style="margin-top: 20px;">';
echo '<thead><tr><th style="width:180px;">⏱️ Time</th><th>📝 Message</th><th style="width:100px;">🎯 Type</th><th style="width:80px;">👤 User</th></tr></thead>';
echo '<tbody>';
foreach ($log_slice as $entry) {
$time = date('d.m.Y. H:i:s', $entry['timestamp']);
$message = esc_html($entry['message']);
$type = esc_html($entry['type']);
$user = get_userdata($entry['user_id'] ?? 0);
$user_display = $user ? esc_html($user->user_login) : '–';
switch ($type) {
case 'error': $color = '#dc3545'; $icon = '❌'; break;
case 'warning': $color = '#ffc107'; $icon = '⚠️'; break;
default: $color = '#198754'; $icon = '✅';
}
echo "<tr>
<td>$time</td>
<td>$icon $message</td>
<td><span style='color: $color; font-weight: bold;'>$type</span></td>
<td>$user_display</td>
</tr>";
}
echo '</tbody></table>';
// 📌 Page navigation
if ($total_pages > 1) {
echo '<div style="margin-top: 15px;">';
for ($i = 1; $i <= $total_pages; $i++) {
$active = ($i === $current_page) ? 'font-weight: bold; text-decoration: underline;' : '';
echo '<a href="' . esc_url(add_query_arg(['paged' => $i, 'per_page' => $per_page])) . '" style="margin-right: 10px; ' . $active . '">Page ' . $i . '</a>';
}
echo '</div>';
}
// 🎛️ Select display per page
echo '<form method="get" style="margin-top: 20px; display: inline-block;">';
echo '<input type="hidden" name="page" value="ur-membership">';
echo '<input type="hidden" name="tab" value="log">';
echo 'Show <input type="number" name="per_page" value="' . esc_attr($per_page) . '" min="5" max="500" style="width:60px;"> entry by page ';
echo '<button type="submit" style="margin-left:10px; background: #198754; color: white; border: none; padding: 8px 20px; border-radius: 4px; cursor: pointer;">🔄 Apply</button>';
echo '</form>';
// 🔁 Add a button below the table for export
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" style="margin-top: 20px; display: inline-block; margin-left: 20px;">';
echo '<input type="hidden" name="action" value="export_ur_process_log">';
echo '<button type="submit" style="background: #0d6efd; color: white; border: none; padding: 8px 20px; border-radius: 4px; cursor: pointer;">⬇️ Export CSV</button>';
echo '</form>';
// 🧹 Clear records button
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" onsubmit="return confirm(\'⚠️ Do you want to delete all log messages?\');" style="margin-top: 20px; display: inline-block; margin-left: 20px;">';
echo '<input type="hidden" name="action" value="clear_ur_process_log">';
echo '<button type="submit" style="background: #dc3545; color: white; border: none; padding: 8px 20px; border-radius: 4px; cursor: pointer;">🧹 Clear records</button>';
echo '</form>';
echo '</div>'; // close padding
echo '</div>'; // close frame
}
function add_to_ur_process_log($message, $type = 'info') {
$log = get_option('ur_process_log', []);
$log[] = [
'timestamp' => current_time('timestamp'),
'message' => wp_strip_all_tags($message), // ⬅️ erases <strong>
'type' => $type,
'user_id' => get_current_user_id()
];
update_option('ur_process_log', $log);
}
add_action('admin_post_clear_ur_process_log', function () {
if (!current_user_can('manage_options')) {
wp_die('⛔ You do not have sufficient permissions to perform this action.');
}
update_option('ur_process_log', []);
set_process_message('🧹 The log process has been deleted.', 'warning');
wp_redirect(admin_url('admin.php?page=ur-membership&tab=log'));
exit;
});
add_action('admin_post_export_ur_process_log', function () {
if (!current_user_can('manage_options')) {
wp_die('⛔ You do not have sufficient permissions to perform this action.');
}
$log = get_option('ur_process_log', []);
if (empty($log)) {
wp_die('📭 No log records are available for export.');
}
// Preparing CSV
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename="ur_process_log.csv"');
$output = fopen('php://output', 'w');
fputcsv($output, ['Time', 'Message', 'Type', 'User']);
foreach ($log as $entry) {
$time = date('d.m.Y. H:i:s', $entry['timestamp']);
$message = $entry['message'];
$type = $entry['type'];
$user = get_userdata($entry['user_id'] ?? 0);
$user_display = $user ? $user->user_login : '–';
fputcsv($output, [$time, $message, $type, $user_display]);
}
fclose($output);
exit;
});
URM – User List
Displays a paginated list of users with their assigned membership roles,
including start and expiration dates. Allows administrators to search,
revoke roles, export user data to CSV, and clean expired role records.
<?php
/**
* URM – User List
*
* Displays a paginated list of users with their assigned membership roles,
* including start and expiration dates. Allows administrators to search,
* revoke roles, export user data to CSV, and clean expired role records.
*
* Author: Black & Copilot
*/
// 👥 URM - User List
function render_ur_user_list_tab($frontend = false) {
if (!function_exists('get_users')) {
echo '<p style="color:red;">⛔ WordPress is not ready — get_users() is not available.</p>';
return;
}
// ✅ Show messages outside of the wrapper
if (isset($_GET['revoked'])) {
$revoked_user_id = intval($_GET['revoked']);
$msg_class = $frontend
? 'style="margin: 0 20px 20px 20px; background: #dff0d8; border: 1px solid #d0e9c6;
padding: 12px; border-radius: 4px; color: #3c763d;"'
: 'class="notice notice-success is-dismissible"';
echo '<div ' . $msg_class . '><p>✅ Role for user ID ' . $revoked_user_id . ' has been revoked.</p></div>';
}
if (isset($_GET['cleaned'])) {
$n = intval($_GET['cleaned']);
$msg_class = $frontend
? 'style="margin: 0 20px 20px 20px; background: #dff0d8; border: 1px solid #d0e9c6;
padding: 12px; border-radius: 4px; color: #3c763d;"'
: 'class="notice notice-success is-dismissible"';
echo '<div ' . $msg_class . '><p>🧹 User removed: <strong>' . $n . '</strong></p></div>';
}
// 👇 Enter the main block of the HTML.
echo '<div style="margin-top: 0px;' . ($frontend ? '' : ' background: #fff;') . ' border: 1px solid #ccc; border-radius: 0px; box-shadow: 0 1px 3px rgba(0,0,0,0.05); overflow: hidden;">';
// 👑 Block title
echo '<div style="' . ($frontend ? '' : 'background: #f7f9fa;') . ' padding: 10px 16px; border-bottom: 1px solid #ccc; font-weight: bold; font-size: 16px;">👥 User record with role changes</div>';
// 🔍 Search
$search = trim($_GET['search_user'] ?? '');
echo '<div style="padding: 20px 20px 10px 20px;">';
echo '<form method="get" action="' . ($frontend ? esc_url(get_permalink()) : '') . '" style="display: flex; gap: 10px; align-items: center;">';
if (!$frontend) {
echo '<input type="hidden" name="page" value="ur-membership">';
echo '<input type="hidden" name="tab" value="users">';
}
echo '<input type="text" name="search_user" value="' . esc_attr($search) . '" placeholder="Search users by ID, username, or email..." style="flex: 1; padding: 8px; border: 1px solid #ccc; border-radius: 4px;">';
echo '<button type="submit" style="padding: 8px 16px; background: #0073aa; color: #fff; border: none; border-radius: 4px; cursor: pointer;">🔎 Search users</button>';
echo '</form>';
echo '</div>';
// 📊 Style and preparation
echo '<div style="padding: 10px 20px 20px 20px;">';
echo '<style>
.urm-user-table th,
.urm-user-table td {
padding: 6px 12px;
border: 1px solid #ccc;
font-size: 14px;
vertical-align: middle;
word-break: break-word;
}
/* 🌓 Stripe colors according to context */
.urm-user-table tr:nth-child(even) {
background-color: ' . ($frontend ? 'inherit' : '#f9f9f9') . ';
}
.urm-user-table tr:nth-child(odd) {
background-color: ' . ($frontend ? 'inherit' : '#ffffff') . ';
}
/* ✨ Hover effect front-end friendly */
.urm-user-table tr:hover {
background-color: ' . ($frontend ? 'rgba(255,255,255,0.05)' : '#eee') . ';
}
</style>';
// 🧾 Table
echo '<table class="urm-user-table" style="width: 100%; border-collapse: collapse; margin-top: 10px;">';
echo '<thead><tr>
<th>🆔 ID</th>
<th>🖼️ Avatar</th>
<th>👤 Username</th>
<th>📧 Email</th>
<th>⏱️ Role start date</th>
<th>⏳ Role expiration date</th>
<th>🎭 Active role</th>
<th>⚙️ Revoke role</th>
</tr></thead>';
echo '<tbody>';
// 📋 Pages and retrieval
$per_page = isset($_GET['per_page']) ? max(1, intval($_GET['per_page'])) : 8;
$paged = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
$offset = ($paged - 1) * $per_page;
$all_users = get_users(['meta_key' => 'ur_role_history', 'meta_compare' => 'EXISTS', 'number' => -1]);
$total_users = count($all_users);
$rows = [];
foreach ($all_users as $user) {
if ($search) {
$match = false;
if (stripos((string)$user->ID, $search) !== false) $match = true;
elseif (stripos($user->user_login, $search) !== false) $match = true;
elseif (stripos($user->user_email, $search) !== false) $match = true;
if (!$match) continue;
}
$history = get_user_meta($user->ID, 'ur_role_history', true);
if (!is_array($history)) continue;
foreach ($history as $entry) {
if (!isset($entry['start'], $entry['end'], $entry['role'])) continue;
$rows[] = [
'user' => $user,
'entry' => $entry,
'latest' => $entry['start'] ?? 0
];
}
}
// ⏱️ Sort by date
usort($rows, fn($a, $b) => $b['latest'] <=> $a['latest']);
$total_pages = ceil(count($rows) / $per_page);
$paginated_rows = array_slice($rows, $offset, $per_page);
$rows_rendered = 0;
foreach ($paginated_rows as $row) {
$user = $row['user'];
$entry = $row['entry'];
$start = date('d.m.Y. H:i:s', $entry['start']);
$end = date('d.m.Y. H:i:s', $entry['end']);
$role = format_role_name($entry['role']);
$duration = $entry['duration'];
echo '<tr>';
echo '<td>' . $user->ID . '</td>';
echo '<td><center>' . get_avatar($user->ID, 40) . '</center></td>';
echo '<td><a href="' . esc_url(get_author_posts_url($user->ID)) . '" target="_blank">' . esc_html($user->user_login) . '</a></td>';
echo '<td><a href="mailto:' . esc_attr($user->user_email) . '">' . esc_html($user->user_email) . '</a></td>';
echo '<td>' . $start . '</td>';
echo '<td>' . $end . '</td>';
echo '<td>' . esc_html(format_role_name($user->roles[0] ?? 'N/A')) . '</td>';
// 🔘 Manual revocation
echo '<td>';
if (function_exists('user_has_active_membership_role') && user_has_active_membership_role($user->ID)) {
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" onsubmit="return confirm(\'⚠️ Are you sure you want to revoke this user role?\');">';
echo '<input type="hidden" name="action" value="revoke_user_role">';
echo '<input type="hidden" name="user_id" value="' . intval($user->ID) . '">';
echo '<input type="hidden" name="redirect_to" value="' . esc_url($_SERVER['REQUEST_URI']) . '">';
echo '<center><button type="submit" style="background: #dc3545; color: white; border: none; padding: 6px 12px; border-radius: 4px; cursor: pointer;">⛔ Revoke role</button></center>';
echo '</form>';
} else {
echo '<center><span style="color: gray;">– – – – –</span></center>';
}
echo '</td>';
echo '</tr>';
$rows_rendered++;
}
if ($rows_rendered === 0) {
echo '<tr><td colspan="8" style="text-align: center; color: gray;">There are no results to display.</td></tr>';
}
echo '</tbody></table>';
// 🔢 Navigation
if ($total_pages > 1) {
echo '<div style="margin-top: 15px;">';
for ($i = 1; $i <= $total_pages; $i++) {
$query = [
'paged' => $i,
'per_page' => $per_page,
'search_user' => $search
];
$url = $frontend ? add_query_arg($query, get_permalink()) : add_query_arg($query);
$active = ($i === $paged) ? 'font-weight: bold;' : '';
echo '<a href="' . esc_url($url) . '" style="margin-right:10px; ' . $active . '">Page ' . $i . '</a>';
}
echo '</div>';
}
// 🎛️ Number of entries per page
echo '<form method="get" action="' . ($frontend ? esc_url(get_permalink()) : esc_url(admin_url('admin.php?page=ur-membership&tab=users'))) . '" style="margin-top: 20px; display: inline-block;">';
if (!$frontend) {
echo '<input type="hidden" name="page" value="ur-membership">';
echo '<input type="hidden" name="tab" value="users">';
}
echo '<input type="hidden" name="search_user" value="' . esc_attr($search) . '">';
echo 'Show <input type="number" name="per_page" value="' . esc_attr($per_page) . '" min="5" max="500" style="width:60px;"> entry by page ';
echo '<button type="submit" style="margin-left:10px; background: #198754; color: white; border: none; padding: 8px 20px; border-radius: 4px; cursor: pointer;">🔄 Apply</button>';
echo '</form>';
// ⬇️ CSV export
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" style="margin-top: 20px; display: inline-block; margin-left: 20px;">';
echo '<input type="hidden" name="action" value="export_ur_user_list">';
echo '<button type="submit" style="background: #0d6efd; color: white; border: none; padding: 8px 20px; border-radius: 4px; cursor: pointer;">⬇️ Export CSV</button>';
echo '</form>';
// 🗑️ Clean the list of users whose roles have expired
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" style="margin-top: 20px; display: inline-block; margin-left: 20px;" onsubmit="return confirm(\'⚠️ Are you sure you want to delete users with expired roles?\');">';
echo '<input type="hidden" name="action" value="ur_user_list_cleanup">';
echo '<button type="submit" style="background: #dc3545; color: white; border: none; padding: 8px 20px; border-radius: 4px; cursor: pointer;">🧹 Clean expired roles</button>';
echo '</form>';
echo '</div>'; // close .padding
echo '</div>'; // close .frame
}
add_shortcode('ur_user_list', function () {
if (!current_user_can('manage_options')) {
return '<center><div style="color:red; font-weight:bold;">⛔ This page is only available to administrators.</div></center>';
}
ob_start();
render_ur_user_list_tab(true);
return ob_get_clean();
});
add_action('admin_post_export_ur_user_list', function () {
if (!current_user_can('manage_options')) wp_die('⛔ You do not have sufficient permissions.');
$users = get_users(['meta_key' => 'ur_role_history', 'meta_compare' => 'EXISTS', 'number' => -1]);
if (empty($users)) wp_die('📭 There are no users for export.');
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename="ur_user_list.csv"');
$output = fopen('php://output', 'w');
fputcsv($output, ['ID', 'Username', 'Email', 'Start', 'Expiration', 'Role', 'Duration']);
foreach ($users as $user) {
$history = get_user_meta($user->ID, 'ur_role_history', true);
if (!is_array($history)) continue;
foreach ($history as $entry) {
fputcsv($output, [
$user->ID,
$user->user_login,
$user->user_email,
date('d.m.Y.', $entry['start']),
date('d.m.Y.', $entry['end']),
format_role_name($entry['role']),
$entry['duration']
]);
}
}
fclose($output);
exit;
});
add_action('admin_post_revoke_user_role', function () {
if (!current_user_can('manage_options')) wp_die('⛔ You do not have sufficient permissions.');
$user_id = intval($_POST['user_id'] ?? 0);
if (!$user_id) wp_die('⛔ Invalid user.');
// 🧠 Debug check
add_to_ur_process_log("🧠 Revert correction — user_id: {$user_id}", 'info');
// 🔪 Attempt to recall the scheduled cron job
$event = wp_get_scheduled_event('reset_user_role', [$user_id]);
if ($event) {
wp_unschedule_event($event->timestamp, 'reset_user_role', [$user_id]);
add_to_ur_process_log("🔧 Cron role reset removed for user ID $user_id.", 'info');
} else {
add_to_ur_process_log("ℹ️ Cron for user ID $user_id not found or has already been executed.", 'info');
}
// ♻️ Reset role
$user = new WP_User($user_id);
$user->set_role('subscriber');
// 📝 Log manual recall
add_to_ur_process_log("⛔ Manual revoked of privileged role for user ID $user_id.", 'warning');
$redirect_url = $_POST['redirect_to'] ?? ($_SERVER['REQUEST_URI'] ?? admin_url('admin.php?page=ur-membership&tab=users'));
$redirect_url = add_query_arg('revoked', $user_id, $redirect_url);
wp_redirect($redirect_url);
exit;
});
add_action('admin_post_ur_user_list_cleanup', function () {
if (!current_user_can('manage_options')) wp_die('⛔ You do not have sufficient permissions.');
$users = get_users([
'meta_key' => 'ur_role_history',
'meta_compare' => 'EXISTS',
'number' => -1
]);
$cleared = 0;
foreach ($users as $user) {
$history = get_user_meta($user->ID, 'ur_role_history', true);
if (!is_array($history) || empty($history)) continue;
// If the user no longer has an active privileged role - they may be cleaned.
if (function_exists('user_has_active_membership_role') && user_has_active_membership_role($user->ID)) {
continue;
}
// Additional check: we do not touch users who still have multiple roles except 'subscriber'
$roles = $user->roles;
if (count($roles) === 1 && $roles[0] === 'subscriber') {
delete_user_meta($user->ID, 'ur_role_history');
add_to_ur_process_log("🧹 Cleared record for user ID {$user->ID}", 'info');
$cleared++;
}
}
wp_redirect(add_query_arg(['cleaned' => $cleared], wp_get_referer() ?: admin_url()));
exit;
});
URM – User Logic
Provides core access control functions for the User Role Membership system.
Includes route validation, role history storage, and admin view permissions.
Used internally by other URM modules to secure functionality.
<?php
/**
* URM – User Logic
*
* Provides core access control functions for the User Role Membership system.
* Includes route validation, role history storage, and admin view permissions.
* Used internally by other URM modules to secure functionality.
*
* Author: Black & Copilot
*/
// 👥 URM - User logic
function urm_global_guard() {
if (!is_user_logged_in() || !current_user_can('manage_options')) {
wp_die('⛔ You do not have sufficient permission to access URM functions.');
}
}
function save_ur_user_role_history($user_id, $role, $duration, $start, $end) {
$history = get_user_meta($user_id, 'ur_role_history', true);
if (!is_array($history)) $history = [];
$history[] = [
'role' => $role,
'duration' => $duration,
'start' => $start,
'end' => $end
];
update_user_meta($user_id, 'ur_role_history', $history);
}
function urm_guard_router_integrity() {
$valid_tabs = ['dashboard', 'log', 'users', 'help'];
$active = $_GET['tab'] ?? 'dashboard';
if (!in_array($active, $valid_tabs)) {
wp_die('⛔ The requested tab is invalid.');
}
}
function urm_guard_admin_view() {
if (!current_user_can('manage_options')) {
add_to_ur_process_log('❌ Unauthorized attempt to access the log view.', 'warning');
wp_die('⛔ Access allowed only to administrators.');
}
}
URM – Instructions
This file provides an overview of the purpose and functionality of the User Role Membership snippet.
Includes detailed setup guidelines, examples of integration with third-party plugins (e.g. Gamipress),
and philosophical notes on the author’s approach to user roles and gamification.
<?php
/**
* URM – Instructions
*
* This file provides an overview of the purpose and functionality of the User Role Membership snippet.
* Includes detailed setup guidelines, examples of integration with third-party plugins (e.g. Gamipress),
* and philosophical notes on the author's approach to user roles and gamification.
*
* Author: Black & Copilot
*/
// 🆘 URM - Instructions
function render_ur_help_tab() {
echo '<div style="margin-top: 0px; background: #fff; border: 1px solid #ccc; border-radius: 0px; box-shadow: 0 1px 3px rgba(0,0,0,0.05); overflow: hidden;">';
// 👑 Frame header
echo '<div style="background: #f7f9fa; padding: 10px 16px; border-bottom: 1px solid #ccc; font-weight: bold; font-size: 16px;">📋 Instructions</div>';
// 📦 Internal content
echo '<div style="padding: 0px 20px 20px 20px;">';
echo '<p style="color: black; font-size: 16px"><strong>📘 Introduction</strong></p>';
echo '<p style="color: gray; font-size: 16px">I created this snippet because I couldn`t find any plugin that would provide me exactly what I need without unnecessary features and options. No plugin provides the option to assign a role with a time duration by spending points that the user has earned through their activity on the site. Also, this snippet does not define role capabilities directly but relies on already existing plugins. One such plugin is the excellent and free plugin "User Role Editor". I personally use the "PeepSo Ultimate Bundle" which includes the "User Restrictions" add-on for configuring user role permissions. This snippet does not contain a payment gateway since it relies on gamification plugins on the other side that have their own add-ons for controlling the balance of user points and the ability to deduct points based on the assignment of user roles. I personally use the "Gamipress" plugin with its add-ons.</p>';
echo '<p style="color: black; font-size: 16px"><strong>📘 What does this snippet do?</strong></p>';
echo '<p style="color: gray; font-size: 16px">The Snippet User Role Membership is used for creating or deleting privileged roles with the possibility of configuring the duration of the role. Time-based roles are created by entering the role name in the field "Enter role name" and by clicking the "Add" button you create a new role. For each duration, it is necessary to create a special role. For different durations, you can use the same role name but add a suffix depending on the time duration. For example: "Basic_1_m" for the role to which you will assign 1 month duration, "Basic_3_m" for 3 months, etc. Between the role name and the continuation, an underscore is mandatory, and the role must be a single word because some plugins do not understand spaces in the role name. Basically, you can use any role name of your choice as long as you make sure to assign the right duration to the right role. The quantity and type of points here are to inform the future created shortcode how many and which points you will deduct for each role, and the shortcode will display this along with other information on the frontend of the page. Regardless of which gamification plugin you are using, you will need to customize this part according to your needs in such a way that value="slug" of your point, and "First" is the name you gave it when creating points in the gamification plugin. In my example, there are three types of points, but you can add or subtract as many types of points as you want.</p>';
$code_snippet = '<!-- Type of points -->
<div style="flex: 1 1 100px;">
<label for="points_type" style="font-weight: bold;">Type of points:</label><br>
<select name="points_type" id="points_type" style="width: 100%;" required>
<option value="first">First</option>
<option value="second">Second</option>
<option value="third">Third</option>
</select>
</div>';
echo '<p style="color: gray; font-size: 16px;"><strong>Regardless of which gamification plugin you use, you will need to customize it:</strong></p>';
echo '<pre style="background: #f5f5f5; padding: 10px; border: 1px solid #ccc; border-radius: 6px; color: black; font-size: 14px;"><code>' . htmlspecialchars($code_snippet) . '</code></pre>';
echo '<p style="color: gray; font-size: 16px">Now in the window "Add role duration" enter the name of the newly created role, select the duration of the role, select quantity and type of points. When you correctly fill in all fields and click the button "Create short code in the table", you will receive a short code that will, when you set it on the frontend, show, for example: "Role: Basic 1 month, Duration: 1 month, Price: 100 Points" and show the button "Change Role". You can name your frontend page where you will place the short codes <b>❌ Morate biti prijavljeni da biste vidjeli svoju ulogu.</b>, <b></b>, and <b>⛔ Morate biti prijavljeni da biste vidjeli svoju ulogu.</b> whatever you want, but the page slug must be "https://example.com/<strong>membership</strong>" or code modification will be required if you want to change it. I suggest that you place the shortcodes <b>❌ Morate biti prijavljeni da biste vidjeli svoju ulogu.</b> and <b></b> at the top of the page, either next to each other or one below the other, so they are always visible to the user. You should design how each individual card will look on your page for users, and attach the corresponding shortcode <b>⛔ Morate biti prijavljeni da biste vidjeli svoju ulogu.</b> that should be consistent with the respective card.</p>';
echo '<p style="color: black; font-size: 16px"><strong>📘 Other options</strong></p>';
echo '<p style="color: gray; font-size: 16px"> When you first try to create shortcodes <b>⛔ Morate biti prijavljeni da biste vidjeli svoju ulogu.</b>, you might make a mistake, but don`t worry, there is a "Delete" button at the end of each row so you can remove the incorrect row and fix the issue. If you, for some reason, want to reset the entire table, you can do so with the "Reset the table" button, and a backup of the table will be automatically created, so if you want to restore it, you can always do that with the "Restore" button. You can also download your records from the table to your PC by clicking on "Download". If you think you no longer need the backup, it can be easily deleted by clicking on "Delete" button.<br><br>User Role Membership monitors the process of assigning or revoking roles to individual users, as well as all other processes that take place in the background. If you click on the "Process monitoring" tab, you will see a detailed list of processes occurring while using this snippet, organized by time and date. You can export the processes in CSV format for analysis or for whatever you need by clicking the "Export CSV" button. You can also clear the log at any time by clicking the "Clear records" button. If the default entry view is too short, set the number of entries you want and confirm by clicking the "Apply" button, or you can browse through pages.<br><br>Every administrator should know firsthand which user is using which role and for what duration. To control roles, we use the "User list" tab. Here we have the option to search for users by user ID number, username, or email. Below the search bar, there is a table listing all users with privileged roles sorted by the most recently activated roles to the oldest roles. Each user has their ID number, avatar, username, email, start date of the role, expiration date of the role, currently active role, and a "Revoke role" button displayed. Clicking on the username takes us to the user`s front-end profile, and clicking on the email opens the administrator`s email client on their computer so they can contact the specific user if necessary.<br><br>The "Revoke role" button serves multiple purposes. The administrator can use it to revoke a user role if the user has committed an offense and exceeded or abused the powers assigned to their role. It can also be used to suspend a role at the user`s request if the user decides to switch to another role before the expiration of their previous role. The "Revoke role" button always returns the user to the default WordPress role. Due to the verification of whether the user has an active privileged role or not, the user will be prevented from selecting <i>"role on role"</i> which means they will receive a message that they cannot add another role to an already active role until their active role expires, so their only option is to contact the administrator who can revoke their currently active role. Administrators can control the "User list" from the frontend using the shortcode <b>⛔ Ova stranica je dostupna samo administratorima. </b> which should be placed on a special page that only administrators will have access to. In addition to the option of how many rows of users they want to display, administrators also have the option to export to CSV, as well as a "Clean expired roles" button that will remove users whose privileged role has expired and who have not renewed it.</p>';
echo '<p style="color: black; font-size: 16px"><strong>📘 What does this snippet not do?</strong></p>';
echo '<p style="color: gray; font-size: 16px">As I wrote in the introduction, this User Role Membership snippet relies on other plugins when it comes to configuring the permissions of a specific role. With this snippet, the created privileged role for User Role Membership does not differ in any way, except in name, from the default role "Subscriber". You will have to make all the fine adjustments using the already existing plugins. Also, this snippet will not charge for the use of the role. He will only set its duration for the user who selects it. You will need to set the payment of a certain amount of points and the type of points in the plugin you are using for gamification. Before using this snippet, make sure that the gamification plugin you are using can deduct points based on the assigned role.</p>';
echo '<p style="color: black; font-size: 16px"><strong>📘 Personal example</strong></p>';
echo '<p style="color: gray; font-size: 16px">Since I am using the "PeepSo Ultimate Bundle" plugin and add-ons for creating a social community, I grant permissions to my created roles through the "User Restrictions" add-on. I have decided on the roles "Basic User", "Advanced User" and "Professional User" which are different in nature, as a higher role level provides more opportunities on the site. For each of those roles, I set the options for selecting time based roles, but with different ways of paying with points.<br><br>For short-term roles, I have enabled payment with points that users of my site can earn through various activities. I have configured 30+ ways for each user to easily earn points every day. In approximately 4 days of active participation, the user can accumulate enough points to unlock "Basic User" role for one month. I have limited the award of points for each action to one per day so that users do not spam the page for points. For longer periods of time, users pay with points awarded as a donation incentiv for donating to the site. Since my site is non-profit, and it does not contain any advertisements, I cover all expenses out of my <i>"own pocket"</i>, so donations are welcome, but users also receive points in countervalue that they can spend on exclusive roles.<br><br>Win-Win arrangement. Users gain an extension of their capabilities and motivation for active participation, while I receive help for server costs and necessary plugins. I am trying to reduce the number of plugins by using snippets, and for the plugins I must have, I am trying to purchase "Life Time" licenses to reduce costs in the future. I also use free plugins if they satisfies my needs. In case I cannot find either a plugin or a snippet that suits me, I have to take matters into my own hands and then a snippet like User Role Membership is created.<br><br>And now about gamification. For gamification, I probably use the best plugin for gamification "Gamipress" and its free and paid add-ons. I created three types of points: Gift point, Referral point, and Donation point. I won`t describe what each point is for because the name itself speaks for it. It is important that some of the points can be exchanged at the exchange, while others, due to their nature, can only be exchanged in one direction. Users are also rewarded when they successfully recommend the site to friends. Yes, all these possibilities are provided by the "Gamipress Bundle" and much more.<br><br>An addition that is important for integration with the User Role Membership snippet is "Restrict Content" and within it the block "Show content if." Let me explain why this add-on. The possibility for "Gamipress" to award or deduct points for certain actions is tremendously vast and there are many integrations through which these capabilities have been expanded. When I created the points that users will pay with, I went to Points Type -> Point name and within each point, I created an automatic deduction of a certain amount of points for granting a specific role. You should do this by selecting "WordPress" using the "Automatic Points Deducts" interface, and then within it, "Get assigned to a role", and then you choose the role that is assigned from the dropdown menu next to it. Below that, enter the number of points you are deducting from the user, and again below that, fill in the label that will be displayed in the user`s earnings and deductions.<br><br>All of this works nicely when it comes to awarding points, but when it comes to deducting points, the situation is not the best because "Deducting points" in the "Gamipress" plugin has one major flaw. If the user`s wallet is empty, "Automatic Points Deducts" will still work and will deduct, for example, -10 points even if the point balance is 0. For this reason, we use the "Restrict Content" add-on -> "Show content if" which will check if the user has enough points to deduct if they want to activate a privileged role. If the user does not have enough points, they will be greeted with a message stating how many points they need to have before the content of the shortcode for changing roles is revealed. If the user has enough points, they will immediately be greeted with the content of our shortcode and a "Change role" button. And when you set all this up correctly following Gamipress documentation, you will have a complete gamification system with concrete application.</p>';
echo '</div>'; // close padding
echo '</div>'; // close frame
}PeepSo Community Blog post improvements
- Inserting CSS inside embed iframe
- Improvements for embedding blog posts in the PeepSo community stream
- Author: mypetcafe.eu
<?php
/**
* PeepSo Community Blog post improvements
*
* Inserting CSS inside <head> embed iframe
* Improvements for embedding blog posts in the PeepSo community stream
*
* Author: mypetcafe.eu
*/
function my_custom_wp_embed_styles() {
?>
<style>
.wp-embed {
font-size: 16px !important;
color: #8C8C8C !important;
border: none !important;
background: none !important;
}
.wp-embed a {
font-size: 18px !important;
color: #e53935 !important;
border: none !important;
background: none !important;
}
.wp-embed-footer {
display: none !important;
}
</style>
<?php
}
add_action('embed_head', 'my_custom_wp_embed_styles');

