DelphianLogic in Action
+Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo
+diff --git a/Answers to technical questions.md b/Answers to technical questions.md
new file mode 100644
index 0000000..2e359d9
--- /dev/null
+++ b/Answers to technical questions.md
@@ -0,0 +1,50 @@
+# Answers to Technical Questions
+
+### 1. How long did you spend on the coding test? What would you add to your solution if you had more time?
+* **Time Spent:** I spent approximately 2 and a half days working on this evaluation. This included setting up the MySQL database layer, creating a reliable PHP MVC folder architecture to handle slide records, implementing the administrative CRUD dashboards, and refining the frontend interface for pixel-perfect responsiveness matching both desktop and mobile target mockups.
+
+* **Production-Grade Features Already Implemented:**
+ 1. **Robust Validation Layer (Create & Edit):** Implemented thorough data sanitization using `trim()` alongside mandatory required string tracking. I also added strict browser-side validation blocks (`required` and explicit asset input `accept` parameters) to guide user entries.
+ 2. **Deep Binary Image Inspections:** Implemented server-side security using PHP's `finfo` extension to analyze the underlying binary magic bytes of file uploads, safely blocking malicious or corrupted injections from breaking layouts regardless of their renamed file extension.
+ 3. **Atomic File Swapping Operations:** Built a safe file-swapping pipeline during record adjustments that ensures older slide assets are *only* pruned from disk (`unlink()`) once the new image upload and MySQL database transactions succeed completely. If a transaction fails, it auto-rolls back file transfers to save space and prevent broken carousel links.
+ 4. **UX Destructive Safeguards:** Wired JavaScript confirmation dialog interceptors on all dashboard element removal controls to protect against unintended record purging.
+ 5. **Separation of Concerns:** Externalized frontend interactive JavaScript controllers into detached component assets (`main.js`), eliminating script parsing overhead from server-side markup trees.
+ 6. **De-coupled Asset Management:** Externalized all custom presentation layers into a dedicated stylesheet layout (`style.css`). This strict separation reduces template overhead, enables native browser asset caching, and radically maximizes Cumulative Layout Shift (CLS) performance scores.
+
+* **What I would add with more time:**
+ 1. **Architecture Upgrade (React & Framework Integration):** If given more time, I would refactor the front-end architecture of this module into a single, cohesive **React.js application**. Instead of server-side PHP loops rendering the static containers, I would create a highly optimized, state-driven reusable component layout. It would dynamically pull database slides through a lightweight **asynchronous Ajax API layer** hosted on an optimized **Laravel**, **CodeIgniter**, or **Yii** REST API controller endpoint. This structural separation would completely eliminate layout flickering, enable smooth fluid layout translations, and drastically decouple the front-end presentation from our core server layer.
+
+---
+
+### 2. How would you track down a performance issue in production? Have you ever had to do this?
+Yes, tracking down latency and system bottlenecks in production requires a highly structured, multi-tier diagnostic approach across the entire stack:
+
+* **Database Tier (PostgreSQL / MySQL):** I look for unoptimized queries, missing foreign key indexes, or heavy sequential table scans. In PostgreSQL, I run `EXPLAIN ANALYZE` on slower queries to track execution tree costs. In MySQL, I utilize the Slow Query Log and implement connection pooling to handle heavy user traffic efficiently.
+* **Backend Application Tier (Laravel / CodeIgniter / Yii):** I profile execution timelines using specialized ecosystem tooling (e.g., Laravel Telescope, debug bars, or Xdebug profiling logs). Common bottlenecks often include "N+1 query problems" which I resolve using eager loading. I optimize performance by caching highly repetitive, static data queries using Redis or Memcached structures.
+* **Frontend Tier (React.js / Ajax / Asset Pipelines):** For client-side latency, I audit network operations in Chrome DevTools to check asynchronous Ajax payload sizes. In React, I prevent unnecessary re-renders of complex visual structures by optimizing the component architecture with hooks like `useMemo`, `useCallback`, and implementing lazy loading for large code splittings.
+* **Workflow & Error Isolation:** I track application issues through centralized error logging platforms and align bug resolutions systematically within our team's **Jira** sprint tracking boards.
+
+---
+
+### 3. Please describe yourself using JSON.
+```json
+{
+ "name": "Ashwini",
+ "role": "Full Stack Developer",
+ "professional_profile": {
+ "philosophy": "Detail-oriented engineer focused on writing highly optimized, clean, and reusable code structures following robust DRY and SOLID design principles.",
+ "methodologies": ["Agile/Scrum", "MVC Architecture", "Object-Oriented Programming (OOP)"]
+ },
+ "technical_skills": {
+ "backend_frameworks": ["Laravel", "CodeIgniter", "Yii", "PHP (Core & OOP)"],
+ "frontend_ecosystem": ["React.js", "JavaScript (ES6+)", "jQuery", "Ajax", "Bootstrap 5", "HTML5/CSS3"],
+ "databases_and_storage": ["PostgreSQL", "MySQL"],
+ "version_control": ["Git", "SVN"],
+ "project_management_tools": ["JIRA"]
+ },
+ "engineering_strengths": {
+ "optimization": "Database indexing, query profiling, caching strategies, and minifying asset payloads to enhance Core Web Vitals.",
+ "reusability": "Designing generic database service layers, building reusable React components, and abstracting shared utility helpers.",
+ "attention_to_detail": "Thorough edge-case testing, cross-browser CSS responsiveness, and strict adherence to Figma/design layout mockups."
+ }
+}
\ No newline at end of file
diff --git a/app/config/config.php b/app/config/config.php
new file mode 100644
index 0000000..d854310
--- /dev/null
+++ b/app/config/config.php
@@ -0,0 +1,19 @@
+connection = null;
+
+ try {
+
+ $this->connection = new PDO(
+ "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME,
+ DB_USER,
+ DB_PASS
+ );
+
+ $this->connection->setAttribute(
+ PDO::ATTR_ERRMODE,
+ PDO::ERRMODE_EXCEPTION
+ );
+
+ } catch (PDOException $e) {
+ die("Connection failed: " . $e->getMessage());
+ }
+
+ return $this->connection;
+ }
+}
\ No newline at end of file
diff --git a/app/controllers/SlideController.php b/app/controllers/SlideController.php
new file mode 100644
index 0000000..fb0dd47
--- /dev/null
+++ b/app/controllers/SlideController.php
@@ -0,0 +1,40 @@
+slideModel = new Slide($db);
+ }
+
+ // GET ALL SLIDES (for frontend or admin list)
+ public function index()
+ {
+ return $this->slideModel->getAllSlides();
+ }
+
+ // CREATE SLIDE
+ public function store($data)
+ {
+ return $this->slideModel->createSlide($data);
+ }
+
+ public function getById($id)
+ {
+ return $this->slideModel->getSlideById($id);
+ }
+
+ public function update($data)
+ {
+ return $this->slideModel->updateSlide($data);
+ }
+
+ public function delete($id)
+ {
+ return $this->slideModel->deleteSlide($id);
+ }
+}
\ No newline at end of file
diff --git a/app/models/slide.php b/app/models/slide.php
new file mode 100644
index 0000000..ba9d0fe
--- /dev/null
+++ b/app/models/slide.php
@@ -0,0 +1,115 @@
+connection = $database;
+ }
+
+ // Get all slides
+ public function getAllSlides()
+ {
+ $query = "SELECT * FROM {$this->table} ORDER BY display_order ASC";
+
+ $statement = $this->connection->prepare($query);
+
+ $statement->execute();
+
+ return $statement->fetchAll(PDO::FETCH_ASSOC);
+ }
+
+ // Create slide
+ public function createSlide($data)
+ {
+ $query = "INSERT INTO {$this->table}
+ (
+ tab_title,
+ tag_line,
+ slide_title,
+ description,
+ button_text,
+ button_link,
+ image,
+ display_order
+ )
+ VALUES
+ (
+ :tab_title,
+ :tag_line,
+ :slide_title,
+ :description,
+ :button_text,
+ :button_link,
+ :image,
+ :display_order
+ )";
+
+ $statement = $this->connection->prepare($query);
+
+ return $statement->execute([
+
+ ':tab_title' => $data['tab_title'],
+ ':tag_line' => $data['tag_line'],
+ ':slide_title' => $data['slide_title'],
+ ':description' => $data['description'],
+ ':button_text' => $data['button_text'],
+ ':button_link' => $data['button_link'],
+ ':image' => $data['image'],
+ ':display_order' => $data['display_order']
+
+ ]);
+ }
+
+ public function getSlideById($id)
+ {
+ $query = "SELECT * FROM slides WHERE id = :id";
+ $stmt = $this->connection->prepare($query);
+ $stmt->bindParam(':id', $id);
+ $stmt->execute();
+
+ return $stmt->fetch(PDO::FETCH_ASSOC);
+ }
+
+ public function updateSlide($data)
+ {
+ $query = "UPDATE slides SET
+ tab_title = :tab_title,
+ tag_line = :tag_line,
+ slide_title = :slide_title,
+ description = :description,
+ button_text = :button_text,
+ button_link = :button_link,
+ image = :image,
+ display_order = :display_order
+ WHERE id = :id";
+
+ $stmt = $this->connection->prepare($query);
+
+ return $stmt->execute([
+ ':tab_title' => $data['tab_title'],
+ ':tag_line' => $data['tag_line'],
+ ':slide_title' => $data['slide_title'],
+ ':description' => $data['description'],
+ ':button_text' => $data['button_text'],
+ ':button_link' => $data['button_link'],
+ ':image' => $data['image'],
+ ':display_order' => $data['display_order'],
+ ':id' => $data['id']
+ ]);
+ }
+
+ public function deleteSlide($id)
+ {
+ $query = "DELETE FROM slides WHERE id = :id";
+
+ $stmt = $this->connection->prepare($query);
+
+ return $stmt->execute([
+ ':id' => $id
+ ]);
+ }
+}
\ No newline at end of file
diff --git a/assets/css/style.css b/assets/css/style.css
new file mode 100644
index 0000000..2442b93
--- /dev/null
+++ b/assets/css/style.css
@@ -0,0 +1,357 @@
+
+body {
+ margin: 0;
+ background: #072b4a; /* Matched to design dark blue */
+ font-family: Arial, sans-serif;
+}
+
+/* ====================================
+MAIN SECTION
+==================================== */
+.main-section {
+ padding: 60px 0;
+}
+
+.main-heading {
+ text-align: center;
+ color: #fff;
+ margin-bottom: 45px;
+}
+
+.main-heading h1 {
+ font-size: 42px;
+ font-weight: normal;
+ margin-bottom: 15px;
+}
+
+.main-heading p {
+ font-size: 16px;
+ color: #b0c4de;
+ max-width: 600px;
+ margin: 0 auto;
+}
+
+.custom-container {
+ width: 100%;
+ max-width: 1140px;
+ margin: 0 auto;
+ padding: 0 15px;
+}
+
+/* ====================================
+DESKTOP SYSTEM (Widths >= 992px)
+==================================== */
+.desktop-view {
+ display: block;
+}
+
+.mobile-view {
+ display: none;
+}
+
+.main-wrapper {
+ display: flex;
+ height: 520px;
+ background: #fff;
+ box-shadow: 0 10px 30px rgba(0,0,0,0.2);
+ overflow: hidden;
+}
+
+/* LEFT TABS */
+.tab-wrapper {
+ width: 28%;
+ background: #f4f5f7;
+ padding: 25px 15px;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ gap: 5px;
+}
+
+.tab-item {
+ background: #fff;
+ padding: 18px 20px;
+ margin-bottom: 15px;
+ font-size: 20px;
+ font-weight: 600;
+ color: #333;
+ cursor: pointer;
+ position: relative;
+ transition: 0.3s;
+ border-radius: 4px;
+ display: flex;
+ align-items: center;
+ gap: 15px;
+ box-shadow: 0 2px 4px rgba(0,0,0,0.02);
+}
+
+.tab-item.active {
+ box-shadow: 0 4px 12px rgba(0,0,0,0.08);
+}
+
+.tab-item.active::after {
+ content: '';
+ position: absolute;
+ right: -15px;
+ top: 50%;
+ transform: translateY(-50%);
+ border-left: 15px solid #74bfd0;
+ border-top: 15px solid transparent;
+ border-bottom: 15px solid transparent;
+ z-index: 10;
+}
+
+.tab-icon {
+ width: 32px;
+ height: 32px;
+ object-fit: contain;
+}
+
+.tab-text {
+ flex-grow: 1;
+}
+
+/* TEAL CENTER SLIDER */
+.content-wrapper {
+ width: 37%;
+ background: #74bfd0;
+ height: 100%;
+}
+
+.content-slider {
+ height: 100%;
+}
+
+.content-slider .slick-list,
+.content-slider .slick-track,
+.content-slider .slick-slide > div {
+ height: 100%;
+}
+
+.slide-box {
+ height: 100%;
+ /* Increase padding to match design, but keep it tight at the top */
+ padding: 50px 40px;
+ display: flex;
+ flex-direction: column;
+ /* CHANGE THIS FROM center TO flex-start */
+ justify-content: flex-start;
+ color: #fff;
+}
+
+.slide-box h5 {
+ font-size: 11px;
+ text-transform: uppercase;
+ margin-bottom: 20px;
+ background: rgba(0, 0, 0, 0.15);
+ align-self: flex-start;
+ padding: 5px 12px;
+ letter-spacing: 1px;
+ font-weight: bold;
+ border-radius: 2px;
+}
+
+.slide-box h2 {
+ font-size: 32px;
+ line-height: 1.3;
+ margin-bottom: 25px;
+ font-weight: normal;
+}
+
+.slide-box .learn-more-link {
+ color: #fff;
+ text-decoration: none;
+ font-size: 16px;
+ font-weight: 500;
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+}
+
+/* RIGHT FRAMED IMAGE SLIDER */
+.image-wrapper {
+ width: 35%;
+ height: 100%;
+ background: #000;
+}
+
+.image-slider {
+ height: 100%;
+}
+
+.image-slider .slick-list,
+.image-slider .slick-track,
+.image-slider .slick-slide > div {
+ height: 100%;
+}
+
+.image-wrapper img {
+ width: 100%;
+ height: 100% !important;
+ object-fit: cover;
+ display: block;
+}
+
+.slick-dots {
+ bottom: 25px !important;
+}
+.slick-dots li button:before {
+ color: #fff !important;
+ opacity: 0.4;
+ font-size: 10px;
+}
+.slick-dots li.slick-active button:before {
+ color: #fff !important;
+ opacity: 1;
+}
+
+/* ====================================
+RESPONSIVE REFACTOR (Under 991px Viewport)
+==================================== */
+@media (max-width: 991px) {
+ .desktop-view {
+ display: none !important;
+ }
+
+ .mobile-view {
+ display: block !important;
+ }
+
+ .main-section {
+ padding: 40px 0;
+ }
+
+ .main-heading h1 {
+ font-size: 32px;
+ }
+
+ .mobile-card {
+ margin-bottom: 16px;
+ background: #fff;
+ border-radius: 6px;
+ overflow: hidden;
+ box-shadow: 0 4px 15px rgba(0,0,0,0.15);
+ }
+
+ .mobile-tab {
+ padding: 16px 20px;
+ cursor: pointer;
+ background: #fff;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ }
+
+ .mobile-tab-left {
+ display: flex;
+ align-items: center;
+ gap: 15px;
+ }
+
+ .mobile-tab-left img {
+ width: 35px;
+ height: 35px;
+ object-fit: contain;
+ }
+
+ .mobile-title {
+ font-size: 20px;
+ font-weight: 600;
+ color: #333;
+ margin: 0;
+ }
+
+ /* Icon box container sizing */
+ .mobile-icon {
+ width: 30px;
+ height: 30px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ }
+
+ .mobile-icon img {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+ }
+
+ .mobile-content {
+ position: relative;
+ }
+
+ /* Teal Top pointer triangle block */
+ .mobile-content::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 50%;
+ transform: translateX(-50%);
+ border-bottom: 10px solid #74bfd0;
+ border-left: 10px solid transparent;
+ border-right: 10px solid transparent;
+ z-index: 5;
+ }
+
+ /* Refactored layout with background fallbacks */
+ .mobile-slide {
+ /* Adjusted top padding to 40px to start text cleanly without a massive drop gap */
+ padding: 40px 25px 55px 25px;
+ text-align: center;
+ color: #fff;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ /* CHANGE THIS FROM center TO flex-start */
+ justify-content: flex-start;
+ min-height: 340px;
+ position: relative;
+ z-index: 1;
+ background-size: cover;
+ background-position: center;
+ background-repeat: no-repeat;
+ }
+
+ /* Pure overlay layer to guarantee image visibility blends smoothly */
+ .mobile-slide::after {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(116, 191, 208, 0.92); /* Matches your design layout overlay tints */
+ z-index: -1;
+ }
+
+ .mobile-slide h5 {
+ font-size: 11px;
+ letter-spacing: 1px;
+ margin-bottom: 15px;
+ text-transform: uppercase;
+ background: rgba(0, 0, 0, 0.15);
+ padding: 5px 12px;
+ font-weight: bold;
+ display: inline-block;
+ border-radius: 2px;
+ }
+
+ .mobile-slide h2 {
+ font-size: 24px;
+ line-height: 1.4;
+ margin-bottom: 20px;
+ font-weight: normal;
+ max-width: 90%;
+ }
+
+ .mobile-slide .learn-more-link {
+ color: #fff;
+ text-decoration: none;
+ font-size: 16px;
+ font-weight: 500;
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ }
+}
diff --git a/assets/images/DL-communication.svg b/assets/images/DL-communication.svg
new file mode 100644
index 0000000..0088544
--- /dev/null
+++ b/assets/images/DL-communication.svg
@@ -0,0 +1,38 @@
+
+
+
+
diff --git a/assets/images/DL-learning.svg b/assets/images/DL-learning.svg
new file mode 100644
index 0000000..bfeb0b5
--- /dev/null
+++ b/assets/images/DL-learning.svg
@@ -0,0 +1,50 @@
+
+
+
+
diff --git a/assets/images/DL-technology.svg b/assets/images/DL-technology.svg
new file mode 100644
index 0000000..59f2ea5
--- /dev/null
+++ b/assets/images/DL-technology.svg
@@ -0,0 +1,48 @@
+
+
+
+
diff --git a/assets/images/arrow-right.svg b/assets/images/arrow-right.svg
new file mode 100644
index 0000000..37d307b
--- /dev/null
+++ b/assets/images/arrow-right.svg
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/assets/images/minus-01.svg b/assets/images/minus-01.svg
new file mode 100644
index 0000000..a0639ca
--- /dev/null
+++ b/assets/images/minus-01.svg
@@ -0,0 +1,9 @@
+
+
+
+
diff --git a/assets/images/plus-01.svg b/assets/images/plus-01.svg
new file mode 100644
index 0000000..d80a766
--- /dev/null
+++ b/assets/images/plus-01.svg
@@ -0,0 +1,9 @@
+
+
+
+
diff --git a/assets/js/main.js b/assets/js/main.js
new file mode 100644
index 0000000..535411f
--- /dev/null
+++ b/assets/js/main.js
@@ -0,0 +1,64 @@
+$(document).ready(function(){
+ // Configuration object to avoid repeating settings
+ const slickOptions = {
+ slidesToShow: 1,
+ slidesToScroll: 1,
+ arrows: false,
+ fade: true
+ };
+
+ /* Initialize Content Slider */
+ $('.content-slider').slick({
+ ...slickOptions,
+ dots: true,
+ asNavFor: '.image-slider'
+ });
+
+ /* Initialize Image Slider */
+ $('.image-slider').slick({
+ ...slickOptions,
+ asNavFor: '.content-slider'
+ });
+
+ /* Sync Left Tabs with Slick Slide Shifts */
+ $('.tab-item').click(function(){
+ const slideIndex = $(this).data('slide');
+ $('.content-slider').slick('slickGoTo', parseInt(slideIndex));
+ });
+
+ /* Active Tab Class Management on Slide Change */
+ $('.content-slider').on('afterChange', function(event, slick, currentSlide){
+ $('.tab-item').removeClass('active');
+ $('.tab-item').extra = $(`.tab-item[data-slide="${currentSlide}"]`).addClass('active');
+ });
+
+ /* Mobile Accordion Execution Logic */
+ $('.mobile-tab').click(function(){
+ const $this = $(this);
+ const parent = $this.closest('.mobile-card');
+ const contentPanel = parent.find('.mobile-content');
+
+ // Target specific icons
+ const currentIconContainer = $this.find('.mobile-icon');
+ const plusIcon = currentIconContainer.data('plus-src');
+ const minusIcon = currentIconContainer.data('minus-src');
+
+ // If clicked item is already open, close it
+ if (contentPanel.is(':visible')) {
+ contentPanel.slideUp();
+ currentIconContainer.find('img').attr('src', plusIcon);
+ return;
+ }
+
+ // Close all other open cards completely and reset their icon graphics to a plus sign
+ $('.mobile-content').slideUp();
+ $('.mobile-icon').each(function() {
+ const $iconBox = $(this);
+ $iconBox.find('img').attr('src', $iconBox.data('plus-src'));
+ });
+
+ // Open current targeted slide smoothly and switch icon to a minus sign
+ contentPanel.slideDown();
+ currentIconContainer.find('img').attr('src', minusIcon);
+ });
+});
\ No newline at end of file
diff --git a/database/database.sql b/database/database.sql
new file mode 100644
index 0000000..84a3a1b
--- /dev/null
+++ b/database/database.sql
@@ -0,0 +1,23 @@
+CREATE TABLE slides (
+
+ id INT AUTO_INCREMENT PRIMARY KEY,
+
+ tab_title VARCHAR(255) NOT NULL,
+
+ tag_line VARCHAR(255) NOT NULL,
+
+ slide_title VARCHAR(255) NOT NULL,
+
+ description TEXT NOT NULL,
+
+ button_text VARCHAR(100) NOT NULL,
+
+ button_link VARCHAR(255) NOT NULL,
+
+ image VARCHAR(255) NOT NULL,
+
+ display_order INT DEFAULT 0,
+
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+
+);
\ No newline at end of file
diff --git a/public/.htaccess b/public/.htaccess
new file mode 100644
index 0000000..cd8bc6f
--- /dev/null
+++ b/public/.htaccess
@@ -0,0 +1,10 @@
+RewriteEngine On
+
+# Force remove trailing slash
+RewriteCond %{REQUEST_FILENAME} !-d
+RewriteRule ^(.+)/$ /$1 [R=301,L]
+
+# Route /admin → /admin/
+RewriteCond %{REQUEST_FILENAME} !-f
+RewriteCond %{REQUEST_FILENAME} !-d
+RewriteRule ^admin$ admin/index.php [L]
\ No newline at end of file
diff --git a/public/admin/create.php b/public/admin/create.php
new file mode 100644
index 0000000..62f3928
--- /dev/null
+++ b/public/admin/create.php
@@ -0,0 +1,159 @@
+connect();
+
+$controller = new SlideController($db);
+
+$message = "";
+
+/* =========================
+ FORM SUBMIT LOGIC
+========================= */
+if (isset($_POST['submit'])) {
+
+ $errors = [];
+ $imageName = "";
+
+ // 1. DATA SANITIZATION & EMPTY FIELDS VALIDATION
+ $tab_title = trim($_POST['tab_title'] ?? '');
+ $tag_line = trim($_POST['tag_line'] ?? '');
+ $slide_title = trim($_POST['slide_title'] ?? '');
+ $description = trim($_POST['description'] ?? '');
+ $button_text = trim($_POST['button_text'] ?? '');
+ $button_link = trim($_POST['button_link'] ?? '');
+ $display_order = trim($_POST['display_order'] ?? 0);
+
+ if (empty($tab_title)) { $errors[] = "Tab Title is required."; }
+ if (empty($tag_line)) { $errors[] = "Tag Line is required."; }
+ if (empty($slide_title)) { $errors[] = "Slide Title is required."; }
+
+ // 2. SECURE IMAGE UPLOAD VALIDATION
+ if (!empty($_FILES['image']['name'])) {
+
+ // Check for internal upload errors
+ if ($_FILES['image']['error'] !== UPLOAD_ERR_OK) {
+ $errors[] = "An error occurred during the image upload.";
+ } else {
+ // Validate file type (MIME Type) to ensure it's an image
+ $fileTmpPath = $_FILES['image']['tmp_name'];
+ $allowedMimeTypes = ['image/jpeg', 'image/png', 'image/jpg', 'image/svg+xml'];
+
+ // Using finfo to check actual file content instead of just the extension
+ $finfo = new finfo(FILEINFO_MIME_TYPE);
+ $mimeType = $finfo->file($fileTmpPath);
+
+ if (!in_array($mimeType, $allowedMimeTypes)) {
+ $errors[] = "Invalid file format. Only JPG, PNG, and SVG formats are permitted.";
+ }
+
+ // Validate file size (optional, e.g., max 3MB)
+ $maxFileSize = 3 * 1024 * 1024; // 3 Megabytes
+ if ($_FILES['image']['size'] > $maxFileSize) {
+ $errors[] = "The uploaded image exceeds the maximum permitted size of 3MB.";
+ }
+ }
+
+ // If no file errors occurred, generate a sanitized filename and prepare upload path
+ if (empty($errors)) {
+ // Clean up original file name to avoid path traversal vulnerabilities
+ $cleanOriginalName = preg_replace("/[^a-zA-Z0-9\._-]/", "", $_FILES['image']['name']);
+ $imageName = time() . '_' . $cleanOriginalName;
+ $targetPath = UPLOAD_PATH . $imageName;
+ }
+ } else {
+ $errors[] = "Please upload a slide image asset.";
+ }
+
+ // 3. EXECUTION OR ERROR HANDLING
+ if (empty($errors)) {
+
+ $data = [
+ 'tab_title' => $tab_title,
+ 'tag_line' => $tag_line,
+ 'slide_title' => $slide_title,
+ 'description' => $description,
+ 'button_text' => $button_text,
+ 'button_link' => $button_link,
+ 'image' => $imageName,
+ 'display_order' => intval($display_order) // Force integer data type
+ ];
+
+ // Perform final file relocation safely right before database insert
+ if (move_uploaded_file($_FILES['image']['tmp_name'], $targetPath)) {
+ $result = $controller->store($data);
+
+ if ($result) {
+ header("Location: index.php");
+ exit;
+ } else {
+ // If DB storage fails, clean up the newly uploaded file to avoid dangling assets
+ if (file_exists($targetPath)) {
+ unlink($targetPath);
+ }
+ $message = "Failed to write slide entry into the database.";
+ }
+ } else {
+ $message = "File system restriction: Could not transfer uploaded image to the storage directory.";
+ }
+ } else {
+ // Flatten array arrays to build a visual notification block for the dashboard view
+ $message = implode("
", $errors);
+ }
+}
+
+?>
+
+
+
+
+
| ID | +Tab Title | +Slide Title | +Image | +Action | +
|---|---|---|---|---|
| = $slide['id']; ?> | += $slide['tab_title']; ?> | += $slide['slide_title']; ?> | +
+ |
+ + Edit + + Delete + + | +
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo
+