feat: complete assessment tasks

This commit is contained in:
Ayobami
2025-08-07 16:34:00 +01:00
parent 463a238503
commit c32a12c13f
7 changed files with 381 additions and 93 deletions
+2 -2
View File
@@ -22,7 +22,7 @@ Route::add('/client/login', function () {
$data = [];
if (empty($_POST['pasword']) || empty($_POST['email'])) {
if (empty($_POST['password']) || empty($_POST['email'])) {
$error = true;
// include_once __DIR__ . '/layout/header/Clientleft_sidebar.php';
include_once __DIR__ . '/client-login.php';
@@ -41,7 +41,7 @@ Route::add('/client/login', function () {
// Insert data into the database using LicenseModel
$userModel = new UserModel();
$result = $userModel->get_by_field('id', $email);
$result = $userModel->get_by_field('email', $email);
// var_dump($result);exit;
if ($result) {
if (password_verify($raw_password, $result['password']) &&
+2 -2
View File
@@ -1,4 +1,4 @@
version: '3.8'
version: "3.8"
services:
php:
@@ -26,7 +26,7 @@ services:
MYSQL_USER: tfu_user
MYSQL_PASSWORD: tfu_password
ports:
- "3306:3306"
- "3307:3306"
volumes:
- mysql_data:/var/lib/mysql
command: --sql_mode=""
+76 -13
View File
@@ -332,7 +332,7 @@ Route::add('/admin/login', function () {
$_SESSION['is_logged_in'] = true;
$_SESSION['role'] = $result['role'];
$_SESSION['user'] = $result['id'];
header('Location: /admin/users');
header('Location: /admin/accesslog');
} else {
$error = true;
include_once __DIR__ . '/login.php';
@@ -501,27 +501,37 @@ Route::add('/admin/license', function () {
check_login();
$format = isset($_GET['format']) ? $_GET['format'] : 'json';
$page = isset($_GET['page']) ? intval($_GET['page']) : 1;
$id = isset($_GET['id']) ? intval($_GET['id']) : 0;
$cursor_id = isset($_GET['cursor']) ? intval($_GET['cursor']) : 0;
$per_page = isset($_GET['size']) ? intval($_GET['size']) : 10;
$sort = isset($_GET['sort']) ? $_GET['sort'] : 'id';
$direction = isset($_GET['direction']) ? $_GET['direction'] : 'ASC';
$relationship_num = isset($_GET['relationship_num']) ? $_GET['relationship_num'] : '';
$email_search = isset($_GET['email']) ? trim($_GET['email']) : '';
$licenseModel = new LicenseModel();
$data = [
'page_title' => 'License',
'relationship_num' => $relationship_num
'relationship_num' => $relationship_num,
'email_search' => $email_search
];
$where = [];
if ($relationship_num != '') {
$where['relationship_num'] = '"' . $relationship_num . '"';
// $where['relationship_num'] = '"' . $relationship_num . '"';
// $where[] = '"' . $relationship_num . '"';
$where[] = "relationship_num = '" . addslashes($relationship_num) . "'";
}
$result = $licenseModel->get_paginated($page, $per_page, $where, $sort, $direction);
// Add fuzzy email search using LIKE
if ($email_search != '') {
$where[] = "email LIKE '%" . addslashes($email_search) . "%'";
}
// Use cursor-based pagination instead of offset-based
$result = $licenseModel->get_cursor_paginated($page, $per_page, $where, $sort, $direction, $cursor_id);
if ($result) {
if ($format == 'json') {
@@ -917,19 +927,28 @@ Route::add('/admin/project', function () {
$per_page = isset($_GET['size']) ? intval($_GET['size']) : 15;
$sort = isset($_GET['sort']) ? $_GET['sort'] : 'id';
$direction = isset($_GET['direction']) ? $_GET['direction'] : 'ASC';
// $relationship_num = isset($_GET['relationship_num']) ? $_GET['relationship_num'] : '';
$project_search = isset($_GET['project_search']) ? $_GET['project_search'] : '';
$webhook_search = isset($_GET['webhook_search']) ? $_GET['webhook_search'] : '';
$projectModel = new ProjectModel();
$data = [
'page_title' => 'Project',
'project_search' => $project_search,
'webhook_search' => $webhook_search
];
$where = [];
// if ($relationship_num != '') {
// $where['relationship_num'] = '"' . $relationship_num . '"';
// }
// Add fuzzy search for project_name
if ($project_search != '') {
$where[] = "project_name LIKE '%" . addslashes($project_search) . "%'";
}
// Add fuzzy search for webhook
if ($webhook_search != '') {
$where[] = "webhook LIKE '%" . addslashes($webhook_search) . "%'";
}
$result = $projectModel->get_paginated($page, $per_page, $where, 'id', 'DESC');
// echo json_encode($result);
@@ -1379,20 +1398,43 @@ Route::add('/admin/report', function () {
$sort = isset($_GET['sort']) ? $_GET['sort'] : 'id';
$direction = isset($_GET['direction']) ? $_GET['direction'] : 'ASC';
$date = isset($_GET['date']) ? $_GET['date'] : '';
$start_date = isset($_GET['start_date']) ? $_GET['start_date'] : '';
$end_date = isset($_GET['end_date']) ? $_GET['end_date'] : '';
$project = isset($_GET['project']) ? $_GET['project'] : '';
$reportModel = new ReportModel();
$data = [
'page_title' => 'Report',
'date' => $date
'date' => $date,
"start_date" => $start_date,
"end_date" => $end_date,
"project" => $project
];
$where = [];
if ($date != '') {
if ($date != '' && empty($start_date) && empty($end_date)) {
$where['date'] = '"' . $date . '"';
}
if (!empty($start_date) && !empty($end_date)) {
$where[] = "date BETWEEN '" . $start_date . "' AND '" . $end_date . "'";
}
if (!empty($start_date) && empty($end_date)) {
$where[] = "date >= '" . $start_date . "'";
}
if (empty($start_date) && !empty($end_date)) {
$where[] = "date <= '" . $end_date . "'";
}
if($project != '') {
$where[] = "project LIKE '%" . addslashes($project) . "%'";
}
$result = $reportModel->get_paginated($page, $per_page, $where, 'id', 'DESC');
// echo json_encode($result);
if ($result) {
@@ -1487,6 +1529,29 @@ Route::add('/admin/license/delete/([0-9]+)', function ($id) {
$licenseModel->real_delete($id);
header('Location: /admin/license');
}, 'get');
Route::add('/admin/license/list/multiselect', function () {
check_login();
$licenseModel = new LicenseModel();
if (isset($_POST['delete'])) {
if (isset($_POST['selected_items']) && !empty($_POST['selected_items'])) {
$ids = explode(',', $_POST['selected_items']);
$ids = array_map('intval', $ids); // Sanitize IDs
$ids_string = implode(', ', $ids);
$licenseModel->real_delete_by_fields([
"id IN ($ids_string)"
]);
}
header('Location: /admin/license');
exit;
}
// If no valid action, redirect back
header('Location: /admin/license');
exit;
}, 'post');
Route::add('/admin/location/delete/([0-9]+)', function ($id) {
check_login();
$locationModel = new LocationModel();
@@ -1927,5 +1992,3 @@ include_once 'cal.php';
include_once 'oauth-routes.php';
Route::run('/');
+198 -43
View File
@@ -5,27 +5,67 @@
color: white;
/* White text color */
}
.bulk-actions {
margin-bottom: 15px;
padding: 10px;
background-color: #f8f9fa;
border-radius: 5px;
display: none;
}
.bulk-actions.show {
display: block;
}
</style>
<div class="container">
<h2 class="text-left">Licenses &nbsp; <a class="btn btn-primary" href="/admin/license/add">Add</a></h2>
<div class="row mt-2 mb-2">
<div class="col-md-6 col-md-offset-3">
<form action="?" method="GET">
<div class="input-group">
<input type="text" name="relationship_num" class="form-control mr-2" placeholder="Enter Relationship #" value="<?php echo $data['relationship_num']; ?>">
<span class="input-group-btn">
<button class="btn btn-primary" type="submit">Search</button>
</span>
</div><!-- /input-group -->
<!-- Bulk Actions Form -->
<div class="bulk-actions" id="bulkActions">
<form action="/admin/license/list/multiselect" method="POST" onsubmit="return confirmBulkDelete()">
<div class="d-flex align-items-center">
<span class="mr-3"><strong>Selected items:</strong> <span id="selectedCount">0</span></span>
<button type="submit" name="delete" class="btn btn-danger btn-sm mr-2">
<i class="fa fa-trash"></i> Delete Selected
</button>
<button type="button" class="btn btn-secondary btn-sm" onclick="clearSelection()">
Clear Selection
</button>
</div>
<input type="hidden" name="selected_items" id="selectedItems" value="">
</form>
</div><!-- /.col-md-6 -->
</div>
<div class="row mt-2 mb-2">
<div class="col-md-10 col-md-offset-1">
<form action="?" method="GET" class="form-inline">
<div class="input-group mr-2 mb-2">
<label class="mr-2" for="relationship_num">Relationship #</label>
<input id="relationship_num" type="text" name="relationship_num" class="form-control" placeholder="Enter Relationship #"
value="<?php echo htmlspecialchars($data['relationship_num'] ?? ''); ?>">
</div>
<div class="input-group mr-2 mb-2">
<label class="mr-2" for="email">Email</label>
<input id="email" type="text" name="email" class="form-control" placeholder="Fuzzy email search..."
value="<?php echo htmlspecialchars($data['email_search'] ?? ''); ?>">
</div>
<!-- Preserve cursor parameter when searching -->
<input type="hidden" name="cursor" value="0">
<span class="input-group-btn mb-2">
<button class="btn btn-primary mr-2" type="submit">Search</button>
<a class="btn btn-secondary" href="/admin/license">Clear</a>
</span>
</form>
</div><!-- /.col-md-10 -->
</div><!-- /.row -->
<!-- Table Responsive Wrapper -->
<div class="table-responsive">
<table class="table table-hover dark-header">
<thead>
<tr>
<th>
<input type="checkbox" id="selectAll" onchange="toggleSelectAll()">
<label for="selectAll" class="ml-1">Select All</label>
</th>
<th>ID</th>
<th>Relationship #</th>
<th>Email</th>
@@ -38,6 +78,7 @@
<tbody>
<?php foreach ($data['data'] as $key => $value) {
echo ' <tr>';
echo ' <td><input type="checkbox" class="item-checkbox" value="' . $value->id . '" onchange="updateBulkActions()"></td>';
echo ' <td>' . $value->id . ' <br/><a class=" text-info" href="/admin/license/edit/' . $value->id . '">edit</a> <a class="text-danger" href="/admin/license/delete/' . $value->id . '">delete</a></td>';
echo ' <td>' . $value->relationship_num . ' </td>';
echo ' <td>' . $value->email . ' </td>';
@@ -53,49 +94,163 @@
</div>
<?php
// Retrieve parameters
// Retrieve cursor pagination parameters
$total = $data['total'];
$currentPage = $data['page'];
$perPage = 10;
$lastId = isset($data['id']) ? $data['id'] : 0;
$currentCursor = isset($_GET['cursor']) ? intval($_GET['cursor']) : 0;
$relationshipNum = isset($_GET['relationship_num']) ? $_GET['relationship_num'] : '';
$emailSearch = isset($_GET['email']) ? $_GET['email'] : '';
// Calculate the number of pages
$totalPages = ceil($total / $perPage);
// Define a range of pages to show at any given time
$range = 2; // This can be adjusted as needed
$startPage = ($currentPage - $range) > 0 ? ($currentPage - $range) : 1;
$endPage = ($currentPage + $range) < $totalPages ? ($currentPage + $range) : $totalPages;
?>
<!-- Pagination -->
<nav aria-label="Page navigation">
<ul class="pagination">
<li class="ml-2">
<a href="?page=1" aria-label="Previous">
<span aria-hidden="true">&laquo;&laquo;</span>
</a>
</li>
<li class="ml-2">
<a href="?page=<?= ($currentPage - 1) > 0 ? $currentPage - 1 : 1 ?>" aria-label="Previous">
<span aria-hidden="true">&laquo;</span>
</a>
</li>
<?php
for ($i = $startPage; $i <= $endPage; $i++) {
echo '<li class="ml-2' . ($currentPage == $i ? ' active' : '') . '"><a href="?page=' . $i . '">' . $i . '</a></li>';
// Build query parameters for pagination links
$queryParams = [];
if (!empty($relationshipNum)) {
$queryParams['relationship_num'] = $relationshipNum;
}
if (!empty($emailSearch)) {
$queryParams['email'] = $emailSearch;
}
// Helper function to build query string
function buildQueryString($params) {
return !empty($params) ? '&' . http_build_query($params) : '';
}
$queryString = buildQueryString($queryParams);
// Determine if we have previous/next pages
$hasPrevious = $currentCursor > 0;
$hasNext = count($data['data']) >= $perPage; // If we got a full page, assume there might be more
?>
<li class="ml-2">
<a href="?page=<?= ($currentPage + 1) < $totalPages ? $currentPage + 1 : $totalPages ?>" aria-label="Next">
<!-- Cursor-based Pagination -->
<nav aria-label="Cursor-based page navigation">
<div class="d-flex justify-content-between align-items-center">
<div class="pagination-info">
<small class="text-muted">
Showing <?= count($data['data']) ?> items
<?php if ($total > 0): ?>
(Total: <?= $total ?> items)
<?php endif; ?>
</small>
</div>
<ul class="pagination mb-0">
<?php if ($hasPrevious): ?>
<li class="page-item">
<a class="page-link" href="?cursor=0<?= $queryString ?>" aria-label="First">
<span aria-hidden="true">&laquo;&laquo;</span>
<span class="sr-only">First</span>
</a>
</li>
<li class="page-item">
<a class="page-link" href="?cursor=<?= max(0, $currentCursor - $perPage) ?><?= $queryString ?>" aria-label="Previous">
<span aria-hidden="true">&laquo;</span>
<span class="sr-only">Previous</span>
</a>
</li>
<?php else: ?>
<li class="page-item disabled">
<span class="page-link">
<span aria-hidden="true">&laquo;&laquo;</span>
</span>
</li>
<li class="page-item disabled">
<span class="page-link">
<span aria-hidden="true">&laquo;</span>
</span>
</li>
<?php endif; ?>
<li class="page-item active">
<span class="page-link">Current</span>
</li>
<?php if ($hasNext): ?>
<li class="page-item">
<a class="page-link" href="?cursor=<?= $lastId ?><?= $queryString ?>" aria-label="Next">
<span aria-hidden="true">&raquo;</span>
<span class="sr-only">Next</span>
</a>
</li>
<li class="ml-2">
<a href="?page=<?= $totalPages ?>" aria-label="Next">
<span aria-hidden="true">&raquo;&raquo;</span>
</a>
<?php else: ?>
<li class="page-item disabled">
<span class="page-link">
<span aria-hidden="true">&raquo;</span>
</span>
</li>
<?php endif; ?>
</ul>
</div>
</nav>
</div>
<script>
function toggleSelectAll() {
const selectAll = document.getElementById('selectAll');
const checkboxes = document.querySelectorAll('.item-checkbox');
checkboxes.forEach(checkbox => {
checkbox.checked = selectAll.checked;
});
updateBulkActions();
}
function updateBulkActions() {
const checkboxes = document.querySelectorAll('.item-checkbox:checked');
const bulkActions = document.getElementById('bulkActions');
const selectedCount = document.getElementById('selectedCount');
const selectedItems = document.getElementById('selectedItems');
const selectAll = document.getElementById('selectAll');
const count = checkboxes.length;
selectedCount.textContent = count;
if (count > 0) {
bulkActions.classList.add('show');
const ids = Array.from(checkboxes).map(cb => cb.value);
selectedItems.value = ids.join(',');
} else {
bulkActions.classList.remove('show');
selectedItems.value = '';
}
// Update select all checkbox state
const allCheckboxes = document.querySelectorAll('.item-checkbox');
const checkedCheckboxes = document.querySelectorAll('.item-checkbox:checked');
if (checkedCheckboxes.length === 0) {
selectAll.indeterminate = false;
selectAll.checked = false;
} else if (checkedCheckboxes.length === allCheckboxes.length) {
selectAll.indeterminate = false;
selectAll.checked = true;
} else {
selectAll.indeterminate = true;
}
}
function clearSelection() {
const checkboxes = document.querySelectorAll('.item-checkbox');
const selectAll = document.getElementById('selectAll');
checkboxes.forEach(checkbox => {
checkbox.checked = false;
});
selectAll.checked = false;
selectAll.indeterminate = false;
updateBulkActions();
}
function confirmBulkDelete() {
const count = document.querySelectorAll('.item-checkbox:checked').length;
if (count === 0) {
alert('Please select at least one item to delete.');
return false;
}
return confirm(`Are you sure you want to delete ${count} selected license(s)? This action cannot be undone.`);
}
</script>
+1 -1
View File
@@ -540,7 +540,7 @@ class MySqlDatabaseService
foreach ($parameters as $key => $value) {
if (is_string($value) && strlen($value) > 0) {
$sql[] = "$key";
$sql[] = "$value";
} else {
$sql[] = "$key = $value";
}
+38 -5
View File
@@ -15,6 +15,28 @@
</div>
</div> -->
<!-- Search Form -->
<div class="row mb-3">
<div class="col-md-12">
<form method="GET" action="/<?php echo $_SESSION['role'] ?>/project" class="form-inline">
<div class="form-group mr-3">
<label for="project_search" class="mr-2">Project Name:</label>
<input type="text" class="form-control" id="project_search" name="project_search"
value="<?php echo htmlspecialchars($data['project_search'] ?? ''); ?>"
placeholder="Search project name...">
</div>
<div class="form-group mr-3">
<label for="webhook_search" class="mr-2">Webhook:</label>
<input type="text" class="form-control" id="webhook_search" name="webhook_search"
value="<?php echo htmlspecialchars($data['webhook_search'] ?? ''); ?>"
placeholder="Search webhook...">
</div>
<button type="submit" class="btn btn-primary mr-2">Search</button>
<a href="/<?php echo $_SESSION['role'] ?>/project" class="btn btn-secondary">Clear</a>
</form>
</div>
</div>
<!-- Table Responsive Wrapper -->
<div class="table-responsive">
<form id="multiselect-form" action="/<?php echo $_SESSION['role'] ?>/project/list/multiselect" method="POST">
@@ -120,28 +142,39 @@
<!-- Pagination -->
<nav aria-label="Page navigation">
<ul class="pagination">
<?php
// Build query string for pagination links
$queryParams = [];
if (!empty($data['project_search'])) {
$queryParams['project_search'] = $data['project_search'];
}
if (!empty($data['webhook_search'])) {
$queryParams['webhook_search'] = $data['webhook_search'];
}
$queryString = !empty($queryParams) ? '&' . http_build_query($queryParams) : '';
?>
<li class="ml-2">
<a href="?page=1" aria-label="Previous">
<a href="?page=1<?= $queryString ?>" aria-label="Previous">
<span aria-hidden="true">&laquo;&laquo;</span>
</a>
</li>
<li class="ml-2">
<a href="?page=<?= ($currentPage - 1) > 0 ? $currentPage - 1 : 1 ?>" aria-label="Previous">
<a href="?page=<?= ($currentPage - 1) > 0 ? $currentPage - 1 : 1 ?><?= $queryString ?>" aria-label="Previous">
<span aria-hidden="true">&laquo;</span>
</a>
</li>
<?php
for ($i = $startPage; $i <= $endPage; $i++) {
echo '<li class="ml-2' . ($currentPage == $i ? ' active' : '') . '"><a href="?page=' . $i . '">' . $i . '</a></li>';
echo '<li class="ml-2' . ($currentPage == $i ? ' active' : '') . '"><a href="?page=' . $i . $queryString . '">' . $i . '</a></li>';
}
?>
<li class="ml-2">
<a href="?page=<?= ($currentPage + 1) < $totalPages ? $currentPage + 1 : $totalPages ?>" aria-label="Next">
<a href="?page=<?= ($currentPage + 1) < $totalPages ? $currentPage + 1 : $totalPages ?><?= $queryString ?>" aria-label="Next">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
<li class="ml-2">
<a href="?page=<?= $totalPages ?>" aria-label="Next">
<a href="?page=<?= $totalPages ?><?= $queryString ?>" aria-label="Next">
<span aria-hidden="true">&raquo;&raquo;</span>
</a>
</li>
+50 -13
View File
@@ -10,16 +10,36 @@
<div class="container">
<h2 class="text-left">Report &nbsp; <a class="btn btn-primary d-none" href="/admin/report/csv?format=csv&date=<?php echo $data['date']; ?>">Export</a></h2>
<div class="row mt-2 mb-2">
<div class="col-md-6 col-md-offset-3">
<form action="?" method="GET">
<div class="input-group">
<input type="date" name="date" class="form-control mr-2" placeholder="" value="<?php echo $data['date']; ?>">
<span class="input-group-btn">
<button class="btn btn-primary" type="submit">Search</button>
<div class="col-md-10 col-md-offset-1">
<form action="?" method="GET" class="form-inline">
<div class="input-group mr-2 mb-2">
<label class="mr-2" for="date">Date</label>
<input id="date" type="date" name="date" class="form-control" placeholder="" value="<?php echo htmlspecialchars($data['date'] ?? ''); ?>">
</div>
<div class="input-group mr-2 mb-2">
<label class="mr-2" for="start_date">Start</label>
<input id="start_date" type="date" name="start_date" class="form-control" placeholder=""
value="<?php echo htmlspecialchars($data['start_date'] ?? ''); ?>">
</div>
<div class="input-group mr-2 mb-2">
<label class="mr-2" for="end_date">End</label>
<input id="end_date" type="date" name="end_date" class="form-control" placeholder=""
value="<?php echo htmlspecialchars($data['end_date'] ?? ''); ?>">
</div>
<div class="input-group mr-2 mb-2">
<label class="mr-2" for="project">Project</label>
<input id="project" type="text" name="project" class="form-control" placeholder="Fuzzy project search..."
value="<?php echo htmlspecialchars($data['project'] ?? ''); ?>">
</div>
<span class="input-group-btn mb-2">
<button class="btn btn-primary mr-2" type="submit">Search</button>
<a class="btn btn-secondary" href="/admin/report">Clear</a>
</span>
</div><!-- /input-group -->
<div class="text-muted small ml-2 mb-2">
Tip: If a start or end date is provided, the single Date filter is ignored.
</div>
</form>
</div><!-- /.col-md-6 -->
</div><!-- /.col-md-10 -->
</div><!-- /.row -->
<!-- Table Responsive Wrapper -->
<div class="table-responsive">
@@ -96,28 +116,45 @@
<!-- Pagination -->
<nav aria-label="Page navigation">
<ul class="pagination">
<?php
// Preserve filters in pagination links
$queryParams = [];
if (!empty($data['date'])) {
$queryParams['date'] = $data['date'];
}
if (!empty($data['start_date'])) {
$queryParams['start_date'] = $data['start_date'];
}
if (!empty($data['end_date'])) {
$queryParams['end_date'] = $data['end_date'];
}
if (!empty($data['project'])) {
$queryParams['project'] = $data['project'];
}
$queryString = !empty($queryParams) ? '&' . http_build_query($queryParams) : '';
?>
<li class="ml-2">
<a href="?page=1" aria-label="Previous">
<a href="?page=1<?= $queryString ?>" aria-label="Previous">
<span aria-hidden="true">&laquo;&laquo;</span>
</a>
</li>
<li class="ml-2">
<a href="?page=<?= ($currentPage - 1) > 0 ? $currentPage - 1 : 1 ?>" aria-label="Previous">
<a href="?page=<?= ($currentPage - 1) > 0 ? $currentPage - 1 : 1 ?><?= $queryString ?>" aria-label="Previous">
<span aria-hidden="true">&laquo;</span>
</a>
</li>
<?php
for ($i = $startPage; $i <= $endPage; $i++) {
echo '<li class="ml-2' . ($currentPage == $i ? ' active' : '') . '"><a href="?page=' . $i . '">' . $i . '</a></li>';
echo '<li class="ml-2' . ($currentPage == $i ? ' active' : '') . '"><a href="?page=' . $i . $queryString . '">' . $i . '</a></li>';
}
?>
<li class="ml-2">
<a href="?page=<?= ($currentPage + 1) < $totalPages ? $currentPage + 1 : $totalPages ?>" aria-label="Next">
<a href="?page=<?= ($currentPage + 1) < $totalPages ? $currentPage + 1 : $totalPages ?><?= $queryString ?>" aria-label="Next">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
<li class="ml-2">
<a href="?page=<?= $totalPages ?>" aria-label="Next">
<a href="?page=<?= $totalPages ?><?= $queryString ?>" aria-label="Next">
<span aria-hidden="true">&raquo;&raquo;</span>
</a>
</li>