1 Commits

Author SHA1 Message Date
7d078cb954 WIP: Blogposts 2025-12-02 17:41:24 +02:00
321 changed files with 19176 additions and 46532 deletions

5
.gitignore vendored
View File

@@ -1,5 +1,6 @@
.env
/vendor/
.htaccess
/assets/uploads/gallery/
/assets/uploads/
/uploads/
/uploads/pop/

158
.htaccess
View File

@@ -1,161 +1,3 @@
# URL Rewrite Rules - Maps old URLs to new directory structure during migration
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
# Don't rewrite existing files or directories
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# === STRIP .PHP EXTENSION ===
# Redirect /page.php to /page (301 permanent redirect)
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.+)\.php$ /$1 [R=301,L]
# Internally rewrite /page to /page.php if page.php exists
RewriteCond %{REQUEST_FILENAME}\.php -f
RewriteRule ^(.+)$ $1.php [L]
# === AUTH PAGES ===
RewriteRule ^login$ src/pages/auth/login.php [L]
RewriteRule ^register$ src/pages/auth/register.php [L]
RewriteRule ^forgot_password$ src/pages/auth/forgot_password.php [L]
RewriteRule ^reset_password$ src/pages/auth/reset_password.php [L]
RewriteRule ^verify$ src/pages/auth/verify.php [L]
RewriteRule ^resend_verification$ src/pages/auth/resend_verification.php [L]
RewriteRule ^change_password$ src/pages/auth/change_password.php [L]
RewriteRule ^update_password$ src/pages/auth/update_password.php [L]
# === MEMBERSHIP PAGES ===
RewriteRule ^membership$ src/pages/memberships/membership.php [L]
RewriteRule ^membership_details$ src/pages/memberships/membership_details.php [L]
RewriteRule ^membership_application$ src/pages/memberships/membership_application.php [L]
RewriteRule ^membership_payment$ src/pages/memberships/membership_payment.php [L]
RewriteRule ^renew_membership$ src/pages/memberships/renew_membership.php [L]
RewriteRule ^member_info$ src/pages/memberships/member_info.php [L]
# === BOOKING PAGES ===
RewriteRule ^bookings$ src/pages/bookings/bookings.php [L]
RewriteRule ^campsites$ src/pages/bookings/campsites.php [L]
RewriteRule ^campsite_booking$ src/pages/bookings/campsite_booking.php [L]
RewriteRule ^add_campsite$ src/pages/add_campsite.php [L]
RewriteRule ^trips$ src/pages/bookings/trips.php [L]
RewriteRule ^trip-details$ src/pages/bookings/trip-details.php [L]
RewriteRule ^course_details$ src/pages/bookings/course_details.php [L]
RewriteRule ^driver_training$ src/pages/bookings/driver_training.php [L]
# === SHOP PAGES ===
RewriteRule ^view_cart$ src/pages/shop/view_cart.php [L]
RewriteRule ^add_to_cart$ src/pages/shop/add_to_cart.php [L]
RewriteRule ^bar_tabs$ src/pages/shop/bar_tabs.php [L]
RewriteRule ^payment_confirmation$ src/pages/shop/payment_confirmation.php [L]
RewriteRule ^confirm$ src/pages/shop/confirm.php [L]
RewriteRule ^confirm2$ src/pages/shop/confirm2.php [L]
# === GALLERY PAGES ===
RewriteRule ^gallery$ src/pages/gallery/gallery.php [L]
RewriteRule ^create_album$ src/pages/gallery/create_album.php [L]
RewriteRule ^edit_album$ src/pages/gallery/create_album.php [L]
RewriteRule ^view_album$ src/pages/gallery/view_album.php [L]
# === EVENTS & BLOG PAGES ===
RewriteRule ^events$ src/pages/events/events.php [L]
RewriteRule ^blog$ src/pages/blog/blog.php [L]
RewriteRule ^blog_details$ src/pages/blog/blog_details.php [L]
RewriteRule ^best_of_the_eastern_cape_2024$ src/pages/events/best_of_the_eastern_cape_2024.php [L]
RewriteRule ^2025_agm_minutes$ src/pages/events/2025_agm_minutes.php [L]
RewriteRule ^agm_content$ src/pages/events/agm_content.php [L]
RewriteRule ^instapage$ src/pages/events/instapage.php [L]
# === OTHER PAGES ===
RewriteRule ^about$ src/pages/other/about.php [L]
RewriteRule ^contact$ src/pages/other/contact.php [L]
RewriteRule ^privacy_policy$ src/pages/other/privacy_policy.php [L]
RewriteRule ^track-map$ src/pages/track-map.php [L]
RewriteRule ^404$ src/pages/other/404.php [L]
RewriteRule ^account_settings$ src/pages/other/account_settings.php [L]
RewriteRule ^rescue_recovery$ src/pages/other/rescue_recovery.php [L]
RewriteRule ^bush_mechanics$ src/pages/other/bush_mechanics.php [L]
RewriteRule ^indemnity$ src/pages/other/indemnity.php [L]
RewriteRule ^indemnity_waiver$ src/pages/other/indemnity_waiver.php [L]
RewriteRule ^basic_indemnity$ src/pages/other/basic_indemnity.php [L]
RewriteRule ^view_indemnity$ src/pages/other/view_indemnity.php [L]
# === PAYMENT RETURN PAGES ===
RewriteRule ^success$ src/pages/payment/success.php [L]
RewriteRule ^failure$ src/pages/payment/failure.php [L]
RewriteRule ^cancel$ src/pages/payment/cancel.php [L]
# === ADMIN PAGES ===
RewriteRule ^admin_members$ src/admin/admin_members.php [L]
RewriteRule ^admin_payments$ src/admin/admin_payments.php [L]
RewriteRule ^admin_web_users$ src/admin/admin_web_users.php [L]
RewriteRule ^admin_events$ src/admin/admin_events.php [L]
RewriteRule ^admin_course_bookings$ src/admin/admin_course_bookings.php [L]
RewriteRule ^admin_camp_bookings$ src/admin/admin_camp_bookings.php [L]
RewriteRule ^admin_trip_bookings$ src/admin/admin_trip_bookings.php [L]
RewriteRule ^admin_visitors$ src/admin/admin_visitors.php [L]
RewriteRule ^admin_efts$ src/admin/admin_efts.php [L]
RewriteRule ^admin_trips$ src/admin/admin_trips.php [L]
RewriteRule ^manage_events$ src/admin/manage_events.php [L]
RewriteRule ^manage_trips$ src/admin/manage_trips.php [L]
# === API/AJAX ENDPOINTS ===
RewriteRule ^fetch_users$ src/api/fetch_users.php [L]
RewriteRule ^fetch_drinks$ src/api/fetch_drinks.php [L]
RewriteRule ^fetch_bar_tabs$ src/api/fetch_bar_tabs.php [L]
RewriteRule ^get_campsites$ src/api/get_campsites.php [L]
RewriteRule ^get_tab_total$ src/api/get_tab_total.php [L]
RewriteRule ^google_validate_login$ src/api/google_validate_login.php [L]
# === PROCESSORS ===
RewriteRule ^validate_login$ src/processors/validate_login.php [L]
RewriteRule ^register_user$ src/processors/register_user.php [L]
RewriteRule ^process_application$ src/processors/process_application.php [L]
RewriteRule ^process_booking$ src/processors/process_booking.php [L]
RewriteRule ^process_camp_booking$ src/processors/process_camp_booking.php [L]
RewriteRule ^process_course_booking$ src/processors/process_course_booking.php [L]
RewriteRule ^process_trip_booking$ src/processors/process_trip_booking.php [L]
RewriteRule ^process_membership_payment$ src/processors/process_membership_payment.php [L]
RewriteRule ^process_payments$ src/processors/process_payments.php [L]
RewriteRule ^process_eft$ src/processors/process_eft.php [L]
RewriteRule ^submit_order$ src/processors/submit_order.php [L]
RewriteRule ^submit_pop$ src/processors/submit_pop.php [L]
RewriteRule ^process_signature$ src/processors/process_signature.php [L]
RewriteRule ^create_bar_tab$ src/processors/create_bar_tab.php [L]
RewriteRule ^update_application$ src/processors/update_application.php [L]
RewriteRule ^update_user$ src/processors/update_user.php [L]
RewriteRule ^upload_profile_picture$ src/processors/upload_profile_picture.php [L]
RewriteRule ^send_reset_link$ src/processors/send_reset_link.php [L]
RewriteRule ^logout$ src/processors/logout.php [L]
RewriteRule ^process_trip$ src/processors/process_trip.php [L]
RewriteRule ^process_event$ src/processors/process_event.php [L]
RewriteRule ^toggle_trip_published$ src/processors/toggle_trip_published.php [L]
RewriteRule ^toggle_event_published$ src/processors/toggle_event_published.php [L]
RewriteRule ^delete_trip$ src/processors/delete_trip.php [L]
RewriteRule ^delete_event$ src/processors/delete_event.php [L]
RewriteRule ^save_album$ src/processors/save_album.php [L]
RewriteRule ^update_album$ src/processors/update_album.php [L]
RewriteRule ^delete_album$ src/processors/delete_album.php [L]
RewriteRule ^delete_photo$ src/processors/delete_photo.php [L]
RewriteRule ^get_album_photos$ src/processors/get_album_photos.php [L]
RewriteRule ^link_membership_user$ src/processors/link_membership_user.php [L]
RewriteRule ^unlink_membership_user$ src/processors/unlink_membership_user.php [L]
# Blog routes
RewriteRule ^admin_blogs$ src/pages/blog/admin_blogs.php [L]
RewriteRule ^user_blogs$ src/pages/blog/user_blogs.php [L]
RewriteRule ^blog_read$ src/pages/blog/blog_read.php [L]
RewriteRule ^blog_edit$ src/pages/blog/blog_edit.php [L]
RewriteRule ^blog_create$ src/processors/blog/blog_create.php [L]
RewriteRule ^blog_delete$ src/processors/blog/blog_delete.php [L]
RewriteRule ^publish_blog$ src/processors/blog/publish_blog.php [L]
RewriteRule ^blog_unpublish$ src/processors/blog/blog_unpublish.php [L]
RewriteRule ^submit_blog$ src/processors/blog/submit_blog.php [L]
RewriteRule ^upload_blog_image$ src/processors/blog/upload_blog_image.php [L]
RewriteRule ^autosave$ src/processors/blog/autosave.php [L]
</IfModule>
php_flag display_errors On
# php_value error_reporting -1
RedirectMatch 403 ^/\.well-known

View File

@@ -1,215 +0,0 @@
# URL Rewrite Rules - Maps old URLs to new directory structure during migration
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
# Don't rewrite existing files or directories
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# === STRIP .PHP EXTENSION ===
# Redirect /page.php to /page (301 permanent redirect)
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.+)\.php$ /$1 [R=301,L]
# Internally rewrite /page to /page.php if page.php exists
RewriteCond %{REQUEST_FILENAME}\.php -f
RewriteRule ^(.+)$ $1.php [L]
# === AUTH PAGES ===
RewriteRule ^login$ src/pages/auth/login.php [L]
RewriteRule ^register$ src/pages/auth/register.php [L]
RewriteRule ^forgot_password$ src/pages/auth/forgot_password.php [L]
RewriteRule ^reset_password$ src/pages/auth/reset_password.php [L]
RewriteRule ^verify$ src/pages/auth/verify.php [L]
RewriteRule ^resend_verification$ src/pages/auth/resend_verification.php [L]
RewriteRule ^change_password$ src/pages/auth/change_password.php [L]
RewriteRule ^update_password$ src/pages/auth/update_password.php [L]
# === MEMBERSHIP PAGES ===
RewriteRule ^membership$ src/pages/memberships/membership.php [L]
RewriteRule ^membership_details$ src/pages/memberships/membership_details.php [L]
RewriteRule ^membership_application$ src/pages/memberships/membership_application.php [L]
RewriteRule ^membership_payment$ src/pages/memberships/membership_payment.php [L]
RewriteRule ^renew_membership$ src/pages/memberships/renew_membership.php [L]
RewriteRule ^member_info$ src/pages/memberships/member_info.php [L]
# === BOOKING PAGES ===
RewriteRule ^bookings$ src/pages/bookings/bookings.php [L]
RewriteRule ^campsites$ src/pages/bookings/campsites.php [L]
RewriteRule ^campsite_booking$ src/pages/bookings/campsite_booking.php [L]
RewriteRule ^add_campsite$ src/pages/add_campsite.php [L]
RewriteRule ^trips$ src/pages/bookings/trips.php [L]
RewriteRule ^trip-details$ src/pages/bookings/trip-details.php [L]
RewriteRule ^course_details$ src/pages/bookings/course_details.php [L]
RewriteRule ^driver_training$ src/pages/bookings/driver_training.php [L]
# === SHOP PAGES ===
RewriteRule ^view_cart$ src/pages/shop/view_cart.php [L]
RewriteRule ^add_to_cart$ src/pages/shop/add_to_cart.php [L]
RewriteRule ^bar_tabs$ src/pages/shop/bar_tabs.php [L]
RewriteRule ^payment_confirmation$ src/pages/shop/payment_confirmation.php [L]
RewriteRule ^confirm$ src/pages/shop/confirm.php [L]
RewriteRule ^confirm2$ src/pages/shop/confirm2.php [L]
# === GALLERY PAGES ===
RewriteRule ^gallery$ src/pages/gallery/gallery.php [L]
RewriteRule ^create_album$ src/pages/gallery/create_album.php [L]
RewriteRule ^edit_album$ src/pages/gallery/create_album.php [L]
RewriteRule ^view_album$ src/pages/gallery/view_album.php [L]
# === EVENTS & BLOG PAGES ===
RewriteRule ^events$ src/pages/events/events.php [L]
RewriteRule ^blog$ src/pages/blog/blog.php [L]
RewriteRule ^blog_details$ src/pages/blog/blog_details.php [L]
RewriteRule ^best_of_the_eastern_cape_2024$ src/pages/events/best_of_the_eastern_cape_2024.php [L]
RewriteRule ^2025_agm_minutes$ src/pages/events/2025_agm_minutes.php [L]
RewriteRule ^agm_content$ src/pages/events/agm_content.php [L]
RewriteRule ^instapage$ src/pages/events/instapage.php [L]
# === OTHER PAGES ===
RewriteRule ^about$ src/pages/other/about.php [L]
RewriteRule ^contact$ src/pages/other/contact.php [L]
RewriteRule ^privacy_policy$ src/pages/other/privacy_policy.php [L]
RewriteRule ^track-map$ src/pages/track-map.php [L]
RewriteRule ^404$ src/pages/other/404.php [L]
RewriteRule ^account_settings$ src/pages/other/account_settings.php [L]
RewriteRule ^rescue_recovery$ src/pages/other/rescue_recovery.php [L]
RewriteRule ^bush_mechanics$ src/pages/other/bush_mechanics.php [L]
RewriteRule ^indemnity$ src/pages/other/indemnity.php [L]
RewriteRule ^indemnity_waiver$ src/pages/other/indemnity_waiver.php [L]
RewriteRule ^basic_indemnity$ src/pages/other/basic_indemnity.php [L]
RewriteRule ^view_indemnity$ src/pages/other/view_indemnity.php [L]
# === ADMIN PAGES ===
RewriteRule ^admin_members$ src/admin/admin_members.php [L]
RewriteRule ^admin_payments$ src/admin/admin_payments.php [L]
RewriteRule ^admin_web_users$ src/admin/admin_web_users.php [L]
RewriteRule ^admin_events$ src/admin/admin_events.php [L]
RewriteRule ^admin_course_bookings$ src/admin/admin_course_bookings.php [L]
RewriteRule ^admin_camp_bookings$ src/admin/admin_camp_bookings.php [L]
RewriteRule ^admin_trip_bookings$ src/admin/admin_trip_bookings.php [L]
RewriteRule ^admin_visitors$ src/admin/admin_visitors.php [L]
RewriteRule ^admin_efts$ src/admin/admin_efts.php [L]
RewriteRule ^admin_trips$ src/admin/admin_trips.php [L]
RewriteRule ^manage_events$ src/admin/manage_events.php [L]
RewriteRule ^manage_trips$ src/admin/manage_trips.php [L]
# === API/AJAX ENDPOINTS ===
RewriteRule ^fetch_users$ src/api/fetch_users.php [L]
RewriteRule ^fetch_drinks$ src/api/fetch_drinks.php [L]
RewriteRule ^fetch_bar_tabs$ src/api/fetch_bar_tabs.php [L]
RewriteRule ^get_campsites$ src/api/get_campsites.php [L]
RewriteRule ^get_tab_total$ src/api/get_tab_total.php [L]
RewriteRule ^google_validate_login$ src/api/google_validate_login.php [L]
# === PROCESSORS ===
RewriteRule ^validate_login$ src/processors/validate_login.php [L]
RewriteRule ^register_user$ src/processors/register_user.php [L]
RewriteRule ^process_application$ src/processors/process_application.php [L]
RewriteRule ^process_booking$ src/processors/process_booking.php [L]
RewriteRule ^process_camp_booking$ src/processors/process_camp_booking.php [L]
RewriteRule ^process_course_booking$ src/processors/process_course_booking.php [L]
RewriteRule ^process_trip_booking$ src/processors/process_trip_booking.php [L]
RewriteRule ^process_membership_payment$ src/processors/process_membership_payment.php [L]
RewriteRule ^process_payments$ src/processors/process_payments.php [L]
RewriteRule ^process_eft$ src/processors/process_eft.php [L]
RewriteRule ^submit_order$ src/processors/submit_order.php [L]
RewriteRule ^submit_pop$ src/processors/submit_pop.php [L]
RewriteRule ^process_signature$ src/processors/process_signature.php [L]
RewriteRule ^create_bar_tab$ src/processors/create_bar_tab.php [L]
RewriteRule ^update_application$ src/processors/update_application.php [L]
RewriteRule ^update_user$ src/processors/update_user.php [L]
RewriteRule ^upload_profile_picture$ src/processors/upload_profile_picture.php [L]
RewriteRule ^send_reset_link$ src/processors/send_reset_link.php [L]
RewriteRule ^logout$ src/processors/logout.php [L]
RewriteRule ^process_trip$ src/processors/process_trip.php [L]
RewriteRule ^process_event$ src/processors/process_event.php [L]
RewriteRule ^toggle_trip_published$ src/processors/toggle_trip_published.php [L]
RewriteRule ^toggle_event_published$ src/processors/toggle_event_published.php [L]
RewriteRule ^delete_trip$ src/processors/delete_trip.php [L]
RewriteRule ^delete_event$ src/processors/delete_event.php [L]
RewriteRule ^save_album$ src/processors/save_album.php [L]
RewriteRule ^update_album$ src/processors/update_album.php [L]
RewriteRule ^delete_album$ src/processors/delete_album.php [L]
RewriteRule ^delete_photo$ src/processors/delete_photo.php [L]
RewriteRule ^get_album_photos$ src/processors/get_album_photos.php [L]
RewriteRule ^link_membership_user$ src/processors/link_membership_user.php [L]
RewriteRule ^unlink_membership_user$ src/processors/unlink_membership_user.php [L]
# Blog routes
RewriteRule ^admin_blogs$ src/pages/blog/admin_blogs.php [L]
RewriteRule ^user_blogs$ src/pages/blog/user_blogs.php [L]
RewriteRule ^blog_read$ src/pages/blog/blog_read.php [L]
RewriteRule ^blog_edit$ src/pages/blog/blog_edit.php [L]
RewriteRule ^blog_create$ src/processors/blog/blog_create.php [L]
RewriteRule ^blog_delete$ src/processors/blog/blog_delete.php [L]
RewriteRule ^publish_blog$ src/processors/blog/publish_blog.php [L]
RewriteRule ^blog_unpublish$ src/processors/blog/blog_unpublish.php [L]
RewriteRule ^submit_blog$ src/processors/blog/submit_blog.php [L]
RewriteRule ^upload_blog_image$ src/processors/blog/upload_blog_image.php [L]
RewriteRule ^autosave$ src/processors/blog/autosave.php [L]
</IfModule>
php_flag display_errors On
# php_value error_reporting -1
RedirectMatch 403 ^/\.well-known
Options -Indexes
<FilesMatch "\.(env|sql|bak|zip|tar|gz|ini)$">
Require all denied
</FilesMatch>
ErrorDocument 404 /404.php
<RequireAll>
Require all granted
Require not ip 4.222.252.98
Require not ip 4.222.252.97
</RequireAll>
<Files .env>
Order allow,deny
Deny from all
</Files>
# ALL CUSTOM ENTRIES SHOULD GO ABOVE THIS LINE
# BEGIN IWORX header
# This file was created by InterWorx-CP
# You may modify this file, but any changes made between
# BEGIN IWORX and END IWORX tags may be lost on future
# updates. Additionally, changes NOT made between these
# tags will not be recognized in the SiteWorx interface.
# END IWORX header
# BEGIN IWORX accesscontrol
# END IWORX accesscontrol
# BEGIN IWORX errordocs
# END IWORX errordocs
# BEGIN IWORX mimetypes
# END IWORX mimetypes
# BEGIN IWORX handlers
# END IWORX handlers
# BEGIN IWORX charset
# END IWORX charset
# BEGIN IWORX redirects
# END IWORX redirects
# BEGIN IWORX phpvars
# END IWORX phpvars
# BEGIN IWORX dirindex
# END IWORX dirindex
# BEGIN IWORX hotlink
# END IWORX hotlink
# BEGIN IWORX passwordprotection
# END IWORX passwordprotection

View File

@@ -1,4 +0,0 @@
; memory_limit = 512M
upload_max_filesize = 64M
post_max_size = 64M
max_execution_time = 120

View File

@@ -1,7 +1,4 @@
<?php
$headerStyle = 'light';
$rootPath = dirname(dirname(dirname(__DIR__)));
include_once($rootPath . '/header.php');
<?php include_once('header02.php');
$page_id = 'agm_minutes';
?>
@@ -68,15 +65,28 @@ $page_id = 'agm_minutes';
float: right;
}
.clearfix {
clear: both;
}
</style>
<?php
$pageTitle = '2025 AGM Minutes';
$breadcrumbs = [['Home' => 'index.php']];
require_once($rootPath . '/components/banner.php');
?>
<section class="page-banner-area pt-50 pb-35 rel z-1 bgs-cover" style="background-image: url('assets/images/blog/2/agm.jpg');">
<div class="banner-overlay"></div>
<div class="container">
<div class="banner-inner text-white">
<h2 class="page-title mb-10" data-aos="fade-left" data-aos-duration="1500" data-aos-offset="50">2025 AGM Minutes & Chairman's Report</h2>
<nav aria-label="breadcrumb">
<ol class="breadcrumb justify-content-center mb-20" data-aos="fade-right" data-aos-delay="200" data-aos-duration="1500" data-aos-offset="50">
<li class="breadcrumb-item"><a href="index.php">Home</a></li>
<li class="breadcrumb-item active">2025 AGM Minutes & Chairman's Report</li>
</ol>
</nav>
</div>
</div>
</section>
<!-- Page Banner End -->
<!-- Blog Detaisl Area start -->
<section class="blog-detaisl-page py-100 rel z-1">
@@ -102,7 +112,7 @@ $page_id = 'agm_minutes';
<div class="item" data-aos="fade-left" data-aos-duration="1500" data-aos-offset="50">
<h6>Tags </h6>
<div class="tag-coulds">
<a href="blog">Reports</a>
<a href="blog.php">Reports</a>
</div>
</div>
@@ -255,4 +265,4 @@ $page_id = 'agm_minutes';
<!-- Blog Detaisl Area end -->
<?php include_once(dirname(dirname(dirname(__DIR__))) . '/components/insta_footer.php'); ?>
<?php include_once("insta_footer.php"); ?>

View File

@@ -1,6 +1,4 @@
<?php
$headerStyle = 'light';
include_once(dirname(dirname(dirname(__DIR__))) . '/header.php');
<?php include_once('header02.php');
?>
@@ -43,4 +41,4 @@ include_once(dirname(dirname(dirname(__DIR__))) . '/header.php');
</section>
<!-- 404 Error Area end -->
<?php include_once(dirname(dirname(dirname(__DIR__))) . '/components/insta_footer.php'); ?>
<?php include_once("insta_footer.php"); ?>

View File

@@ -1,8 +1,4 @@
<?php
$headerStyle = 'light';
// Determine the correct path to header.php based on file location
$rootPath = dirname(dirname(dirname(__DIR__)));
include_once($rootPath . '/header.php');
<?php include_once('header02.php');
?>
<style>
@@ -41,11 +37,31 @@ include_once($rootPath . '/header.php');
}
</style>
<!-- Page Banner Start -->
<?php
$pageTitle = 'About';
$breadcrumbs = [['Home' => 'index.php']];
require_once($rootPath . '/components/banner.php');
$bannerFolder = 'assets/images/banners/';
$bannerImages = glob($bannerFolder . '*.{jpg,jpeg,png,webp}', GLOB_BRACE);
$randomBanner = 'assets/images/base4/camping.jpg'; // default fallback
if (!empty($bannerImages)) {
$randomBanner = $bannerImages[array_rand($bannerImages)];
}
?>
<section class="page-banner-area pt-50 pb-35 rel z-1 bgs-cover" style="background-image: url('<?php echo $randomBanner; ?>');">
<!-- Overlay PNG -->
<div class="banner-overlay"></div>
<div class="container">
<div class="banner-inner text-white mb-50">
<h2 class="page-title mb-10" data-aos="fade-left" data-aos-duration="1500" data-aos-offset="50">About</h2>
<nav aria-label="breadcrumb">
<ol class="breadcrumb justify-content-center mb-20" data-aos="fade-right" data-aos-delay="200" data-aos-duration="1500" data-aos-offset="50">
<li class="breadcrumb-item"><a href="index.php">Home</a></li>
<li class="breadcrumb-item active">About</li>
</ol>
</nav>
</div>
</div>
</section>
<!-- Benefit Area start -->
<section class="benefit-area mt-100 rel z-1">
<div class="container">
@@ -103,12 +119,8 @@ require_once($rootPath . '/components/banner.php');
</div>
<div class="gallery-slider-active">
<?php
$folder = $rootPath . '/assets/images/opendays/';
$folder = 'assets/images/opendays/';
$images = glob($folder . '*.{jpg,jpeg,png,gif}', GLOB_BRACE);
// Convert absolute paths to web-relative paths
$images = array_map(function($path) use ($rootPath) {
return str_replace($rootPath, '', $path);
}, $images);
// Shuffle and pick first 5
shuffle($images);
@@ -198,12 +210,8 @@ require_once($rootPath . '/components/banner.php');
</div>
</div>
<div class="gallery-slider-active"><?php
$folder = $rootPath . '/assets/images/memories/';
$folder = 'assets/images/memories/';
$images = glob($folder . '*.{jpg,jpeg,png,gif}', GLOB_BRACE);
// Convert absolute paths to web-relative paths
$images = array_map(function($path) use ($rootPath) {
return str_replace($rootPath, '', $path);
}, $images);
// Shuffle and pick first 5
shuffle($images);
@@ -238,7 +246,7 @@ require_once($rootPath . '/components/banner.php');
<div class="cta-item" style="background-image: url(assets/images/trips/1_01.jpg);">
<span class="category">Extended Trips</span>
<h2>Come and Explore Africa and beyond</h2>
<a href="<?= url('trips') ?>" class="theme-btn style-two bgc-secondary">
<a href="trips.php" class="theme-btn style-two bgc-secondary">
<span data-hover="Explore Tours">Explore Trips</span>
<i class="fal fa-arrow-right"></i>
</a>
@@ -248,7 +256,7 @@ require_once($rootPath . '/components/banner.php');
<div class="cta-item" style="background-image: url(assets/images/courses/driver_training.png);">
<span class="category">Driver Training</span>
<h2>Level up your 4x4 Driving Skills</h2>
<a href="<?= url('driver_training') ?>" class="theme-btn style-two">
<a href="driver_training.php" class="theme-btn style-two">
<span data-hover="Explore Tours">Explore Training</span>
<i class="fal fa-arrow-right"></i>
</a>
@@ -258,7 +266,7 @@ require_once($rootPath . '/components/banner.php');
<div class="cta-item" style="background-image: url(assets/images/base4/camping.jpg);">
<span class="category">Events</span>
<h2>See whats cooking at BASE4!</h2>
<a href="<?= url('events') ?>" class="theme-btn style-two bgc-secondary">
<a href="events.php" class="theme-btn style-two bgc-secondary">
<span data-hover="Explore Tours">Explore Events</span>
<i class="fal fa-arrow-right"></i>
</a>
@@ -281,4 +289,4 @@ require_once($rootPath . '/components/banner.php');
<!-- Blog Area end -->
<?php include_once($rootPath . '/components/insta_footer.php'); ?>
<?php include_once("insta_footer.php"); ?>

View File

@@ -1,6 +1,5 @@
<?php
$headerStyle = 'light';
include_once(dirname(dirname(dirname(__DIR__))) . '/header.php');
include_once('header02.php');
// Assuming you have the user ID stored in the session
$user_id = $_SESSION['user_id'];
@@ -60,7 +59,7 @@ $user = $result->fetch_assoc();
<div class="row align-items-center">
<div class="col-lg-12">
<div class="comment-form bgc-lighter z-1 rel mb-30 rmb-55">
<form id="accountForm" name="accountForm" method="post" action="update_user">
<form id="accountForm" name="accountForm" method="post" action="update_user.php">
<div class="section-title py-20">
<h2>Account Settings</h2>
<div id="responseMessage"></div> <!-- Message display area -->
@@ -102,7 +101,6 @@ $user = $result->fetch_assoc();
<input type="email" id="email" name="email" class="form-control" value="<?php echo $user['email']; ?>" required>
</div>
</div>
<input type="hidden" name="csrf_token" value="<?php echo generateCSRFToken(); ?>">
<div class="col-md-12">
<div class="form-group mb-0">
<button type="submit" class="theme-btn style-two" style="width:100%;">Update Info</button>
@@ -114,8 +112,7 @@ $user = $result->fetch_assoc();
<!-- Change Password Form -->
<form id="changePasswordForm" name="changePasswordForm" action="change_password" method="post">
<input type="hidden" name="csrf_token" value="<?php echo generateCSRFToken(); ?>">
<form id="changePasswordForm" name="changePasswordForm" action="change_password.php" method="post">
<div class="col-md-12 mt-20">
<h4>Change Password</h4>
<div id="responseMessage2"></div> <!-- Message display area -->
@@ -163,27 +160,29 @@ $user = $result->fetch_assoc();
formData.append('profile_picture', $('#profile_picture')[0].files[0]);
$.ajax({
url: 'upload_profile_picture',
url: 'upload_profile_picture.php',
type: 'POST',
data: formData,
contentType: false,
processData: false,
dataType: 'json',
success: function(response) {
// Parse response if needed
if (typeof response === "string") {
response = JSON.parse(response);
}
if (response.status === 'success') {
$('#responseMessage').html('<div class="alert alert-success">' + response.message + '</div>');
// Reload the current page after a short delay
setTimeout(function() {
// Update the profile picture source with cache-busting query string
// Reload the current page
window.location.reload();
}, 1500);
$('#responseMessage').html('<div class="alert alert-success">' + response.message + '</div>');
} else {
$('#responseMessage').html('<div class="alert alert-danger">' + response.message + '</div>');
}
},
error: function(xhr, status, error) {
console.log('AJAX Error:', status, error);
console.log('Response Text:', xhr.responseText);
$('#responseMessage').html('<div class="alert alert-danger">Error uploading profile picture: ' + error + '</div>');
error: function() {
$('#responseMessage').html('<div class="alert alert-danger">Error uploading profile picture.</div>');
}
});
});
@@ -194,7 +193,7 @@ $user = $result->fetch_assoc();
event.preventDefault(); // Prevent default form submission
$.ajax({
url: 'update_user',
url: 'update_user.php',
type: 'POST',
data: $(this).serialize(),
success: function(response) {
@@ -221,7 +220,7 @@ $user = $result->fetch_assoc();
event.preventDefault(); // Prevent default form submission
$.ajax({
url: 'change_password',
url: 'change_password.php',
type: 'POST',
data: $(this).serialize(),
success: function(response) {
@@ -244,4 +243,4 @@ $user = $result->fetch_assoc();
});
</script>
<?php include_once(dirname(dirname(dirname(__DIR__))) . '/components/insta_footer.php'); ?>
<?php include_once("insta_footer.php"); ?>

55
add_campsite.php Normal file
View File

@@ -0,0 +1,55 @@
<?php include_once('connection.php');
include_once('functions.php');
require_once("env.php");
session_start();
$user_id = $_SESSION['user_id']; // assuming you're storing it like this
// campsites.php
$conn = openDatabaseConnection();
// Get text inputs
$name = $_POST['name'];
$desc = $_POST['description'];
$lat = $_POST['latitude'];
$lng = $_POST['longitude'];
$website = $_POST['website'];
$telephone = $_POST['telephone'];
// Handle file upload
$thumbnailPath = null;
if (isset($_FILES['thumbnail']) && $_FILES['thumbnail']['error'] == 0) {
$uploadDir = "assets/uploads/campsites/";
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0777, true);
}
$filename = time() . "_" . basename($_FILES["thumbnail"]["name"]);
$targetFile = $uploadDir . $filename;
if (move_uploaded_file($_FILES["thumbnail"]["tmp_name"], $targetFile)) {
$thumbnailPath = $targetFile;
}
}
$id = isset($_POST['id']) ? intval($_POST['id']) : 0;
if ($id > 0) {
// UPDATE
if ($thumbnailPath) {
$stmt = $conn->prepare("UPDATE campsites SET name=?, description=?, latitude=?, longitude=?, website=?, telephone=?, thumbnail=? WHERE id=?");
$stmt->bind_param("ssddsssi", $name, $desc, $lat, $lng, $website, $telephone, $thumbnailPath, $id);
} else {
$stmt = $conn->prepare("UPDATE campsites SET name=?, description=?, latitude=?, longitude=?, website=?, telephone=? WHERE id=?");
$stmt->bind_param("ssddssi", $name, $desc, $lat, $lng, $website, $telephone, $id);
}
} else {
// INSERT
$stmt = $conn->prepare("INSERT INTO campsites (name, description, latitude, longitude, website, telephone, thumbnail, user_id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->bind_param("ssddsssi", $name, $desc, $lat, $lng, $website, $telephone, $thumbnailPath, $user_id);
}
$stmt->execute();
header("Location: campsites.php");
?>

View File

@@ -1,16 +1,7 @@
<?php
$rootPath = dirname(dirname(dirname(__DIR__)));
require_once($rootPath . "/src/config/env.php");
require_once($rootPath . "/src/config/connection.php");
require_once($rootPath . "/src/config/functions.php");
require_once($rootPath . "/header.php");
<?php include_once('header02.php');
checkAdmin();
checkUserSession();
$pageTitle = 'Manage Blog Posts';
$breadcrumbs = [['Home' => 'index']];
require_once($rootPath . '/components/banner.php');
$result = $conn->prepare("
SELECT
b.blog_id,
@@ -24,7 +15,7 @@ $result = $conn->prepare("
u.profile_pic
FROM blogs b
JOIN users u ON b.author = u.user_id
WHERE b.status = 'published'
WHERE b.status != 'deleted'
ORDER BY b.date DESC
");
@@ -68,7 +59,29 @@ $posts = $result->get_result();
$bannerFolder = 'assets/images/banners/';
$bannerImages = glob($bannerFolder . '*.{jpg,jpeg,png,webp}', GLOB_BRACE);
$randomBanner = 'assets/images/base4/camping.jpg'; // default fallback
if (!empty($bannerImages)) {
$randomBanner = $bannerImages[array_rand($bannerImages)];
}
?>
<section class="page-banner-area pt-50 pb-35 rel z-1 bgs-cover" style="background-image: url('<?php echo $randomBanner; ?>');">
<!-- Overlay PNG -->
<div class="banner-overlay"></div>
<div class="container">
<div class="banner-inner text-white">
<h2 class="page-title mb-10" data-aos="fade-left" data-aos-duration="1500" data-aos-offset="50">Admin Blogs</h2>
<nav aria-label="breadcrumb">
<ol class="breadcrumb justify-content-center mb-20" data-aos="fade-right" data-aos-delay="200" data-aos-duration="1500" data-aos-offset="50">
<li class="breadcrumb-item"><a href="index.php">Home</a></li>
<li class="breadcrumb-item active">Admin Blogs</li>
</ol>
</nav>
</div>
</div>
</section>
<!-- Page Banner End -->
<!-- Blog List Area start -->
<section class="blog-list-page py-100 rel z-1">
@@ -76,7 +89,7 @@ $bannerImages = glob($bannerFolder . '*.{jpg,jpeg,png,webp}', GLOB_BRACE);
<div class="row">
<div class="col-lg-12">
<h2>Manage Blog Posts</h2>
<h2>My Posts</h2>
<?php if (isset($_SESSION['message'])): ?>
<div class="alert alert-warning message-box">
<?php echo $_SESSION['message']; ?>
@@ -87,13 +100,11 @@ $bannerImages = glob($bannerFolder . '*.{jpg,jpeg,png,webp}', GLOB_BRACE);
<a href="blog_create.php">+ New Post</a>
<?php while ($post = $posts->fetch_assoc()):
// Determine cover image - use provided image or fallback placeholder
$coverImage = $post["image"] ? $post["image"] : 'assets/images/placeholder.jpg';
// Output the HTML structure with dynamic data
echo '
<div class="destination-item style-three bgc-lighter booking" data-aos="fade-up" data-aos-duration="1500" data-aos-offset="50">
<div class="image" style="width:200px;height:200px;">
<img src="' . htmlspecialchars($coverImage) . '" alt="' . htmlspecialchars($post["title"]) . '">
<img src="' . $post["image"] . '" alt="' . $post["title"] . '">
</div>
<div class="content" style="width:100%;">
<div class="destination-header d-flex align-items-start gap-3">
@@ -106,11 +117,10 @@ $bannerImages = glob($bannerFolder . '*.{jpg,jpeg,png,webp}', GLOB_BRACE);
</div>
<p>' . $post["description"] . '</p>
<div class="destination-footer">
<div class="btn-group" style="display:flex; justify-content:flex-end; gap:10px;">
<a href="blog_edit.php?token=' . encryptData($post["blog_id"], $salt) . '" data-bs-toggle="tooltip" data-bs-placement="top" title="Edit"><span class="material-icons">edit</span></a>
<a href="blog_read.php?token=' . encryptData($post["blog_id"], $salt) . '" data-bs-toggle="tooltip" data-bs-placement="top" title="Preview"><span class="material-icons">visibility</span></a>
<button type="button" class="publish-btn" data-blog-id="' . $post["blog_id"] . '" data-status="' . $post["status"] . '" data-bs-toggle="tooltip" data-bs-placement="top" title="' . ($post["status"] == "published" ? "Unpublish" : "Publish") . '" style="background:none; border:none; cursor:pointer; color:inherit;"><span class="material-icons">' . ($post["status"] == "published" ? "cloud_off" : "cloud_upload") . '</span></button>
<a href="blog_delete.php?token=' . encryptData($post["blog_id"], $salt) . '" data-bs-toggle="tooltip" data-bs-placement="top" title="Delete"><span class="material-icons">delete</span></a>
<div class="btn-group" style="display:flex; justify-content:flex-end; gap:10px; margin-top:10px;">
<a href="blog_edit.php?token='.encryptData($post["blog_id"], $salt).'" class="btn btn-sm" data-bs-toggle="tooltip" data-bs-placement="top" title="Edit"><i class="bi bi-pencil"></i></a>
<a href="blog_read.php?token='.encryptData($post["blog_id"], $salt).'" class="btn btn-sm" data-bs-toggle="tooltip" data-bs-placement="top" title="Preview"><i class="bi bi-eye"></i></a>
<a href="blog_delete.php?token='.encryptData($post["blog_id"], $salt).'" class="btn btn-sm" data-bs-toggle="tooltip" data-bs-placement="top" title="Delete"><i class="bi bi-trash"></i></a>
</div>
</div>
</div>
@@ -128,38 +138,7 @@ $bannerImages = glob($bannerFolder . '*.{jpg,jpeg,png,webp}', GLOB_BRACE);
<script>
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
tooltipTriggerList.forEach(el => new bootstrap.Tooltip(el));
// Handle publish/unpublish button clicks
document.querySelectorAll('.publish-btn').forEach(btn => {
btn.addEventListener('click', function() {
const blogId = this.dataset.blogId;
const status = this.dataset.status;
const action = status === 'published' ? 'unpublish' : 'publish';
const endpoint = status === 'published' ? 'blog_unpublish' : 'publish_blog';
const formData = new FormData();
formData.append('id', blogId);
fetch(endpoint, {
method: 'POST',
body: formData
})
.then(response => {
if (response.ok) {
alert(action.charAt(0).toUpperCase() + action.slice(1) + ' successful!');
location.reload();
} else {
alert(action + ' failed.');
console.error('Error:', response.statusText);
}
})
.catch(err => {
console.error('Error:', err);
alert(action + ' failed due to network error.');
});
});
});
</script>
<?php include_once($rootPath . '/components/insta_footer.php'); ?>
<?php include_once("insta_footer.php"); ?>

View File

@@ -1,7 +1,4 @@
<?php
$headerStyle = 'light';
$rootPath = dirname(dirname(__DIR__));
include_once($rootPath . '/header.php');
<?php include_once('header02.php');
checkAdmin();
?>
@@ -224,4 +221,4 @@ if (!empty($bannerImages)) {
?>
</div>
</section>
<?php include_once($rootPath . '/components/insta_footer.php'); ?>
<?php include_once("insta_footer.php"); ?>

View File

@@ -1,11 +1,8 @@
<?php
$headerStyle = 'light';
$rootPath = dirname(dirname(__DIR__));
include_once($rootPath . '/header.php');
<?php include_once('header02.php');
checkAdmin();
// Fetch all trips
$courseSql = "SELECT date, course_id, course_type FROM courses";
$courseSql = "SELECT date, course_id, course_type FROM courses WHERE DATE(date) >= CURDATE()";
$courseResult = $conn->query($courseSql);
if (!$courseResult) {
@@ -244,4 +241,4 @@ if (!empty($bannerImages)) {
?>
</div>
</section>
<?php include_once($rootPath . '/components/insta_footer.php'); ?>
<?php include_once("insta_footer.php"); ?>

View File

@@ -1,7 +1,4 @@
<?php
$headerStyle = 'light';
$rootPath = dirname(dirname(__DIR__));
include_once($rootPath . '/header.php');
<?php include_once('header02.php');
checkAdmin();
?>
@@ -224,4 +221,4 @@ if (!empty($bannerImages)) {
<!-- Tour List Area end -->
<?php include_once($rootPath . '/components/insta_footer.php'); ?>
<?php include_once("insta_footer.php"); ?>

View File

@@ -1,7 +1,4 @@
<?php
$headerStyle = 'light';
$rootPath = dirname(dirname(__DIR__));
include_once($rootPath . '/header.php');
<?php include_once('header02.php');
checkAdmin();
if ($_SERVER["REQUEST_METHOD"] === "POST" && isset($_POST['accept_indemnity'])) {
@@ -14,10 +11,10 @@ if ($_SERVER["REQUEST_METHOD"] === "POST" && isset($_POST['accept_indemnity']))
}
}
// SQL query to fetch membership applications
$stmt = $conn->prepare("SELECT user_id, first_name, last_name, tel_cell, email, dob, accept_indemnity FROM membership_application");
$stmt->execute();
$result = $stmt->get_result();
// SQL query to fetch data
$sql = "SELECT user_id, first_name, last_name, tel_cell, email, dob, accept_indemnity FROM membership_application";
$result = $conn->query($sql);
?>
<style>
table {
@@ -235,4 +232,4 @@ if (!empty($bannerImages)) {
<!-- Tour List Area end -->
<?php include_once($rootPath . '/components/insta_footer.php'); ?>
<?php include_once("insta_footer.php"); ?>

View File

@@ -1,7 +1,4 @@
<?php
$headerStyle = 'light';
$rootPath = dirname(dirname(__DIR__));
include_once($rootPath . '/header.php');
<?php include_once('header02.php');
checkAdmin();
?>
@@ -208,4 +205,4 @@ if (!empty($bannerImages)) {
<!-- Tour List Area end -->
<?php include_once($rootPath . '/components/insta_footer.php'); ?>
<?php include_once("insta_footer.php"); ?>

View File

@@ -1,7 +1,4 @@
<?php
$headerStyle = 'light';
$rootPath = dirname(dirname(__DIR__));
include_once($rootPath . '/header.php');
<?php include_once('header02.php');
checkAdmin();
// Fetch all trips
@@ -174,7 +171,7 @@ if (!empty($bannerImages)) {
// Fetch bookings for the current trip
$bookingsSql = "SELECT b.user_id, b.num_vehicles, b.num_adults, b.num_children, b.num_pensioners, b.radio, b.status,
u.first_name, u.last_name, u.profile_pic,
u.first_name, u.last_name,
(b.total_amount - b.discount_amount) AS paid
FROM bookings b
INNER JOIN users u ON b.user_id = u.user_id
@@ -237,4 +234,4 @@ if (!empty($bannerImages)) {
?>
</div>
</section>
<?php include_once($rootPath . '/components/insta_footer.php'); ?>
<?php include_once("insta_footer.php"); ?>

View File

@@ -1,7 +1,4 @@
<?php
$headerStyle = 'light';
$rootPath = dirname(dirname(__DIR__));
include_once($rootPath . '/header.php');
<?php include_once('header02.php');
checkAdmin();
// SQL query to fetch data
$sql = "SELECT ip_address, user_id, page_url, referrer_url, visit_time, country FROM visitor_logs WHERE NOT (ip_address = '185.203.122.69' OR ip_address = '156.155.29.213') ORDER BY visit_time DESC";
@@ -201,4 +198,4 @@ if (!empty($bannerImages)) {
<!-- Tour List Area end -->
<?php include_once($rootPath . '/components/insta_footer.php'); ?>
<?php include_once("insta_footer.php"); ?>

View File

@@ -1,8 +1,5 @@
<?php
$headerStyle = 'light';
$rootPath = dirname(dirname(__DIR__));
include_once($rootPath . '/header.php');
checkAdmin();
<?php include_once('header02.php');
checkSuperAdmin();
// SQL query to fetch data
$sql = "SELECT user_id, first_name, last_name, email, member, date_joined, token, is_verified, profile_pic FROM users";
$result = $conn->query($sql);
@@ -256,7 +253,7 @@ if (!empty($bannerImages)) {
const name = this.dataset.name;
const token = this.dataset.token;
fetch('resend_verification', {
fetch('resend_verification.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
@@ -281,4 +278,4 @@ if (!empty($bannerImages)) {
</script>
<?php include_once($rootPath . '/components/insta_footer.php'); ?>
<?php include_once("insta_footer.php"); ?>

View File

@@ -1,6 +1,12 @@
@charset "UTF-8";
/*----------------------------------------------------------------------
4WDCSA.co.za CSS Stylesheet
Template Name: Ravelo - Travel & Tour Booking HTML Template
Template URI: https://webtend.net/demo/html/ravelo/
Author: WebTend
Author URI: https://webtend.net/
Version: 1.0
Note: This is Main Style CSS File. */
/*----------------------------------------------------------------------
CSS INDEX
----------------------
@@ -7118,8 +7124,7 @@ blockquote {
/* Comments */
.comments {
border-radius: 10px;
/* border: 1px solid var(--border-color); */
}
border: 1px solid var(--border-color); }
.comment-body {
padding: 50px; }

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 494 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 374 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 482 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 MiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 24 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 2.6 MiB

View File

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

View File

@@ -1,66 +0,0 @@
/**
* TRACK MAP WITH LEAFLET.JS
*
* Basic Leaflet map test
*/
console.log('Track map script loaded2');
// Check if Leaflet is available
if (typeof L === 'undefined') {
console.error('Leaflet library not loaded!');
} else {
console.log('Leaflet library is available, version:', L.version);
}
document.addEventListener('DOMContentLoaded', () => {
console.log('DOM loaded, initializing map...');
const mapElement = document.getElementById('map');
console.log('Map element:', mapElement);
if (!mapElement) {
console.error('Map element not found!');
return;
}
console.log('Map element dimensions:', mapElement.offsetWidth, 'x', mapElement.offsetHeight);
try {
// Image dimensions: 2876 x 2035 pixels
const imageWidth = 2876;
const imageHeight = 2035;
// Create map with simple CRS (pixel coordinates)
// Note: Leaflet uses [y, x] format, so bounds are [[0, 0], [height, width]]
const bounds = [[0, 0], [imageHeight, imageWidth]];
const map = L.map('map', {
crs: L.CRS.Simple,
minZoom: -2,
maxZoom: 2,
center: [imageHeight / 2, imageWidth / 2],
zoom: -1
});
console.log('Map object created with CRS.Simple:', map);
// Add aerial image overlay
const imageUrl = '/assets/images/track-aerial.jpg';
L.imageOverlay(imageUrl, bounds).addTo(map);
console.log('Aerial image overlay added');
// Add SVG overlay
const svgUrl = '/assets/images/track-route.svg';
L.imageOverlay(svgUrl, bounds, {
opacity: 0.8,
interactive: false
}).addTo(map);
console.log('SVG route overlay added');
// Fit map to image bounds
map.fitBounds(bounds);
console.log('Map initialized successfully');
} catch (error) {
console.error('Error initializing map:', error);
}
});

View File

@@ -46,7 +46,7 @@
<div class="header-inner rel d-flex align-items-center">
<div class="logo-outer">
<div class="logo"><a href="index"><img src="assets/images/logos/logo-two.png" alt="Logo" title="Logo"></a></div>
<div class="logo"><a href="index.php"><img src="assets/images/logos/logo-two.png" alt="Logo" title="Logo"></a></div>
</div>
<div class="nav-outer mx-lg-auto ps-xxl-5 clearfix">
@@ -71,7 +71,7 @@
<ul class="navigation clearfix">
<li class="dropdown current"><a href="#">Home</a>
<ul>
<li><a href="index">Travel Agency</a></li>
<li><a href="index.php">Travel Agency</a></li>
<li><a href="index2.html">City Tou</a></li>
<li><a href="index3.html">Tour Package</a></li>
</ul>
@@ -161,7 +161,7 @@
<!--Appointment Form-->
<div class="appointment-form">
<form method="post" action="contact">
<form method="post" action="contact.php">
<div class="form-group">
<input type="text" name="text" value="" placeholder="Name" required>
</div>
@@ -182,9 +182,9 @@
<!--Social Icons-->
<div class="social-style-one">
<a href="contact"><i class="fab fa-twitter"></i></a>
<a href="contact"><i class="fab fa-facebook-f"></i></a>
<a href="contact"><i class="fab fa-instagram"></i></a>
<a href="contact.php"><i class="fab fa-twitter"></i></a>
<a href="contact.php"><i class="fab fa-facebook-f"></i></a>
<a href="contact.php"><i class="fab fa-instagram"></i></a>
<a href="#"><i class="fab fa-pinterest-p"></i></a>
</div>
</div>
@@ -201,7 +201,7 @@
<h2 class="page-title mb-10" data-aos="fade-left" data-aos-duration="1500" data-aos-offset="50">Bali, Indonesia</h2>
<nav aria-label="breadcrumb">
<ol class="breadcrumb justify-content-center mb-20" data-aos="fade-right" data-aos-delay="200" data-aos-duration="1500" data-aos-offset="50">
<li class="breadcrumb-item"><a href="index">Home</a></li>
<li class="breadcrumb-item"><a href="index.php">Home</a></li>
<li class="breadcrumb-item active">Tour Details</li>
</ol>
</nav>
@@ -795,7 +795,7 @@
<i class="fal fa-arrow-right"></i>
</button>
<div class="text-center">
<a href="contact">Need some help?</a>
<a href="contact.php">Need some help?</a>
</div>
</form>
</div>
@@ -871,7 +871,7 @@
<div class="col col-small" data-aos="fade-up" data-aos-duration="1500" data-aos-offset="50">
<div class="footer-widget footer-text">
<div class="footer-logo mb-40">
<a href="index"><img src="assets/images/logos/logo.png" alt="Logo"></a>
<a href="index.php"><img src="assets/images/logos/logo.png" alt="Logo"></a>
</div>
<div class="footer-map">
<iframe src="https://www.google.com/maps/embed?pb=!1m10!1m8!1m3!1d96777.16150026117!2d-74.00840582560909!3d40.71171357405996!3m2!1i1024!2i768!4f13.1!5e0!3m2!1sen!2sbd!4v1706508986625!5m2!1sen!2sbd" style="border:0; width: 100%;" allowfullscreen="" loading="lazy" referrerpolicy="no-referrer-when-downgrade"></iframe>
@@ -899,7 +899,7 @@
<ul class="list-style-three">
<li><a href="about.html">About Company</a></li>
<li><a href="blog.html">Community Blog</a></li>
<li><a href="contact">Jobs and Careers</a></li>
<li><a href="contact.php">Jobs and Careers</a></li>
<li><a href="blog.html">latest News Blog</a></li>
</ul>
</div>
@@ -937,7 +937,7 @@
<div class="row">
<div class="col-lg-5">
<div class="copyright-text text-center text-lg-start">
<p>@Copy 2024 <a href="index">Ravelo</a>, All rights reserved</p>
<p>@Copy 2024 <a href="index.php">Ravelo</a>, All rights reserved</p>
</div>
</div>
<div class="col-lg-7 text-center text-lg-end">

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

101
autosave.php Normal file
View File

@@ -0,0 +1,101 @@
<?php
require_once("env.php");
require_once("session.php");
require_once("connection.php");
require_once("functions.php");
if (!isset($_SESSION['user_id'])) {
http_response_code(401);
echo "Not authorized";
exit;
}
$article_id = (int)($_POST['id'] ?? 0);
$title = $_POST['title'] ?? '';
$content = $_POST['content'] ?? '';
$description = $_POST['subtitle'] ?? '';
$category = $_POST['category'] ?? '';
$user_id = $_SESSION['user_id'];
// Default to current user
$author_id = $_SESSION['user_id'];
// Allow override if admin
$role = getUserRole();
if (($role === 'admin' || $role === 'superadmin') && isset($_POST['author'])) {
$author_id = (int)$_POST['author'];
}
echo $author_id;
$cover_image_path = null;
// Only attempt upload if a file was submitted
if (!empty($_FILES['cover_image']['name'])) {
$uploadDir = __DIR__ . "/uploads/blogs/".$article_id."/images/";
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0777, true);
}
// Sanitize and rename file
$originalName = basename($_FILES['cover_image']['name']);
$originalName = preg_replace("/[^a-zA-Z0-9\._-]/", "_", $originalName); // remove unsafe characters
$targetPath = $uploadDir . $originalName;
$publicPath = "/uploads/blogs/".$article_id."/images/" . $originalName;
// Error detection before upload
$fileError = $_FILES['cover_image']['error'];
if ($fileError !== UPLOAD_ERR_OK) {
$errorMessages = [
UPLOAD_ERR_INI_SIZE => 'The uploaded file exceeds the upload_max_filesize directive in php.ini.',
UPLOAD_ERR_FORM_SIZE => 'The uploaded file exceeds the MAX_FILE_SIZE directive in the HTML form.',
UPLOAD_ERR_PARTIAL => 'The uploaded file was only partially uploaded.',
UPLOAD_ERR_NO_FILE => 'No file was uploaded.',
UPLOAD_ERR_NO_TMP_DIR => 'Missing a temporary folder.',
UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk.',
UPLOAD_ERR_EXTENSION => 'A PHP extension stopped the upload.',
];
$errorMessage = $errorMessages[$fileError] ?? 'Unknown upload error.';
http_response_code(500);
echo "Upload error: $errorMessage";
exit;
}
// Skip upload if identical file already exists
if (file_exists($targetPath)) {
$cover_image_path = $publicPath;
} else {
if (move_uploaded_file($_FILES['cover_image']['tmp_name'], $targetPath)) {
$cover_image_path = $publicPath;
} else {
http_response_code(500);
echo "Failed to move uploaded file.";
exit;
}
}
}
// Prepare SQL with/without image update
if ($cover_image_path) {
$stmt = $conn->prepare("
UPDATE blogs
SET title = ?, content = ?, description = ?, category = ?, image = ?, author = ?
WHERE blog_id = ?
");
$stmt->bind_param("ssssssi", $title, $content, $description, $category, $cover_image_path, $author_id, $article_id);
} else {
$stmt = $conn->prepare("
UPDATE blogs
SET title = ?, content = ?, description = ?, category = ?, author = ?
WHERE blog_id = ?
");
$stmt->bind_param("ssssii", $title, $content, $description, $category, $author_id, $article_id);
}
if ($stmt->execute()) {
echo "Saved";
} else {
http_response_code(500);
echo "Database update failed: " . $stmt->error;
}

View File

@@ -1,6 +1,4 @@
<?php
$headerStyle = 'light';
include_once(dirname(dirname(dirname(__DIR__))) . '/header.php');
<?php include_once('header02.php');
checkUserSession();
$user_id = $_SESSION['user_id'];
unset($_SESSION['cart']);
@@ -157,7 +155,6 @@ unset($_SESSION['cart']);
</div>
<div class="modal-body">
<form id="barTabForm">
<input type="hidden" name="csrf_token" value="<?php echo generateCSRFToken(); ?>">
<div class="form-group">
<label for="userSelect">Select User</label>
<input type="text" id="userSelect" class="form-control" placeholder="Search User" required>
@@ -176,7 +173,7 @@ unset($_SESSION['cart']);
$('#userSelect').autocomplete({
source: function(request, response) {
$.ajax({
url: 'fetch_users',
url: 'fetch_users.php',
method: 'GET',
dataType: 'json',
success: function(data) {
@@ -215,7 +212,7 @@ unset($_SESSION['cart']);
$('#barTabForm').submit(function(e) {
e.preventDefault(); // Prevent default form submission
$.ajax({
url: 'create_bar_tab',
url: 'create_bar_tab.php',
method: 'POST',
data: $(this).serialize(), // Send form data, including user_id
dataType: 'json',
@@ -239,7 +236,7 @@ unset($_SESSION['cart']);
// Fetch and render bar tabs
function loadBarTabs() {
$.ajax({
url: 'fetch_bar_tabs',
url: 'fetch_bar_tabs.php',
method: 'GET',
dataType: 'json',
success: function(data) {
@@ -289,7 +286,7 @@ unset($_SESSION['cart']);
// Fetch available drinks for the selected tab
$.ajax({
url: 'fetch_drinks',
url: 'fetch_drinks.php',
method: 'GET',
data: {
tab_id: tabId
@@ -346,7 +343,7 @@ unset($_SESSION['cart']);
// Add the drink to the cart (session)
$.ajax({
url: 'add_to_cart',
url: 'add_to_cart.php',
method: 'POST',
data: {
tab_id: tabId,
@@ -414,7 +411,7 @@ unset($_SESSION['cart']);
// Submit the order
$.ajax({
url: 'submit_order',
url: 'submit_order.php',
method: 'POST',
data: {
tab_id: tabId
@@ -455,7 +452,7 @@ unset($_SESSION['cart']);
function fetchTabTotal(tabId) {
console.log("fetching tab total...")
$.ajax({
url: 'get_tab_total',
url: 'get_tab_total.php',
method: 'POST',
data: {
tab_id: tabId
@@ -482,4 +479,4 @@ unset($_SESSION['cart']);
<?php include_once(dirname(dirname(dirname(__DIR__))) . '/components/insta_footer.php'); ?>
<?php include_once("insta_footer.php"); ?>

View File

@@ -1,7 +1,4 @@
<?php
$headerStyle = 'light';
$rootPath = dirname(dirname(dirname(__DIR__)));
include_once($rootPath . '/header.php');
<?php include_once('header02.php');
// Assuming you have the user ID stored in the session
if (isset($_SESSION['user_id'])) {
$user_id = $_SESSION['user_id'];
@@ -39,11 +36,33 @@ if (isset($_SESSION['user_id'])) {
}
</style>
<!-- Page Banner Start -->
<?php
$pageTitle = 'Indemnity';
$breadcrumbs = [['Home' => 'index.php']];
require_once($rootPath . '/components/banner.php');
$bannerFolder = 'assets/images/banners/';
$bannerImages = glob($bannerFolder . '*.{jpg,jpeg,png,webp}', GLOB_BRACE);
$randomBanner = 'assets/images/base4/camping.jpg'; // default fallback
if (!empty($bannerImages)) {
$randomBanner = $bannerImages[array_rand($bannerImages)];
}
?>
<section class="page-banner-area pt-50 pb-35 rel z-1 bgs-cover" style="background-image: url('<?php echo $randomBanner; ?>');">
<div class="banner-overlay"></div>
<div class="container">
<div class="banner-inner text-white">
<h2 class="page-title mb-10" data-aos="fade-left" data-aos-duration="1500" data-aos-offset="50">Indemnity</h2>
<nav aria-label="breadcrumb">
<ol class="breadcrumb justify-content-center mb-20" data-aos="fade-right" data-aos-delay="200" data-aos-duration="1500" data-aos-offset="50">
<li class="breadcrumb-item"><a href="index.php">Home</a></li>
<li class="breadcrumb-item ">Membership</li>
<li class="breadcrumb-item ">Application</li>
<li class="breadcrumb-item active">Indemnity</li>
<li class="breadcrumb-item ">Payment</li>
</ol>
</nav>
</div>
</div>
</section>
<!-- Page Banner End -->
@@ -94,7 +113,7 @@ if (isset($_SESSION['user_id'])) {
var dataUrl = signaturePad.toDataURL(); // Get signature as base64 image
$.ajax({
url: 'process_signature',
url: 'process_signature.php',
type: 'POST',
data: {
signature: dataUrl // Send the base64 signature image
@@ -130,4 +149,4 @@ if (isset($_SESSION['user_id'])) {
</script>
<?php include_once(dirname(dirname(dirname(__DIR__))) . '/components/insta_footer.php') ?>
<?php include_once('insta_footer.php') ?>

View File

@@ -1,7 +1,4 @@
<?php
$headerStyle = 'light';
$rootPath = dirname(dirname(dirname(__DIR__)));
include_once($rootPath . '/header.php');
<?php include_once('header02.php');
$page_id = 'best_0f_ec';
?>
@@ -68,15 +65,31 @@ $page_id = 'best_0f_ec';
float: right;
}
.clearfix {
clear: both;
}
</style>
<?php
$pageTitle = 'Best of the Eastern Cape 2024';
$breadcrumbs = [['Home' => 'index.php']];
require_once($rootPath . '/components/banner.php');
?>
<section class="page-banner-area pt-50 pb-35 rel z-1 bgs-cover" style="background-image: url('assets/images/blog/1/cover.jpg');">
<div class="banner-overlay"></div>
<div class="container">
<div class="banner-inner text-white">
<h2 class="page-title mb-10" data-aos="fade-left" data-aos-duration="1500" data-aos-offset="50">Best of the Eastern Cape 2024</h2>
<nav aria-label="breadcrumb">
<ol class="breadcrumb justify-content-center mb-20" data-aos="fade-right" data-aos-delay="200" data-aos-duration="1500" data-aos-offset="50">
<li class="breadcrumb-item"><a href="index.php">Home</a></li>
<li class="breadcrumb-item active">Best of the Eastern Cape 2024</li>
</ol>
</nav>
</div>
</div>
</section>
<!-- Page Banner End -->
<!-- Blog Detaisl Area start -->
<section class="blog-detaisl-page py-100 rel z-1">
@@ -337,10 +350,10 @@ $page_id = 'best_0f_ec';
<h4>Richard M. Fudge</h4>
<p>The world is a book, and those who do not travel read only one page. Every journey we undertake is a chapter filled with lessons, experiences, and stories.</p>
<div class="social-icons">
<a href="contact"><i class="fab fa-facebook-f"></i></a>
<a href="contact"><i class="fab fa-twitter"></i></a>
<a href="contact"><i class="fab fa-linkedin-in"></i></a>
<a href="contact"><i class="fab fa-instagram"></i></a>
<a href="contact.php"><i class="fab fa-facebook-f"></i></a>
<a href="contact.php"><i class="fab fa-twitter"></i></a>
<a href="contact.php"><i class="fab fa-linkedin-in"></i></a>
<a href="contact.php"><i class="fab fa-instagram"></i></a>
</div>
</div>
</div>
@@ -432,4 +445,4 @@ $page_id = 'best_0f_ec';
<!-- Blog Detaisl Area end -->
<?php include_once(dirname(dirname(dirname(__DIR__))) . '/components/insta_footer.php'); ?>
<?php include_once("insta_footer.php"); ?>

View File

@@ -1,8 +1,4 @@
<?php
$headerStyle = 'light';
$rootPath = dirname(dirname(dirname(__DIR__)));
include_once($rootPath . '/header.php');
?>
<?php include_once('header02.php') ?>
<style>
.image {
@@ -30,11 +26,35 @@ include_once($rootPath . '/header.php');
}
</style><?php
$pageTitle = 'Blogs';
$breadcrumbs = [['Home' => 'index.php']];
require_once($rootPath . '/components/banner.php');
</style>
<?php
$bannerFolder = 'assets/images/banners/';
$bannerImages = glob($bannerFolder . '*.{jpg,jpeg,png,webp}', GLOB_BRACE);
$randomBanner = 'assets/images/base4/camping.jpg'; // default fallback
if (!empty($bannerImages)) {
$randomBanner = $bannerImages[array_rand($bannerImages)];
}
?>
<section class="page-banner-area pt-50 pb-35 rel z-1 bgs-cover" style="background-image: url('<?php echo $randomBanner; ?>');">
<!-- Overlay PNG -->
<div class="banner-overlay"></div>
<div class="container">
<div class="banner-inner text-white">
<h2 class="page-title mb-10" data-aos="fade-left" data-aos-duration="1500" data-aos-offset="50">Blogs</h2>
<nav aria-label="breadcrumb">
<ol class="breadcrumb justify-content-center mb-20" data-aos="fade-right" data-aos-delay="200" data-aos-duration="1500" data-aos-offset="50">
<li class="breadcrumb-item"><a href="index.php">Home</a></li>
<li class="breadcrumb-item active">Blogs</li>
</ol>
</nav>
</div>
</div>
</section>
<!-- Page Banner End -->
<!-- Blog List Area start -->
@@ -43,12 +63,9 @@ include_once($rootPath . '/header.php');
<div class="row">
<div class="col-lg-8">
<?php
// Query to retrieve data from blogs table
$status = 'published';
$stmt = $conn->prepare("SELECT blog_id, title, date, category, image, description, author, members_only, link FROM blogs WHERE status = ? ORDER BY date DESC");
$stmt->bind_param("s", $status);
$stmt->execute();
$result = $stmt->get_result();
// Query to retrieve data from the trips table
$sql = "SELECT blog_id, title, date, category, image, description, author, members_only, link FROM blogs WHERE status = 'published' ORDER BY date DESC";
$result = $conn->query($sql);
if ($result->num_rows > 0) {
// Loop through each row
@@ -69,7 +86,7 @@ include_once($rootPath . '/header.php');
$icon = "fa-lock";
} else {
if (getUserMemberStatus($_SESSION['user_id'])) {
$blog_link = $row['link'];
$blog_link = "blog_read.php?token=".encryptData($blog_id, $salt);
$button_hover = "Read More";
$icon = "fa-arrow-right";
} else {
@@ -79,7 +96,7 @@ include_once($rootPath . '/header.php');
}
}
} else {
$blog_link = $row['link'];
$blog_link = "blog_read.php?token=".encryptData($blog_id, $salt);
$button_hover = "Read More";
$icon = "fa-arrow-right";
}
@@ -88,10 +105,10 @@ include_once($rootPath . '/header.php');
echo '
<div class="blog-item style-three" data-aos="fade-up" data-aos-duration="1500" data-aos-offset="50">
<div class="image">
<img style="border-radius:20px;" src="assets/images/blog/' . $blog_id . '/' . $image . '" alt="Blog List">
<img style="border-radius:20px;" src="' . $image . '" alt="Blog List">
</div>
<div class="content">
<a href="' . url('blog') . '" class="category">' . $category . '</a>
<a href="blog.php" class="category">' . $category . '</a>
<h5><a href="' . $blog_link . '">' . $title . '</a></h5>
<ul class="blog-meta">
<li><i class="far fa-calendar-alt"></i> <a href="#">' . $date . '</a></li>
@@ -205,7 +222,7 @@ include_once($rootPath . '/header.php');
<div class="content text-white">
<span class="h6">Explore The World</span>
<h3>Become a Member</h3>
<a href="<?= url('membership') ?>" class="theme-btn style-two bgc-secondary">
<a href="membership.php" class="theme-btn style-two bgc-secondary">
<span data-hover="Explore Now">Join Now</span>
<i class="fal fa-arrow-right"></i>
</a>
@@ -225,4 +242,4 @@ include_once($rootPath . '/header.php');
<!-- Blog List Area end -->
<?php include_once(dirname(dirname(dirname(__DIR__))) . '/components/insta_footer.php'); ?>
<?php include_once("insta_footer.php"); ?>

View File

@@ -1,9 +1,9 @@
<?php
$rootPath = dirname(dirname(dirname(__DIR__)));
require_once($rootPath . "/src/config/env.php");
require_once($rootPath . "/src/config/connection.php");
require_once($rootPath . "/src/config/functions.php");
session_start();
// session_start();
require_once("env.php");
require_once("session.php");
require_once("connection.php");
require_once("functions.php");
if (!isset($_SESSION['user_id'])) {
die("Not logged in");

View File

@@ -1,9 +1,8 @@
<?php
$rootPath = dirname(dirname(dirname(__DIR__)));
require_once($rootPath . "/src/config/env.php");
require_once($rootPath . "/src/config/connection.php");
require_once($rootPath . "/src/config/functions.php");
session_start();
require_once("env.php");
require_once("session.php");
require_once("connection.php");
require_once("functions.php");
if (!isset($_SESSION['user_id'])) {
$_SESSION['message'] = "Not authorized.";

View File

@@ -1,8 +1,4 @@
<?php
$headerStyle = 'light';
$rootPath = dirname(dirname(dirname(__DIR__)));
include_once($rootPath . '/header.php');
?>
<?php include_once('header02.php') ?>
<style>
.image {
@@ -33,11 +29,20 @@ include_once($rootPath . '/header.php');
<?php
$pageTitle = 'Blog Details';
$breadcrumbs = [['Home' => 'index.php'], ['Blogs' => 'blog.php']];
require_once($rootPath . '/components/banner.php');
?>
<section class="page-banner-area pt-50 pb-35 rel z-1 bgs-cover" style="background-image: url('assets/images/blog/1/cover.jpg');">
<div class="container">
<div class="banner-inner text-white">
<h2 class="page-title mb-10" data-aos="fade-left" data-aos-duration="1500" data-aos-offset="50">Best of the Eastern Cape 2024</h2>
<nav aria-label="breadcrumb">
<ol class="breadcrumb justify-content-center mb-20" data-aos="fade-right" data-aos-delay="200" data-aos-duration="1500" data-aos-offset="50">
<li class="breadcrumb-item"><a href="index.php">Home</a></li>
<li class="breadcrumb-item active">Best of the Eastern Cape 2024</li>
</ol>
</nav>
</div>
</div>
</section>
<!-- Page Banner End -->
<!-- Blog Detaisl Area start -->
@@ -193,10 +198,10 @@ include_once($rootPath . '/header.php');
<h4>Richard M. Fudge</h4>
<p>The world is a book, and those who do not travel read only one page. Every journey we undertake is a chapter filled with lessons, experiences, and stories.</p>
<div class="social-icons">
<a href="contact"><i class="fab fa-facebook-f"></i></a>
<a href="contact"><i class="fab fa-twitter"></i></a>
<a href="contact"><i class="fab fa-linkedin-in"></i></a>
<a href="contact"><i class="fab fa-instagram"></i></a>
<a href="contact.php"><i class="fab fa-facebook-f"></i></a>
<a href="contact.php"><i class="fab fa-twitter"></i></a>
<a href="contact.php"><i class="fab fa-linkedin-in"></i></a>
<a href="contact.php"><i class="fab fa-instagram"></i></a>
</div>
</div>
</div>
@@ -444,7 +449,7 @@ include_once($rootPath . '/header.php');
<ul class="list-style-three">
<li><a href="about.html">About Company</a></li>
<li><a href="blog.html">Community Blog</a></li>
<li><a href="contact">Jobs and Careers</a></li>
<li><a href="contact.php">Jobs and Careers</a></li>
<li><a href="blog.html">latest News Blog</a></li>
</ul>
</div>

View File

@@ -1,19 +1,11 @@
<?php
$rootPath = dirname(dirname(dirname(__DIR__)));
require_once($rootPath . "/src/config/env.php");
require_once($rootPath . "/src/config/connection.php");
require_once($rootPath . "/src/config/functions.php");
require_once($rootPath . "/header.php");
include_once('header02.php');
// Ensure the user is logged in
if (!isset($_SESSION['user_id'])) {
die("User not logged in.");
}
$pageTitle = 'Edit Blog Post';
$breadcrumbs = [['Home' => 'index'], ['My Blog Posts' => 'user_blogs']];
require_once($rootPath . '/components/banner.php');
$token = $_GET['token'];
// Sanitize the trip_id to prevent SQL injection
$blog_id = intval(decryptData($token, $salt)); // Ensures $trip_id is treated as an integer
@@ -39,7 +31,7 @@ $stmt->close();
selector: '#content',
plugins: 'image code link',
toolbar: 'undo redo | blocks | bold italic | alignleft aligncenter alignright | code | image | link',
images_upload_url: 'upload_blog_image?blog_id=<?= $blog_id ?>',
images_upload_url: 'upload.php?blog_id=<?= $blog_id ?>',
image_class_list: [
{ title: 'None', value: '' },
{ title: 'Left Align', value: 'img-left' },
@@ -184,7 +176,7 @@ $stmt->close();
formData.append("cover_image", coverImageInput.files[0]);
}
return fetch("autosave", {
return fetch("autosave.php", {
method: "POST",
body: formData
}).then(response => {
@@ -192,15 +184,12 @@ $stmt->close();
document.getElementById("autosave-status").innerText = "Draft autosaved at " + new Date().toLocaleTimeString();
return true;
} else {
return response.text().then(errorText => {
document.getElementById("autosave-status").innerText = "Autosave failed: " + errorText;
console.error("Autosave failed", response.status, errorText);
document.getElementById("autosave-status").innerText = "Autosave failed";
console.error("Autosave failed", response.statusText);
return false;
});
}
}).catch(err => {
console.error("Autosave error:", err);
document.getElementById("autosave-status").innerText = "Autosave error: " + err.message;
return false;
});
}
@@ -209,15 +198,10 @@ $stmt->close();
setInterval(autosavePost, 15000);
// Manual autosave button
const manualSaveBtn = document.getElementById("manualSaveBtn");
if (manualSaveBtn) {
manualSaveBtn.addEventListener("click", autosavePost);
}
document.getElementById("manualSaveBtn").addEventListener("click", autosavePost);
// Manual publish button
const manualPostBtn = document.getElementById("manualPostBtn");
if (manualPostBtn) {
manualPostBtn.addEventListener("click", function () {
document.getElementById("manualPostBtn").addEventListener("click", function () {
autosavePost().then(success => {
if (!success) return;
@@ -225,7 +209,7 @@ $stmt->close();
const publishData = new FormData();
publishData.append("id", articleId);
fetch("publish_blog", {
fetch("publish_blog.php", {
method: "POST",
body: publishData
}).then(response => {
@@ -243,12 +227,9 @@ $stmt->close();
});
});
});
}
// Manual unpublish button
const manualDraftBtn = document.getElementById("manualDraftBtn");
if (manualDraftBtn) {
manualDraftBtn.addEventListener("click", function () {
document.getElementById("manualDraftBtn").addEventListener("click", function () {
autosavePost().then(success => {
if (!success) return;
@@ -256,7 +237,7 @@ $stmt->close();
const publishData = new FormData();
publishData.append("id", articleId);
fetch("blog_unpublish", {
fetch("blog_unpublish.php", {
method: "POST",
body: publishData
}).then(response => {
@@ -274,9 +255,11 @@ $stmt->close();
});
});
});
}
</script>
</script>
<?php include_once($rootPath . '/components/insta_footer.php'); ?>
<?php include_once("insta_footer.php"); ?>

View File

@@ -1,23 +1,17 @@
<?php
$rootPath = dirname(dirname(dirname(__DIR__)));
require_once($rootPath . "/src/config/env.php");
require_once($rootPath . "/src/config/connection.php");
require_once($rootPath . "/src/config/functions.php");
require_once($rootPath . "/header.php");
<?php include_once('header02.php');
$token = $_GET['token'];
// Sanitize the trip_id to prevent SQL injection
$blog_id = intval(decryptData($token, $salt)); // Ensures $trip_id is treated as an integer
$pageTitle = 'Blog Post';
$breadcrumbs = [['Home' => 'index'], ['Blog' => 'blog']];
require_once($rootPath . '/components/banner.php');
$page_id = 'blog_'.$blog_id;
echo getCommentCount($page_id);
$stmt = $conn->prepare("
SELECT a.blog_id, a.title, a.category, a.description, a.content, a.date, a.author,
u.first_name, u.last_name, u.user_id
SELECT a.title, a.category, a.description, a.content, a.date,
u.first_name, u.last_name
FROM blogs a
JOIN users u ON a.author = u.user_id
WHERE a.blog_id = ?
@@ -32,8 +26,6 @@ if ($result->num_rows === 0) {
$row = $result->fetch_assoc();
$author = htmlspecialchars($row['first_name'] . ' ' . $row['last_name']);
$author_id = $row['author'];
$is_author = (isset($_SESSION['user_id']) && $_SESSION['user_id'] == $author_id);
?>
@@ -107,14 +99,30 @@ $is_author = (isset($_SESSION['user_id']) && $_SESSION['user_id'] == $author_id)
</style>
<?php
// Dynamically set page title to blog title
if (isset($row) && !empty($row['title'])) {
$pageTitle = htmlspecialchars($row['title']);
} else {
$pageTitle = 'Blog Post';
}
$bannerFolder = 'assets/images/banners/';
$bannerImages = glob($bannerFolder . '*.{jpg,jpeg,png,webp}', GLOB_BRACE);
$randomBanner = 'assets/images/base4/camping.jpg'; // default fallback
if (!empty($bannerImages)) {
$randomBanner = $bannerImages[array_rand($bannerImages)];
}
?>
<section class="page-banner-area pt-50 pb-35 rel z-1 bgs-cover" style="background-image: url('<?php echo $randomBanner; ?>');">
<div class="banner-overlay"></div>
<div class="container">
<div class="banner-inner text-white">
<h2 class="page-title mb-10" data-aos="fade-left" data-aos-duration="1500" data-aos-offset="50"><?= htmlspecialchars($row['title']) ?></h2>
<nav aria-label="breadcrumb">
<ol class="breadcrumb justify-content-center mb-20" data-aos="fade-right" data-aos-delay="200" data-aos-duration="1500" data-aos-offset="50">
<li class="breadcrumb-item"><a href="index.php">Home</a></li>
<li class="breadcrumb-item active"><?= htmlspecialchars($row['title']) ?></li>
</ol>
</nav>
</div>
</div>
</section>
<!-- Page Banner End -->
<!-- Blog Detaisl Area start -->
<section class="blog-detaisl-page py-100 rel z-1">
@@ -127,45 +135,12 @@ if (isset($row) && !empty($row['title'])) {
<li><img src="assets/images/pp/default.png" alt="Admin"> <a href="#"><?= $author?></a></li>
<li><i class="far fa-calendar-alt"></i> <a href="#"><?= htmlspecialchars($row['date']) ?></a></li>
<li><i class="far fa-comments"></i> <a href="#">Comments (<?= getCommentCount($page_id);?>)</a></li>
<?php if ($is_author): ?>
<li><a href="blog_edit.php?token=<?php echo encryptData($blog_id, $salt); ?>">Edit Post</a></li>
<?php endif; ?>
</ul>
<?= $row['content'] ?>
</div>
</div>
<div class="col-lg-4 col-md-8 col-sm-10 rmt-75">
<div class="blog-sidebar">
<div class="widget widget-gallery" data-aos="fade-up" data-aos-duration="1500" data-aos-offset="50">
<h5 class="widget-title">Gallery</h5>
<div class="gallery">
<?php
$folder = $rootPath . '/uploads/blogs/' . $blog_id . '/';
$files = glob($folder . '*.{jpg,jpeg,png,webp}', GLOB_BRACE);
if ($files && count($files) > 0) {
shuffle($files); // Randomize the order
foreach ($files as $file) {
$relativePath = '/uploads/blogs/' . $blog_id . '/' . basename($file);
echo '<a href="' . $relativePath . '" style="width: 110px; height: 110px; overflow: hidden; display: inline-block; margin: 2px;">';
echo '<img src="' . $relativePath . '" alt="Gallery" style="width: 100%; height: 100%; object-fit: cover; display: block;">';
echo '</a>';
}
} else {
echo '<p style="font-size: 0.9em; color: #999;">No images available</p>';
}
?>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-8">
<hr class="mb-45">
<div class="tag-share">
<div class="tag-share mb-50">
<div class="item" data-aos="fade-left" data-aos-duration="1500" data-aos-offset="50">
<h6>Tags </h6>
<div class="tag-coulds">
@@ -173,9 +148,29 @@ if (isset($row) && !empty($row['title'])) {
</div>
</div>
</div>
<?php include_once($rootPath . '/src/pages/other/comment_box.php'); ?>
<?php include_once('comment_box.php'); ?>
</div>
<div class="col-lg-4 col-md-8 col-sm-10 rmt-75">
<div class="blog-sidebar">
<div class="widget widget-gallery" data-aos="fade-up" data-aos-duration="1500" data-aos-offset="50">
<h5 class="widget-title">Gallery</h5>
<div class="gallery">
<?php
$folder = 'uploads/blogs/'.$blog_id.'/images/';
$files = glob($folder . '*.{jpg,jpeg,png,webp}', GLOB_BRACE);
shuffle($files); // Randomize the order
foreach ($files as $file) {
echo '<a href="' . $file . '" style="width: 110px; height: 110px; overflow: hidden; display: inline-block; margin: 2px;">';
echo '<img src="' . $file . '" alt="Gallery" style="width: 100%; height: 100%; object-fit: cover; display: block;">';
echo '</a>';
}
?>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<?php include_once($rootPath . '/components/insta_footer.php'); ?>
<?php include_once("insta_footer.php"); ?>

31
blog_unpublish.php Normal file
View File

@@ -0,0 +1,31 @@
<?php
require_once("env.php");
require_once("session.php");
require_once("connection.php");
require_once("functions.php");
if (!isset($_SESSION['user_id'])) {
http_response_code(401);
echo "Not authorized";
exit;
}
$article_id = (int)($_POST['id'] ?? 0);
$user_id = $_SESSION['user_id'];
if ($article_id <= 0) {
http_response_code(400);
echo "Invalid blog ID";
exit;
}
$stmt = $conn->prepare("UPDATE blogs SET status = 'draft' WHERE blog_id = ? AND author = ?");
$stmt->bind_param("ii", $article_id, $user_id);
if ($stmt->execute()) {
echo "Published";
} else {
http_response_code(500);
echo "Failed to publish: " . $stmt->error;
}
?>

View File

@@ -1,7 +1,4 @@
<?php
$headerStyle = 'light';
$rootPath = dirname(dirname(dirname(__DIR__)));
include_once($rootPath . '/header.php');
<?php include_once('header02.php');
checkUserSession();
$user_id = $_SESSION['user_id'];
@@ -60,10 +57,28 @@ $user_id = $_SESSION['user_id'];
</style>
</style>
<?php
$pageTitle = 'My Bookings';
$breadcrumbs = [['Home' => 'index.php']];
require_once($rootPath . '/components/banner.php');
$bannerFolder = 'assets/images/banners/';
$bannerImages = glob($bannerFolder . '*.{jpg,jpeg,png,webp}', GLOB_BRACE);
$randomBanner = 'assets/images/base4/camping.jpg'; // default fallback
if (!empty($bannerImages)) {
$randomBanner = $bannerImages[array_rand($bannerImages)];
}
?>
<section class="page-banner-area pt-50 pb-35 rel z-1 bgs-cover" style="background-image: url('<?php echo $randomBanner; ?>');">
<div class="banner-overlay"></div>
<div class="container">
<div class="banner-inner text-white mb-50">
<h2 class="page-title mb-10" data-aos="fade-left" data-aos-duration="1500" data-aos-offset="50">My Bookings</h2>
<nav aria-label="breadcrumb">
<ol class="breadcrumb justify-content-center mb-20" data-aos="fade-right" data-aos-delay="200" data-aos-duration="1500" data-aos-offset="50">
<li class="breadcrumb-item"><a href="index.php">Home</a></li>
<li class="breadcrumb-item active">My bookings</li>
</ol>
</nav>
</div>
</div>
</section>
<!-- Tour List Area start -->
<section class="tour-list-page py-100 rel z-1">
@@ -114,7 +129,6 @@ $user_id = $_SESSION['user_id'];
// Loop through each row
while ($row = $result->fetch_assoc()) {
$booking_id = $row['booking_id'];
$payment_id = $row['payment_id'];
$booking_type = $row['booking_type'];
$from_date = $row['from_date'];
$to_date = $row['to_date'];
@@ -268,8 +282,8 @@ $user_id = $_SESSION['user_id'];
<div class="destination-footer">
<span class="price"><span>Booking Total: R ' . number_format($amount, 2) . '</span></span>';
if ($status == "AWAITING PAYMENT") {
echo '<a href="' . getPaymentLinkByPaymentId($payment_id) . '" class="theme-btn style-two style-three">
<span data-hover="PAY NOW">' . $status . '</span>
echo '<a href="payment_confirmation.php?token=' . encryptData($booking_id, $salt) . '" class="theme-btn style-two style-three">
<span data-hover="PAYMENT INFO">' . $status . '</span>
</a>';
} else {
echo '<a href="" class="theme-btn style-two style-three">
@@ -323,4 +337,4 @@ $user_id = $_SESSION['user_id'];
<?php include_once(dirname(dirname(dirname(__DIR__))) . '/components/insta_footer.php'); ?>
<?php include_once("insta_footer.php"); ?>

View File

@@ -1,15 +1,9 @@
<?php
$headerStyle = 'light';
$rootPath = dirname(dirname(dirname(__DIR__)));
include_once($rootPath . '/header.php');
<?php include_once('header02.php');
checkUserSession();
// SQL query to fetch dates for bush mechanics
$stmt = $conn->prepare("SELECT course_id, date FROM courses WHERE course_type = ? AND date >= CURDATE()");
$course_type = 'bush_mechanics';
$stmt->bind_param("s", $course_type);
$stmt->execute();
$result = $stmt->get_result();
// SQL query to fetch dates for driver training
$sql = "SELECT course_id, date FROM courses WHERE course_type = 'bush_mechanics'";
$result = $conn->query($sql);
$page_id = 'bush_mechanics';
?>
@@ -24,11 +18,32 @@ $page_id = 'bush_mechanics';
font-size: 16px;
}
</style><?php
$pageTitle = 'Bush Mechanics';
$breadcrumbs = [['Home' => 'index.php']];
require_once($rootPath . '/components/banner.php');
</style>
<?php
$bannerFolder = 'assets/images/banners/';
$bannerImages = glob($bannerFolder . '*.{jpg,jpeg,png,webp}', GLOB_BRACE);
$randomBanner = 'assets/images/base4/camping.jpg'; // default fallback
if (!empty($bannerImages)) {
$randomBanner = $bannerImages[array_rand($bannerImages)];
}
?>
<section class="page-banner-area pt-50 pb-35 rel z-1 bgs-cover" style="background-image: url('<?php echo $randomBanner; ?>');">
<div class="banner-overlay"></div>
<div class="container">
<div class="banner-inner text-white">
<h2 class="page-title mb-10" data-aos="fade-left" data-aos-duration="1500" data-aos-offset="50">Bush Mechanics</h2>
<nav aria-label="breadcrumb">
<ol class="breadcrumb justify-content-center mb-20" data-aos="fade-right" data-aos-delay="200" data-aos-duration="1500" data-aos-offset="50">
<li class="breadcrumb-item"><a href="index.php">Home</a></li>
<li class="breadcrumb-item active">Bush Mechanics</li>
</ol>
</nav>
</div>
</div>
</section>
<!-- Page Banner End -->
<!-- Product Details Start -->
<section class="product-details pt-100">
@@ -79,7 +94,7 @@ $page_id = 'bush_mechanics';
<hr class="mt-40">
<div class="blog-sidebar tour-sidebar">
<div class="widget widget-booking" data-aos="fade-up" data-aos-duration="1500" data-aos-offset="50">
<form action="process_course_booking" method="POST">
<form action="process_course_booking.php" method="POST">
<ul class="tickets clearfix">
<li>
Select Date
@@ -99,7 +114,7 @@ $page_id = 'bush_mechanics';
</select>
</li>
<?php
if ($is_member || $pending_member) {
if ($is_member) {
echo '
<li>
Additional Members <span class="price"></span>
@@ -154,23 +169,13 @@ $page_id = 'bush_mechanics';
<label for="agreeCheckbox" id="agreeLabel" style="color: #888;">I have read and agree to the indemnity terms</label>
</div>
</div>
<input type="hidden" name="csrf_token" value="<?php echo generateCSRFToken(); ?>">
<?php
$button_text = "PROCEED TO PAYMENT";
$button_disabled = "";
if (!$result || $result->num_rows == 0) {
$button_text = "No booking dates available";
$button_disabled = "disabled";
}
?>
<button type="submit" class="theme-btn style-two w-100 mt-15 mb-5" <?php echo $button_disabled; ?>>
<span data-hover="<?php echo $button_text; ?>"><?php echo $button_text; ?></span>
<button type="submit" class="theme-btn style-two w-100 mt-15 mb-5">
<span data-hover="Book Now">Book Now</span>
<i class="fal fa-arrow-right"></i>
</button>
<div class="text-center">
<a href="contact">You will be redirected to iKhokha's Secure payment gateway.</a>
<a href="contact.php">Need some help?</a>
</div>
<img src="assets/images/logos/ikhokha.png"alt="Secure Payment Badges" style="max-width: 200px; display: block; margin: 10px auto 0;">
</form>
</div>
@@ -347,12 +352,11 @@ $page_id = 'bush_mechanics';
// Function to calculate booking total
function calculateTotal() {
// Get selected values from the form
var additional_members = parseInt($('#members').val()) || 0; // Default to 0 if not selected
var additional_nonmembers = parseInt($('#non-members').val()) || 0; // Default to 0 if not selected
var members = parseInt($('#members').val()) || 0; // Default to 1 vehicle if not selected
var nonmembers = parseInt($('#non-members').val()) || 0; // Default to 1 adult if not selected
// Fetch PHP variables
var isMember = <?php echo $is_member ? 'true' : 'false'; ?>;
var pendingMember = <?php echo $pending_member ? 'true' : 'false'; ?>;
var cost_members = <?= getPrice('bush_mechanics', 'member');?>;
var cost_nonmembers = <?= getPrice('bush_mechanics', 'nonmember');?>;
@@ -360,11 +364,11 @@ $page_id = 'bush_mechanics';
var total = 0;
// Calculate cost for members
if (isMember || pendingMember) {
total = (cost_members) + (additional_members * cost_members) + (additional_nonmembers * cost_nonmembers);
if (isMember) {
total = (cost_members) + (members * cost_members) + (nonmembers * cost_nonmembers);
} else {
// Calculate cost for non-members
total = (cost_nonmembers) + (additional_nonmembers * cost_nonmembers);
total = (cost_nonmembers) + (members * cost_members) + (nonmembers * cost_nonmembers);
}
// Update total price in the DOM
@@ -383,4 +387,4 @@ $page_id = 'bush_mechanics';
</script>
<?php include_once(dirname(dirname(dirname(__DIR__))) . '/components/insta_footer.php') ?>
<?php include_once('insta_footer.php') ?>

View File

@@ -1,6 +1,4 @@
<?php
$headerStyle = 'light';
include_once(dirname(dirname(dirname(__DIR__))) . '/header.php');
<?php include_once('header02.php');
checkUserSession();
?>
@@ -78,7 +76,7 @@ checkUserSession();
</div>
<div class="widget widget-booking" data-aos="fade-up" data-aos-duration="1500" data-aos-offset="50">
<h5 class="widget-title">Book your Campsite</h5>
<form action="process_camp_booking" method="POST">
<form action="process_camp_booking.php" method="POST">
<div class="date mb-25">
<b>From Date</b>
<input type="date" id="from_date" name="from_date">
@@ -125,7 +123,6 @@ checkUserSession();
<?php endif ?>
<h6>Total: <span id="booking_total" class="price">-</span></h6>
<input type="hidden" name="csrf_token" value="<?php echo generateCSRFToken(); ?>">
<button type="submit" class="theme-btn style-two w-100 mt-15 mb-5">
<span data-hover="Book Now">Book Now</span>
<i class="fal fa-arrow-right"></i>
@@ -214,4 +211,4 @@ checkUserSession();
});
</script>
<?php include_once(dirname(dirname(dirname(__DIR__))) . '/components/insta_footer.php') ?>
<?php include_once('insta_footer.php') ?>

208
campsites.php Normal file
View File

@@ -0,0 +1,208 @@
<?php include_once('header02.php');
$conn = openDatabaseConnection();
$result = $conn->query("SELECT * FROM campsites");
$campsites = [];
while ($row = $result->fetch_assoc()) {
$campsites[] = $row;
}
?>
<style>
#map {
height: 600px;
width: 100%;
}
.gm-style .info-box {
max-width: 250px;
}
.info-box img {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
</style>
<?php
$bannerFolder = 'assets/images/banners/';
$bannerImages = glob($bannerFolder . '*.{jpg,jpeg,png,webp}', GLOB_BRACE);
$randomBanner = 'assets/images/base4/camping.jpg'; // default fallback
if (!empty($bannerImages)) {
$randomBanner = $bannerImages[array_rand($bannerImages)];
}
?>
<section class="page-banner-area pt-50 pb-35 rel z-1 bgs-cover" style="background-image: url('<?php echo $randomBanner; ?>');">
<div class="banner-overlay"></div>
<div class="container">
<div class="banner-inner text-white mb-50">
<h2 class="page-title mb-10" data-aos="fade-left" data-aos-duration="1500" data-aos-offset="50">Campsites</h2>
<nav aria-label="breadcrumb">
<ol class="breadcrumb justify-content-center mb-20" data-aos="fade-right" data-aos-delay="200" data-aos-duration="1500" data-aos-offset="50">
<li class="breadcrumb-item"><a href="index.php">Home</a></li>
<li class="breadcrumb-item active">Campsites</li>
</ol>
</nav>
</div>
</div>
</section>
<!-- Tour List Area start -->
<section class="tour-list-page py-100 rel z-1">
<div class="container">
<div class="row">
<div class="col-lg-12">
<div id="map" style="width: 100%; height: 500px;"></div>
<!-- Add Campsite Modal -->
</div>
</div>
</div>
</section>
<div class="modal fade" id="addCampsiteModal" tabindex="-1">
<div class="modal-dialog">
<form id="addCampsiteForm" method="POST" action="add_campsite.php" enctype="multipart/form-data">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Add Campsite</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="hidden" name="latitude" id="latitude">
<input type="hidden" name="longitude" id="longitude">
<div class="mb-3">
<label class="form-label">Campsite Name</label>
<input type="text" class="form-control" name="name" required>
</div>
<div class="mb-3">
<label class="form-label">Description</label>
<textarea class="form-control" name="description" rows="3"></textarea>
</div>
<div class="mb-3">
<label class="form-label">Booking URL</label>
<input type="url" class="form-control" name="website">
</div>
<div class="mb-3">
<label class="form-label">Phone Number</label>
<input type="text" class="form-control" name="telephone">
</div>
<div class="mb-3">
<label class="form-label">Thumbnail Image</label>
<input type="file" class="form-control" name="thumbnail" accept="image/*">
</div>
</div>
<div class="modal-footer">
<button class="btn btn-primary" type="submit">Save Campsite</button>
<button class="btn btn-secondary" type="button" data-bs-dismiss="modal">Cancel</button>
</div>
</div>
</form>
</div>
</div>
<script>
let map;
const campsites = <?php echo json_encode($campsites); ?>;
function initMap() {
map = new google.maps.Map(document.getElementById("map"), {
center: {
lat: -28.0,
lng: 24.0
}, // SA center
zoom: 6,
});
map.addListener("click", function(e) {
const lat = e.latLng.lat();
const lng = e.latLng.lng();
document.getElementById("latitude").value = lat;
document.getElementById("longitude").value = lng;
const addModal = new bootstrap.Modal(document.getElementById("addCampsiteModal"));
addModal.show();
});
// Load existing campsites from PHP
fetch("get_campsites.php")
.then(response => response.json())
.then(data => {
data.forEach(site => {
const marker = new google.maps.Marker({
position: {
lat: parseFloat(site.latitude),
lng: parseFloat(site.longitude)
},
map,
title: site.name,
});
const content = `
<div class="info-box">
<strong>${site.name}</strong><br>
${site.description ? site.description + "<br>" : ""}
${site.website ? `<a href="${site.website}" target="_blank">Visit Website</a><br>` : ""}
${site.telephone ? `Phone: ${site.telephone}<br>` : ""}
${site.thumbnail ? `<img src="${site.thumbnail}" style="width: 100%; max-width: 200px; border-radius: 8px; margin-top: 5px;">` : ""}
${site.user && site.user.first_name ? `
<div class="user-info mt-2 d-flex align-items-center">
<img src="${site.user.profile_pic}" style="width: 40px; height: 40px; border-radius: 50%; object-fit: cover; margin-right: 10px;">
<div>
<small>Added by:</small><br>
<strong>${site.user.first_name} ${site.user.last_name}</strong>
</div>
</div>` : ""}
<br>
<button class="btn btn-sm btn-warning mt-2" onclick='editCampsite(${JSON.stringify(site)})'>Edit</button>
<a href="https://www.google.com/maps/dir/?api=1&destination=${site.latitude},${site.longitude}" target="_blank" class="btn btn-sm btn-outline-primary mt-2 ms-2">Get Directions</a>
</div>
`;
const infowindow = new google.maps.InfoWindow({
content: content
});
marker.addListener("click", () => {
infowindow.open(map, marker);
});
});
})
.catch(err => console.error("Failed to load campsites:", err));
}
function editCampsite(site) {
// Pre-fill form
document.querySelector("#addCampsiteForm input[name='name']").value = site.name;
document.querySelector("#addCampsiteForm textarea[name='description']").value = site.description || "";
document.querySelector("#addCampsiteForm input[name='website']").value = site.website || "";
document.querySelector("#addCampsiteForm input[name='telephone']").value = site.telephone || "";
document.querySelector("#addCampsiteForm input[name='latitude']").value = site.latitude;
document.querySelector("#addCampsiteForm input[name='longitude']").value = site.longitude;
// Add hidden ID input
let idInput = document.querySelector("#addCampsiteForm input[name='id']");
if (!idInput) {
idInput = document.createElement("input");
idInput.type = "hidden";
idInput.name = "id";
document.querySelector("#addCampsiteForm").appendChild(idInput);
}
idInput.value = site.id;
// Show the modal
const addModal = new bootstrap.Modal(document.getElementById("addCampsiteModal"));
addModal.show();
}
</script>
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyC-JuvnbUYc8WGjQBFFVZtKiv5_bFJoWLU&callback=initMap" async defer></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<?php include_once("insta_footer.php"); ?>

View File

@@ -1,9 +1,8 @@
<?php
$rootPath = dirname(dirname(dirname(__DIR__)));
require_once($rootPath . '/src/config/env.php');
require_once($rootPath . '/src/config/session.php');
require_once($rootPath . '/src/config/connection.php');
require_once($rootPath . '/src/config/functions.php');
require_once("env.php");
require_once("session.php");
require_once("connection.php");
require_once("functions.php");
$response = array('status' => 'error', 'message' => 'Something went wrong');

View File

@@ -1,320 +0,0 @@
<?php
/**
* DatabaseService Class
*
* Provides a centralized database abstraction layer for all database operations.
* Enforces prepared statements, proper error handling, and type safety.
*
* @package 4WDCSA
* @version 1.0
*/
class DatabaseService {
private $conn;
private $lastError = null;
private $lastQuery = null;
/**
* Constructor - Initialize database connection
*
* @param mysqli $connection The MySQLi connection object
*/
public function __construct($connection) {
if (!$connection) {
throw new Exception("Database connection failed");
}
$this->conn = $connection;
}
/**
* Get the last error message
*
* @return string|null The last error or null if no error
*/
public function getLastError() {
return $this->lastError;
}
/**
* Get the last executed query
*
* @return string|null The last query or null
*/
public function getLastQuery() {
return $this->lastQuery;
}
/**
* Execute a SELECT query with parameter binding
*
* @param string $query SQL query with ? placeholders
* @param array $params Parameters to bind
* @param string $types Type specification string (e.g., "isi" for int, string, int)
* @return array|false Array of results or false on error
*/
public function select($query, $params = [], $types = "") {
try {
$this->lastQuery = $query;
$stmt = $this->conn->prepare($query);
if (!$stmt) {
$this->lastError = "Prepare failed: " . $this->conn->error;
return false;
}
if (!empty($params) && !empty($types)) {
if (!$stmt->bind_param($types, ...$params)) {
$this->lastError = "Bind failed: " . $stmt->error;
return false;
}
}
if (!$stmt->execute()) {
$this->lastError = "Execute failed: " . $stmt->error;
return false;
}
$result = $stmt->get_result();
$data = [];
while ($row = $result->fetch_assoc()) {
$data[] = $row;
}
$stmt->close();
return $data;
} catch (Exception $e) {
$this->lastError = $e->getMessage();
return false;
}
}
/**
* Execute a SELECT query returning a single row
*
* @param string $query SQL query with ? placeholders
* @param array $params Parameters to bind
* @param string $types Type specification string
* @return array|false Single row as associative array or false
*/
public function selectOne($query, $params = [], $types = "") {
$results = $this->select($query, $params, $types);
return ($results && count($results) > 0) ? $results[0] : false;
}
/**
* Execute an INSERT query
*
* @param string $query SQL query with ? placeholders
* @param array $params Parameters to bind
* @param string $types Type specification string
* @return int|false Last insert ID or false on error
*/
public function insert($query, $params = [], $types = "") {
try {
$this->lastQuery = $query;
$stmt = $this->conn->prepare($query);
if (!$stmt) {
$this->lastError = "Prepare failed: " . $this->conn->error;
return false;
}
if (!empty($params) && !empty($types)) {
if (!$stmt->bind_param($types, ...$params)) {
$this->lastError = "Bind failed: " . $stmt->error;
return false;
}
}
if (!$stmt->execute()) {
$this->lastError = "Execute failed: " . $stmt->error;
return false;
}
$insertId = $stmt->insert_id;
$stmt->close();
return $insertId;
} catch (Exception $e) {
$this->lastError = $e->getMessage();
return false;
}
}
/**
* Execute an UPDATE query
*
* @param string $query SQL query with ? placeholders
* @param array $params Parameters to bind
* @param string $types Type specification string
* @return int|false Number of affected rows or false on error
*/
public function update($query, $params = [], $types = "") {
try {
$this->lastQuery = $query;
$stmt = $this->conn->prepare($query);
if (!$stmt) {
$this->lastError = "Prepare failed: " . $this->conn->error;
return false;
}
if (!empty($params) && !empty($types)) {
if (!$stmt->bind_param($types, ...$params)) {
$this->lastError = "Bind failed: " . $stmt->error;
return false;
}
}
if (!$stmt->execute()) {
$this->lastError = "Execute failed: " . $stmt->error;
return false;
}
$affectedRows = $stmt->affected_rows;
$stmt->close();
return $affectedRows;
} catch (Exception $e) {
$this->lastError = $e->getMessage();
return false;
}
}
/**
* Execute a DELETE query
*
* @param string $query SQL query with ? placeholders
* @param array $params Parameters to bind
* @param string $types Type specification string
* @return int|false Number of affected rows or false on error
*/
public function delete($query, $params = [], $types = "") {
return $this->update($query, $params, $types);
}
/**
* Execute an arbitrary query (for complex queries)
*
* @param string $query SQL query with ? placeholders
* @param array $params Parameters to bind
* @param string $types Type specification string
* @return mixed Query result or false on error
*/
public function execute($query, $params = [], $types = "") {
try {
$this->lastQuery = $query;
$stmt = $this->conn->prepare($query);
if (!$stmt) {
$this->lastError = "Prepare failed: " . $this->conn->error;
return false;
}
if (!empty($params) && !empty($types)) {
if (!$stmt->bind_param($types, ...$params)) {
$this->lastError = "Bind failed: " . $stmt->error;
return false;
}
}
if (!$stmt->execute()) {
$this->lastError = "Execute failed: " . $stmt->error;
return false;
}
$stmt->close();
return true;
} catch (Exception $e) {
$this->lastError = $e->getMessage();
return false;
}
}
/**
* Count rows matching a condition
*
* @param string $table Table name
* @param string $where WHERE clause (without WHERE keyword)
* @param array $params Parameters to bind
* @param string $types Type specification string
* @return int|false Row count or false on error
*/
public function count($table, $where = "1=1", $params = [], $types = "") {
$query = "SELECT COUNT(*) as count FROM {$table} WHERE {$where}";
$result = $this->selectOne($query, $params, $types);
return ($result) ? (int)$result['count'] : false;
}
/**
* Check if a record exists
*
* @param string $table Table name
* @param string $where WHERE clause (without WHERE keyword)
* @param array $params Parameters to bind
* @param string $types Type specification string
* @return bool True if record exists, false otherwise
*/
public function exists($table, $where, $params = [], $types = "") {
$count = $this->count($table, $where, $params, $types);
return ($count !== false && $count > 0);
}
/**
* Get the MySQLi connection object for advanced operations
*
* @return mysqli The MySQLi connection
*/
public function getConnection() {
return $this->conn;
}
/**
* Start a transaction
*
* @return bool Success status
*/
public function beginTransaction() {
try {
$this->conn->begin_transaction();
return true;
} catch (Exception $e) {
$this->lastError = $e->getMessage();
return false;
}
}
/**
* Commit a transaction
*
* @return bool Success status
*/
public function commit() {
try {
$this->conn->commit();
return true;
} catch (Exception $e) {
$this->lastError = $e->getMessage();
return false;
}
}
/**
* Rollback a transaction
*
* @return bool Success status
*/
public function rollback() {
try {
$this->conn->rollback();
return true;
} catch (Exception $e) {
$this->lastError = $e->getMessage();
return false;
}
}
}
?>

View File

@@ -1,120 +0,0 @@
<?php
class IkhokhaClient {
private string $appId;
private string $appSecret;
private string $apiUrl;
public function __construct() {
// Try getenv first, then fallback to $_ENV if available
$this->appId = getenv('IKHOKHA_APP_ID') ?: ($_ENV['IKHOKHA_APP_ID'] ?? '');
$this->appSecret = getenv('IKHOKHA_APP_SECRET') ?: ($_ENV['IKHOKHA_APP_SECRET'] ?? '');
$this->apiUrl = getenv('IKHOKHA_API_URL') ?: ($_ENV['IKHOKHA_API_URL'] ?? '');
}
/**
* Make a request to the iKhokha API. Signs the payload per API docs.
* $endpoint should be the path portion starting with '/public-api/...'
*/
private function request(string $endpoint, array $data, string $method = 'POST') {
// Validate apiUrl
if (empty($this->apiUrl)) {
return ['error' => true, 'errno' => 3, 'message' => 'IKHOKHA_API_URL is not configured in environment'];
}
// If the configured API URL already contains the endpoint path, use it as-is.
if ((function_exists('str_ends_with') && str_ends_with($this->apiUrl, $endpoint)) ||
(substr_compare($this->apiUrl, $endpoint, -strlen($endpoint)) === 0)) {
$url = $this->apiUrl;
} else {
$url = rtrim($this->apiUrl, '/') . $endpoint;
}
$body = json_encode($data);
// Build payload to sign: path + body and apply escape rules per iKhokha docs
$parsed = parse_url($url);
$path = $parsed['path'] ?? $endpoint;
$payloadToSign = $path . $body;
// Escape function from iKhokha example
$escapeString = function ($str) {
$escaped = preg_replace(['/[\\\"\'\"]/u', '/\x00/'], ['\\\\$0', '\\0'], (string)$str);
$cleaned = str_replace('\/', '/', $escaped);
return $cleaned;
};
$escapedPayload = $escapeString($payloadToSign);
$signature = hash_hmac('sha256', $escapedPayload, $this->appSecret);
$ch = curl_init($url);
$headers = [
'Content-Type: application/json',
"IK-APPID: {$this->appId}",
"IK-SIGN: {$signature}"
];
// Optional debug logging to logs/ikhokha.log when IKHOKHA_DEBUG_LOG is true
$debugLog = getenv('IKHOKHA_DEBUG_LOG') ?: ($_ENV['IKHOKHA_DEBUG_LOG'] ?? null);
if ($debugLog) {
$logPath = dirname(__DIR__) . '/logs/ikhokha.log';
$logEntry = [
'time' => date('c'),
'url' => $url,
'headers' => $headers,
'body' => $data,
'signature' => $signature
];
@file_put_contents($logPath, json_encode(['request' => $logEntry]) . PHP_EOL, FILE_APPEND | LOCK_EX);
}
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
if (strtoupper($method) === 'POST') {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
} else {
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
}
$response = curl_exec($ch);
$errno = curl_errno($ch);
$error = curl_error($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
// Log response if debug enabled
if (!empty($debugLog)) {
$logPath = dirname(__DIR__) . '/logs/ikhokha.log';
$respEntry = [
'time' => date('c'),
'http_code' => $httpCode,
'errno' => $errno,
'error' => $error,
'response' => $response
];
@file_put_contents($logPath, json_encode(['response' => $respEntry]) . PHP_EOL, FILE_APPEND | LOCK_EX);
}
if ($response === false) {
return ['error' => true, 'message' => $error, 'errno' => $errno];
}
return json_decode($response, true);
}
/**
* Create a payment link using the iKhokha create payment endpoint.
* $body must match iKhokha request schema (amount in smallest unit, urls, externalTransactionID, etc.)
*/
public function createPaymentLink(array $body) {
return $this->request('/public-api/v1/api/payment', $body, 'POST');
}
public function getPaymentStatus($paymentId) {
// Use the GET status endpoint
$endpoint = '/public-api/v1/api/getStatus/' . urlencode($paymentId);
return $this->request($endpoint, [], 'GET');
}
}

152
comment_box.php Normal file
View File

@@ -0,0 +1,152 @@
<?php
if (!isset($page_id)) {
die("Page ID not set for comment system.");
}
$conn = openDatabaseConnection();
// Handle comment post
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['submit_comment'])) {
$comment = $conn->real_escape_string(trim($_POST['comment']));
if (!empty($comment)) {
$stmt = $conn->prepare("INSERT INTO comments (page_id, user_id, comment) VALUES (?, ?, ?)");
$stmt->bind_param("sss", $page_id, $user_id, $comment);
if ($stmt->execute()) {
header("Location: " . $_SERVER['REQUEST_URI']);
exit;
}
}
}
// Fetch comments
$stmt = $conn->prepare("SELECT user_id, comment, created_at FROM comments WHERE page_id = ? ORDER BY created_at DESC");
$stmt->bind_param("s", $page_id);
$stmt->execute();
$result = $stmt->get_result();
?>
<div>
<h5>Comments</h5>
<div class="comments">
<?php while ($row = $result->fetch_assoc()): ?>
<div class="comment-body" data-aos="fade-up" data-aos-duration="1500" data-aos-offset="50">
<div>
<img class="profile-pic" src="<?= getProfilePic($user_id); ?>" alt="Author">
</div>
<div class="">
<h6><?= getFullName($row['user_id']); ?></h6>
<?php
if (getUserMemberStatus($row['user_id'])){
echo '<div class="badge badge-primary badge-pill">MEMBER</div>';
}
?>
<em><?= $row['created_at'] ?></em>
<!-- <div class="ratting">
<i class="fas fa-star"></i>
<i class="fas fa-star"></i>
<i class="fas fa-star"></i>
<i class="fas fa-star"></i>
<i class="fas fa-star-half-alt"></i>
</div> -->
<p><?= nl2br(htmlspecialchars($row['comment'])) ?></p>
<!-- <a class="read-more" href="#">Reply <i class="far fa-angle-right"></i></a> -->
</div>
</div>
<?php endwhile; ?>
</div>
<!-- <h5>Add A Comment</h5> -->
<form method="POST" id="comment-form" class="comment-form bgc-lighter z-1 rel mt-30" name="review-form" action="" data-aos="fade-up" data-aos-duration="1500" data-aos-offset="50">
<div class="row gap-20">
<div class="col-md-12">
<div class="form-group">
<textarea name="comment" id="comment" class="form-control" rows="5" placeholder="Add comment..." required></textarea>
</div>
</div>
<div class="col-md-12">
<div class="form-group mb-0">
<button type="submit" name="submit_comment" class="theme-btn bgc-secondary style-two">
<span data-hover="Submit reviews">Add comment</span>
<i class="fal fa-arrow-right"></i>
</button>
</div>
</div>
</div>
</form>
</div>
<style>
.comment-box {
border: 1px solid #ccc;
padding: 10px;
max-width: 600px;
}
.comment-box form input,
.comment-box form textarea {
width: 100%;
margin-bottom: 8px;
}
.comments-list {
margin-top: 20px;
}
.comment {
border-top: 1px solid #eee;
padding-top: 10px;
margin-top: 10px;
}
.profile-pic {
width: 50px;
height: 50px;
border-radius: 50%;
margin-right: 10px;
object-fit: cover;
/* Ensures the image fits without distortion */
}
.badge {
display: inline-block;
padding: 0.4em 0.8em;
font-size: 0.875rem;
font-weight: 600;
color: white;
border-radius: 0.375em;
margin-right: 0.5em;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.badge-primary {
background-color: #e90000;
}
.badge-success {
background-color: #28a745;
}
.badge-warning {
background-color: #ffc107;
color: #212529;
}
.badge-danger {
background-color: #dc3545;
}
.badge-info {
background-color: #17a2b8;
}
.badge-pill {
border-radius: 999px;
}
</style>

View File

@@ -1,80 +0,0 @@
<?php
/**
* REUSABLE PAGE BANNER COMPONENT
*
* Displays a page banner with background image, title, and breadcrumb navigation.
*
* Usage in your page:
*
* <?php
* $pageTitle = 'About';
* $bannerImage = 'assets/images/blog/cover.jpg'; // optional
* require_once('components/banner.php');
* ?>
*
* Parameters:
* $pageTitle (required) - Page title to display
* $bannerImage (optional) - URL to banner background image. If not set, uses random banner
* $breadcrumbs (optional) - Array of breadcrumb items. Default: [['Home' => 'index.php']]
* $classes (optional) - Additional CSS classes for banner section
*/
// Default values
$pageTitle = $pageTitle ?? 'Page';
$bannerImage = $bannerImage ?? '';
$breadcrumbs = $breadcrumbs ?? [['Home' => 'index.php']];
$classes = $classes ?? '';
// If no banner image provided, try to use random banner
if (empty($bannerImage)) {
// Try to determine root path if not already set
if (!isset($rootPath)) {
$rootPath = $_SERVER['DOCUMENT_ROOT'] ?? dirname(__DIR__);
}
$bannerFolder = $rootPath . '/assets/images/banners/';
$bannerImages = glob($bannerFolder . '*.{jpg,jpeg,png,webp}', GLOB_BRACE);
// Convert absolute paths back to web-relative paths
$bannerImages = array_map(function($path) use ($rootPath) {
return str_replace($rootPath, '', $path);
}, $bannerImages);
$bannerImage = !empty($bannerImages) ? $bannerImages[array_rand($bannerImages)] : '/assets/images/base4/camping.jpg';
}
// Add the page title to breadcrumbs as last item (not a link)
$breadcrumbItems = [];
foreach ($breadcrumbs as $item) {
foreach ($item as $label => $url) {
$breadcrumbItems[] = ['label' => $label, 'url' => $url];
}
}
$breadcrumbItems[] = ['label' => $pageTitle, 'url' => null];
?>
<!-- Page Banner Start -->
<section class="page-banner-area pt-50 pb-35 rel z-1 bgs-cover <?php echo $classes; ?>" style="background-image: url('<?php echo $bannerImage; ?>');">
<!-- Overlay PNG -->
<div class="banner-overlay"></div>
<div class="container">
<div class="banner-inner text-white mb-50">
<h2 class="page-title mb-10" data-aos="fade-left" data-aos-duration="1500" data-aos-offset="50">
<?php echo htmlspecialchars($pageTitle); ?>
</h2>
<nav aria-label="breadcrumb">
<ol class="breadcrumb justify-content-center mb-20" data-aos="fade-right" data-aos-delay="200" data-aos-duration="1500" data-aos-offset="50">
<?php foreach ($breadcrumbItems as $item): ?>
<li class="breadcrumb-item <?php echo $item['url'] === null ? 'active' : ''; ?>">
<?php if ($item['url']): ?>
<a href="<?php echo htmlspecialchars($item['url']); ?>">
<?php echo htmlspecialchars($item['label']); ?>
</a>
<?php else: ?>
<?php echo htmlspecialchars($item['label']); ?>
<?php endif; ?>
</li>
<?php endforeach; ?>
</ol>
</nav>
</div>
</div>
</section>
<!-- Page Banner End -->

15
connection.php Normal file
View File

@@ -0,0 +1,15 @@
<?php
$dbhost = $_ENV['DB_HOST'];
$dbuser = $_ENV['DB_USER'];
$dbpass = $_ENV['DB_PASS'];
$dbname = $_ENV['DB_NAME'];
$salt = $_ENV['SALT'];
if(!$conn = mysqli_connect($dbhost, $dbuser, $dbpass, $dbname)){
die("Failed to connect: " . mysqli_connect_error());
}
date_default_timezone_set('Africa/Johannesburg');

View File

@@ -1,8 +1,4 @@
<?php
$headerStyle = 'light';
$rootPath = dirname(dirname(dirname(__DIR__)));
include_once($rootPath . '/header.php');
?>
<?php include_once('header02.php') ?>
<style>
.image {
@@ -28,11 +24,32 @@ include_once($rootPath . '/header.php');
display: block;
/* Prevents inline whitespace issues */
}
</style><?php
$pageTitle = 'Contact Us';
$breadcrumbs = [['Home' => 'index.php']];
require_once($rootPath . '/components/banner.php');
</style>
<?php
$bannerFolder = 'assets/images/banners/';
$bannerImages = glob($bannerFolder . '*.{jpg,jpeg,png,webp}', GLOB_BRACE);
$randomBanner = 'assets/images/base4/camping.jpg'; // default fallback
if (!empty($bannerImages)) {
$randomBanner = $bannerImages[array_rand($bannerImages)];
}
?>
<section class="page-banner-area pt-50 pb-35 rel z-1 bgs-cover" style="background-image: url('<?php echo $randomBanner; ?>');">
<div class="banner-overlay"></div>
<div class="container">
<div class="banner-inner text-white">
<h2 class="page-title mb-10" data-aos="fade-left" data-aos-duration="1500" data-aos-offset="50">Contact Us</h2>
<nav aria-label="breadcrumb">
<ol class="breadcrumb justify-content-center mb-20" data-aos="fade-right" data-aos-delay="200" data-aos-duration="1500" data-aos-offset="50">
<li class="breadcrumb-item"><a href="index.html">Home</a></li>
<li class="breadcrumb-item active">Contact Us</li>
</ol>
</nav>
</div>
</div>
</section>
<!-- Page Banner End -->
<!-- Contact Info Area start -->
@@ -110,4 +127,4 @@ include_once($rootPath . '/header.php');
<!-- Contact Map End -->
<?php include_once(dirname(dirname(dirname(__DIR__))) . '/components/insta_footer.php'); ?>
<?php include_once("insta_footer.php"); ?>

View File

@@ -1,23 +1,26 @@
<?php
$headerStyle = 'light';
$rootPath = dirname(dirname(dirname(__DIR__)));
include_once($rootPath . '/header.php');
<?php include_once('header02.php');
// SQL query to fetch dates for driver training
$stmt = $conn->prepare("SELECT course_id, date FROM courses WHERE course_type = ?");
$course_type = 'driver_training';
$stmt->bind_param("s", $course_type);
$stmt->execute();
$result = $stmt->get_result();
$sql = "SELECT course_id, date FROM courses WHERE course_type = 'driver_training'";
$result = $conn->query($sql);
?>
<?php
$pageTitle = 'Course Details';
$breadcrumbs = [['Home' => 'index.php']];
require_once($rootPath . '/components/banner.php');
?>
<!-- Page Banner Start -->
<section class="page-banner-area pt-50 pb-35 rel z-1 bgs-cover" style="background-image: url(assets/images/banner/banner.jpg);">
<div class="container">
<div class="banner-inner text-white">
<h2 class="page-title mb-10" data-aos="fade-left" data-aos-duration="1500" data-aos-offset="50">4X4 Driver Training</h2>
<nav aria-label="breadcrumb">
<ol class="breadcrumb justify-content-center mb-20" data-aos="fade-right" data-aos-delay="200" data-aos-duration="1500" data-aos-offset="50">
<li class="breadcrumb-item"><a href="index.php">Home</a></li>
<li class="breadcrumb-item active">4X4 Driver Training</li>
</ol>
</nav>
</div>
</div>
</section>
<!-- Page Banner End -->
<!-- Product Details Start -->
@@ -68,7 +71,6 @@ $result = $stmt->get_result();
<p>Our 4x4 Basic Training Course equips you with the essential skills and knowledge to confidently tackle off-road terrains. Learn vehicle mechanics, driving techniques, obstacle navigation, and recovery methods while promoting safe and responsible off-road practices. Perfect for beginners and new 4x4 owners!</p>
<hr class="mt-40">
<form action="#" class="add-to-cart pt-15 pb-30">
<input type="hidden" name="csrf_token" value="<?php echo generateCSRFToken(); ?>">
<label for="course_date">Select a Date:</label>
<select name="course_date" id="course_date" required>
<!-- <option value="" disabled selected>-- Select a Date --</option> -->
@@ -300,4 +302,4 @@ $result = $stmt->get_result();
<!-- Shop Details Area end -->
<?php include_once(dirname(dirname(dirname(__DIR__))) . '/components/insta_footer.php') ?>
<?php include_once('insta_footer.php') ?>

View File

@@ -1,54 +1,39 @@
<?php
$rootPath = dirname(dirname(__DIR__));
require_once($rootPath . "/src/config/session.php");
require_once($rootPath . "/src/config/connection.php");
require_once($rootPath . "/src/config/functions.php");
// CSRF Token Validation
if (!isset($_POST['csrf_token']) || !validateCSRFToken($_POST['csrf_token'])) {
http_response_code(403);
echo json_encode(['status' => 'error', 'message' => 'Security token validation failed.']);
exit();
}
require_once("session.php");
require_once("connection.php");
require_once("functions.php");
// Check if user_id is set in the POST request
if (isset($_POST['user_id']) && !empty($_POST['user_id'])) {
// Validate user_id as integer
$user_id = intval($_POST['user_id']);
if ($user_id <= 0) {
echo json_encode(['status' => 'error', 'message' => 'Invalid user ID.']);
exit();
}
// Sanitize the input to prevent SQL injection
$user_id = mysqli_real_escape_string($conn, $_POST['user_id']);
$created_at = date('Y-m-d H:i:s'); // Use current date and time for created_at
// First, check if a bar tab already exists for this user_id
$stmt = $conn->prepare("SELECT * FROM bar_tabs WHERE user_id = ? LIMIT 1");
$stmt->bind_param("i", $user_id);
$stmt->execute();
$checkResult = $stmt->get_result();
$checkSql = "SELECT * FROM bar_tabs WHERE user_id = '$user_id' LIMIT 1";
$checkResult = mysqli_query($conn, $checkSql);
if ($checkResult->num_rows > 0) {
if (mysqli_num_rows($checkResult) > 0) {
// If a bar tab already exists for this user_id, return an error message
echo json_encode(['status' => 'error', 'message' => 'A bar tab already exists for this user.']);
} else {
// Prepare the SQL query to insert a new record into the bar_tabs table
$stmt = $conn->prepare("INSERT INTO bar_tabs (user_id) VALUES (?)");
$stmt->bind_param("i", $user_id);
$sql = "INSERT INTO bar_tabs (user_id) VALUES ('$user_id')";
// Execute the query
if ($stmt->execute()) {
if (mysqli_query($conn, $sql)) {
// If the insertion is successful, return a success message
echo json_encode(['status' => 'success', 'message' => 'Bar tab created successfully.']);
} else {
// If there's an error, return an error message
echo json_encode(['status' => 'error', 'message' => 'Error: ' . $conn->error]);
echo json_encode(['status' => 'error', 'message' => 'Error: ' . mysqli_error($conn)]);
}
}
} else {
// If user_id is not provided, return an error message
echo json_encode(['status' => 'error', 'message' => 'User ID is required.']);
}
// Close the database connection
mysqli_close($conn);
?>

View File

@@ -1,368 +0,0 @@
# DatabaseService Usage Examples
This document shows how to refactor existing code to use the new `DatabaseService` class for cleaner, more maintainable database operations.
## Current State
Files are using the procedural MySQLi pattern:
```php
$stmt = $conn->prepare("SELECT * FROM users WHERE email = ?");
$stmt->bind_param("s", $email);
$stmt->execute();
$result = $stmt->get_result();
$row = $result->fetch_assoc();
$stmt->close();
```
## Example 1: Simple SELECT (admin_members.php)
### Current Code
```php
$stmt = $conn->prepare("SELECT user_id, first_name, last_name, tel_cell, email, dob, accept_indemnity FROM membership_application");
$stmt->execute();
$result = $stmt->get_result();
// Then in HTML/JS loop:
while ($row = $result->fetch_assoc()) {
// display row
}
```
### Using DatabaseService
```php
// Simple - get all records
$members = $db->select("SELECT user_id, first_name, last_name, tel_cell, email, dob, accept_indemnity FROM membership_application");
// In HTML/JS loop:
foreach ($members as $row) {
// display row
}
```
**Benefits:**
- No manual `bind_param()`, `execute()`, `close()` needed
- Returns array directly
- Automatic error tracking via `$db->getLastError()`
---
## Example 2: SELECT with Parameters (validate_login.php)
### Current Code
```php
$query = "SELECT * FROM users WHERE email = ?";
$stmt = $conn->prepare($query);
$stmt->bind_param("s", $email);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows == 1) {
$row = $result->fetch_assoc();
// use $row
}
$stmt->close();
```
### Using DatabaseService
```php
$user = $db->selectOne(
"SELECT * FROM users WHERE email = ?",
[$email],
"s" // s = string type
);
if ($user) {
// use $user - returns false if no row found
}
```
**Benefits:**
- One-liner for single row
- Handles null checks automatically
- Type specification clear in parameters
---
## Example 3: INSERT (validate_login.php)
### Current Code
```php
$query = "INSERT INTO users (email, first_name, last_name, profile_pic, password, is_verified) VALUES (?, ?, ?, ?, ?, ?)";
$stmt = $conn->prepare($query);
$is_verified = 1;
$stmt->bind_param("sssssi", $email, $first_name, $last_name, $picture, $password, $is_verified);
if ($stmt->execute()) {
$user_id = $conn->insert_id; // ❌ Bug: insert_id from $conn, not $stmt
// use $user_id
}
$stmt->close();
```
### Using DatabaseService
```php
$user_id = $db->insert(
"INSERT INTO users (email, first_name, last_name, profile_pic, password, is_verified) VALUES (?, ?, ?, ?, ?, ?)",
[$email, $first_name, $last_name, $picture, $password, 1],
"sssssi"
);
if ($user_id) {
// $user_id contains the auto-increment ID
} else {
$error = $db->getLastError();
}
```
**Benefits:**
- Returns insert ID directly
- Automatic error handling
- Cleaner parameter list
---
## Example 4: UPDATE (admin_members.php)
### Current Code
```php
$user_id = intval($_POST['user_id']);
$stmt = $conn->prepare("UPDATE membership_application SET accept_indemnity = 1 WHERE user_id = ?");
if ($stmt) {
$stmt->bind_param("i", $user_id);
$stmt->execute();
$stmt->close();
}
```
### Using DatabaseService
```php
$user_id = intval($_POST['user_id']);
$affectedRows = $db->update(
"UPDATE membership_application SET accept_indemnity = 1 WHERE user_id = ?",
[$user_id],
"i"
);
if ($affectedRows !== false) {
// Updated successfully, $affectedRows = number of rows changed
}
```
**Benefits:**
- Returns affected row count
- No manual statement closing
- Error available via `$db->getLastError()`
---
## Example 5: COUNT / EXISTS
### Current Pattern (Need 3 lines)
```php
$stmt = $conn->prepare("SELECT COUNT(*) as count FROM users WHERE email = ?");
$stmt->bind_param("s", $email);
$stmt->execute();
$result = $stmt->get_result();
$row = $result->fetch_assoc();
if ($row['count'] > 0) { /* exists */ }
$stmt->close();
```
### Using DatabaseService (One line)
```php
$exists = $db->exists("users", "email = ?", [$email], "s");
if ($exists) {
// User exists
}
```
**Benefits:**
- Boolean result
- Intent is clear
- One-liner
---
## Example 6: Multiple Rows with Filtering
### Current Code
```php
$status = 'active';
$stmt = $conn->prepare("SELECT * FROM members WHERE status = ? ORDER BY last_name ASC");
$stmt->bind_param("s", $status);
$stmt->execute();
$result = $stmt->get_result();
$members = [];
while ($row = $result->fetch_assoc()) {
$members[] = $row;
}
$stmt->close();
```
### Using DatabaseService
```php
$members = $db->select(
"SELECT * FROM members WHERE status = ? ORDER BY last_name ASC",
['active'],
"s"
);
```
**Benefits:**
- Returns array directly
- No loop needed
- 2 lines vs 8 lines
---
## Example 7: Error Handling
### Current Pattern
```php
$stmt = $conn->prepare("SELECT * FROM users WHERE id = ?");
if (!$stmt) {
echo "Prepare failed: " . $conn->error;
exit();
}
$stmt->bind_param("i", $id);
if (!$stmt->execute()) {
echo "Execute failed: " . $stmt->error;
exit();
}
```
### Using DatabaseService
```php
$user = $db->selectOne("SELECT * FROM users WHERE id = ?", [$id], "i");
if ($user === false) {
$error = $db->getLastError();
error_log("Database error: " . $error);
// handle error
}
```
**Benefits:**
- Error handling centralized
- No null checks for each step
- Debug via `$db->getLastQuery()`
---
## Example 8: Transactions
### Current Pattern
```php
$conn->begin_transaction();
try {
$stmt = $conn->prepare("INSERT INTO orders ...");
$stmt->execute();
$stmt = $conn->prepare("UPDATE inventory ...");
$stmt->execute();
$conn->commit();
} catch (Exception $e) {
$conn->rollback();
}
```
### Using DatabaseService
```php
$db->beginTransaction();
$order_id = $db->insert("INSERT INTO orders ...", [...], "...");
if ($order_id === false) {
$db->rollback();
exit("Order creation failed");
}
$updated = $db->update("UPDATE inventory ...", [...], "...");
if ($updated === false) {
$db->rollback();
exit("Inventory update failed");
}
$db->commit();
```
**Benefits:**
- Unified transaction API
- Built-in error checking
- Clean rollback on failure
---
## Type Specification Reference
When using DatabaseService methods, specify parameter types:
| Type | Meaning | Example |
|------|---------|---------|
| `"i"` | Integer | `user_id = 5` |
| `"d"` | Double/Float | `price = 19.99` |
| `"s"` | String | `email = 'test@example.com'` |
| `"b"` | Blob | Binary data |
Examples:
```php
// Single parameter
$db->select("SELECT * FROM users WHERE id = ?", [123], "i");
// Multiple parameters
$db->select(
"SELECT * FROM users WHERE email = ? AND status = ?",
["test@example.com", "active"],
"ss"
);
// Mixed types
$db->select(
"SELECT * FROM orders WHERE user_id = ? AND total > ? AND date = ?",
[5, 100.50, "2025-01-01"],
"ids" // integer, double, string
);
```
---
## Migration Path
### Phase 1: New Code
Start using `$db` for all new features and AJAX endpoints.
### Phase 2: High-Traffic Files
Refactor popular files:
1. `validate_login.php` - Login is critical
2. `functions.php` - Helper functions
3. `admin_members.php`, `admin_payments.php` - Admin pages
### Phase 3: Gradual Rollout
As each file is refactored, commit and test thoroughly before moving to next.
### Phase 4: Full Migration
Eventually all procedural `$conn->prepare()` patterns replaced.
---
## Benefits Summary
| Aspect | Before | After |
|--------|--------|-------|
| Lines per query | 5-8 | 1-3 |
| Error handling | Manual checks | Automatic |
| Type safety | bind_param() | Parameter array |
| Statement closing | Manual | Automatic |
| Insert ID handling | `$conn->insert_id` (buggy) | Direct return |
| Debugging | Check multiple vars | `getLastError()`, `getLastQuery()` |
| Consistency | Varies | Unified API |
---
## Next Steps
1. Start with one file (e.g., `admin_members.php`)
2. Convert simple queries first
3. Test thoroughly
4. Commit and move to next file
5. Keep `$conn` available for complex queries that don't fit the standard patterns
The `$db` service makes your code **cleaner, safer, and easier to maintain**.

View File

@@ -1,680 +0,0 @@
-- phpMyAdmin SQL Dump
-- version 5.2.2
-- https://www.phpmyadmin.net/
--
-- Host: db
-- Generation Time: Dec 02, 2025 at 07:32 PM
-- Server version: 8.0.41
-- PHP Version: 8.2.27
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
START TRANSACTION;
SET time_zone = "+00:00";
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
--
-- Database: `4wdcsa`
--
-- --------------------------------------------------------
--
-- Table structure for table `bar_items`
--
DROP TABLE IF EXISTS `bar_items`;
CREATE TABLE `bar_items` (
`item_id` int NOT NULL,
`price` decimal(10,2) DEFAULT NULL,
`description` varchar(64) DEFAULT NULL,
`image` varchar(255) DEFAULT NULL,
`qty` int DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-- --------------------------------------------------------
--
-- Table structure for table `bar_tabs`
--
DROP TABLE IF EXISTS `bar_tabs`;
CREATE TABLE `bar_tabs` (
`tab_id` int NOT NULL,
`user_id` int DEFAULT NULL,
`image` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-- --------------------------------------------------------
--
-- Table structure for table `bar_transactions`
--
DROP TABLE IF EXISTS `bar_transactions`;
CREATE TABLE `bar_transactions` (
`transaction_id` int NOT NULL,
`user_id` int DEFAULT NULL,
`item_price` decimal(10,2) DEFAULT NULL,
`item_name` varchar(64) DEFAULT NULL,
`eft_id` varchar(255) DEFAULT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`item_id` int DEFAULT NULL,
`tab_id` int DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-- --------------------------------------------------------
--
-- Table structure for table `blacklist`
--
DROP TABLE IF EXISTS `blacklist`;
CREATE TABLE `blacklist` (
`blacklist_id` int NOT NULL,
`ip` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- --------------------------------------------------------
--
-- Table structure for table `blogs`
--
DROP TABLE IF EXISTS `blogs`;
CREATE TABLE `blogs` (
`blog_id` int NOT NULL,
`title` varchar(255) DEFAULT NULL,
`date` date DEFAULT NULL,
`category` varchar(255) DEFAULT NULL,
`description` text,
`image` varchar(255) DEFAULT NULL,
`author` int DEFAULT NULL,
`link` varchar(255) DEFAULT NULL,
`members_only` tinyint(1) NOT NULL DEFAULT '1',
`content` text,
`status` enum('draft','published','deleted') CHARACTER SET latin1 COLLATE latin1_swedish_ci DEFAULT 'draft'
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-- --------------------------------------------------------
--
-- Table structure for table `bookings`
--
DROP TABLE IF EXISTS `bookings`;
CREATE TABLE `bookings` (
`booking_id` int NOT NULL,
`booking_type` varchar(255) COLLATE utf8mb4_general_ci NOT NULL,
`user_id` int NOT NULL,
`from_date` date DEFAULT NULL,
`to_date` date DEFAULT NULL,
`num_vehicles` int NOT NULL DEFAULT '1',
`num_adults` int NOT NULL DEFAULT '0',
`num_children` int NOT NULL DEFAULT '0',
`add_firewood` tinyint(1) DEFAULT '0',
`total_amount` decimal(10,2) DEFAULT NULL,
`discount_amount` decimal(10,2) NOT NULL DEFAULT '0.00',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`status` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL,
`payment_id` varchar(255) COLLATE utf8mb4_general_ci NOT NULL,
`trip_id` int DEFAULT NULL,
`radio` tinyint(1) DEFAULT '0',
`course_id` int DEFAULT NULL,
`course_non_members` int DEFAULT '0',
`eft_id` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL,
`accept_indemnity` tinyint(1) DEFAULT '0',
`num_pensioners` int DEFAULT '0',
`notes` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- --------------------------------------------------------
--
-- Table structure for table `campsites`
--
DROP TABLE IF EXISTS `campsites`;
CREATE TABLE `campsites` (
`id` int NOT NULL,
`name` varchar(255) NOT NULL,
`description` text,
`latitude` float(10,6) NOT NULL,
`longitude` float(10,6) NOT NULL,
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`website` varchar(255) DEFAULT NULL,
`telephone` varchar(50) DEFAULT NULL,
`thumbnail` varchar(255) DEFAULT NULL,
`user_id` int DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- --------------------------------------------------------
--
-- Table structure for table `comments`
--
DROP TABLE IF EXISTS `comments`;
CREATE TABLE `comments` (
`comment_id` int NOT NULL,
`page_id` varchar(255) NOT NULL,
`user_id` varchar(100) NOT NULL,
`comment` text NOT NULL,
`created_at` datetime DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- --------------------------------------------------------
--
-- Table structure for table `courses`
--
DROP TABLE IF EXISTS `courses`;
CREATE TABLE `courses` (
`course_id` int NOT NULL,
`course_type` varchar(255) COLLATE utf8mb4_general_ci NOT NULL,
`date` date NOT NULL,
`capacity` int NOT NULL,
`booked` int NOT NULL,
`cost_members` decimal(10,2) NOT NULL,
`cost_nonmembers` decimal(10,2) NOT NULL,
`instructor` varchar(255) COLLATE utf8mb4_general_ci NOT NULL,
`instructor_email` varchar(255) COLLATE utf8mb4_general_ci NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- --------------------------------------------------------
--
-- Table structure for table `efts`
--
DROP TABLE IF EXISTS `efts`;
CREATE TABLE `efts` (
`eft_id` varchar(255) NOT NULL,
`booking_id` int DEFAULT NULL,
`user_id` int NOT NULL,
`status` varchar(64) NOT NULL,
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`amount` decimal(10,2) NOT NULL,
`description` varchar(255) DEFAULT NULL,
`membershipfee_id` int DEFAULT NULL,
`proof_of_payment` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-- --------------------------------------------------------
--
-- Table structure for table `events`
--
DROP TABLE IF EXISTS `events`;
CREATE TABLE `events` (
`event_id` int NOT NULL,
`date` date DEFAULT NULL,
`time` time DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
`image` varchar(255) DEFAULT NULL,
`description` text,
`feature` varchar(255) DEFAULT NULL,
`location` varchar(255) DEFAULT NULL,
`type` varchar(255) DEFAULT NULL,
`promo` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-- --------------------------------------------------------
--
-- Table structure for table `legacy_members`
--
DROP TABLE IF EXISTS `legacy_members`;
CREATE TABLE `legacy_members` (
`legacy_id` varchar(12) NOT NULL,
`last_name` varchar(255) DEFAULT NULL,
`first_name` varchar(255) DEFAULT NULL,
`amount` varchar(12) DEFAULT NULL,
`phone_number` varchar(16) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- --------------------------------------------------------
--
-- Table structure for table `membership_application`
--
DROP TABLE IF EXISTS `membership_application`;
CREATE TABLE `membership_application` (
`application_id` int NOT NULL,
`user_id` int NOT NULL,
`first_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`last_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`id_number` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`dob` date DEFAULT NULL,
`occupation` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`tel_cell` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`email` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`spouse_first_name` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL,
`spouse_last_name` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL,
`spouse_id_number` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL,
`spouse_dob` date DEFAULT NULL,
`spouse_occupation` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL,
`spouse_tel_cell` varchar(20) COLLATE utf8mb4_general_ci DEFAULT NULL,
`spouse_email` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL,
`child_name1` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL,
`child_dob1` date DEFAULT NULL,
`child_name2` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL,
`child_dob2` date DEFAULT NULL,
`child_name3` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL,
`child_dob3` date DEFAULT NULL,
`physical_address` text COLLATE utf8mb4_general_ci,
`postal_address` text COLLATE utf8mb4_general_ci,
`interests_hobbies` text COLLATE utf8mb4_general_ci,
`vehicle_make` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL,
`vehicle_model` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL,
`vehicle_year` varchar(10) COLLATE utf8mb4_general_ci DEFAULT NULL,
`vehicle_registration` varchar(20) COLLATE utf8mb4_general_ci DEFAULT NULL,
`secondary_vehicle_make` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL,
`secondary_vehicle_model` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL,
`secondary_vehicle_year` varchar(10) COLLATE utf8mb4_general_ci DEFAULT NULL,
`secondary_vehicle_registration` varchar(20) COLLATE utf8mb4_general_ci DEFAULT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`accept_indemnity` tinyint(1) NOT NULL DEFAULT '0',
`sig` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
`code` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- --------------------------------------------------------
--
-- Table structure for table `membership_fees`
--
DROP TABLE IF EXISTS `membership_fees`;
CREATE TABLE `membership_fees` (
`fee_id` int NOT NULL,
`user_id` int NOT NULL,
`payment_amount` decimal(10,2) NOT NULL,
`payment_date` date DEFAULT NULL,
`payment_status` varchar(255) COLLATE utf8mb4_general_ci DEFAULT 'PENDING',
`membership_start_date` date NOT NULL,
`membership_end_date` date NOT NULL,
`due_date` date DEFAULT NULL,
`renewal_reminder_sent` tinyint(1) DEFAULT '0',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`payment_id` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- --------------------------------------------------------
--
-- Table structure for table `password_resets`
--
DROP TABLE IF EXISTS `password_resets`;
CREATE TABLE `password_resets` (
`id` int NOT NULL,
`user_id` int NOT NULL,
`token` varchar(255) COLLATE utf8mb4_general_ci NOT NULL,
`expires_at` datetime NOT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- --------------------------------------------------------
--
-- Table structure for table `payments`
--
DROP TABLE IF EXISTS `payments`;
CREATE TABLE `payments` (
`payment_id` varchar(255) NOT NULL,
`user_id` int NOT NULL,
`amount` decimal(10,2) NOT NULL,
`status` varchar(255) NOT NULL,
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`description` varchar(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-- --------------------------------------------------------
--
-- Table structure for table `prices`
--
DROP TABLE IF EXISTS `prices`;
CREATE TABLE `prices` (
`price_id` int NOT NULL,
`description` varchar(255) DEFAULT NULL,
`type` varchar(255) DEFAULT NULL,
`amount` decimal(10,2) DEFAULT NULL,
`amount_nonmembers` decimal(10,2) DEFAULT NULL,
`detail` text
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- --------------------------------------------------------
--
-- Table structure for table `trips`
--
DROP TABLE IF EXISTS `trips`;
CREATE TABLE `trips` (
`trip_id` int NOT NULL,
`trip_name` varchar(255) COLLATE utf8mb4_general_ci NOT NULL,
`start_date` date NOT NULL,
`end_date` date NOT NULL,
`short_description` text COLLATE utf8mb4_general_ci NOT NULL,
`long_description` text COLLATE utf8mb4_general_ci NOT NULL,
`vehicle_capacity` int NOT NULL,
`cost_members` decimal(10,2) NOT NULL,
`cost_nonmembers` decimal(10,2) NOT NULL,
`location` varchar(255) COLLATE utf8mb4_general_ci NOT NULL,
`places_booked` int DEFAULT NULL,
`booking_fee` decimal(10,2) NOT NULL,
`trip_code` varchar(12) COLLATE utf8mb4_general_ci DEFAULT NULL,
`published` tinyint(1) NOT NULL DEFAULT '0',
`cost_pensioner_member` decimal(10,2) NOT NULL,
`cost_pensioner` decimal(10,2) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- --------------------------------------------------------
--
-- Table structure for table `users`
--
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
`user_id` int NOT NULL,
`first_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`last_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`email` varchar(255) COLLATE utf8mb4_general_ci NOT NULL,
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`member` tinyint(1) NOT NULL DEFAULT '0',
`date_joined` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`is_verified` tinyint(1) NOT NULL DEFAULT '0',
`token` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`phone_number` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`profile_pic` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'assets/images/pp/default.png',
`role` enum('user','admin','superadmin','') COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'user',
`type` enum('google','credentials') COLLATE utf8mb4_general_ci NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- --------------------------------------------------------
--
-- Table structure for table `visitor_logs`
--
DROP TABLE IF EXISTS `visitor_logs`;
CREATE TABLE `visitor_logs` (
`id` int NOT NULL,
`ip_address` varchar(45) NOT NULL,
`page_url` text NOT NULL,
`referrer_url` text,
`visit_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`user_id` int DEFAULT NULL,
`country` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
--
-- Indexes for dumped tables
--
--
-- Indexes for table `bar_items`
--
ALTER TABLE `bar_items`
ADD PRIMARY KEY (`item_id`);
--
-- Indexes for table `bar_tabs`
--
ALTER TABLE `bar_tabs`
ADD PRIMARY KEY (`tab_id`);
--
-- Indexes for table `bar_transactions`
--
ALTER TABLE `bar_transactions`
ADD PRIMARY KEY (`transaction_id`);
--
-- Indexes for table `blacklist`
--
ALTER TABLE `blacklist`
ADD PRIMARY KEY (`blacklist_id`);
--
-- Indexes for table `blogs`
--
ALTER TABLE `blogs`
ADD PRIMARY KEY (`blog_id`);
--
-- Indexes for table `bookings`
--
ALTER TABLE `bookings`
ADD PRIMARY KEY (`booking_id`),
ADD KEY `user_id` (`user_id`);
--
-- Indexes for table `campsites`
--
ALTER TABLE `campsites`
ADD PRIMARY KEY (`id`);
--
-- Indexes for table `comments`
--
ALTER TABLE `comments`
ADD PRIMARY KEY (`comment_id`);
--
-- Indexes for table `courses`
--
ALTER TABLE `courses`
ADD PRIMARY KEY (`course_id`);
--
-- Indexes for table `efts`
--
ALTER TABLE `efts`
ADD PRIMARY KEY (`eft_id`);
--
-- Indexes for table `events`
--
ALTER TABLE `events`
ADD PRIMARY KEY (`event_id`);
--
-- Indexes for table `legacy_members`
--
ALTER TABLE `legacy_members`
ADD PRIMARY KEY (`legacy_id`);
--
-- Indexes for table `membership_application`
--
ALTER TABLE `membership_application`
ADD PRIMARY KEY (`application_id`);
--
-- Indexes for table `membership_fees`
--
ALTER TABLE `membership_fees`
ADD PRIMARY KEY (`fee_id`);
--
-- Indexes for table `password_resets`
--
ALTER TABLE `password_resets`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `token` (`token`),
ADD KEY `user_id` (`user_id`);
--
-- Indexes for table `payments`
--
ALTER TABLE `payments`
ADD PRIMARY KEY (`payment_id`);
--
-- Indexes for table `prices`
--
ALTER TABLE `prices`
ADD PRIMARY KEY (`price_id`);
--
-- Indexes for table `trips`
--
ALTER TABLE `trips`
ADD PRIMARY KEY (`trip_id`);
--
-- Indexes for table `users`
--
ALTER TABLE `users`
ADD PRIMARY KEY (`user_id`);
--
-- Indexes for table `visitor_logs`
--
ALTER TABLE `visitor_logs`
ADD PRIMARY KEY (`id`);
--
-- AUTO_INCREMENT for dumped tables
--
--
-- AUTO_INCREMENT for table `bar_items`
--
ALTER TABLE `bar_items`
MODIFY `item_id` int NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `bar_tabs`
--
ALTER TABLE `bar_tabs`
MODIFY `tab_id` int NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `bar_transactions`
--
ALTER TABLE `bar_transactions`
MODIFY `transaction_id` int NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `blacklist`
--
ALTER TABLE `blacklist`
MODIFY `blacklist_id` int NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `blogs`
--
ALTER TABLE `blogs`
MODIFY `blog_id` int NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `bookings`
--
ALTER TABLE `bookings`
MODIFY `booking_id` int NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `campsites`
--
ALTER TABLE `campsites`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `comments`
--
ALTER TABLE `comments`
MODIFY `comment_id` int NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `courses`
--
ALTER TABLE `courses`
MODIFY `course_id` int NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `events`
--
ALTER TABLE `events`
MODIFY `event_id` int NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `membership_application`
--
ALTER TABLE `membership_application`
MODIFY `application_id` int NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `membership_fees`
--
ALTER TABLE `membership_fees`
MODIFY `fee_id` int NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `password_resets`
--
ALTER TABLE `password_resets`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `prices`
--
ALTER TABLE `prices`
MODIFY `price_id` int NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `trips`
--
ALTER TABLE `trips`
MODIFY `trip_id` int NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `users`
--
ALTER TABLE `users`
MODIFY `user_id` int NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `visitor_logs`
--
ALTER TABLE `visitor_logs`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- Constraints for dumped tables
--
--
-- Constraints for table `bookings`
--
ALTER TABLE `bookings`
ADD CONSTRAINT `bookings_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`user_id`) ON DELETE CASCADE;
--
-- Constraints for table `password_resets`
--
ALTER TABLE `password_resets`
ADD CONSTRAINT `password_resets_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`user_id`) ON DELETE CASCADE;
COMMIT;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

View File

@@ -1,176 +0,0 @@
# Events Management Admin System
## Overview
A complete admin system for managing events on the 4WDCSA website, following the same patterns as the trip management system.
## Files Created
### 1. `/src/admin/manage_events.php`
**Purpose**: Form for creating and editing events
**Features**:
- Create new events form
- Edit existing events form
- Fields:
- Event Name (required)
- Event Type (required) - e.g., Workshop, Training, Rally
- Location (required)
- Date (required)
- Time (required)
- Feature/Category (required) - e.g., Off-Road Training, Social Event
- Description (required) - Full text description
- Event Image (required for new, optional for updates)
- Promotional Image (optional) - Displayed when users click "View Promo"
- Published Status (checkbox) - Controls visibility on website
**Technical Details**:
- AJAX form submission to `process_event` endpoint
- Image upload with validation
- CSRF token protection
- Responsive Bootstrap grid layout (col-md-6 fields)
- Success/error message display with auto-redirect
### 2. `/src/admin/process_event.php`
**Purpose**: Backend endpoint for handling event CRUD operations
**Endpoints**:
- `POST /process_event` - Create/Update event
- `GET /process_event?action=delete&event_id={id}` - Delete event
**Features**:
- Create new events with image uploads
- Update existing events with optional image replacement
- Delete events and associated image files
- CSRF token validation
- Image type validation (JPEG, PNG, GIF, WebP)
- File organization in `/assets/images/events/`
- Automatic timestamp management (created_at, updated_at)
- User tracking (created_by stores admin user_id)
**Image Handling**:
- Main event image: Stored with unique ID prefix
- Promo image: Stored with `_promo_` prefix
- Both uploaded to `/assets/images/events/`
### 3. `/src/admin/admin_events.php`
**Purpose**: Admin dashboard for managing all events
**Features**:
- List all events with sortable columns
- Real-time search/filter across all columns
- Create new event button
- Edit event link for each row
- Delete event with confirmation dialog
- Status badges (Published/Draft)
- Responsive table with alternating row colors
- Rounded corners on even rows
**Sortable Columns**:
- Event Name
- Type
- Location
- Date
- Status
**Actions**:
- Edit - Redirects to manage_events.php with event_id
- Delete - Removes event and associated files
## Database Schema Changes
### Migration File: `/docs/migrations/001_add_events_tracking_columns.sql`
**Columns Added to events table**:
- `created_by` (int) - References user who created the event
- `published` (tinyint(1)) - Boolean flag for publication status (default 0/false)
- `created_at` (timestamp) - Automatic timestamp when event is created
- `updated_at` (timestamp) - Automatic timestamp updated on modification
**Indexes Added**:
- `idx_date` - For sorting and filtering by date
- `idx_published` - For filtering published/draft events
- `idx_created_by` - For tracking who created events
## Design Patterns
### Follows Trip Management System Architecture
- Same form layout and styling (`.comment-form.bgc-lighter`)
- Same table styling with sortable headers and filters
- Same image upload and validation patterns
- AJAX submission with success/error messaging
- Auto-redirect on successful operation
### Image Organization
```
/assets/images/events/
├── {unique_id}_{original_filename}.jpg (event images)
└── {unique_id}_promo_{original_filename}.jpg (promo images)
```
### Front-end Integration
The existing `/src/pages/events/events.php` displays published events:
- Shows event image, name, location, date, time
- Feature description and full description
- "View Promo" button displays promotional image in modal
## Usage Workflow
### Creating an Event
1. Navigate to `/src/admin/manage_events.php`
2. Fill in all required fields
3. Upload event image
4. Optionally upload promotional image
5. Check "Publish Event" if ready to display
6. Submit form via AJAX
7. Redirected to admin_events.php list view
### Editing an Event
1. Click "Edit" button on admin_events.php
2. Modify any fields
3. Image upload is optional - existing image retained if not changed
4. Update timestamps and user tracking automatic
5. Submit form
6. Redirected back to list view
### Deleting an Event
1. Click "Delete" button on admin_events.php
2. Confirm deletion in dialog
3. Event and associated image files removed from server
4. Page automatically refreshes
### Publishing/Unpublishing
- Toggle "Publish Event" checkbox before saving
- Only published events appear on `/src/pages/events/events.php`
- Draft events hidden from public view
## Security Features
1. **CSRF Token Protection**: All forms include CSRF token validation
2. **Admin-only Access**: `checkAdmin()` function validates user permissions
3. **File Validation**: Image type checking (JPEG, PNG, GIF, WebP)
4. **SQL Injection Prevention**: Prepared statements with parameter binding
5. **XSS Prevention**: `htmlspecialchars()` used for output escaping
## Styling Classes
**Form Container**: `.comment-form.bgc-lighter.z-1.rel.mb-30.rmb-55`
**Action Buttons**: `.btn-edit`, `.btn-delete`
**Status Badges**: `.badge.badge-published`, `.badge.badge-draft`
**Tables**: Uses sortable header styling with visual sort indicators
## Browser Compatibility
- Modern browsers with AJAX/Fetch API support
- JavaScript enabled required for filtering and sorting
- File input accepts image MIME types
## Future Enhancement Opportunities
1. Bulk event operations (bulk delete, publish multiple)
2. Event categories/tags system
3. Event capacity limits with registrations
4. Email notifications for published events
5. Event calendar view
6. Event image gallery (multiple images per event)
7. Recurring events support
8. Event attendee tracking

Some files were not shown because too many files have changed in this diff Show More