Restructure: move all files from Public HTML/ to root for Hostinger deployment
This commit is contained in:
Executable
+405
@@ -0,0 +1,405 @@
|
||||
<?php
|
||||
/**
|
||||
* MSPE Pages API
|
||||
* Stores metadata, SEO fields, and section content for static pages.
|
||||
* Also manages global sections (header, footer, CTA, cookie banner).
|
||||
*/
|
||||
|
||||
require_once 'config.php';
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$id = $_GET['id'] ?? null;
|
||||
$type = $_GET['type'] ?? null; // 'global' for global sections
|
||||
|
||||
switch ($method) {
|
||||
case 'GET':
|
||||
if ($type === 'global') {
|
||||
getGlobalSections();
|
||||
} elseif ($id) {
|
||||
getPage($id);
|
||||
} else {
|
||||
getPages();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'POST':
|
||||
requireAuth();
|
||||
if ($type === 'global') {
|
||||
saveGlobalSection();
|
||||
} else {
|
||||
savePage();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
jsonResponse(['success' => false, 'message' => 'Method not allowed'], 405);
|
||||
}
|
||||
|
||||
// ── Page defaults ────────────────────────────────────────────
|
||||
function getDefaultPages() {
|
||||
return [
|
||||
[
|
||||
'slug' => 'home',
|
||||
'title' => 'Home Page',
|
||||
'icon' => 'fa-home',
|
||||
'url' => '/index.html',
|
||||
'description' => 'Main landing page with hero, services, and CTA sections',
|
||||
'status' => 'published',
|
||||
'meta_title' => 'MSPE | Architects of Digital Resilience',
|
||||
'meta_description' => 'Next-generation cybersecurity, cloud innovation, and technology consulting.',
|
||||
'sections' => [
|
||||
['key' => 'hero', 'label' => 'Hero', 'fields' => [
|
||||
['name' => 'heading', 'type' => 'text', 'label' => 'Heading', 'value' => ''],
|
||||
['name' => 'subheading', 'type' => 'textarea', 'label' => 'Subheading', 'value' => ''],
|
||||
['name' => 'cta_text', 'type' => 'text', 'label' => 'CTA Button Text', 'value' => ''],
|
||||
['name' => 'cta_link', 'type' => 'text', 'label' => 'CTA Button Link', 'value' => ''],
|
||||
]],
|
||||
['key' => 'services', 'label' => 'Services', 'fields' => [
|
||||
['name' => 'heading', 'type' => 'text', 'label' => 'Section Heading', 'value' => ''],
|
||||
['name' => 'subheading', 'type' => 'textarea', 'label' => 'Section Subheading', 'value' => ''],
|
||||
]],
|
||||
['key' => 'stats', 'label' => 'Stats', 'fields' => [
|
||||
['name' => 'heading', 'type' => 'text', 'label' => 'Section Heading', 'value' => ''],
|
||||
]],
|
||||
['key' => 'cta', 'label' => 'CTA', 'fields' => [
|
||||
['name' => 'heading', 'type' => 'text', 'label' => 'CTA Heading', 'value' => ''],
|
||||
['name' => 'text', 'type' => 'textarea', 'label' => 'CTA Text', 'value' => ''],
|
||||
['name' => 'button_text', 'type' => 'text', 'label' => 'Button Text', 'value' => ''],
|
||||
['name' => 'button_link', 'type' => 'text', 'label' => 'Button Link', 'value' => ''],
|
||||
]],
|
||||
],
|
||||
],
|
||||
[
|
||||
'slug' => 'about',
|
||||
'title' => 'About Us',
|
||||
'icon' => 'fa-building',
|
||||
'url' => '/about.html',
|
||||
'description' => 'Company story, mission, values, and team information',
|
||||
'status' => 'published',
|
||||
'meta_title' => 'About Us - MSPE',
|
||||
'meta_description' => 'Learn about MSPE — our story, mission, values, and the team behind your digital resilience.',
|
||||
'sections' => [
|
||||
['key' => 'story', 'label' => 'Story', 'fields' => [
|
||||
['name' => 'heading', 'type' => 'text', 'label' => 'Heading', 'value' => ''],
|
||||
['name' => 'content', 'type' => 'textarea', 'label' => 'Content', 'value' => ''],
|
||||
]],
|
||||
['key' => 'mission', 'label' => 'Mission', 'fields' => [
|
||||
['name' => 'heading', 'type' => 'text', 'label' => 'Heading', 'value' => ''],
|
||||
['name' => 'content', 'type' => 'textarea', 'label' => 'Content', 'value' => ''],
|
||||
]],
|
||||
['key' => 'values', 'label' => 'Values', 'fields' => [
|
||||
['name' => 'heading', 'type' => 'text', 'label' => 'Heading', 'value' => ''],
|
||||
]],
|
||||
['key' => 'team', 'label' => 'Team', 'fields' => [
|
||||
['name' => 'heading', 'type' => 'text', 'label' => 'Heading', 'value' => ''],
|
||||
['name' => 'subheading', 'type' => 'textarea', 'label' => 'Subheading', 'value' => ''],
|
||||
]],
|
||||
],
|
||||
],
|
||||
[
|
||||
'slug' => 'services',
|
||||
'title' => 'Services',
|
||||
'icon' => 'fa-concierge-bell',
|
||||
'url' => '/services.html',
|
||||
'description' => 'Detailed service offerings and pricing plans',
|
||||
'status' => 'published',
|
||||
'meta_title' => 'Services - MSPE',
|
||||
'meta_description' => 'Explore our cybersecurity, cloud, and IT support services.',
|
||||
'sections' => [
|
||||
['key' => 'it_support', 'label' => 'IT Support', 'fields' => [
|
||||
['name' => 'heading', 'type' => 'text', 'label' => 'Heading', 'value' => ''],
|
||||
['name' => 'content', 'type' => 'textarea', 'label' => 'Content', 'value' => ''],
|
||||
]],
|
||||
['key' => 'security', 'label' => 'Security', 'fields' => [
|
||||
['name' => 'heading', 'type' => 'text', 'label' => 'Heading', 'value' => ''],
|
||||
['name' => 'content', 'type' => 'textarea', 'label' => 'Content', 'value' => ''],
|
||||
]],
|
||||
['key' => 'cloud', 'label' => 'Cloud', 'fields' => [
|
||||
['name' => 'heading', 'type' => 'text', 'label' => 'Heading', 'value' => ''],
|
||||
['name' => 'content', 'type' => 'textarea', 'label' => 'Content', 'value' => ''],
|
||||
]],
|
||||
['key' => 'pricing', 'label' => 'Pricing', 'fields' => [
|
||||
['name' => 'heading', 'type' => 'text', 'label' => 'Heading', 'value' => ''],
|
||||
]],
|
||||
],
|
||||
],
|
||||
[
|
||||
'slug' => 'portfolio',
|
||||
'title' => 'Portfolio',
|
||||
'icon' => 'fa-briefcase',
|
||||
'url' => '/portfolio.html',
|
||||
'description' => 'Showcase of completed projects and case studies',
|
||||
'status' => 'published',
|
||||
'meta_title' => 'Portfolio - MSPE',
|
||||
'meta_description' => 'See our completed projects, case studies, and client success stories.',
|
||||
'sections' => [
|
||||
['key' => 'projects', 'label' => 'Projects', 'fields' => [
|
||||
['name' => 'heading', 'type' => 'text', 'label' => 'Heading', 'value' => ''],
|
||||
['name' => 'subheading', 'type' => 'textarea', 'label' => 'Subheading', 'value' => ''],
|
||||
]],
|
||||
['key' => 'case_studies', 'label' => 'Case Studies', 'fields' => [
|
||||
['name' => 'heading', 'type' => 'text', 'label' => 'Heading', 'value' => ''],
|
||||
]],
|
||||
['key' => 'clients', 'label' => 'Clients', 'fields' => [
|
||||
['name' => 'heading', 'type' => 'text', 'label' => 'Heading', 'value' => ''],
|
||||
]],
|
||||
],
|
||||
],
|
||||
[
|
||||
'slug' => 'news',
|
||||
'title' => 'News & Events',
|
||||
'icon' => 'fa-newspaper',
|
||||
'url' => '/news.html',
|
||||
'description' => 'Company news, blog posts, and upcoming events',
|
||||
'status' => 'published',
|
||||
'meta_title' => 'News & Events - MSPE',
|
||||
'meta_description' => 'Stay updated with the latest MSPE news, tech insights, and events.',
|
||||
'sections' => [
|
||||
['key' => 'news_list', 'label' => 'News List', 'fields' => [
|
||||
['name' => 'heading', 'type' => 'text', 'label' => 'Heading', 'value' => ''],
|
||||
]],
|
||||
['key' => 'newsletter', 'label' => 'Newsletter', 'fields' => [
|
||||
['name' => 'heading', 'type' => 'text', 'label' => 'Heading', 'value' => ''],
|
||||
['name' => 'subheading', 'type' => 'textarea', 'label' => 'Subheading', 'value' => ''],
|
||||
]],
|
||||
],
|
||||
],
|
||||
[
|
||||
'slug' => 'contact',
|
||||
'title' => 'Contact',
|
||||
'icon' => 'fa-envelope',
|
||||
'url' => '/contact.html',
|
||||
'description' => 'Contact form, location, and FAQ section',
|
||||
'status' => 'published',
|
||||
'meta_title' => 'Contact Us - MSPE',
|
||||
'meta_description' => 'Get in touch with MSPE for cybersecurity, cloud, and IT services.',
|
||||
'sections' => [
|
||||
['key' => 'form', 'label' => 'Form', 'fields' => [
|
||||
['name' => 'heading', 'type' => 'text', 'label' => 'Heading', 'value' => ''],
|
||||
['name' => 'subheading', 'type' => 'textarea', 'label' => 'Subheading', 'value' => ''],
|
||||
]],
|
||||
['key' => 'info_cards', 'label' => 'Info Cards', 'fields' => [
|
||||
['name' => 'address', 'type' => 'textarea', 'label' => 'Address', 'value' => ''],
|
||||
['name' => 'phone', 'type' => 'text', 'label' => 'Phone', 'value' => ''],
|
||||
['name' => 'email', 'type' => 'text', 'label' => 'Email', 'value' => ''],
|
||||
['name' => 'hours', 'type' => 'text', 'label' => 'Business Hours', 'value' => ''],
|
||||
]],
|
||||
['key' => 'faq', 'label' => 'FAQ', 'fields' => [
|
||||
['name' => 'heading', 'type' => 'text', 'label' => 'Heading', 'value' => ''],
|
||||
]],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
function getDefaultGlobalSections() {
|
||||
return [
|
||||
[
|
||||
'slug' => 'header',
|
||||
'label' => 'Header / Navigation',
|
||||
'icon' => 'fa-bars',
|
||||
'description' => 'Logo, menu items, CTA button',
|
||||
'fields' => [
|
||||
['name' => 'cta_text', 'type' => 'text', 'label' => 'CTA Button Text', 'value' => ''],
|
||||
['name' => 'cta_link', 'type' => 'text', 'label' => 'CTA Button Link', 'value' => ''],
|
||||
],
|
||||
],
|
||||
[
|
||||
'slug' => 'footer',
|
||||
'label' => 'Footer',
|
||||
'icon' => 'fa-shoe-prints',
|
||||
'description' => 'Links, contact info, social media',
|
||||
'fields' => [
|
||||
['name' => 'tagline', 'type' => 'textarea', 'label' => 'Footer Tagline', 'value' => ''],
|
||||
['name' => 'copyright', 'type' => 'text', 'label' => 'Copyright Text', 'value' => ''],
|
||||
],
|
||||
],
|
||||
[
|
||||
'slug' => 'cta',
|
||||
'label' => 'CTA Section',
|
||||
'icon' => 'fa-bullhorn',
|
||||
'description' => 'Call-to-action banner',
|
||||
'fields' => [
|
||||
['name' => 'heading', 'type' => 'text', 'label' => 'Heading', 'value' => ''],
|
||||
['name' => 'text', 'type' => 'textarea', 'label' => 'Text', 'value' => ''],
|
||||
['name' => 'button_text', 'type' => 'text', 'label' => 'Button Text', 'value' => ''],
|
||||
['name' => 'button_link', 'type' => 'text', 'label' => 'Button Link', 'value' => ''],
|
||||
],
|
||||
],
|
||||
[
|
||||
'slug' => 'cookie',
|
||||
'label' => 'Cookie Banner',
|
||||
'icon' => 'fa-cookie',
|
||||
'description' => 'GDPR compliance notice',
|
||||
'fields' => [
|
||||
['name' => 'message', 'type' => 'textarea', 'label' => 'Banner Message', 'value' => ''],
|
||||
['name' => 'button_text', 'type' => 'text', 'label' => 'Accept Button Text', 'value' => ''],
|
||||
['name' => 'link_text', 'type' => 'text', 'label' => 'Policy Link Text', 'value' => ''],
|
||||
['name' => 'link_url', 'type' => 'text', 'label' => 'Policy URL', 'value' => ''],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
// ── Handlers ─────────────────────────────────────────────────
|
||||
|
||||
function getPages() {
|
||||
global $db;
|
||||
$storedPages = $db->getAll('pages');
|
||||
$storedMap = [];
|
||||
foreach ($storedPages as $p) {
|
||||
$slug = $p['slug'] ?? '';
|
||||
if ($slug) $storedMap[$slug] = $p;
|
||||
}
|
||||
|
||||
$pages = getDefaultPages();
|
||||
foreach ($pages as &$page) {
|
||||
if (isset($storedMap[$page['slug']])) {
|
||||
$stored = $storedMap[$page['slug']];
|
||||
// Merge saved values into section fields
|
||||
if (!empty($stored['meta_title'])) $page['meta_title'] = $stored['meta_title'];
|
||||
if (!empty($stored['meta_description'])) $page['meta_description'] = $stored['meta_description'];
|
||||
if (isset($stored['status'])) $page['status'] = $stored['status'];
|
||||
if (!empty($stored['sections_data'])) {
|
||||
$savedData = $stored['sections_data']; // key => { fieldName => value }
|
||||
foreach ($page['sections'] as &$section) {
|
||||
if (isset($savedData[$section['key']])) {
|
||||
foreach ($section['fields'] as &$field) {
|
||||
if (isset($savedData[$section['key']][$field['name']])) {
|
||||
$field['value'] = $savedData[$section['key']][$field['name']];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isset($stored['updated_at'])) $page['updated_at'] = $stored['updated_at'];
|
||||
}
|
||||
}
|
||||
|
||||
jsonResponse(['success' => true, 'data' => $pages]);
|
||||
}
|
||||
|
||||
function getPage($id) {
|
||||
global $db;
|
||||
$pages = getDefaultPages();
|
||||
$default = null;
|
||||
foreach ($pages as $p) {
|
||||
if ($p['slug'] === $id) { $default = $p; break; }
|
||||
}
|
||||
if (!$default) {
|
||||
jsonResponse(['success' => false, 'message' => 'Page not found'], 404);
|
||||
}
|
||||
|
||||
$stored = $db->query('pages', ['slug' => $id]);
|
||||
if (!empty($stored)) {
|
||||
$stored = array_values($stored)[0];
|
||||
if (!empty($stored['meta_title'])) $default['meta_title'] = $stored['meta_title'];
|
||||
if (!empty($stored['meta_description'])) $default['meta_description'] = $stored['meta_description'];
|
||||
if (isset($stored['status'])) $default['status'] = $stored['status'];
|
||||
if (!empty($stored['sections_data'])) {
|
||||
$savedData = $stored['sections_data'];
|
||||
foreach ($default['sections'] as &$section) {
|
||||
if (isset($savedData[$section['key']])) {
|
||||
foreach ($section['fields'] as &$field) {
|
||||
if (isset($savedData[$section['key']][$field['name']])) {
|
||||
$field['value'] = $savedData[$section['key']][$field['name']];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isset($stored['updated_at'])) $default['updated_at'] = $stored['updated_at'];
|
||||
}
|
||||
|
||||
jsonResponse(['success' => true, 'data' => $default]);
|
||||
}
|
||||
|
||||
function savePage() {
|
||||
global $db;
|
||||
$data = getRequestBody();
|
||||
|
||||
if (empty($data['slug'])) {
|
||||
jsonResponse(['success' => false, 'message' => 'Page slug required'], 400);
|
||||
}
|
||||
|
||||
$slug = sanitize($data['slug']);
|
||||
$record = [
|
||||
'slug' => $slug,
|
||||
'meta_title' => sanitize($data['meta_title'] ?? ''),
|
||||
'meta_description' => sanitize($data['meta_description'] ?? ''),
|
||||
'status' => in_array($data['status'] ?? '', ['published', 'draft']) ? $data['status'] : 'published',
|
||||
'sections_data' => $data['sections_data'] ?? [],
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
];
|
||||
|
||||
$existing = $db->query('pages', ['slug' => $slug]);
|
||||
if (!empty($existing)) {
|
||||
$id = array_values($existing)[0]['id'];
|
||||
$db->update('pages', $id, $record);
|
||||
} else {
|
||||
$db->insert('pages', $record);
|
||||
}
|
||||
|
||||
jsonResponse(['success' => true, 'message' => 'Page saved successfully']);
|
||||
}
|
||||
|
||||
// ── Global sections ──────────────────────────────────────────
|
||||
|
||||
function getGlobalSections() {
|
||||
global $db;
|
||||
$stored = $db->query('pages', ['slug' => '__global__']);
|
||||
$savedData = [];
|
||||
if (!empty($stored)) {
|
||||
$savedData = array_values($stored)[0]['sections_data'] ?? [];
|
||||
}
|
||||
|
||||
$globals = getDefaultGlobalSections();
|
||||
foreach ($globals as &$section) {
|
||||
if (isset($savedData[$section['slug']])) {
|
||||
foreach ($section['fields'] as &$field) {
|
||||
if (isset($savedData[$section['slug']][$field['name']])) {
|
||||
$field['value'] = $savedData[$section['slug']][$field['name']];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
jsonResponse(['success' => true, 'data' => $globals]);
|
||||
}
|
||||
|
||||
function saveGlobalSection() {
|
||||
global $db;
|
||||
$data = getRequestBody();
|
||||
|
||||
if (empty($data['section_slug'])) {
|
||||
jsonResponse(['success' => false, 'message' => 'Section slug required'], 400);
|
||||
}
|
||||
|
||||
$sectionSlug = sanitize($data['section_slug']);
|
||||
$fieldValues = $data['fields'] ?? [];
|
||||
|
||||
// Load existing global record
|
||||
$existing = $db->query('pages', ['slug' => '__global__']);
|
||||
$record = [];
|
||||
if (!empty($existing)) {
|
||||
$record = array_values($existing)[0];
|
||||
}
|
||||
|
||||
$sectionsData = $record['sections_data'] ?? [];
|
||||
$sectionsData[$sectionSlug] = $fieldValues;
|
||||
|
||||
$saveData = [
|
||||
'slug' => '__global__',
|
||||
'sections_data' => $sectionsData,
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
];
|
||||
|
||||
if (!empty($existing)) {
|
||||
$id = array_values($existing)[0]['id'];
|
||||
$db->update('pages', $id, $saveData);
|
||||
} else {
|
||||
$db->insert('pages', $saveData);
|
||||
}
|
||||
|
||||
jsonResponse(['success' => true, 'message' => 'Global section saved']);
|
||||
}
|
||||
Reference in New Issue
Block a user