Plugins can inject dynamic sections into user profiles. This is the mechanism for adding content like transaction ratings, badges, activity feeds, or any plugin-specific data to a user's profile page.
Convention #
plugins/{plugin_name}/profile/{plugin_name}.profile.php -> view_profile
plugins/{plugin_name}/profile/{plugin_name}.profile_edit.php -> edit_profile
Both files are optional. A plugin can provide one, both, or neither.
Discovery #
ProfileManager::getProfileExtensions($context) scans plugins/*/profile/ for matching files.
- Context
'view' looks for {name}.profile.php - Context
'edit' looks for {name}.profile_edit.php - Results are cached (static) — disk scan happens once per request
Z_DEPRECATED folder is excluded- Extensions are sorted alphabetically by plugin name
Rendering #
ProfileManager::renderProfileExtensions($user, $cfg, $context) handles inclusion:
1. Iterates over discovered extensions
2. Preloads each plugin's locale via getlocale()
3. Includes the extension file
Available Variables #
Every profile extension file receives these variables in scope:
| Variable | Type | Description |
|----------|------|-------------|
| $user | array | Full user row from the users table |
| $cfg | array | Global configuration |
| $profile_context | string | 'view' or 'edit' |
| $profile_userid | int | User ID of the profile being displayed |
The plugin's locale array (e.g. $dialmarketplace) is already loaded.
Rules #
- No
frameheader() / framefooter() in extensions. They render inside an existing frame. - Use
return; to exit cleanly if there is nothing to display. - Always close the PHP tag (
?>). - No
exit() or die(). - Check table existence before querying (
SQL::tableExists()).
Example: Transaction Ratings #
File: plugins/marketplace/profile/marketplace.profile.php
<?php
use Beamreactor\Database\SQL;
// $user, $cfg, $profile_context, $profile_userid available
// $dialmarketplace already loaded
if(!SQL::tableExists('marketplace_ratings')) return;
$count = SQL::queryValue(
'SELECT COUNT(*) FROM marketplace_ratings WHERE seller_id = ?',
[$profile_userid]
);
if($count < 1) return;
$avg = SQL::queryValue(
'SELECT AVG(rating) FROM marketplace_ratings WHERE seller_id = ?',
[$profile_userid]
);
$ratings = SQL::query(
'SELECT rating, comment, buyer_username, created_at
FROM marketplace_ratings
WHERE seller_id = ?
ORDER BY created_at DESC LIMIT 10',
[$profile_userid]
);
echo '<h3>' . ($dialmarketplace[20] ?? 'Transaction Reviews') . '</h3>';
echo '<p>' . round($avg, 1) . '/5 (' . $count . ' ' . ($dialmarketplace[21] ?? 'reviews') . ')</p>';
foreach($ratings as $r)
{
$stars = str_repeat('★', (int)$r['rating']) . str_repeat('☆', 5 - (int)$r['rating']);
echo '<p>' . $stars . ' - ' . htmlspecialchars($r['comment']);
echo ' <small>(' . htmlspecialchars($r['buyer_username']) . ')</small></p>';
}
?>
Example: Edit Mode Extension #
File: plugins/marketplace/profile/marketplace.profile_edit.php
<?php
use Beamreactor\Database\SQL;
// Only show if user has seller status
$seller = SQL::queryFirst(
'SELECT seller_bio, accept_trades FROM marketplace_sellers WHERE userid = ?',
[$profile_userid]
);
if(!$seller) return;
echo '<div class="form-section">';
echo '<h3>' . ($dialmarketplace[30] ?? 'Seller Settings') . '</h3>';
echo '<div class="form-group">';
echo '<label for="seller_bio">' . ($dialmarketplace[31] ?? 'Seller Bio') . '</label>';
echo '<textarea id="seller_bio" name="marketplace_seller_bio" maxlength="1000">';
echo htmlspecialchars($seller['seller_bio'] ?? '');
echo '</textarea>';
echo '</div>';
echo '</div>';
?>