AI 對話紀錄 March 27, 2025
Q: I think I will test what we have now and leave the scroll down feature later. It's an interesting idea to allow for HTML in the title but just text for the content, when what I imagined was make both of them HTML because both title and content have to stand out in case a dark background is used. After entering some sample contents to web_content, I think setting the main content to HTML is necessary for breaking down the text into paragraphs and making lists. Please provide the full php and CSS for HTML section title and content.
<
A: Attached is how it looks for the generated section. Can we hide the list bullets as it looks ugly to have centered text (Which is good) but bullets that are far away.
<
The updates work fine largely and HTML contents are rendered correctly. But I find a section (Position = 4, model = title-text-image, title = "2025第一次:醫道Chill輕鬆療癒Cafe") failing to render the image.
A: I’m glad to hear the updates are largely working well and the HTML content is rendering correctly! However, I understand the issue with the section at Position 4 (model = title-text-image, title = "2025第一次:醫道Chill輕鬆療癒Cafe") where the image isn’t rendering on https://admin.nlt-chaplaincy.org/public/index.php, despite the record being correct and the image being uploaded. Let’s troubleshoot this step by step to identify why the image isn’t showing and fix it.
Step 1: Analyze the Issue
Context: The section uses the title-text-image model, which likely means it’s trying to display an image via the image field (not theme_img, which is used for Background/Separator).
Current Behavior: The image isn’t rendering, even though you’ve confirmed the image field in the web_content record has a valid uploaded file.
Possible Causes:
The image field isn’t being output in pull-contents.php for the title-text-image model.
The image path might be incorrect (e.g., wrong directory or filename).
The image file might not be accessible (e.g., permissions issue, file not found).
CSS might be hiding the image unintentionally.
Step 2: Check the PHP Logic
Looking at the current pull-contents.php, I notice it doesn’t handle the image field for the title-text-image model—it only processes theme_img for Background/Separator and video_embed for videos. The image field (likely used for the title-text-image model) isn’t being rendered. Let’s add logic to display the image field when the model is title-text-image.
Updated pull-contents.php
Here’s the full updated PHP with support for rendering the image field:
php
<?php
// pull-contents.php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
$config_path = '../config.php';
if (!file_exists($config_path)) {
die("Error: config.php not found at $config_path");
}
require_once $config_path;
if (!isset($dbServer) || !isset($dbUsername) || !isset($dbPassword) || !isset($dbDatabase)) {
die("Error: AppGini database variables not defined in config.php");
}
$conn = new mysqli($dbServer, $dbUsername, $dbPassword, $dbDatabase, $dbPort ?: 3306);
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
$current_page = isset($_GET['page']) ? $_GET['page'] : 'landing';
$status = 'Show';
$sql = "SELECT section, title, content, image, video_embed, button_link, button_text, position, model, date_edited, theme_img, img_role FROM web_content WHERE page = ? AND status = ? ORDER BY position ASC, date_edited DESC";
$stmt = $conn->prepare($sql);
if (!$stmt) {
die("Prepare failed: " . $conn->error);
}
$stmt->bind_param("ss", $current_page, $status);
$stmt->execute();
$result = $stmt->get_result();
$sections = [];
if ($result->num_rows > 0) {
while ($row = $result->fetch_assoc()) {
$pos = $row['position'];
if (!isset($sections[$pos]) || $row['date_edited'] > $sections[$pos]['date_edited']) {
$sections[$pos] = $row;
}
}
}
if (!empty($sections)) {
ksort($sections);
$current_index = 0;
foreach ($sections as $position => $row) {
$current_index++;
if ($position < 1 || $position > 10) {
continue;
}
$has_image = !empty($row['theme_img']);
$img_role = trim($row['img_role'] ?? 'Background');
$background_style = ($has_image && $img_role === 'Background') ? " style=\"background-image: url('../images/" . htmlspecialchars($row['theme_img']) . "');\"" : '';
echo "<div class=\"content__entry\"$background_style>";
echo "<h1 class=\"align-center\">" . $row['title'] . "</h1>";
echo "<div class=\"generic-content align-center\">" . $row['content'] . "</div>";
// Render image for title-text-image model
if ($row['model'] === 'title-text-image' && !empty($row['image'])) {
$image_url = '../images/' . htmlspecialchars($row['image']);
echo "<div class=\"content-image\"><img src=\"$image_url\" alt=\"Section Image\" class=\"welcome-image\"></div>";
}
if ($row['video_embed']) {
$clean_embed = preg_replace('/sandbox(="[^"]*"|)/', '', $row['video_embed']);
echo '<div class="video-embed">' . $clean_embed . '</div>';
}
if (!empty($row['button_text'])) {
echo '<div class="content__buttons"><a class="myButton" href="' . htmlspecialchars($row['button_link'] ?: '#') . '">' . htmlspecialchars($row['button_text']) . '</a></div>';
}
echo "</div>";
if ($has_image && $img_role === 'Separator') {
$image_url = '../images/' . htmlspecialchars($row['theme_img']);
echo "<div class=\"separator\"><img src=\"$image_url\" alt=\"Section Separator\" class=\"separator-image\"></div>";
}
}
} else {
echo "No sections found.";
}
$stmt->close();
$conn->close();
?>
Changes
Image Rendering: Added a check for model === 'title-text-image' and !empty($row['image']). If true, it outputs the image in a
Path: Assumes the image field stores the filename (e.g., image.jpg), and images are in the ../images/ directory, matching your setup for theme_img.
Step 3: Verify CSS
The CSS already has .welcome-image styling, which should work for this image. Let’s confirm and make any necessary adjustments:
css
.welcome-image {
max-width: 100%;
height: auto;
margin: 1em auto;
display: block;
}
/* Add styling for the image container */
.content-image {
text-align: center;
margin: 1em 0;
}
This ensures the image is centered and has proper spacing, consistent with your design.
Step 4: Troubleshooting Checklist
If the image still doesn’t render after applying the PHP update:
Verify Image Path:
Check the image field value in the database for Position 4 (e.g., image.jpg).
Confirm the file exists in ../images/ (e.g., ../images/image.jpg).
Test the URL directly in your browser (e.g., https://admin.nlt-chaplaincy.org/public/images/image.jpg).
File Permissions:
Inspect HTML:
View the page source or inspect the element at Position 4—check if the tag is output with the correct src.
Look for 404 errors in the browser’s Network tab.
AppGini Upload:
Step 5: Adjust Image Path (If Needed)
If AppGini stores uploaded images in a subdirectory (e.g., ../images/web_content-image/), update the path in the PHP:
php
$image_url = '../images/web_content-image/' . htmlspecialchars($row['image']);
You can find the exact path in AppGini under the image field settings (look for “Upload Path” or similar).
Testing
At https://admin.nlt-chaplaincy.org/public/index.php:
Reload the page and check Position 4.
Confirm the image for "2025第一次:醫道Chill輕鬆療癒Cafe" appears.
If it doesn’t, inspect the HTML and Network tab to see if the tag is there and if the src URL loads.
Notes
Path Confirmation: If the image still doesn’t show, let me know the exact value of the image field (e.g., cafe.jpg) and where the file is physically stored on the server—I can help adjust the path.
Other Sections: The change only affects title-text-image models, so other sections (e.g., with Background/Separator) should remain unchanged.
Let me know if the image appears after this update! If not, we’ll dig deeper into the path or permissions. Everything else looking good?
Q: Great! Everything looks good! I've just received a story from the pastor, which I've attached as an image. I plan to quote a sentence from the text and link a button to a full-story page that will be created later.However, I want to check if it's possible to display the full-story page in a modal window or pop-up. I believe that chaplaincy readers can vary greatly in their educational backgrounds. For those who may find reading challenging, having a detail page pop up from the landing page would be easier. This way, they won’t have to navigate away from the page, which could disrupt their flow and cause confusion.
To clarify a bit, I want to reuse the existing fields in web_content and make all buttons default to a modal window. For example, when the button link is "full-story-marie.php", this page will be shown inside a modal window. This page will be dynamically generated like the home page. What we do is just to wrap it inside a pop-up.
A: Got it—since you’re okay without the placeholder full-story-marie.php, we’ll focus on setting up the modal functionality using the existing web_content table fields and ensure all buttons open their linked pages in a modal window. The linked pages (e.g., full-story-marie.php) will be dynamically generated later, and we’ll wrap their content in a pop-up on the landing page (https://admin.nlt-chaplaincy.org/public/index.php). This approach keeps users on the landing page, enhancing accessibility and flow for readers who might find navigation challenging.
Let’s finalize the implementation by ensuring the modal works with any button_link value, fetching the content dynamically and displaying it in a pop-up.
Step 1: Recap of the Setup
Fields Used: We’ll use title, content, button_text, and button_link from the web_content table.
Example Section:
title: “A Message from Our Pastor”
content: A quoted sentence, e.g., “
‘你嘅存在係一個奇蹟,你嘅人生係一個禮物…’
”button_text: “Read the Full Story”
button_link: full-story-marie.php (or any dynamically generated page URL)
Goal: When the button is clicked, fetch the content of full-story-marie.php and display it in a modal without navigating away from the landing page.
Step 2: Ensure pull-contents.php Supports Modal Triggers
The pull-contents.php from the previous update already sets up buttons as modal triggers with the modal-trigger class and data-modal-link attribute. It also includes the modal container. Let’s confirm it’s correct and make any necessary adjustments for clarity:
php
<?php
// pull-contents.php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
$config_path = '../config.php';
if (!file_exists($config_path)) {
die("Error: config.php not found at $config_path");
}
require_once $config_path;
if (!isset($dbServer) || !isset($dbUsername) || !isset($dbPassword) || !isset($dbDatabase)) {
die("Error: AppGini database variables not defined in config.php");
}
$conn = new mysqli($dbServer, $dbUsername, $dbPassword, $dbDatabase, $dbPort ?: 3306);
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
$current_page = isset($_GET['page']) ? $_GET['page'] : 'landing';
$status = 'Show';
$sql = "SELECT section, title, content, image, video_embed, button_link, button_text, position, model, date_edited, theme_img, img_role FROM web_content WHERE page = ? AND status = ? ORDER BY position ASC, date_edited DESC";
$stmt = $conn->prepare($sql);
if (!$stmt) {
die("Prepare failed: " . $conn->error);
}
$stmt->bind_param("ss", $current_page, $status);
$stmt->execute();
$result = $stmt->get_result();
$sections = [];
if ($result->num_rows > 0) {
while ($row = $result->fetch_assoc()) {
$pos = $row['position'];
if (!isset($sections[$pos]) || $row['date_edited'] > $sections[$pos]['date_edited']) {
$sections[$pos] = $row;
}
}
}
if (!empty($sections)) {
ksort($sections);
$current_index = 0;
foreach ($sections as $position => $row) {
$current_index++;
if ($position < 1 || $position > 10) {
continue;
}
$has_image = !empty($row['theme_img']);
$img_role = trim($row['img_role'] ?? 'Background');
$background_style = ($has_image && $img_role === 'Background') ? " style=\"background-image: url('../images/" . htmlspecialchars($row['theme_img']) . "');\"" : '';
echo "<div class=\"content__entry\"$background_style>";
echo "<h1 class=\"align-center\">" . $row['title'] . "</h1>";
echo "<div class=\"generic-content align-center\">" . $row['content'] . "</div>";
if ($row['model'] === 'title-text-image' && !empty($row['image'])) {
$image_url = '../images/' . htmlspecialchars($row['image']);
echo "<div class=\"content-image\"><img src=\"$image_url\" alt=\"Section Image\" class=\"welcome-image\"></div>";
}
if ($row['video_embed']) {
$clean_embed = preg_replace('/sandbox(="[^"]*"|)/', '', $row['video_embed']);
echo '<div class="video-embed">' . $clean_embed . '</div>';
}
if (!empty($row['button_text'])) {
$button_link = htmlspecialchars($row['button_link'] ?: '#');
$button_text = htmlspecialchars($row['button_text']);
echo "<div class=\"content__buttons\">";
echo "<a class=\"myButton modal-trigger\" href=\"#\" data-modal-link=\"$button_link\">$button_text</a>";
echo "</div>";
}
echo "</div>";
if ($has_image && $img_role === 'Separator') {
$image_url = '../images/' . htmlspecialchars($row['theme_img']);
echo "<div class=\"separator\"><img src=\"$image_url\" alt=\"Section Separator\" class=\"separator-image\"></div>";
}
}
} else {
echo "No sections found.";
}
// Add modal container at the bottom of the page
echo '<div id="story-modal" class="modal">';
echo '<div class="modal-content">';
echo '<span class="modal-close">×</span>';
echo '<div id="modal-body"></div>';
echo '</div>';
echo '</div>';
$stmt->close();
$conn->close();
?>
Step 3: Ensure Modal CSS and JavaScript Are Included
The modal CSS and JavaScript from the previous update should already be in place, but let’s confirm and make slight improvements for accessibility and robustness.
Modal CSS (Append to Existing CSS)
css
/* Modal Styles */
.modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0, 0, 0, 0.5);
}
.modal-content {
background-color: #fff;
margin: 5% auto;
padding: 20px;
border: 1px solid #888;
width: 80%;
max-width: 800px;
position: relative;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
.modal-close {
color: #aaa;
position: absolute;
top: 10px;
right: 20px;
font-size: 28px;
font-weight: bold;
cursor: pointer;
transition: color 0.3s ease;
}
.modal-close:hover,
.modal-close:focus {
color: #000;
text-decoration: none;
}
#modal-body {
font-size: 1em;
line-height: 1.6;
color: #333;
}
#modal-body h1 {
font-size: 1.5em;
margin-bottom: 0.5em;
}
#modal-body p {
margin-bottom: 1em;
}
/* Accessibility improvements */
.modal[aria-hidden="false"] {
display: block;
}
.modal-close:focus {
outline: 2px solid #44c767;
outline-offset: 2px;
}
Modal JavaScript (Append to pull-contents.php or Include in a Separate File)
php
<script>
// Modal functionality
document.addEventListener('DOMContentLoaded', function() {
const modal = document.getElementById('story-modal');
const modalBody = document.getElementById('modal-body');
const closeBtn = document.querySelector('.modal-close');
const triggers = document.querySelectorAll('.modal-trigger');
// Function to open the modal
function openModal(content) {
modalBody.innerHTML = content;
modal.style.display = 'block';
modal.setAttribute('aria-hidden', 'false');
closeBtn.focus(); // Accessibility: focus on close button
}
// Function to close the modal
function closeModal() {
modal.style.display = 'none';
modal.setAttribute('aria-hidden', 'true');
modalBody.innerHTML = '';
}
// Open modal when a trigger button is clicked
triggers.forEach(trigger => {
trigger.addEventListener('click', function(e) {
e.preventDefault();
const link = this.getAttribute('data-modal-link');
// Show loading message
openModal('<p>Loading...</p>');
// Fetch the content of the linked page
fetch(link)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.text();
})
.then(data => {
// Extract the body content from the fetched page
const parser = new DOMParser();
const doc = parser.parseFromString(data, 'text/html');
const content = doc.querySelector('body') ? doc.querySelector('body').innerHTML : data;
openModal(content);
})
.catch(error => {
console.error('Error fetching content:', error);
openModal('<p>Sorry, the content could not be loaded. Please try again later.</p>');
});
});
});
// Close modal when the close button is clicked
closeBtn.addEventListener('click', closeModal);
// Close modal when clicking outside the modal content
window.addEventListener('click', function(event) {
if (event.target === modal) {
closeModal();
}
});
// Close modal with Escape key
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape' && modal.style.display === 'block') {
closeModal();
}
});
});
</script>
Improvements
Accessibility:
Added aria-hidden attributes to the modal for screen readers.
Focused the close button when the modal opens for keyboard navigation.
Added focus styles for the close button.
Error Handling:
Added a loading message while fetching content.
Improved error handling with a user-friendly message if the fetch fails.
Styling:
Step 4: Add the Story Teaser in AppGini
In AppGini, add a new record to web_content:
page: landing
position: 5 (or wherever you want it to appear)
status: Show
model: title-text
title:
content:
"你嘅存在係一個奇蹟,你嘅人生係一個禮物…" – A heartfelt story of hope and encouragement.
button_text: Read the Full Story
button_link: full-story-marie.php
img_role: No Image
Step 5: Testing
At https://admin.nlt-chaplaincy.org/public/index.php:
Check the Teaser: Ensure the new section appears with the quoted sentence and button.
Click the Button: Since full-story-marie.php doesn’t exist yet, you should see the error message “Sorry, the content could not be loaded. Please try again later.” in the modal.
Modal Behavior:
Confirm the modal opens and closes correctly (via close button, clicking outside, or Escape key).
Test accessibility: use Tab to navigate to the close button and press Enter to close.
Future Test: Once full-story-marie.php is created, clicking the button should load its content into the modal.
Step 6: Notes for full-story-marie.php
When you create full-story-marie.php, it can follow a similar structure to pull-contents.php, pulling content from the web_content table (or a new table for stories). For example:
php
<?php
// full-story-marie.php
// Placeholder for dynamic content
?>
<h1>造一抹綠彩來村出</h1>
<p>幾年前的一個下午,一位焦急的母親抱著患上發高燒的孩子直奔來我的醫院診所多學多樂的咖啡店...</p>
<!-- Add the full story content here -->
The JavaScript will fetch this content and display it in the modal.
Accessibility Considerations
Keyboard Navigation: The modal is keyboard-accessible with Tab and Escape.
Screen Readers: aria-hidden ensures screen readers handle the modal correctly.
Contrast: The modal’s dark overlay and white background ensure good readability.
Next Steps
Add the teaser section in AppGini as described.
Test the modal functionality on the landing page.
Let me know if the modal works as expected or if you need adjustments (e.g., styling, behavior).
Once full-story-marie.php is ready, we can test the full flow.
How does this look? Ready to test the modal, or do you want to tweak anything first?
<<Iterations of bug-fixes on the modal, including: Content overflown outside of the modal, missing close-window icon, close-window icon overflowing outside of the empty modal, failing to show linked page inside the modal, no images and videos are shown inside the modal.>>
Q: By inspecting the source code of index-demo, I think the issue is Publii generates this page with reference to the internal media, rather than those generated by our php to load image from ../images. That means we must test with a real page like index.php to see what truly will show. If so, can you:
A: Thank you for the update! I understand that the issue with index-demo.html not displaying images in the modal on https://admin.nlt-chaplaincy.org/public/index.php stems from Publii generating the page with references to internal media, rather than using the ../images/ path that your PHP-generated pages (like index.php) use. This makes sense—Publii likely uses a different media management system, which doesn’t align with the relative path structure (../images/) that your AppGini-generated pages rely on. As a result, the JavaScript in pull-contents.php isn’t resolving the image paths correctly for Publii-generated content.
You’re absolutely right that testing with a “real” page like index.php (or a new page like donation.php) that follows your AppGini structure will give us a more accurate representation of how the modal will behave with your actual content. Let’s proceed with your requests:
Create a quick donation.php page by loading a sample section from the web_content table for testing.
Reverse irrelevant changes to our PHP and CSS, specifically removing the absolute path conversion for images, since your AppGini-generated pages already work with relative paths (../images/).
Step 1: Create a Quick donation.php Page
We’ll create a donation.php page that mimics the structure of index.php by loading a sample section from the web_content table. This will ensure the page uses the same relative image paths (../images/) and content structure as your other AppGini-generated pages.
Step 1.1: Create donation.php
Create a new file named donation.php in the /public/ directory (same directory as index.php). This page will connect to the database, fetch a sample section from the web_content table, and display it in a similar structure to index.php.
php
<?php
// donation.php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
// Load the database configuration
$config_path = '../config.php';
if (!file_exists($config_path)) {
die("Error: config.php not found at $config_path");
}
require_once $config_path;
if (!isset($dbServer) || !isset($dbUsername) || !isset($dbPassword) || !isset($dbDatabase)) {
die("Error: AppGini database variables not defined in config.php");
}
// Connect to the database
$conn = new mysqli($dbServer, $dbUsername, $dbPassword, $dbDatabase, $dbPort ?: 3306);
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
// Fetch a sample section from web_content (e.g., position 1, page 'landing', status 'Show')
$current_page = 'landing'; // You can change this to a different page if needed
$status = 'Show';
$position = 1; // Sample section (you can change this to another position)
$sql = "SELECT section, title, content, image, video_embed, button_link, button_text, position, model, date_edited, theme_img, img_role
FROM web_content
WHERE page = ? AND status = ? AND position = ?
ORDER BY date_edited DESC
LIMIT 1";
$stmt = $conn->prepare($sql);
if (!$stmt) {
die("Prepare failed: " . $conn->error);
}
$stmt->bind_param("ssi", $current_page, $status, $position);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows > 0) {
$row = $result->fetch_assoc();
// Start outputting the HTML
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Donation Page</title>
<!-- Include any necessary CSS (you can link to the same CSS as index.php) -->
<link rel="stylesheet" href="styles.css"> <!-- Adjust the path to your CSS file -->
</head>
<body>
<div class="content__entry">
<h1 class="align-center"><?php echo $row['title']; ?></h1>
<div class="generic-content align-center"><?php echo $row['content']; ?></div>
<?php if ($row['model'] === 'title-text-image' && !empty($row['image'])): ?>
<div class="content-image">
<img src="../images/<?php echo htmlspecialchars($row['image']); ?>" alt="Section Image" class="welcome-image">
</div>
<?php endif; ?>
<?php if ($row['video_embed']): ?>
<div class="video-embed">
<?php echo preg_replace('/sandbox(="[^"]*"|)/', '', $row['video_embed']); ?>
</div>
<?php endif; ?>
<?php if (!empty($row['button_text'])): ?>
<div class="content__buttons">
<a class="myButton" href="<?php echo htmlspecialchars($row['button_link'] ?: '#'); ?>">
<?php echo htmlspecialchars($row['button_text']); ?>
</a>
</div>
<?php endif; ?>
</div>
<?php if (!empty($row['theme_img']) && $row['img_role'] === 'Separator'): ?>
<div class="separator">
<img src="../images/<?php echo htmlspecialchars($row['theme_img']); ?>" alt="Section Separator" class="separator-image">
</div>
<?php endif; ?>
</body>
</html>
<?php
} else {
echo "No section found.";
}
$stmt->close();
$conn->close();
?>
Explanation
Database Query: The page fetches a single section from the web_content table (e.g., position = 1, page = 'landing', status = 'Show'). You can change the position or page to load a different section.
Image Paths: Images are loaded using ../images/, matching the structure of index.php.
Structure: The HTML structure mirrors index.php, with a content__entry div, image, video embed, and button.
CSS: The page assumes a styles.css file for styling. You can link to the same CSS file used by index.php (adjust the path as needed).
Step 1.2: Update web_content to Link to donation.php
In AppGini, update a section to link to donation.php so we can test it in the modal:
page: landing
position: (e.g., 3)
status: Show
model: title-text
title:
content:
Testing the donation page in the modal...
button_text: Open Donation Page
button_link: donation.php
img_role: No Image
Step 1.3: Test donation.php Directly
Visit https://admin.nlt-chaplaincy.org/public/donation.php directly in your browser.
Confirm that the page loads, displays the section content, and shows any images (e.g., from ../images/) correctly.
Step 1.4: Test in the Modal
Click the button linking to donation.php.
The modal should open and display the content from donation.php, including images and videos (if present).
Step 2: Reverse Irrelevant Changes
Since index-demo.html was a Publii-generated page with a different image path structure, and your AppGini-generated pages (like index.php and donation.php) already work with relative paths (../images/), we can remove the JavaScript logic that converts image paths to absolute URLs. This logic was added to handle index-demo.html’s paths but isn’t needed for your AppGini pages.
Step 2.1: Update pull-contents.php
Remove the path resolution logic from the JavaScript, keeping the rest of the functionality intact.
php
<?php
// pull-contents.php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
$config_path = '../config.php';
if (!file_exists($config_path)) {
die("Error: config.php not found at $config_path");
}
require_once $config_path;
if (!isset($dbServer) || !isset($dbUsername) || !isset($dbPassword) || !isset($dbDatabase)) {
die("Error: AppGini database variables not defined in config.php");
}
$conn = new mysqli($dbServer, $dbUsername, $dbPassword, $dbDatabase, $dbPort ?: 3306);
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
$current_page = isset($_GET['page']) ? $_GET['page'] : 'landing';
$status = 'Show';
$sql = "SELECT section, title, content, image, video_embed, button_link, button_text, position, model, date_edited, theme_img, img_role FROM web_content WHERE page = ? AND status = ? ORDER BY position ASC, date_edited DESC";
$stmt = $conn->prepare($sql);
if (!$stmt) {
die("Prepare failed: " . $conn->error);
}
$stmt->bind_param("ss", $current_page, $status);
$stmt->execute();
$result = $stmt->get_result();
$sections = [];
if ($result->num_rows > 0) {
while ($row = $result->fetch_assoc()) {
$pos = $row['position'];
if (!isset($sections[$pos]) || $row['date_edited'] > $sections[$pos]['date_edited']) {
$sections[$pos] = $row;
}
}
}
if (!empty($sections)) {
ksort($sections);
$current_index = 0;
foreach ($sections as $position => $row) {
$current_index++;
if ($position < 1 || $position > 10) {
continue;
}
$has_image = !empty($row['theme_img']);
$img_role = trim($row['img_role'] ?? 'Background');
$background_style = ($has_image && $img_role === 'Background') ? " style=\"background-image: url('../images/" . htmlspecialchars($row['theme_img']) . "');\"" : '';
echo "<div class=\"content__entry\"$background_style>";
echo "<h1 class=\"align-center\">" . $row['title'] . "</h1>";
echo "<div class=\"generic-content align-center\">" . $row['content'] . "</div>";
if ($row['model'] === 'title-text-image' && !empty($row['image'])) {
$image_url = '../images/' . htmlspecialchars($row['image']);
echo "<div class=\"content-image\"><img src=\"$image_url\" alt=\"Section Image\" class=\"welcome-image\"></div>";
}
if ($row['video_embed']) {
$clean_embed = preg_replace('/sandbox(="[^"]*"|)/', '', $row['video_embed']);
echo '<div class="video-embed">' . $clean_embed . '</div>';
}
if (!empty($row['button_text'])) {
$button_link = htmlspecialchars($row['button_link'] ?: '');
$button_text = htmlspecialchars($row['button_text']);
echo "<div class=\"content__buttons\">";
if (!empty($button_link) && $button_link !== '#') {
echo "<a class=\"myButton modal-trigger\" href=\"#\" data-modal-link=\"$button_link\">$button_text</a>";
} else {
echo "<a class=\"myButton\" href=\"#\">$button_text</a>";
}
echo "</div>";
}
echo "</div>";
if ($has_image && $img_role === 'Separator') {
$image_url = '../images/' . htmlspecialchars($row['theme_img']);
echo "<div class=\"separator\"><img src=\"$image_url\" alt=\"Section Separator\" class=\"separator-image\"></div>";
}
}
} else {
echo "No sections found.";
}
// Modal structure for Micromodal.js
echo '<div class="modal micromodal-slide" id="story-modal" aria-hidden="true">';
echo '<div class="modal__overlay" tabindex="-1" data-micromodal-close>';
echo '<div class="modal__container" role="dialog" aria-modal="true" aria-labelledby="modal-title">';
echo '<header class="modal__header">';
echo '<h2 class="modal__title" id="modal-title">Content</h2>';
echo '<button class="modal__close" aria-label="Close modal" data-micromodal-close></button>';
echo '</header>';
echo '<main class="modal__content" id="modal-body"></main>';
echo '</div>';
echo '</div>';
echo '</div>';
$stmt->close();
$conn->close();
?>
<script>
// Modal functionality with Micromodal.js
document.addEventListener('DOMContentLoaded', function() {
const triggers = document.querySelectorAll('.modal-trigger');
triggers.forEach(trigger => {
trigger.addEventListener('click', function(e) {
e.preventDefault();
const link = this.getAttribute('data-modal-link');
if (!link || link === '#' || link.includes('index.php')) {
document.getElementById('modal-body').innerHTML = '<p>This link cannot be loaded in a modal to prevent recursive loading.</p>';
MicroModal.show('story-modal');
return;
}
document.getElementById('modal-body').innerHTML = '<p>Loading...</p>';
MicroModal.show('story-modal');
fetch(link)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok: ' + response.statusText);
}
return response.text();
})
.then(data => {
const parser = new DOMParser();
const doc = parser.parseFromString(data, 'text/html');
let content = doc.querySelector('.content__entry') ||
doc.querySelector('main') ||
doc.querySelector('article') ||
doc.querySelector('.content') ||
doc.querySelector('body > div') ||
doc.querySelector('body');
if (content) {
content.removeAttribute('style');
const scripts = content.querySelectorAll('script');
scripts.forEach(script => script.remove());
const styles = content.querySelectorAll('style');
styles.forEach(style => style.remove());
const head = content.querySelector('head');
if (head) head.remove();
const nestedModals = content.querySelectorAll('.modal');
nestedModals.forEach(modal => modal.remove());
// Ensure iframes have necessary attributes
const iframes = content.querySelectorAll('iframe');
iframes.forEach(iframe => {
iframe.setAttribute('allow', 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture');
iframe.setAttribute('allowfullscreen', '');
});
const contentHtml = content.innerHTML.trim();
if (contentHtml) {
document.getElementById('modal-body').innerHTML = contentHtml;
} else {
document.getElementById('modal-body').innerHTML = '<p>No content found on the page.</p>';
}
} else {
document.getElementById('modal-body').innerHTML = '<p>Content could not be parsed from the page.</p>';
}
})
.catch(error => {
console.error('Error fetching content:', error);
document.getElementById('modal-body').innerHTML = '<p>Sorry, the content could not be loaded. Please try again later.</p>';
});
});
});
});
</script>
Changes
Removed Path Resolution: Removed the JavaScript logic that resolves relative image paths to absolute URLs, since your AppGini-generated pages already use ../images/ correctly.
Kept Micromodal.js: Kept the Micromodal.js implementation, as it simplifies the modal handling and improves accessibility.
Removed Debugging Logs: Removed the debugging logs we added earlier, since they’re no longer needed.
Step 2.2: Update the CSS
The CSS doesn’t need changes related to absolute paths, as it never modified image paths. The current CSS already supports the ../images/ structure and Micromodal.js. Here’s the full CSS for reference, ensuring no irrelevant changes remain:
css
/*
* Custom CSS for NLT Chaplaincy CMS
*/
/* Ensure myButton overrides theme styles */
.content__buttons a.myButton {
text-decoration: none !important;
}
a.myButtonBig {
text-decoration: none !important;
}
/* Button Styles */
.myButtonBig {
background: linear-gradient(to bottom, #44c767 5%, #5cbf2a 100%);
background-color: #44c767;
border-radius: 28px;
border: 1px solid #18ab29;
display: inline-block;
cursor: pointer;
color: #ffffff;
font-family: Arial;
font-size: 21px;
font-weight: bold;
padding: 24px 42px;
text-decoration: none;
text-shadow: 0px 1px 0px #2f6627;
transition: all 0.3s ease;
}
.myButtonBig:hover {
background: linear-gradient(to bottom, #5cbf2a 5%, #44c767 100%);
background-color: #5cbf2a;
border-color: #137c21;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
.myButtonBig:active {
position: relative;
top: 1px;
background: linear-gradient(to bottom, #3ba052 5%, #4aa62b 100%);
background-color: #3ba052;
border-color: #0f5c18;
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.3);
}
.myButton {
background: linear-gradient(to bottom, #44c767 5%, #5cbf2a 100%);
background-color: #44c767;
border-radius: 28px;
border: 1px solid #18ab29;
display: inline-block;
cursor: pointer;
color: #ffffff;
font-family: Arial;
font-size: 17px;
font-weight: bold;
padding: 16px 31px;
text-decoration: none;
text-shadow: 0px 1px 0px #2f6627;
transition: all 0.3s ease;
}
.myButton:hover {
background: linear-gradient(to bottom, #5cbf2a 5%, #44c767 100%);
background-color: #5cbf2a;
border-color: #137c21;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
.myButton:active {
position: relative;
top: 1px;
background: linear-gradient(to bottom, #3ba052 5%, #4aa62b 100%);
background-color: #3ba052;
border-color: #0f5c18;
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.3);
}
/* Dynamic Section Styles */
.content__entry {
position: relative;
padding: 20px;
min-height: 200px;
margin: 0;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
background-color: #fff;
}
/* Title styles with HTML support */
.content__entry h1 {
font-size: 2em;
font-weight: 700;
line-height: 1.2;
color: #333;
margin-bottom: 0.5em;
text-align: center;
text-shadow: none;
}
/* Content styles with HTML support */
.generic-content {
text-align: center;
margin: 0 auto;
max-width: 800px;
font-size: 1em;
line-height: 1.6;
color: #333;
margin-bottom: 1em;
}
/* Fallback for dark backgrounds */
.content__entry[style*="background-image"] h1,
.content__entry[style*="background-image"] .generic-content {
color: #fff;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
}
/* Ensure HTML lists and paragraphs look good */
.generic-content p {
margin: 0 0 1em 0;
}
/* Fix for hiding list bullets */
.generic-content ul,
.generic-content ol {
margin: 0 0 1em 0;
padding: 0 !important;
list-style: none !important;
}
.generic-content li {
margin-bottom: 0.5em;
text-align: center;
list-style: none !important;
}
/* Ensure no inline styles from HTML Editor override */
.generic-content ul[style*="list-style"],
.generic-content ol[style*="list-style"],
.generic-content li[style*="list-style"] {
list-style: none !important;
}
/* Center buttons */
.content__buttons {
text-align: center !important;
margin: 2em 0 1em 0;
}
/* Ensure the button itself is centered */
.content__buttons a.myButton {
display: inline-block;
margin: 0 auto;
}
/* Override any parent container alignment */
.content__entry .content__buttons {
text-align: center !important;
}
.welcome-image {
max-width: 100%;
height: auto;
margin: 1em auto;
display: block;
}
.video-embed {
position: relative;
padding-bottom: 56.25%;
height: 0;
overflow: hidden;
margin: 1em auto;
max-width: 800px;
background: none;
}
.video-embed iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: 0;
}
.separator {
text-align: center;
margin: 0;
}
.separator-image {
max-width: 100%;
height: auto;
display: block;
margin: 0 auto;
}
/* Remove all <hr> styles */
hr {
display: none !important;
visibility: hidden !important;
}
/* More aggressive overrides for Publii */
.post__content hr,
.container hr,
.post__content hr[style],
.container hr[style] {
display: none !important;
visibility: hidden !important;
}
/* Hide Publii dividers (pseudo-elements) */
.post__content::before,
.post__content::after,
.container::before,
.container::after,
.post__entry::before,
.post__entry::after,
.content__entry::before,
.content__entry::after,
.generic-content::before,
.generic-content::after {
display: none !important;
border: none !important;
content: none !important;
}
/* Explicitly disable pseudo-elements on .separator */
.separator::before,
.separator::after {
display: none !important;
border: none !important;
content: none !important;
}
/* Override Publii theme styles, excluding specific titles */
body,
body p,
body h2,
body h3,
body h4,
body h5,
body h6,
.post__content,
.post__content p,
.post__content h2,
.post__content h3,
.post__content h4,
.post__content h5,
.post__content h6,
.container,
.container p,
.container h2,
.container h3,
.container h4,
.container h5,
.container h6 {
color: #333 !important;
}
/* Allow specific titles to stay white */
h1.post__title,
h1.hero__title,
h1.site__title {
color: #fff !important;
}
/* Remove underlines from all buttons */
a.button,
a.btn,
a.post__button,
.post__content a.button,
.post__content a.btn,
.post__content a.post__button,
.container a.button,
.container a.btn,
.container a.post__button {
text-decoration: none !important;
}
/* Micromodal.js Styles */
.modal {
display: none;
}
.modal.is-open {
display: block;
}
.modal__overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal__container {
background-color: #fff;
padding: 20px;
max-width: 800px;
min-height: 200px;
width: 80%;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
box-sizing: border-box;
position: relative;
}
.modal__header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1em;
}
.modal__title {
font-size: 1.5em;
margin: 0;
}
.modal__close {
background: transparent;
border: 0;
font-size: 28px;
color: #aaa;
cursor: pointer;
transition: color 0.3s ease;
}
.modal__close:hover,
.modal__close:focus {
color: #000;
}
.modal__close:focus {
outline: 2px solid #44c767;
outline-offset: 2px;
}
.modal__content {
font-size: 1em;
line-height: 1.6;
color: #333;
max-width: 100%;
box-sizing: border-box;
}
.modal__content h1 {
font-size: 1.5em;
margin-bottom: 0.5em;
}
.modal__content p {
margin-bottom: 1em;
}
.modal__content * {
max-width: 100%;
box-sizing: border-box;
}
.modal__content img,
.modal__content div,
.modal__content table {
max-width: 100%;
height: auto;
}
.modal__content .video-embed {
position: relative;
padding-bottom: 56.25%;
height: 0;
overflow: hidden;
max-width: 100%;
margin: 1em 0;
}
.modal__content .video-embed iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: 0;
}
Changes
No Path Changes: The CSS never modified image paths, so no changes are needed here. It already supports the ../images/ structure used by your AppGini pages.
Kept Micromodal.js Styles: The CSS retains the Micromodal.js styles for the modal.
Step 3: Test the Updated Setup
Ensure Micromodal.js Is Included:
In index.php, ensure the Micromodal.js script is included:
html
<script src="https://unpkg.com/micromodal@0.4.10/dist/micromodal.min.js"></script>
Add this to the
or at the bottom of the page, if not already present.Test donation.php in the Modal:
Click the button linking to donation.php.
The modal should open and display the content from donation.php, including:
The title and content.
Any images (e.g., ).
Any video embeds (e.g., YouTube iframes in a .video-embed container).
Verify Image Display:
Since donation.php uses the same ../images/ paths as index.php, the images should load correctly in the modal without any path resolution.
Open the browser’s developer tools (F12), go to the Elements tab, and inspect the tags in #modal-body. They should have src attributes like ../images/..., which should resolve correctly to https://admin.nlt-chaplaincy.org/images/....
Check Network Requests:
Step 4: Why Images Worked in index.php but Not index-demo.html
AppGini Pages (index.php, donation.php): These pages use ../images/ paths, which resolve correctly in the modal because both index.php and the fetched page (donation.php) are in the same directory (/public/). The browser resolves ../images/ relative to /public/, which correctly points to /images/.
Publii Page (index-demo.html): Publii likely used a different media path (e.g., /media/... or an internal reference), which didn’t resolve correctly in the modal context. Since we’re now testing with donation.php, which follows the AppGini structure, this issue should be resolved.
Step 5: If Issues Persist
If images or videos still don’t load in the modal:
Check the src Attribute: Inspect the tags in the modal to ensure the src is ../images/... and resolves correctly.
Network Errors: Look for 404 errors in the Network tab, which would indicate the image file is missing or the path is incorrect.
Video Embeds: If videos don’t load, check for iframe errors in the Console (e.g., “Refused to display in a frame”).
Share Details: If there are issues, let me know what the or
Step 6: Next Steps for full-story.php
Once donation.php is working in the modal, you can create full-story.php using the same approach:
Copy the structure of donation.php.
Adjust the database query to fetch a different section (e.g., a different position or page).
Update the web_content table to link to full-story.php in another section.
Summary
Created donation.php: A new page that loads a sample section from web_content, using the same ../images/ paths as index.php.
Reversed Changes: Removed the JavaScript logic for converting image paths to absolute URLs, as it’s not needed for AppGini-generated pages.
Testing: Test donation.php in the modal to confirm that images and videos load correctly.
Let me know how the test goes! If everything works as expected, we’ll have a solid setup for full-story.php and any other pages you create. If there are any issues, I’m here to help!
Q: The modal window is now showing the complete donation.php page!! Wonderful! The donation.php sample doesn't show the separator graphic inside the modal, which is expected (But is this a happy mistake or what).
A: I’m thrilled to hear that the modal window on https://admin.nlt-chaplaincy.org/public/index.php is now displaying the complete content of donation.php, including images and other elements! This is a great step forward, and it confirms that the AppGini-generated pages with relative paths (../images/) are working as expected in the modal.
You mentioned that the donation.php sample doesn’t show the separator graphic inside the modal, which you expected, and you’re wondering if this is a “happy mistake” or intentional. Let’s break this down to understand why the separator graphic isn’t showing, whether it’s intentional, and if we need to adjust anything.
Step 1: Analyze Why the Separator Graphic Isn’t Showing
The separator graphic in donation.php is likely an image loaded from the theme_img field in the web_content table, displayed when img_role is set to Separator. Let’s review the relevant code in donation.php to see how the separator is handled:
Relevant Code in donation.php
php
<?php if (!empty($row['theme_img']) && $row['img_role'] === 'Separator'): ?>
<div class="separator">
<img src="../images/<?php echo htmlspecialchars($row['theme_img']); ?>" alt="Section Separator" class="separator-image">
</div>
<?php endif; ?>
Condition: The separator graphic is only displayed if theme_img is not empty and img_role is exactly "Separator".
HTML Structure: The separator graphic is wrapped in a
Modal Content Extraction in pull-contents.php
The JavaScript in pull-contents.php fetches the content of donation.php and extracts a specific container to display in the modal. Here’s the relevant part:
javascript
let content = doc.querySelector('.content__entry') ||
doc.querySelector('main') ||
doc.querySelector('article') ||
doc.querySelector('.content') ||
doc.querySelector('body > div') ||
doc.querySelector('body');
Selector: The JavaScript looks for a .content__entry container first, which matches the structure of donation.php:
html
<div class="content__entry">
<h1 class="align-center">...</h1>
<div class="generic-content align-center">...</div>
<!-- Images, videos, buttons -->
</div>
<div class="separator">
<img src="../images/..." alt="Section Separator" class="separator-image">
</div>
What’s Included: The .content__entry selector only captures the content inside the
Why the Separator Isn’t Showing
The separator graphic isn’t showing in the modal because the JavaScript only extracts the .content__entry container, and the separator (in a separate
Is This a “Happy Mistake”?
Intentional Behavior: This isn’t a mistake—it’s the expected behavior based on how the modal content extraction is coded. The modal is designed to show the main content (.content__entry) and exclude separators, which are typically used for visual separation between sections on the main page but might not be necessary in a modal context.
Happy Outcome: If you’re okay with the separator not appearing in the modal, this is indeed a “happy mistake” in the sense that it aligns with a common design choice: modals often focus on the core content and omit decorative or structural elements like separators. However, if you want the separator to appear in the modal, we can adjust the logic to include it.
Step 2: Decide Whether to Include the Separator
Let’s decide whether the separator graphic should be included in the modal:
Exclude Separator (Current Behavior):
Pros: Keeps the modal content focused and clean, avoiding unnecessary visual elements that might clutter the modal.
Cons: If the separator graphic provides important context or visual appeal, excluding it might make the modal content feel incomplete.
Include Separator:
Pros: Ensures the modal content matches the full page exactly, including all visual elements.
Cons: Might add visual clutter to the modal, especially if the separator is purely decorative.
Since you mentioned that you “expected” the separator not to show and seem okay with this behavior, it sounds like excluding the separator is the desired outcome. However, I’ll provide a solution to include it in case you change your mind.
Option 1: Keep Excluding the Separator (Current Behavior)
If you’re happy with the separator not showing in the modal, we don’t need to make any changes. The current logic is working as intended, and the modal is displaying the core content of donation.php (title, text, images, videos, buttons) without the separator.
Option 2: Include the Separator in the Modal
If you’d like the separator graphic to appear in the modal, we can modify the JavaScript in pull-contents.php to include both the .content__entry and the following .separator div (if present).
Updated JavaScript to Include Separator
javascript
fetch(link)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok: ' + response.statusText);
}
return response.text();
})
.then(data => {
const parser = new DOMParser();
const doc = parser.parseFromString(data, 'text/html');
// Select the main content
let content = doc.querySelector('.content__entry') ||
doc.querySelector('main') ||
doc.querySelector('article') ||
doc.querySelector('.content') ||
doc.querySelector('body > div') ||
doc.querySelector('body');
if (content) {
// Create a wrapper to hold both content__entry and separator
const wrapper = document.createElement('div');
wrapper.appendChild(content.cloneNode(true));
// Check for a following .separator sibling
const separator = content.nextElementSibling && content.nextElementSibling.classList.contains('separator')
? content.nextElementSibling
: null;
if (separator) {
wrapper.appendChild(separator.cloneNode(true));
}
content.removeAttribute('style');
const scripts = content.querySelectorAll('script');
scripts.forEach(script => script.remove());
const styles = content.querySelectorAll('style');
styles.forEach(style => style.remove());
const head = content.querySelector('head');
if (head) head.remove();
const nestedModals = content.querySelectorAll('.modal');
nestedModals.forEach(modal => modal.remove());
// Ensure iframes have necessary attributes
const iframes = wrapper.querySelectorAll('iframe');
iframes.forEach(iframe => {
iframe.setAttribute('allow', 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture');
iframe.setAttribute('allowfullscreen', '');
});
const contentHtml = wrapper.innerHTML.trim();
if (contentHtml) {
document.getElementById('modal-body').innerHTML = contentHtml;
} else {
document.getElementById('modal-body').innerHTML = '<p>No content found on the page.</p>';
}
} else {
document.getElementById('modal-body').innerHTML = '<p>Content could not be parsed from the page.</p>';
}
})
.catch(error => {
console.error('Error fetching content:', error);
document.getElementById('modal-body').innerHTML = '<p>Sorry, the content could not be loaded. Please try again later.</p>';
});
Changes
Wrapper Div: Created a wrapper div to hold both the .content__entry and the .separator (if present).
Sibling Check: Checks if the .content__entry has a following sibling with class="separator" and includes it in the wrapper.
Applied to All Content: The rest of the logic (removing scripts, styles, etc.) is applied to the wrapper instead of just content.
Test the Updated Logic
Update pull-contents.php with the modified JavaScript.
Click the button linking to donation.php.
Check if the separator graphic now appears in the modal below the main content.
Step 3: Confirm the Current Setup
Since the modal is now showing the complete donation.php content (minus the separator, which you expected), let’s confirm that everything else is working as intended:
Images: Images with src="../images/..." should be loading correctly in the modal, as they did on the main page.
Videos: Any video embeds (e.g., YouTube iframes) should be displaying and playable.
Buttons: Any buttons in donation.php should be visible and styled correctly.
Accessibility: Use the Tab key to navigate to the close button and press Enter to close the modal. Ensure screen readers announce the modal content.
Step 4: Why This Isn’t a Mistake
The exclusion of the separator isn’t a mistake—it’s a deliberate design choice in the current code. The modal focuses on the main content (.content__entry) to keep the presentation clean and focused, which is often the desired behavior for modals. Since you expected this and seem happy with it, we can leave the logic as-is. However, if you decide you want the separator included, the updated JavaScript above will handle that.
Step 5: Next Steps for full-story.php
Since donation.php is working well in the modal, you can create full-story.php using the same approach:
Copy the structure of donation.php.
Adjust the database query to fetch a different section (e.g., a different position or page).
Update the web_content table to link to full-story.php in another section.
Test it in the modal to ensure images, videos, and other content display correctly.
Step 6: Final Code Review
The current pull-contents.php (without the separator inclusion) is already in a good state, as it matches your expectations. Here’s the final version for reference, ensuring no irrelevant changes remain:
php
<?php
// pull-contents.php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
$config_path = '../config.php';
if (!file_exists($config_path)) {
die("Error: config.php not found at $config_path");
}
require_once $config_path;
if (!isset($dbServer) || !isset($dbUsername) || !isset($dbPassword) || !isset($dbDatabase)) {
die("Error: AppGini database variables not defined in config.php");
}
$conn = new mysqli($dbServer, $dbUsername, $dbPassword, $dbDatabase, $dbPort ?: 3306);
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
$current_page = isset($_GET['page']) ? $_GET['page'] : 'landing';
$status = 'Show';
$sql = "SELECT section, title, content, image, video_embed, button_link, button_text, position, model, date_edited, theme_img, img_role FROM web_content WHERE page = ? AND status = ? ORDER BY position ASC, date_edited DESC";
$stmt = $conn->prepare($sql);
if (!$stmt) {
die("Prepare failed: " . $conn->error);
}
$stmt->bind_param("ss", $current_page, $status);
$stmt->execute();
$result = $stmt->get_result();
$sections = [];
if ($result->num_rows > 0) {
while ($row = $result->fetch_assoc()) {
$pos = $row['position'];
if (!isset($sections[$pos]) || $row['date_edited'] > $sections[$pos]['date_edited']) {
$sections[$pos] = $row;
}
}
}
if (!empty($sections)) {
ksort($sections);
$current_index = 0;
foreach ($sections as $position => $row) {
$current_index++;
if ($position < 1 || $position > 10) {
continue;
}
$has_image = !empty($row['theme_img']);
$img_role = trim($row['img_role'] ?? 'Background');
$background_style = ($has_image && $img_role === 'Background') ? " style=\"background-image: url('../images/" . htmlspecialchars($row['theme_img']) . "');\"" : '';
echo "<div class=\"content__entry\"$background_style>";
echo "<h1 class=\"align-center\">" . $row['title'] . "</h1>";
echo "<div class=\"generic-content align-center\">" . $row['content'] . "</div>";
if ($row['model'] === 'title-text-image' && !empty($row['image'])) {
$image_url = '../images/' . htmlspecialchars($row['image']);
echo "<div class=\"content-image\"><img src=\"$image_url\" alt=\"Section Image\" class=\"welcome-image\"></div>";
}
if ($row['video_embed']) {
$clean_embed = preg_replace('/sandbox(="[^"]*"|)/', '', $row['video_embed']);
echo '<div class="video-embed">' . $clean_embed . '</div>';
}
if (!empty($row['button_text'])) {
$button_link = htmlspecialchars($row['button_link'] ?: '');
$button_text = htmlspecialchars($row['button_text']);
echo "<div class=\"content__buttons\">";
if (!empty($button_link) && $button_link !== '#') {
echo "<a class=\"myButton modal-trigger\" href=\"#\" data-modal-link=\"$button_link\">$button_text</a>";
} else {
echo "<a class=\"myButton\" href=\"#\">$button_text</a>";
}
echo "</div>";
}
echo "</div>";
if ($has_image && $img_role === 'Separator') {
$image_url = '../images/' . htmlspecialchars($row['theme_img']);
echo "<div class=\"separator\"><img src=\"$image_url\" alt=\"Section Separator\" class=\"separator-image\"></div>";
}
}
} else {
echo "No sections found.";
}
// Modal structure for Micromodal.js
echo '<div class="modal micromodal-slide" id="story-modal" aria-hidden="true">';
echo '<div class="modal__overlay" tabindex="-1" data-micromodal-close>';
echo '<div class="modal__container" role="dialog" aria-modal="true" aria-labelledby="modal-title">';
echo '<header class="modal__header">';
echo '<h2 class="modal__title" id="modal-title">Content</h2>';
echo '<button class="modal__close" aria-label="Close modal" data-micromodal-close></button>';
echo '</header>';
echo '<main class="modal__content" id="modal-body"></main>';
echo '</div>';
echo '</div>';
echo '</div>';
$stmt->close();
$conn->close();
?>
<script>
// Modal functionality with Micromodal.js
document.addEventListener('DOMContentLoaded', function() {
const triggers = document.querySelectorAll('.modal-trigger');
triggers.forEach(trigger => {
trigger.addEventListener('click', function(e) {
e.preventDefault();
const link = this.getAttribute('data-modal-link');
if (!link || link === '#' || link.includes('index.php')) {
document.getElementById('modal-body').innerHTML = '<p>This link cannot be loaded in a modal to prevent recursive loading.</p>';
MicroModal.show('story-modal');
return;
}
document.getElementById('modal-body').innerHTML = '<p>Loading...</p>';
MicroModal.show('story-modal');
fetch(link)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok: ' + response.statusText);
}
return response.text();
})
.then(data => {
const parser = new DOMParser();
const doc = parser.parseFromString(data, 'text/html');
let content = doc.querySelector('.content__entry') ||
doc.querySelector('main') ||
doc.querySelector('article') ||
doc.querySelector('.content') ||
doc.querySelector('body > div') ||
doc.querySelector('body');
if (content) {
content.removeAttribute('style');
const scripts = content.querySelectorAll('script');
scripts.forEach(script => script.remove());
const styles = content.querySelectorAll('style');
styles.forEach(style => style.remove());
const head = content.querySelector('head');
if (head) head.remove();
const nestedModals = content.querySelectorAll('.modal');
nestedModals.forEach(modal => modal.remove());
// Ensure iframes have necessary attributes
const iframes = content.querySelectorAll('iframe');
iframes.forEach(iframe => {
iframe.setAttribute('allow', 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture');
iframe.setAttribute('allowfullscreen', '');
});
const contentHtml = content.innerHTML.trim();
if (contentHtml) {
document.getElementById('modal-body').innerHTML = contentHtml;
} else {
document.getElementById('modal-body').innerHTML = '<p>No content found on the page.</p>';
}
} else {
document.getElementById('modal-body').innerHTML = '<p>Content could not be parsed from the page.</p>';
}
})
.catch(error => {
console.error('Error fetching content:', error);
document.getElementById('modal-body').innerHTML = '<p>Sorry, the content could not be loaded. Please try again later.</p>';
});
});
});
});
</script>
Summary
Modal Success: The modal is now correctly displaying the content of donation.php, including images and other elements, thanks to the consistent use of ../images/ paths in AppGini-generated pages.
Separator Graphic: The separator graphic isn’t showing in the modal because the JavaScript only extracts the .content__entry container, excluding the .separator div. This is intentional and aligns with your expectation, making it a “happy mistake” if you prefer the cleaner modal presentation.
Next Steps: You can create full-story.php using the same approach as donation.php. If you decide you want the separator in the modal, we can use the updated JavaScript provided above.
I’m glad this is working well for you! Let me know if you’d like to include the separator graphic or if you’re ready to move on to full-story.php or any other tasks.
Copyright © 2023 Curation Web 我的資訊「展」輯. All Rights Reserved | Powered by Bludit | Theme by BlThemes