Lecteur Markdown
BEAMREACTOR_PROFILE_SYSTEM
BeamReactor – Profile System & Profile Extensions #
> Version: 1.0
> Date: 2026-03-24
> Audience: Plugin developers for the BeamReactor CMS
> Scope: User profiles, ProfileManager class, profile extension mechanism
---
1. Overview #
The profile system handles user profile display and editing through three core files:
| File | Role |
|------|------|
| `members/view_profile.php` | Public profile display |
| `members/edit_profile.php` | Profile editing (logged-in user) |
| `lib/profilemanager/ProfileManager.class.php` | Profile data operations & extension discovery |
Profiles are stored in the main users table (`$cfg['dbtable']`).
---
2. ProfileManager Class #
Namespace: `Beamreactor\Members\ProfileManager`
Auto-loaded via CoreAutoloader.
Instantiation #
use Beamreactor\Members\ProfileManager;
$profileManager = new ProfileManager($cfg);
Core Methods #
| Method | Returns | Description |
|--------|---------|-------------|
| `getProfileBySession(array $session)` | `array\|false` | Fetch profile matching session data |
| `getProfileByUsername(string $username)` | `array\|false` | Fetch profile by username |
| `getProfileById(int $userid)` | `array\|false` | Fetch profile by user ID |
| `updateEmail(int $userid, string $email)` | `bool` | Update email address |
| `updateInfo(int $userid, string $info)` | `bool` | Update bio/info text |
| `updateSkin(int $userid, string $skin)` | `bool` | Change skin preference |
| `updateLanguage(int $userid, string $language)` | `bool` | Change language preference |
| `updateNewsletter(int $userid, int $enabled)` | `bool` | Toggle newsletter subscription |
| `updatePrivacy(int $userid, int $privacy)` | `bool` | Set privacy level (0-4) |
| `updateAvatar(string $username, array $file, array $cfg)` | `array` | Upload and process avatar |
| `validateProfileData(array $data)` | `array` | Validate profile fields |
| `getAvailableSkins()` | `array` | List installed skins |
| `getAvailableLanguages()` | `array` | List available languages |
Privacy Levels #
| Level | Visibility |
|-------|-----------|
| 0 | Public |
| 1 | Members only |
| 2 | Friends only |
| 3 | Private |
| 4 | Hidden |
---
3. Profile Extensions #
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>';
?>
---
4. Integration Points #
Where Extensions Render #
- view_profile.php: After the "About" section, before end of page
- edit_profile.php: Before the submit button, inside the main form
Processing Edit Data #
Profile extensions that add form fields in `edit` mode must handle their own POST processing. This is typically done in the plugin's main handler (`handlers/{name}.mod.php`) or by hooking into `update_profile.php`.