mirror of
https://github.com/itflow-org/itflow
synced 2026-09-05 22:35:12 +00:00
Feature: Major spruce up of the client portal profile page, added Department, Location, Title, Phone with Edit, editing Pin, Recent signins and recent activity along with a seperate activity page
This commit is contained in:
@@ -4,45 +4,501 @@
|
||||
* User profile
|
||||
*/
|
||||
|
||||
// Read by client/includes/header.php and footer.php - see the note there.
|
||||
// Must be set before inc_all.php, which pulls the header in.
|
||||
$portal_load_phone_inputs = true;
|
||||
|
||||
header("Content-Security-Policy: default-src 'self'");
|
||||
|
||||
require_once 'includes/inc_all.php';
|
||||
|
||||
/*
|
||||
* check_login.php runs these through escapeSql(), which is for writing to the
|
||||
* database rather than printing. It leaves a backslash in front of any quote in
|
||||
* the value - O'Brien renders as O\'Brien - which is why the name was already
|
||||
* wrapped in stripslashes() here. Email, PIN and company were printed raw, and
|
||||
* the company name had no escaping of any kind. Normalise all four in one place
|
||||
* and escape at the point of output.
|
||||
*/
|
||||
$contact_name = escapeHtml(stripslashes($session_contact_name));
|
||||
$contact_email = escapeHtml(stripslashes($session_contact_email));
|
||||
$contact_pin = escapeHtml(stripslashes($session_contact_pin));
|
||||
$client_name = escapeHtml(stripslashes($session_client_name));
|
||||
|
||||
/*
|
||||
* check_login.php loads the handful of contact columns every portal page needs.
|
||||
* The rest are only wanted here, so they are fetched on this page rather than
|
||||
* added to a query that runs on every request. Scoped to the session contact
|
||||
* and their client - nothing here reads an id from the request.
|
||||
*/
|
||||
$row = mysqli_fetch_assoc(mysqli_query(
|
||||
$mysqli,
|
||||
"SELECT contact_department, contact_extension, contact_location_id, contact_mobile,
|
||||
contact_mobile_country_code, contact_phone, contact_phone_country_code
|
||||
FROM contacts
|
||||
WHERE contact_id = $session_contact_id AND contact_client_id = $session_client_id
|
||||
LIMIT 1"
|
||||
));
|
||||
|
||||
$contact_title = escapeHtml(stripslashes($session_contact_title));
|
||||
$contact_department = escapeHtml($row['contact_department']);
|
||||
$contact_phone_country_code = escapeHtml($row['contact_phone_country_code']);
|
||||
$contact_extension = escapeHtml($row['contact_extension']);
|
||||
$contact_mobile_country_code = escapeHtml($row['contact_mobile_country_code']);
|
||||
$contact_location_id = intval($row['contact_location_id']);
|
||||
|
||||
/*
|
||||
* Two renderings of the same digits. The tables show them formatted for
|
||||
* reading; the modal inputs get formatPhoneNumber(..., false), which is the
|
||||
* form-input variant - intl-tel-input runs in separateDialCode mode, so the
|
||||
* visible input holds the national number only and the dial code lives in the
|
||||
* hidden field beside it.
|
||||
*/
|
||||
$contact_phone = escapeHtml(formatPhoneNumber($row['contact_phone'], $row['contact_phone_country_code']));
|
||||
$contact_mobile = escapeHtml(formatPhoneNumber($row['contact_mobile'], $row['contact_mobile_country_code']));
|
||||
$contact_phone_input = escapeHtml(formatPhoneNumber($row['contact_phone'], $row['contact_phone_country_code'], false));
|
||||
$contact_mobile_input = escapeHtml(formatPhoneNumber($row['contact_mobile'], $row['contact_mobile_country_code'], false));
|
||||
|
||||
/*
|
||||
* The location is joined on client too, so a contact whose location_id points
|
||||
* at another client's row - stale data, a moved contact - gets nothing rather
|
||||
* than another company's address.
|
||||
*/
|
||||
$contact_location = '';
|
||||
$contact_location_address = '';
|
||||
if (!empty($contact_location_id)) {
|
||||
$location = mysqli_fetch_assoc(mysqli_query(
|
||||
$mysqli,
|
||||
"SELECT location_address, location_city, location_country, location_name, location_state, location_zip
|
||||
FROM locations
|
||||
WHERE location_id = $contact_location_id
|
||||
AND location_client_id = $session_client_id
|
||||
AND location_archived_at IS NULL
|
||||
LIMIT 1"
|
||||
));
|
||||
if ($location) {
|
||||
$contact_location = escapeHtml($location['location_name']);
|
||||
$contact_location_address = nl2br(escapeHtml(formatAddress(
|
||||
$location['location_address'],
|
||||
$location['location_city'],
|
||||
$location['location_state'],
|
||||
$location['location_zip'],
|
||||
$location['location_country']
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
$login_method = $_SESSION['login_method'] ?? 'local';
|
||||
|
||||
if ($login_method === 'local') {
|
||||
$login_method_display = 'Password';
|
||||
} elseif ($login_method === 'azure') {
|
||||
$login_method_display = 'Microsoft account';
|
||||
} else {
|
||||
$login_method_display = escapeHtml(ucfirst($login_method));
|
||||
}
|
||||
|
||||
/*
|
||||
* These three decide which sections of the portal a contact can reach - the
|
||||
* same flags contactCan() switches on - so the answer to "why can't I see
|
||||
* invoices" is on this page rather than only in the agent's copy of the record.
|
||||
*
|
||||
* All three are booleans. The billing row used to be written as
|
||||
* ($session_contact_is_billing_contact == $session_contact_id), comparing a
|
||||
* bool to a contact id. PHP casts the id to bool for that comparison, so it
|
||||
* gave the right answer for every real contact and would only have misreported
|
||||
* at id 0 - working by luck rather than by meaning.
|
||||
*/
|
||||
$contact_roles = [
|
||||
['Primary', (bool) $session_contact_primary, 'Everything in the portal'],
|
||||
['Billing', (bool) $session_contact_is_billing_contact, 'Invoices, quotes and payments'],
|
||||
['Technical', (bool) $session_contact_is_technical_contact, 'Assets, documents, domains and contacts'],
|
||||
];
|
||||
|
||||
/*
|
||||
* Recent activity, both scoped on log_user_id - the portal user this contact
|
||||
* signs in as. logAudit() fills that from the $session_user_id global, and
|
||||
* login.php deliberately sets it before logging a client login ("Option B" in
|
||||
* that file), so both the sign-ins and the actions carry it. An agent working
|
||||
* on this client writes their own user id, so their work never appears here.
|
||||
*
|
||||
* Successful sign-ins only. Failed attempts are logged without a reliable user
|
||||
* id - login.php cannot always resolve one from a bad email - so they cannot
|
||||
* honestly be attributed to this contact.
|
||||
*/
|
||||
$sql_logins = mysqli_query(
|
||||
$mysqli,
|
||||
"SELECT log_created_at, log_ip FROM logs
|
||||
WHERE log_type = 'Client Login'
|
||||
AND log_action = 'Success'
|
||||
AND log_user_id = $session_user_id
|
||||
AND log_client_id = $session_client_id
|
||||
ORDER BY log_id DESC
|
||||
LIMIT 5"
|
||||
);
|
||||
|
||||
$sql_actions = mysqli_query(
|
||||
$mysqli,
|
||||
"SELECT log_action, log_created_at, log_description, log_type FROM logs
|
||||
WHERE log_user_id = $session_user_id
|
||||
AND log_client_id = $session_client_id
|
||||
AND log_type != 'Client Login'
|
||||
ORDER BY log_id DESC
|
||||
LIMIT 5"
|
||||
);
|
||||
|
||||
?>
|
||||
|
||||
<h2>Profile</h2>
|
||||
<h3>Profile</h3>
|
||||
<hr>
|
||||
|
||||
<p>Name: <?= stripslashes(escapeHtml($session_contact_name)) ?></p>
|
||||
<p>Email: <?= $session_contact_email ?></p>
|
||||
<p>PIN: <?= $session_contact_pin ?></p>
|
||||
<p>Client: <?= $session_client_name ?></p>
|
||||
<br>
|
||||
<p>Client Primary Contact: <?php if ($session_contact_primary == 1) {echo "Yes"; } else {echo "No";} ?></p>
|
||||
<p>Client Technical Contact: <?php if ($session_contact_is_technical_contact) {echo "Yes"; } else {echo "No";} ?></p>
|
||||
<p>Client Billing Contact: <?php if ($session_contact_is_billing_contact == $session_contact_id) {echo "Yes"; } else {echo "No";} ?></p>
|
||||
<br>
|
||||
<p>Login via: <?= $_SESSION['login_method'] ?> </p>
|
||||
<p>User ID: <?= $_SESSION['user_id'] ?> </p>
|
||||
<div class="row">
|
||||
|
||||
<div class="col-lg-7 mb-4">
|
||||
|
||||
<table class="table table-bordered border border-dark">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th colspan="2">Your details</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th class="w-25">Name</th>
|
||||
<td><?= $contact_name ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Email</th>
|
||||
<td><?= $contact_email ?></td>
|
||||
</tr>
|
||||
<?php if (!empty($contact_title)) { ?>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<td><?= $contact_title ?></td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
<?php if (!empty($contact_department)) { ?>
|
||||
<tr>
|
||||
<th>Department</th>
|
||||
<td><?= $contact_department ?></td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
<tr>
|
||||
<th>Company</th>
|
||||
<td><?= $client_name ?></td>
|
||||
</tr>
|
||||
<?php if (!empty($contact_location)) { ?>
|
||||
<tr>
|
||||
<th>Location</th>
|
||||
<td>
|
||||
<?= $contact_location ?>
|
||||
<?php if (!empty($contact_location_address)) { ?>
|
||||
<br><small class="text-muted"><?= $contact_location_address ?></small>
|
||||
<?php } ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
<tr>
|
||||
<th>Phone</th>
|
||||
<td>
|
||||
<?php if (empty($contact_phone)) { ?>
|
||||
<span class="text-muted">Not set</span>
|
||||
<?php } else { ?>
|
||||
<?= $contact_phone ?>
|
||||
<?php if (!empty($contact_extension)) { ?>
|
||||
<span class="text-muted">ext. <?= $contact_extension ?></span>
|
||||
<?php } ?>
|
||||
<?php } ?>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary float-end"
|
||||
data-bs-toggle="modal" data-bs-target="#phoneModal">Edit</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Mobile</th>
|
||||
<td>
|
||||
<?php if (empty($contact_mobile)) { ?>
|
||||
<span class="text-muted">Not set</span>
|
||||
<?php } else { ?>
|
||||
<?= $contact_mobile ?>
|
||||
<?php } ?>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary float-end"
|
||||
data-bs-toggle="modal" data-bs-target="#phoneModal">Edit</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<small class="text-muted">
|
||||
To change your name, email, title, department or location, raise a ticket and we will
|
||||
update them for you.
|
||||
</small>
|
||||
|
||||
<!-- // Show option to change password if auth provider is local -->
|
||||
<?php if ($_SESSION['login_method'] == 'local'): ?>
|
||||
<hr>
|
||||
<div class="col-md-6">
|
||||
<h4>Password</h4>
|
||||
<form action="post.php" method="post" autocomplete="off">
|
||||
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
|
||||
<div class="mb-3">
|
||||
<label>New Password</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="fa fa-fw fa-lock"></i></span>
|
||||
<input type="password" class="form-control" minlength="8" required data-toggle="password" name="new_password" placeholder="Leave blank for no change" autocomplete="new-password">
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" name="edit_profile" class="btn btn-primary text-bold mt-3"><i class="fas fa-check me-2"></i>Save password</button>
|
||||
</form>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<div class="col-lg-5 mb-4">
|
||||
|
||||
<table class="table table-bordered border border-dark">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th colspan="2">Portal access</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($contact_roles as $contact_role) { ?>
|
||||
<tr>
|
||||
<th class="w-50">
|
||||
<?= $contact_role[0] ?> contact
|
||||
<br><small class="text-muted fw-normal"><?= $contact_role[2] ?></small>
|
||||
</th>
|
||||
<td class="align-middle">
|
||||
<?php if ($contact_role[1]) { ?>
|
||||
<span class="p-2 badge text-bg-success">Yes</span>
|
||||
<?php } else { ?>
|
||||
<span class="p-2 badge text-bg-secondary">No</span>
|
||||
<?php } ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
<tr>
|
||||
<th>Sign in with</th>
|
||||
<td class="align-middle">
|
||||
<?= $login_method_display ?>
|
||||
<?php if ($login_method === 'local') { ?>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary float-end"
|
||||
data-bs-toggle="modal" data-bs-target="#passwordModal">Change</button>
|
||||
<?php } ?>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
Phone PIN
|
||||
<br><small class="text-muted fw-normal">Confirms it is you when you call</small>
|
||||
</th>
|
||||
<td class="align-middle">
|
||||
<?php if (empty($contact_pin)) { ?>
|
||||
<span class="text-muted">Not set</span>
|
||||
<?php } else { ?>
|
||||
<span class="font-monospace"><?= $contact_pin ?></span>
|
||||
<?php } ?>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary float-end"
|
||||
data-bs-toggle="modal" data-bs-target="#pinModal">
|
||||
<?= empty($contact_pin) ? 'Set' : 'Change' ?>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-lg-5 mb-4">
|
||||
|
||||
<table class="table table-bordered border border-dark">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th colspan="2">Recent sign-ins</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (mysqli_num_rows($sql_logins) == 0) { ?>
|
||||
<tr>
|
||||
<td colspan="2" class="text-muted">No sign-ins recorded yet.</td>
|
||||
</tr>
|
||||
<?php } else { ?>
|
||||
<?php while ($row = mysqli_fetch_assoc($sql_logins)) { ?>
|
||||
<tr>
|
||||
<td><?= portalDateTime($row['log_created_at']) ?></td>
|
||||
<td class="font-monospace"><?= escapeHtml($row['log_ip']) ?></td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<small class="text-muted">
|
||||
Somewhere here you do not recognise? Raise a ticket and change your password.
|
||||
</small>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-lg-7 mb-4">
|
||||
|
||||
<table class="table table-bordered border border-dark">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th colspan="2">Recent activity</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (mysqli_num_rows($sql_actions) == 0) { ?>
|
||||
<tr>
|
||||
<td colspan="2" class="text-muted">Nothing recorded yet.</td>
|
||||
</tr>
|
||||
<?php } else { ?>
|
||||
<?php while ($row = mysqli_fetch_assoc($sql_actions)) { ?>
|
||||
<tr>
|
||||
<td class="text-nowrap"><?= portalDateTime($row['log_created_at']) ?></td>
|
||||
<td><?= escapeHtml($row['log_description']) ?></td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a href="activity.php"><i class="fa fa-fw fa-list me-2"></i>View all your activity</a>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<p class="text-muted">
|
||||
<small>Portal user ID <?= intval($_SESSION['user_id']) ?> — quote this if we ask for it.</small>
|
||||
</p>
|
||||
|
||||
|
||||
<div class="modal fade" id="phoneModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<form action="post.php" method="post" autocomplete="off">
|
||||
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
|
||||
|
||||
<div class="modal-header bg-dark">
|
||||
<h5 class="modal-title text-white"><i class="fa fa-fw fa-phone me-2"></i>Your phone numbers</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<p class="text-muted">
|
||||
Keeping these current means we can reach you about a ticket without hunting
|
||||
for a number. Clear a field to remove it.
|
||||
</p>
|
||||
|
||||
<label for="contactPhone">Phone / <span class="text-secondary">Extension</span></label>
|
||||
<div class="row g-2">
|
||||
<div class="col-8 mb-3">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="fa fa-fw fa-phone"></i></span>
|
||||
<input type="hidden" name="phone_country_code" value="<?= $contact_phone_country_code ?>">
|
||||
<input type="tel" class="form-control" id="contactPhone" name="phone"
|
||||
placeholder="Phone Number" maxlength="200" value="<?= $contact_phone_input ?>"
|
||||
data-itflow-phone="phone_country_code">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4 mb-3">
|
||||
<input type="text" class="form-control" id="contactExtension" name="extension"
|
||||
inputmode="numeric" maxlength="200" placeholder="ext."
|
||||
value="<?= $contact_extension ?>">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label for="contactMobile">Mobile</label>
|
||||
<div class="mb-3">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="fa fa-fw fa-mobile-alt"></i></span>
|
||||
<input type="hidden" name="mobile_country_code" value="<?= $contact_mobile_country_code ?>">
|
||||
<input type="tel" class="form-control" id="contactMobile" name="mobile"
|
||||
placeholder="Mobile Number" maxlength="200" value="<?= $contact_mobile_input ?>"
|
||||
data-itflow-phone="mobile_country_code">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" name="set_contact_phone" class="btn btn-primary text-bold">
|
||||
<i class="fas fa-check me-2"></i>Save
|
||||
</button>
|
||||
<button type="button" class="btn btn-light" data-bs-dismiss="modal">
|
||||
<i class="fas fa-times me-2"></i>Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="pinModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<form action="post.php" method="post" autocomplete="off">
|
||||
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
|
||||
|
||||
<div class="modal-header bg-dark">
|
||||
<h5 class="modal-title text-white">
|
||||
<i class="fa fa-fw fa-key me-2"></i><?= empty($contact_pin) ? 'Set a phone PIN' : 'Change your phone PIN' ?>
|
||||
</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<p class="text-muted">
|
||||
We ask for this to confirm it is really you when you call. Pick something you
|
||||
will remember but that is not easy to guess.
|
||||
</p>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="contactPin"><?= empty($contact_pin) ? 'New PIN' : 'Replace with' ?></label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="fa fa-fw fa-key"></i></span>
|
||||
<input type="text" class="form-control" id="contactPin" name="pin"
|
||||
minlength="4" maxlength="255" placeholder="At least 4 characters"
|
||||
autocomplete="off" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" name="set_contact_pin" class="btn btn-primary text-bold">
|
||||
<i class="fas fa-check me-2"></i><?= empty($contact_pin) ? 'Save PIN' : 'Update PIN' ?>
|
||||
</button>
|
||||
<button type="button" class="btn btn-light" data-bs-dismiss="modal">
|
||||
<i class="fas fa-times me-2"></i>Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($login_method === 'local') { ?>
|
||||
<div class="modal fade" id="passwordModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<form action="post.php" method="post" autocomplete="off">
|
||||
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
|
||||
|
||||
<div class="modal-header bg-dark">
|
||||
<h5 class="modal-title text-white"><i class="fa fa-fw fa-lock me-2"></i>Change your password</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="newPassword">New password</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="fa fa-fw fa-lock"></i></span>
|
||||
<input type="password" class="form-control" id="newPassword" name="new_password"
|
||||
minlength="8" placeholder="At least 8 characters" autocomplete="new-password">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" name="edit_profile" class="btn btn-primary text-bold">
|
||||
<i class="fas fa-check me-2"></i>Save password
|
||||
</button>
|
||||
<button type="button" class="btn btn-light" data-bs-dismiss="modal">
|
||||
<i class="fas fa-times me-2"></i>Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
|
||||
<?php
|
||||
require_once 'includes/footer.php';
|
||||
|
||||
Reference in New Issue
Block a user