40090-vm/includes/app.php
Flatlogic Bot d6311a3a5e 1.0
2026-05-26 10:05:49 +00:00

710 lines
27 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
require_once __DIR__ . '/../db/config.php';
function app_env(string $key, string $fallback = ''): string
{
$serverValue = $_SERVER[$key] ?? null;
if (is_string($serverValue) && trim($serverValue) !== '') {
return trim($serverValue);
}
$envValue = getenv($key);
if (is_string($envValue) && trim($envValue) !== '') {
return trim($envValue);
}
return $fallback;
}
function h(mixed $value): string
{
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
}
function text_length(string $value): int
{
return function_exists('mb_strlen') ? mb_strlen($value) : strlen($value);
}
function site_name(): string
{
return app_env('PROJECT_NAME', 'Genitri Ecco Wellness');
}
function site_tagline(): string
{
return 'Nature-Based Experiential Tourism & Eco Wellness Services';
}
function default_meta_description(): string
{
return app_env(
'PROJECT_DESCRIPTION',
'Integrated nature-based experience ecosystem for eco wellness, experiential tourism, hospitality collaboration, and strategic partnership.'
);
}
function page_url(string $path, array $query = []): string
{
return $query ? $path . '?' . http_build_query($query) : $path;
}
function navigation_items(): array
{
return [
['key' => 'home', 'label' => 'Home', 'href' => 'index.php'],
['key' => 'about', 'label' => 'About Us', 'href' => 'about.php'],
['key' => 'services', 'label' => 'Services', 'href' => 'services.php'],
['key' => 'portfolio', 'label' => 'Portfolio', 'href' => 'portfolio.php'],
['key' => 'partnership', 'label' => 'Partnership', 'href' => 'partnership.php'],
['key' => 'publications', 'label' => 'Publications', 'href' => 'publications.php'],
['key' => 'contact', 'label' => 'Contact Us', 'href' => 'contact.php'],
];
}
function default_service_catalog(): array
{
return [
[
'slug' => 'eco-wellness',
'track' => 'immersive',
'category' => 'Eco Wellness Package',
'headline' => 'Preventive wellbeing journeys grounded in place.',
'summary' => 'Curated wellness experiences that combine guided recovery, quiet hospitality, and structured facilitation for premium groups.',
'audience' => 'Clinics, communities, executive wellness groups',
'duration' => 'Half day to 3 days',
'outcome' => 'Participants leave with a calmer baseline, curated routines, and professionally coordinated aftercare recommendations.',
'includes' => [
'Guided breathing, mobility, and reflection sessions',
'Nature immersion flow with on-site facilitator',
'Healthy hospitality setup and light nourishment',
'Operational coordinator, safety briefing, and guest support',
],
],
[
'slug' => 'retreat',
'track' => 'immersive',
'category' => 'Retreat Package',
'headline' => 'Immersive retreats with quiet luxury and operational discipline.',
'summary' => 'Multi-day retreat programs for restorative groups, leadership circles, or curated community cohorts seeking a trusted delivery partner.',
'audience' => 'Leadership teams, private communities, destination hosts',
'duration' => '2 to 4 days',
'outcome' => 'Guests receive a coherent retreat arc, documented experience moments, and a host-ready operating structure.',
'includes' => [
'Retreat flow design from arrival to closing circle',
'Hospitality curation and accommodation coordination',
'Experience documentation and facilitator scheduling',
'Transportation, guest movement, and contingency planning',
],
],
[
'slug' => 'corporate',
'track' => 'corporate',
'category' => 'Corporate Package',
'headline' => 'Nature-based programs for teams that need focus, recovery, and alignment.',
'summary' => 'Corporate offsites and culture journeys that balance executive standards, wellness objectives, and measurable group outcomes.',
'audience' => 'Companies, HR teams, executive assistants, corporate communities',
'duration' => '1 to 2 days',
'outcome' => 'A more connected team, clean guest logistics, and a premium experience that avoids generic event energy.',
'includes' => [
'Team reset sessions and guided collaboration moments',
'Hospitality coordination with venue and catering partners',
'Brand-safe itineraries, agendas, and support crew',
'Post-program recap for internal reporting',
],
],
[
'slug' => 'hospitality',
'track' => 'operational',
'category' => 'Hospitality Package',
'headline' => 'Hospitality collaborations that expand destination value.',
'summary' => 'Program overlays for properties, destinations, or operators wanting to add curated wellness and experiential layers to their guest offering.',
'audience' => 'Hotels, villas, destination operators, stay partners',
'duration' => 'Modular activation',
'outcome' => 'Partners gain a credible package layer, documented service standards, and a stronger experiential positioning.',
'includes' => [
'Experience packaging and guest-ready positioning',
'Venue flow, staffing, and hospitality service mapping',
'Wellness or retreat modules fitted to the property',
'Partner coordination with clear operating notes',
],
],
[
'slug' => 'event-mice',
'track' => 'corporate',
'category' => 'Event & MICE Package',
'headline' => 'Operationally credible events with experiential depth.',
'summary' => 'MICE and event support for organizations that need premium guest handling, destination texture, and dependable execution.',
'audience' => 'Associations, brands, institutions, MICE organizers',
'duration' => 'Single activation to multi-day agenda',
'outcome' => 'An event environment that feels curated, calm, and professionally managed from transport to closing session.',
'includes' => [
'Run-of-show design and ecosystem partner alignment',
'Guest logistics, transport, and venue operations support',
'Curated destination layers for delegates or VIP groups',
'Documentation for post-event portfolio use',
],
],
[
'slug' => 'custom-experience',
'track' => 'operational',
'category' => 'Custom Experience Package',
'headline' => 'Flexible program architecture for bespoke briefs.',
'summary' => 'A consultative route for institutions or partners who need tailored combinations of wellness, hospitality, logistics, and activation.',
'audience' => 'Strategic partners, institutions, multi-stakeholder projects',
'duration' => 'Scoped during consultation',
'outcome' => 'A custom proposal with the right operating modules, partner map, and delivery assumptions for the brief.',
'includes' => [
'Discovery call and brief clarification',
'Package architecture and ecosystem matchmaking',
'Operational assumptions for location, scale, and timing',
'Proposal-ready summary to support internal decision-making',
],
],
];
}
function service_by_slug(string $slug): ?array
{
foreach (service_catalog() as $service) {
if ($service['slug'] === $slug) {
return $service;
}
}
return null;
}
function default_portfolio_catalog(): array
{
return [
[
'category' => 'Retreat Documentation',
'title' => 'Immersive reset for healthcare leaders',
'summary' => 'Three-day private retreat with guided wellness, quiet hospitality, and destination movement planning.',
'detail' => 'Built around trusted facilitation, transport sequencing, curated dining, and premium guest care.',
'metric' => '48 delegates · 3 days · full service coordination',
],
[
'category' => 'Hospitality Activities',
'title' => 'Property-based eco wellness activation',
'summary' => 'Hospitality collaboration that layered preventive wellness modules into a destination stay offer.',
'detail' => 'Designed to expand partner value without disrupting existing property operations.',
'metric' => '6 touchpoints · 2 host teams · reusable package model',
],
[
'category' => 'Event Showcase',
'title' => 'Institutional offsite with nature-led programming',
'summary' => 'Corporate gathering that fused structured sessions, outdoor recovery windows, and logistics support.',
'detail' => 'Focused on calm execution, executive-safe service, and documented experience moments.',
'metric' => '120 participants · transport + venue + documentation',
],
[
'category' => 'Operational Support',
'title' => 'Partner coordination for a multi-vendor destination brief',
'summary' => 'Cross-functional partner map covering venue, mobility, hospitality, and merchandise support.',
'detail' => 'Organized into clear workstreams to reduce friction and improve delivery certainty.',
'metric' => '9 partner lanes · single briefing track · review-ready notes',
],
];
}
function default_partnership_categories(): array
{
return [
['title' => 'Medical Partner', 'summary' => 'Preventive health programs, professional credibility, and trusted wellness positioning.'],
['title' => 'Food & Beverage Partner', 'summary' => 'Healthy dining curation, premium service alignment, and guest experience enhancement.'],
['title' => 'Hospitality Partner', 'summary' => 'Hotels, villas, and destination stays that want elevated experiential packages.'],
['title' => 'Transportation Partner', 'summary' => 'Ground movement, transfer planning, and reliable guest logistics.'],
['title' => 'Vendor Partner', 'summary' => 'Operational vendors for events, staging, equipment, or curated supporting services.'],
['title' => 'Venue Partner', 'summary' => 'Destination spaces that fit wellness, retreat, and premium gathering formats.'],
['title' => 'Merchandise Partner', 'summary' => 'Thoughtful branded items, gifting, and supporting program materials.'],
['title' => 'Talent Partner', 'summary' => 'Facilitators, wellness practitioners, speakers, and curated experience talent.'],
['title' => 'Facilities Partner', 'summary' => 'Technical, utility, and infrastructure support for safe, smooth delivery.'],
];
}
function default_publication_catalog(): array
{
return [
[
'slug' => 'governance-wellness',
'category' => 'Whitepaper',
'date' => '2026-05-12',
'title' => 'Governance as a differentiator in eco wellness operations',
'summary' => 'A concise look at why ethical operating systems increase trust in nature-based programs.',
'body' => [
'Nature-based experiences become institution-ready when governance is visible, practical, and embedded into the delivery model.',
'For GBP, governance is not an afterthought. It shapes partner qualification, guest flow, documentation, safety considerations, and post-program reporting.',
'Publishing this kind of operational insight helps the website function as both a service platform and a credibility engine for future collaborations.',
],
],
[
'slug' => 'hospitality-ecosystem',
'category' => 'Strategic Insight',
'date' => '2026-04-28',
'title' => 'Why hospitality ecosystems outperform isolated program offers',
'summary' => 'Integrated destinations create stronger guest journeys when transport, venue, and wellness layers are coordinated early.',
'body' => [
'Standalone experiences can look attractive in a brochure but collapse under operational pressure when the surrounding ecosystem is weak.',
'An integrated hospitality ecosystem aligns venue readiness, movement planning, service staffing, food design, and escalation paths before guests arrive.',
'That alignment is what allows a premium minimalist brand like Genitri Ecco Wellness to stay calm, polished, and credible at scale.',
],
],
[
'slug' => 'experience-customization',
'category' => 'Ecosystem Update',
'date' => '2026-03-30',
'title' => 'A practical framework for customizing destination experiences',
'summary' => 'How inquiry intake, partner matching, and brief translation can stay lightweight while still feeling premium.',
'body' => [
'Customization is most effective when the intake process captures the essentials: audience, timing, desired outcome, and operating constraints.',
'From there, GBP can match the right service modules, partner types, and delivery assumptions without overcomplicating the first conversation.',
'This is why the first MVP slice focuses on a strategic inquiry desk that feels both editorial and operational.',
],
],
];
}
function publication_by_slug(string $slug): ?array
{
foreach (publication_catalog() as $publication) {
if ($publication['slug'] === $slug) {
return $publication;
}
}
return null;
}
function governance_values(): array
{
return [
['title' => 'Excellence with Integrity', 'summary' => 'Premium execution grounded in transparent standards, safety, and reliable delivery.'],
['title' => 'Shared Value Creation', 'summary' => 'Programs and partnerships designed to benefit guests, collaborators, and the wider ecosystem.'],
['title' => 'Sustainable Equilibrium', 'summary' => 'Nature, hospitality, and operations balanced with long-term resilience in mind.'],
];
}
function objective_list(): array
{
return [
'Build institutional trust',
'Showcase operational capability',
'Open strategic partnership routes',
'Generate qualified inquiry',
'Provide curated package services',
'Enable experience customization',
'Display ecosystem portfolio',
'Build publication authority',
'Prepare governance infrastructure',
];
}
function inquiry_type_options(): array
{
return [
'Strategic Partnership',
'Package Customization',
'Operational Collaboration',
'Publication / Media',
];
}
function interest_groups(): array
{
$services = [];
foreach (service_catalog() as $service) {
$services[] = $service['category'];
}
$partners = [];
foreach (partnership_categories() as $partner) {
$partners[] = $partner['title'];
}
return [
'Curated Programs' => $services,
'Partnership Routes' => $partners,
];
}
function scale_options(): array
{
return [
'Private group (1030)',
'Team offsite (3180)',
'Corporate / MICE (81200)',
'Multi-site activation (200+)',
];
}
function location_options(): array
{
return [
'Tegal / Bregas corridor',
'Central Java destination',
'Partner property or venue',
'To be discussed during scoping',
];
}
function active_class(string $activePage, string $key): string
{
return $activePage === $key ? 'active' : '';
}
function current_year(): string
{
return gmdate('Y');
}
function csrf_token(): string
{
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(16));
}
return $_SESSION['csrf_token'];
}
function verify_csrf_token(?string $token): bool
{
return is_string($token) && hash_equals($_SESSION['csrf_token'] ?? '', $token);
}
function flash_set(string $type, string $message): void
{
$_SESSION['flash'] = [
'type' => $type,
'message' => $message,
];
}
function flash_get(): ?array
{
if (empty($_SESSION['flash']) || !is_array($_SESSION['flash'])) {
return null;
}
$flash = $_SESSION['flash'];
unset($_SESSION['flash']);
return $flash;
}
function old_value(array $source, string $key, string $fallback = ''): string
{
$value = $source[$key] ?? $fallback;
return trim((string) $value);
}
function is_selected(?string $candidate, ?string $value): string
{
return ((string) $candidate === (string) $value) ? 'selected' : '';
}
function app_db_connection(): ?PDO
{
static $attempted = false;
static $pdo = null;
static $error = null;
if ($attempted) {
return $pdo;
}
$attempted = true;
try {
$pdo = db();
$error = null;
} catch (Throwable $exception) {
$pdo = null;
$error = $exception->getMessage();
}
$GLOBALS['APP_DB_ERROR'] = $error;
return $pdo;
}
function app_db_error(): ?string
{
app_db_connection();
$error = $GLOBALS['APP_DB_ERROR'] ?? null;
return is_string($error) && $error !== '' ? $error : null;
}
function using_preview_storage(): bool
{
return app_db_connection() === null;
}
function inquiry_table_sql(): string
{
return <<<'SQL'
CREATE TABLE IF NOT EXISTS ecosystem_inquiries (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
reference_code VARCHAR(32) NOT NULL UNIQUE,
inquiry_type VARCHAR(80) NOT NULL,
interest_area VARCHAR(120) NOT NULL,
contact_name VARCHAR(120) NOT NULL,
organization_name VARCHAR(160) NOT NULL,
email VARCHAR(160) NOT NULL,
phone VARCHAR(40) DEFAULT NULL,
target_date VARCHAR(40) DEFAULT NULL,
event_scale VARCHAR(80) DEFAULT NULL,
preferred_location VARCHAR(120) DEFAULT NULL,
notes TEXT NOT NULL,
status VARCHAR(30) NOT NULL DEFAULT 'New',
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX idx_status_created (status, created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
SQL;
}
function ensure_inquiry_table(): bool
{
static $ensured = false;
if ($ensured) {
return true;
}
$pdo = app_db_connection();
if (!$pdo) {
return false;
}
try {
$pdo->exec(inquiry_table_sql());
$ensured = true;
return true;
} catch (Throwable $exception) {
$GLOBALS['APP_DB_ERROR'] = $exception->getMessage();
return false;
}
}
function normalize_multiline(string $value): string
{
$normalized = preg_replace("/\r\n?|\r/", "\n", $value);
$normalized = preg_replace("/\n{3,}/", "\n\n", (string) $normalized);
return trim((string) $normalized);
}
function validate_inquiry(array $input): array
{
$data = [
'inquiry_type' => trim((string) ($input['inquiry_type'] ?? '')),
'interest_area' => trim((string) ($input['interest_area'] ?? '')),
'contact_name' => trim((string) ($input['contact_name'] ?? '')),
'organization_name' => trim((string) ($input['organization_name'] ?? '')),
'email' => trim((string) ($input['email'] ?? '')),
'phone' => trim((string) ($input['phone'] ?? '')),
'target_date' => trim((string) ($input['target_date'] ?? '')),
'event_scale' => trim((string) ($input['event_scale'] ?? '')),
'preferred_location' => trim((string) ($input['preferred_location'] ?? '')),
'notes' => normalize_multiline((string) ($input['notes'] ?? '')),
];
$errors = [];
if ($data['inquiry_type'] === '') {
$errors['inquiry_type'] = 'Choose the type of collaboration you want to discuss.';
}
if ($data['interest_area'] === '') {
$errors['interest_area'] = 'Select the package or partnership route that best matches your brief.';
}
if ($data['contact_name'] === '') {
$errors['contact_name'] = 'Add the primary contact name.';
}
if ($data['organization_name'] === '') {
$errors['organization_name'] = 'Add the company or organization name.';
}
if ($data['email'] === '' || !filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
$errors['email'] = 'Use a valid email address so the team can respond.';
}
if ($data['notes'] === '') {
$errors['notes'] = 'Describe the program scope, partner intent, or operational need.';
} elseif (text_length($data['notes']) < 24) {
$errors['notes'] = 'Add a little more detail so the brief is useful for review.';
} elseif (text_length($data['notes']) > 900) {
$errors['notes'] = 'Keep the brief under 900 characters for this first intake step.';
}
if ($data['phone'] !== '' && text_length($data['phone']) > 40) {
$errors['phone'] = 'Phone number is too long.';
}
if ($data['target_date'] !== '' && !preg_match('/^\d{4}-\d{2}-\d{2}$/', $data['target_date'])) {
$errors['target_date'] = 'Use a valid preferred date.';
}
return [
'data' => $data,
'errors' => $errors,
];
}
function generate_reference_code(): string
{
return 'GBP-' . gmdate('ymd') . '-' . strtoupper(bin2hex(random_bytes(2)));
}
function save_inquiry(array $input): array
{
$validated = validate_inquiry($input);
$data = $validated['data'];
$errors = $validated['errors'];
if ($errors !== []) {
return [
'success' => false,
'errors' => $errors,
'data' => $data,
];
}
$inquiry = $data;
$inquiry['reference_code'] = generate_reference_code();
$inquiry['status'] = 'New';
$inquiry['created_at'] = gmdate('Y-m-d H:i:s');
if (ensure_inquiry_table()) {
try {
$pdo = app_db_connection();
if ($pdo instanceof PDO) {
$statement = $pdo->prepare(
'INSERT INTO ecosystem_inquiries
(reference_code, inquiry_type, interest_area, contact_name, organization_name, email, phone, target_date, event_scale, preferred_location, notes, status)
VALUES
(:reference_code, :inquiry_type, :interest_area, :contact_name, :organization_name, :email, :phone, :target_date, :event_scale, :preferred_location, :notes, :status)'
);
$statement->bindValue(':reference_code', $inquiry['reference_code']);
$statement->bindValue(':inquiry_type', $inquiry['inquiry_type']);
$statement->bindValue(':interest_area', $inquiry['interest_area']);
$statement->bindValue(':contact_name', $inquiry['contact_name']);
$statement->bindValue(':organization_name', $inquiry['organization_name']);
$statement->bindValue(':email', $inquiry['email']);
$statement->bindValue(':phone', $inquiry['phone'] ?: null, $inquiry['phone'] === '' ? PDO::PARAM_NULL : PDO::PARAM_STR);
$statement->bindValue(':target_date', $inquiry['target_date'] ?: null, $inquiry['target_date'] === '' ? PDO::PARAM_NULL : PDO::PARAM_STR);
$statement->bindValue(':event_scale', $inquiry['event_scale'] ?: null, $inquiry['event_scale'] === '' ? PDO::PARAM_NULL : PDO::PARAM_STR);
$statement->bindValue(':preferred_location', $inquiry['preferred_location'] ?: null, $inquiry['preferred_location'] === '' ? PDO::PARAM_NULL : PDO::PARAM_STR);
$statement->bindValue(':notes', $inquiry['notes']);
$statement->bindValue(':status', $inquiry['status']);
$statement->execute();
$inquiry['id'] = (int) $pdo->lastInsertId();
return [
'success' => true,
'inquiry' => $inquiry,
'storage' => 'database',
];
}
} catch (Throwable $exception) {
$GLOBALS['APP_DB_ERROR'] = $exception->getMessage();
}
}
$_SESSION['preview_inquiries'] = $_SESSION['preview_inquiries'] ?? [];
$_SESSION['preview_inquiry_counter'] = (int) ($_SESSION['preview_inquiry_counter'] ?? 0) + 1;
$inquiry['id'] = (int) $_SESSION['preview_inquiry_counter'];
$_SESSION['preview_inquiries'][$inquiry['reference_code']] = $inquiry;
return [
'success' => true,
'inquiry' => $inquiry,
'storage' => 'session',
'warning' => 'Database connectivity is not available, so this submission is stored only for the current preview session.',
];
}
function session_inquiries(): array
{
$items = array_values($_SESSION['preview_inquiries'] ?? []);
usort($items, static fn (array $left, array $right): int => strcmp($right['created_at'], $left['created_at']));
return $items;
}
function list_inquiries(): array
{
if (ensure_inquiry_table()) {
try {
$pdo = app_db_connection();
if ($pdo instanceof PDO) {
$statement = $pdo->query('SELECT * FROM ecosystem_inquiries ORDER BY created_at DESC, id DESC LIMIT 50');
return $statement->fetchAll() ?: [];
}
} catch (Throwable $exception) {
$GLOBALS['APP_DB_ERROR'] = $exception->getMessage();
}
}
return session_inquiries();
}
function find_inquiry(?int $id = null, ?string $reference = null): ?array
{
if (ensure_inquiry_table()) {
try {
$pdo = app_db_connection();
if ($pdo instanceof PDO) {
if ($reference !== null && $reference !== '') {
$statement = $pdo->prepare('SELECT * FROM ecosystem_inquiries WHERE reference_code = :reference LIMIT 1');
$statement->bindValue(':reference', $reference);
$statement->execute();
$found = $statement->fetch();
return is_array($found) ? $found : null;
}
if ($id !== null && $id > 0) {
$statement = $pdo->prepare('SELECT * FROM ecosystem_inquiries WHERE id = :id LIMIT 1');
$statement->bindValue(':id', $id, PDO::PARAM_INT);
$statement->execute();
$found = $statement->fetch();
return is_array($found) ? $found : null;
}
}
} catch (Throwable $exception) {
$GLOBALS['APP_DB_ERROR'] = $exception->getMessage();
}
}
foreach (session_inquiries() as $item) {
if (($reference !== null && $reference !== '' && $item['reference_code'] === $reference) || ($id !== null && (int) $item['id'] === $id)) {
return $item;
}
}
return null;
}
function format_date_label(?string $date): string
{
$value = trim((string) $date);
if ($value === '') {
return 'To be coordinated';
}
try {
return (new DateTimeImmutable($value))->format('j M Y');
} catch (Throwable) {
return $value;
}
}