Auto commit: 2025-12-18T06:13:19.239Z

This commit is contained in:
Flatlogic Bot 2025-12-18 06:13:19 +00:00
parent 0c361cd8ae
commit cff6fa6b35
5 changed files with 463 additions and 62 deletions

View File

@ -9,6 +9,98 @@ if (!isset($_SESSION['user_id']) || $_SESSION['user_role'] !== 'admin') {
require_once __DIR__ . '/../db/config.php'; require_once __DIR__ . '/../db/config.php';
$pdo = db(); $pdo = db();
// CSRF Protection
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
header('Content-Type: application/json');
$response = ['success' => false, 'message' => 'Invalid request.'];
if (!isset($_POST['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
$response['message'] = 'CSRF token validation failed.';
echo json_encode($response);
exit;
}
$action = $_POST['action'] ?? '';
$link_id = $_POST['link_id'] ?? null;
if (!$link_id || !is_numeric($link_id)) {
$response['message'] = 'Invalid Link ID.';
echo json_encode($response);
exit;
}
switch ($action) {
case 'delete':
try {
$stmt = $pdo->prepare("DELETE FROM links WHERE id = ?");
$stmt->execute([$link_id]);
if ($stmt->rowCount()) {
$response = ['success' => true, 'message' => 'Link deleted successfully.'];
} else {
$response['message'] = 'Link not found or could not be deleted.';
}
} catch (PDOException $e) {
$response['message'] = 'Database error: ' . $e->getMessage();
}
break;
case 'toggle_status':
$current_status = $_POST['current_status'] ?? '';
$new_status = ($current_status === 'paused') ? 'approved' : 'paused'; // Toggle between paused and approved
try {
$stmt = $pdo->prepare("UPDATE links SET status = ? WHERE id = ?");
$stmt->execute([$new_status, $link_id]);
if ($stmt->rowCount()) {
$response = ['success' => true, 'message' => 'Link status updated successfully to ' . $new_status . '.', 'new_status' => $new_status];
} else {
$response['message'] = 'Link not found or status could not be updated.';
}
} catch (PDOException $e) {
$response['message'] = 'Database error: ' . $e->getMessage();
}
break;
// Add 'edit' case later
case 'edit':
$title = trim($_POST['title'] ?? '');
$url = trim($_POST['url'] ?? '');
$description = trim($_POST['description'] ?? '');
$subcategory_id = $_POST['subcategory_id'] ?? null;
$status = $_POST['status'] ?? 'pending';
if (empty($title) || empty($url) || !filter_var($url, FILTER_VALIDATE_URL) || !is_numeric($subcategory_id)) {
$response['message'] = 'Invalid input for editing link.';
echo json_encode($response);
exit;
}
try {
$stmt = $pdo->prepare("UPDATE links SET title = ?, url = ?, description = ?, subcategory_id = ?, status = ? WHERE id = ?");
$stmt->execute([$title, $url, $description, $subcategory_id, $status, $link_id]);
if ($stmt->rowCount()) {
$response = ['success' => true, 'message' => 'Link updated successfully.'];
} else {
$response['message'] = 'Link not found or no changes made.';
}
} catch (PDOException $e) {
$response['message'] = 'Database error: ' . $e->getMessage();
}
break;
default:
$response['message'] = 'Unknown action.';
break;
}
echo json_encode($response);
exit;
}
$links = $pdo->query("SELECT l.*, u.username, s.name as subcategory_name, c.name as category_name $links = $pdo->query("SELECT l.*, u.username, s.name as subcategory_name, c.name as category_name
FROM links l FROM links l
JOIN users u ON l.user_id = u.id JOIN users u ON l.user_id = u.id
@ -16,6 +108,8 @@ $links = $pdo->query("SELECT l.*, u.username, s.name as subcategory_name, c.name
JOIN categories c ON s.category_id = c.id JOIN categories c ON s.category_id = c.id
ORDER BY l.created_at DESC")->fetchAll(); ORDER BY l.created_at DESC")->fetchAll();
// Fetch subcategories for the edit form
$subcategories = $pdo->query("SELECT sc.id, sc.name AS subcategory_name, c.name AS category_name FROM subcategories sc JOIN categories c ON sc.category_id = c.id ORDER BY c.name, sc.name")->fetchAll();
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
@ -70,15 +164,17 @@ $links = $pdo->query("SELECT l.*, u.username, s.name as subcategory_name, c.name
<tr><td colspan="7">No links submitted yet.</td></tr> <tr><td colspan="7">No links submitted yet.</td></tr>
<?php else: ?> <?php else: ?>
<?php foreach ($links as $link): ?> <?php foreach ($links as $link): ?>
<tr> <tr data-id="<?php echo $link['id']; ?>">
<td><?php echo htmlspecialchars($link['title']); ?></td> <td class="link-title"><?php echo htmlspecialchars($link['title']); ?></td>
<td><a href="<?php echo htmlspecialchars($link['url']); ?>" target="_blank"><?php echo htmlspecialchars(substr($link['url'], 0, 50)); ?>...</a></td> <td class="link-url"><a href="<?php echo htmlspecialchars($link['url']); ?>" target="_blank"><?php echo htmlspecialchars(substr($link['url'], 0, 50)); ?>...</a></td>
<td><?php echo htmlspecialchars($link['category_name']); ?> > <?php echo htmlspecialchars($link['subcategory_name']); ?></td> <td><?php echo htmlspecialchars($link['category_name']); ?> > <span class="link-subcategory-name" data-id="<?php echo $link['subcategory_id']; ?>"><?php echo htmlspecialchars($link['subcategory_name']); ?></span></td>
<td><?php echo htmlspecialchars($link['username']); ?></td> <td><?php echo htmlspecialchars($link['username']); ?></td>
<td><span class="badge bg-<?php echo $link['status'] === 'approved' ? 'success' : ($link['status'] === 'pending' ? 'warning' : 'danger'); ?>"><?php echo htmlspecialchars($link['status']); ?></span></td> <td><span class="badge bg-<?php echo $link['status'] === 'approved' ? 'success' : ($link['status'] === 'pending' ? 'warning' : ($link['status'] === 'paused' ? 'info' : 'danger')); ?> link-status"><?php echo htmlspecialchars($link['status']); ?></span></td>
<td><?php echo date("Y-m-d", strtotime($link['created_at'])); ?></td> <td><?php echo date("Y-m-d", strtotime($link['created_at'])); ?></td>
<td> <td>
<!-- Future actions --> <button class="btn btn-sm btn-primary edit-link-btn" data-id="<?php echo $link['id']; ?>" data-bs-toggle="modal" data-bs-target="#editLinkModal">Edit</button>
<button class="btn btn-sm btn-<?php echo $link['status'] === 'paused' ? 'success' : 'warning'; ?> toggle-status-btn" data-id="<?php echo $link['id']; ?>" data-status="<?php echo $link['status']; ?>"><?php echo $link['status'] === 'paused' ? 'Unpause' : 'Pause'; ?></button>
<button class="btn btn-sm btn-danger delete-link-btn" data-id="<?php echo $link['id']; ?>">Delete</button>
</td> </td>
</tr> </tr>
<?php endforeach; ?> <?php endforeach; ?>
@ -91,9 +187,149 @@ $links = $pdo->query("SELECT l.*, u.username, s.name as subcategory_name, c.name
</div> </div>
</div> </div>
<!-- Edit Link Modal -->
<div class="modal fade" id="editLinkModal" tabindex="-1" aria-labelledby="editLinkModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="editLinkModalLabel">Edit Link</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="editLinkForm">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
<input type="hidden" name="action" value="edit">
<input type="hidden" name="link_id" id="editLinkId">
<div class="mb-3">
<label for="editLinkTitle" class="form-label">Title</label>
<input type="text" class="form-control" id="editLinkTitle" name="title" required>
</div>
<div class="mb-3">
<label for="editLinkUrl" class="form-label">URL</label>
<input type="url" class="form-control" id="editLinkUrl" name="url" required>
</div>
<div class="mb-3">
<label for="editLinkDescription" class="form-label">Description</label>
<textarea class="form-control" id="editLinkDescription" name="description" rows="3"></textarea>
</div>
<div class="mb-3">
<label for="editLinkSubcategory" class="form-label">Subcategory</label>
<select class="form-select" id="editLinkSubcategory" name="subcategory_id" required>
<?php foreach ($subcategories as $sc): ?>
<option value="<?php echo $sc['id']; ?>"><?php echo htmlspecialchars($sc['category_name'] . ' > ' . $sc['subcategory_name']); ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<label for="editLinkStatus" class="form-label">Status</label>
<select class="form-select" id="editLinkStatus" name="status" required>
<option value="pending">Pending</option>
<option value="approved">Approved</option>
<option value="rejected">Rejected</option>
<option value="paused">Paused</option>
</select>
</div>
<button type="submit" class="btn btn-primary">Save changes</button>
</form>
</div>
</div>
</div>
</div>
<footer class="footer"> <footer class="footer">
<p>&copy; <?php echo date("Y"); ?> <?php echo htmlspecialchars($_SERVER['PROJECT_NAME'] ?? 'Web Directory'); ?>. All Rights Reserved.</p> <p>&copy; <?php echo date("Y"); ?> <?php echo htmlspecialchars($_SERVER['PROJECT_NAME'] ?? 'Web Directory'); ?>. All Rights Reserved.</p>
</footer> </footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const csrfToken = document.querySelector('input[name="csrf_token"]').value;
document.querySelectorAll('.delete-link-btn').forEach(button => {
button.addEventListener('click', function() {
const linkId = this.dataset.id;
if (confirm('Are you sure you want to delete this link?')) {
sendAction(linkId, 'delete', {});
}
});
});
document.querySelectorAll('.toggle-status-btn').forEach(button => {
button.addEventListener('click', function() {
const linkId = this.dataset.id;
const currentStatus = this.dataset.status;
// Determine new status based on current for toggling between paused and approved
// If current status is 'paused', next is 'approved'. Otherwise, 'paused'.
const newStatus = (currentStatus === 'paused') ? 'approved' : 'paused';
if (confirm(`Are you sure you want to ${newStatus === 'paused' ? 'pause' : 'unpause'} this link?`)) {
sendAction(linkId, 'toggle_status', { current_status: currentStatus });
}
});
});
document.querySelectorAll('.edit-link-btn').forEach(button => {
button.addEventListener('click', function() {
const linkId = this.dataset.id;
const row = this.closest('tr');
document.getElementById('editLinkId').value = linkId;
document.getElementById('editLinkTitle').value = row.querySelector('.link-title').textContent;
document.getElementById('editLinkUrl').value = row.querySelector('.link-url a').href;
document.getElementById('editLinkDescription').value = row.querySelector('.link-description') ? row.querySelector('.link-description').textContent : '';
document.getElementById('editLinkStatus').value = row.querySelector('.link-status').textContent.trim();
// Select the correct subcategory in the dropdown
const subcategoryId = row.querySelector('.link-subcategory-name').dataset.id;
const subcategorySelect = document.getElementById('editLinkSubcategory');
for (let i = 0; i < subcategorySelect.options.length; i++) {
if (subcategorySelect.options[i].value == subcategoryId) {
subcategorySelect.selectedIndex = i;
break;
}
}
});
});
document.getElementById('editLinkForm').addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
const linkId = document.getElementById('editLinkId').value;
const data = {};
formData.forEach((value, key) => data[key] = value);
sendAction(linkId, 'edit', data);
});
function sendAction(linkId, action, additionalData) {
const data = {
csrf_token: csrfToken,
action: action,
link_id: linkId,
...additionalData
};
fetch('links.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams(data)
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert(data.message);
window.location.reload(); // Simple reload for now, can be optimized later
} else {
alert('Error: ' + data.message);
}
})
.catch(error => {
console.error('Fetch error:', error);
alert('An error occurred while processing your request.');
});
}
});
</script>
</body> </body>
</html> </html>

View File

@ -1,73 +1,87 @@
/* --- Modern Japanese Retro Theme --- */ /* --- Modern Japanese Retro Theme with more Pizazz --- */
:root { :root {
--primary-color: #5A8D8D; /* Deep Teal */ --primary-color: #007bff; /* Vibrant Blue */
--accent-color: #D65A5A; /* Muted Red */ --secondary-color: #ff4081; /* Pink Accent */
--bg-light: #F8F8F8; /* Off-white background */ --tertiary-color: #f0f2f5; /* Light Gray Background */
--text-dark: #333333; --text-dark: #212529;
--text-light: #666666; --text-medium: #495057;
--border-color: #E0E0E0; --text-light: #ced4da;
--shadow-light: rgba(0, 0, 0, 0.05); --border-color: #dee2e6;
--shadow-light: rgba(0, 0, 0, 0.1);
--white: #ffffff;
--gradient-start: #e0f2f7; /* Light blue for gradient */
--gradient-end: #f0f8ff; /* Lighter blue for gradient */
} }
body { body {
font-family: 'Noto Sans JP', sans-serif; font-family: 'Noto Sans JP', sans-serif;
background-color: var(--bg-light); background: linear-gradient(to bottom right, var(--gradient-start), var(--gradient-end));
color: var(--text-dark); color: var(--text-dark);
line-height: 1.6; line-height: 1.6;
margin: 0;
padding: 0;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
} }
.header { .header {
background: var(--primary-color); background: var(--white);
color: var(--bg-light); color: var(--primary-color);
padding: 20px 25px; padding: 10px 25px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
border-bottom: none; border-bottom: 1px solid var(--border-color);
box-shadow: 0 2px 4px var(--shadow-light); box-shadow: 0 2px 8px var(--shadow-light);
} }
.header h1 { .header h1 {
font-family: 'Zen Old Mincho', serif; font-family: 'Zen Old Mincho', serif;
font-size: 2.8em; font-size: 2.5em;
color: var(--bg-light); color: var(--primary-color);
margin: 0; margin: 0;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2); text-shadow: 2px 2px 4px rgba(0,0,0,0.1);
} }
.auth-links a { .auth-links a {
margin-left: 20px; margin-left: 20px;
color: var(--bg-light); color: var(--primary-color);
text-decoration: none; text-decoration: none;
font-weight: 300; font-weight: 500;
transition: color 0.3s ease; transition: color 0.3s ease, transform 0.2s ease;
} }
.auth-links a:hover { .auth-links a:hover {
color: var(--accent-color); color: var(--secondary-color);
transform: translateY(-2px);
} }
.container { .main-wrapper {
background-color: #FFFFFF; padding-top: 25px; /* Increased space below the header */
border: 1px solid var(--border-color); }
box-shadow: 0 5px 15px var(--shadow-light);
border-radius: 8px; .content-section {
overflow: hidden; /* For inner elements like category-list and content */ background-color: var(--white);
border-radius: 12px;
overflow: hidden;
margin-bottom: 30px; /* Increased space between sections */
box-shadow: 0 4px 15px var(--shadow-light);
transition: all 0.3s ease;
} }
.category-list { .category-list {
background-color: #FAFAFA; background-color: var(--white);
border-right: 1px solid var(--border-color); padding: 25px;
padding: 20px; border-bottom: 1px solid var(--border-color);
} }
.category-list h3 { .category-list h3 {
font-family: 'Zen Old Mincho', serif; font-family: 'Zen Old Mincho', serif;
font-size: 1.8rem; font-size: 1.7rem;
color: var(--primary-color); color: var(--primary-color);
border-bottom: 2px solid var(--accent-color); border-bottom: 3px solid var(--secondary-color);
padding-bottom: 10px; padding-bottom: 12px;
margin-bottom: 20px; margin-bottom: 20px;
} }
@ -80,52 +94,56 @@ body {
} }
.category-list .nav-link:hover { .category-list .nav-link:hover {
color: var(--primary-color); color: var(--secondary-color);
transform: translateX(5px); transform: translateX(5px);
} }
.content { .content {
padding: 25px; padding: 25px;
background-color: var(--white);
} }
.content h2 { .content h2 {
font-family: 'Zen Old Mincho', serif; font-family: 'Zen Old Mincho', serif;
color: var(--primary-color); color: var(--primary-color);
border-bottom: 2px solid var(--border-color); font-size: 2rem;
padding-bottom: 12px; padding-bottom: 15px;
margin-bottom: 25px; margin-bottom: 25px;
border-bottom: 1px solid var(--border-color);
} }
.link-item { .link-item {
margin-bottom: 20px; margin-bottom: 20px;
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
padding: 15px; padding: 15px;
border-radius: 6px; border-radius: 8px;
background-color: #FFFFFF; background-color: var(--white);
transition: box-shadow 0.3s ease, transform 0.2s ease; display: flex;
align-items: flex-start;
transition: box-shadow 0.3s ease, transform 0.3s ease;
} }
.link-item:hover { .link-item:hover {
box-shadow: 0 8px 20px var(--shadow-light); box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
transform: translateY(-3px); transform: translateY(-5px);
} }
.link-item .thumbnail { .link-item .thumbnail {
width: 150px; /* Slightly larger thumbnail */ width: 140px;
height: 100px; height: 90px;
object-fit: cover; object-fit: cover;
border: 1px solid var(--border-color);
border-radius: 4px; border-radius: 4px;
margin-right: 20px; margin-right: 20px;
float: left; flex-shrink: 0;
} }
.link-item-body { .link-item-body {
overflow: hidden; flex-grow: 1;
} }
.link-item-title { .link-item-title {
font-size: 1.3rem; font-size: 1.25rem;
font-weight: 700; font-weight: 700;
margin-bottom: 5px; margin-bottom: 5px;
} }
@ -137,13 +155,13 @@ body {
} }
.link-item-title a:hover { .link-item-title a:hover {
color: var(--accent-color); color: var(--secondary-color);
text-decoration: underline; text-decoration: underline;
} }
.link-item-url { .link-item-url {
font-size: 0.9rem; font-size: 0.9rem;
color: var(--text-light); color: var(--text-medium);
margin-bottom: 8px; margin-bottom: 8px;
display: block; display: block;
} }
@ -157,8 +175,79 @@ body {
text-align: center; text-align: center;
padding: 25px 0; padding: 25px 0;
background-color: var(--primary-color); background-color: var(--primary-color);
color: var(--bg-light); color: var(--white);
font-size: 0.9rem; font-size: 0.9rem;
margin-top: 40px; margin-top: 40px;
box-shadow: 0 -2px 4px var(--shadow-light); box-shadow: 0 -2px 8px var(--shadow-light);
}
/* Featured Section Styles */
.featured-section {
background: linear-gradient(to bottom, #fff5e6, #ffe0b3); /* Warm gradient background */
border-radius: 12px;
padding: 25px;
box-shadow: 0 4px 15px rgba(255, 160, 0, 0.1);
border: 1px solid #ffcc80;
}
.featured-section h3 {
font-family: 'Zen Old Mincho', serif;
color: #e65100; /* Darker orange for heading */
font-size: 1.6rem;
border-bottom: 3px solid #ff9800; /* Orange underline */
padding-bottom: 10px;
margin-bottom: 20px;
}
.featured-item {
background-color: var(--white);
border: 1px solid #ffecb3;
border-radius: 8px;
padding: 15px;
margin-bottom: 15px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
transition: transform 0.2s ease;
}
.featured-item:hover {
transform: translateY(-3px);
}
.featured-item h4 {
color: var(--primary-color);
font-size: 1.1rem;
margin-bottom: 8px;
}
.featured-item p {
font-size: 0.9rem;
color: var(--text-medium);
margin-bottom: 12px;
}
.featured-item .btn {
font-size: 0.85rem;
padding: 8px 15px;
border-radius: 5px;
transition: all 0.3s ease;
}
.featured-item .btn-primary {
background-color: var(--primary-color);
border-color: var(--primary-color);
}
.featured-item .btn-primary:hover {
background-color: #0056b3;
border-color: #0056b3;
}
.featured-item .btn-secondary {
background-color: var(--secondary-color);
border-color: var(--secondary-color);
}
.featured-item .btn-secondary:hover {
background-color: #c00c4e;
border-color: #c00c4e;
} }

58
db/apply_migrations.php Normal file
View File

@ -0,0 +1,58 @@
<?php
require_once __DIR__ . '/config.php';
echo "Applying migrations...
";
try {
$pdo = db();
// Create migrations table if it doesn't exist
$pdo->exec("
CREATE TABLE IF NOT EXISTS `migrations` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`migration_name` VARCHAR(255) NOT NULL UNIQUE,
`applied_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
");
$migrationsDir = __DIR__ . '/migrations/';
$migrationFiles = glob($migrationsDir . '*.sql');
sort($migrationFiles);
foreach ($migrationFiles as $file) {
$migrationName = basename($file);
// Check if migration has already been applied
$stmt = $pdo->prepare("SELECT COUNT(*) FROM `migrations` WHERE `migration_name` = ?");
$stmt->execute([$migrationName]);
if ($stmt->fetchColumn() > 0) {
echo "Skipping already applied migration: $migrationName
";
continue;
}
echo "Applying migration: $migrationName
";
$sql = file_get_contents($file);
$pdo->exec($sql);
// Record the applied migration
$stmt = $pdo->prepare("INSERT INTO `migrations` (`migration_name`) VALUES (?)");
$stmt->execute([$migrationName]);
echo "Successfully applied migration: $migrationName
";
}
echo "All migrations applied.
";
} catch (PDOException $e) {
echo "Database error: " . $e->getMessage() . "
";
exit(1);
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "
";
exit(1);
}

View File

@ -0,0 +1 @@
ALTER TABLE `links` MODIFY COLUMN `status` ENUM('pending', 'approved', 'rejected', 'paused') NOT NULL DEFAULT 'pending';

View File

@ -92,9 +92,9 @@ $current_links = $link_stmt->fetchAll();
</div> </div>
</header> </header>
<div class="container my-4"> <div class="main-wrapper container my-4">
<div class="row"> <div class="row">
<!-- Categories Sidebar --> <!-- Left Column - Categories -->
<div class="col-md-3"> <div class="col-md-3">
<aside class="category-list"> <aside class="category-list">
<h3>Categories</h3> <h3>Categories</h3>
@ -109,8 +109,8 @@ $current_links = $link_stmt->fetchAll();
</aside> </aside>
</div> </div>
<!-- Main Content --> <!-- Center Column - Main Content -->
<div class="col-md-9"> <div class="col-md-6">
<main class="content"> <main class="content">
<h2><?php echo htmlspecialchars($current_category_name); ?></h2> <h2><?php echo htmlspecialchars($current_category_name); ?></h2>
@ -151,8 +151,25 @@ $current_links = $link_stmt->fetchAll();
</main> </main>
</div> </div>
<!-- Right Column -->
<div class="col-md-3">
<aside class="featured-section">
<h3>Featured Content</h3>
<div class="featured-item">
<h4>Special Link 1</h4>
<p>A description for a special featured link.</p>
<a href="#" class="btn btn-sm btn-primary">View More</a>
</div>
<div class="featured-item mt-3">
<h4>Announcement</h4>
<p>Check out our latest updates!</p>
<a href="#" class="btn btn-sm btn-secondary">Read Blog</a>
</div>
</aside>
</div>
</div> </div>
</div> </div><!-- /main-wrapper -->
<footer class="footer"> <footer class="footer">
<p>&copy; <?php echo date("Y"); ?> <?php echo htmlspecialchars($_SERVER['PROJECT_NAME'] ?? 'Web Directory'); ?>. All Rights Reserved.</p> <p>&copy; <?php echo date("Y"); ?> <?php echo htmlspecialchars($_SERVER['PROJECT_NAME'] ?? 'Web Directory'); ?>. All Rights Reserved.</p>