Remove index.php CodeIgniter

User
remove index.php from url codeignter
Assistant
In CodeIgniter, by default URLs look like this:

http://example.com/index.php/controller/method

To remove `index.php` from the URL, you need to do two things:

### **1. Update `config.php`**
Open `application/config/config.php` (CodeIgniter 3) or `app/Config/App.php` (CodeIgniter 4) and set:

$config['index_page'] = '';

For CI4:

public string $indexPage = '';

### **2. Add `.htaccess` file in your project root**
If using **Apache**, create an `.htaccess` file in your root (where `index.php` is) with this content:

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteBase /

    # Redirect to non-www (optional)
    # RewriteCond %{HTTP_HOST} ^www.(.*)$ [NC]
    # RewriteRule ^(.*)$ http://%1/$1 [R=301,L]

    # Send everything to index.php
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^(.*)$ index.php/$1 [L]
</IfModule>

### **3. Enable mod_rewrite in Apache**
Run in terminal (if not already enabled):

a2enmod rewrite
systemctl restart apache2

### **4. Allow overrides in Apache config**
Edit your Apache site config (`/etc/apache2/sites-available/000-default.conf` or your vhost):

<Directory /var/www/html>
    AllowOverride All
</Directory>

Then restart Apache:

systemctl restart apache2

✅ Now your URLs will look like:

http://example.com/controller/method

Do you want me to give you the **exact `.htaccess`** depending on whether you’re using **CodeIgniter 3** or **CodeIgniter 4**?

User
# BEGIN cPanel-generated php ini directives, do not edit
# Manual editing of this file may result in unexpected behavior.
# To make changes to this file, use the cPanel MultiPHP INI Editor (Home >> Software >> MultiPHP INI Editor)
# For more information, read our documentation (https://go.cpanel.net/EA4ModifyINI)
# END cPanel-generated php ini directives, do not edit

# BEGIN URL Rewriting

RewriteEngine On

# Removes index.php from URLs
RewriteCond %{THE_REQUEST} ^GET.*index.php [NC]
RewriteRule (.*?)index.php/*(.*) /$1$2 [R=301,L]

# Directs all web requests through the site index file
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php/$1 [L,QSA]

# END URL Rewriting

Assistant
Nice — you’re almost there. A couple of small tweaks will make it rock‑solid and avoid edge‑case loops.

Below are **drop‑in .htaccess** snippets you can use on cPanel, depending on your CodeIgniter version and whether the app lives in a subfolder.

## CodeIgniter 3 (root or subfolder)

**Also set** `application/config/config.php` →
`$config['index_page'] = '';`

**.htaccess (root of the CI app):**

# BEGIN cPanel-generated php ini directives, do not edit
# (leave your cPanel lines as-is)
# END cPanel-generated php ini directives, do not edit

# BEGIN URL Rewriting
<IfModule mod_rewrite.c>
    RewriteEngine On

    # If your app is in a subfolder, change to /your-subfolder/
    RewriteBase /

    # Redirect /index.php/... to /...
    RewriteCond %{THE_REQUEST} s/+index.php[?/ ] [NC]
    RewriteRule ^index.php/?(.*)$ /$1 [R=301,L]

    # Front controller: send everything to index.php, except real files/dirs
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^(.*)$ index.php/$1 [L,QSA]
</IfModule>
# END URL Rewriting

> If the app is in a subfolder (e.g., `example.com/app/`), set `RewriteBase /app/`.

## CodeIgniter 4 (served from **public/**)

**Also set** `app/Config/App.php` →
`public string $indexPage = '';`

**If your domain points to the app root and NOT directly to /public, use this at the project root**:

# BEGIN cPanel-generated php ini directives, do not edit
# (leave your cPanel lines as-is)
# END cPanel-generated php ini directives, do not edit

# BEGIN URL Rewriting
<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteBase /

    # Redirect /index.php/... to clean URL
    RewriteCond %{THE_REQUEST} s/+index.php[?/ ] [NC]
    RewriteRule ^index.php/?(.*)$ /$1 [R=301,L]

    # CI4 front controller is public/index.php
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^(.*)$ public/index.php?/$1 [L,QSA]
</IfModule>
# END URL Rewriting

**If your hosting lets you set the DocumentRoot to `/public`,** then put this simpler file inside `/public/.htaccess` and point the domain there:

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteBase /

    # Clean /index.php
    RewriteCond %{THE_REQUEST} s/+index.php[?/ ] [NC]
    RewriteRule ^index.php/?(.*)$ /$1 [R=301,L]

    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^(.*)$ index.php?/$1 [L,QSA]
</IfModule>

### Common gotchas to check
– **Apache overrides:** In cPanel, ensure your vhost/htaccess allows `AllowOverride All` (usually already true).
– **mod_rewrite enabled:** Typically enabled on cPanel. If not, ask support to enable.
– **Cache:** Clear browser cache after changing rewrite rules to avoid stale redirects.
– **HTTPS/WWW preferences:** Add your preferred canonical redirect *above* the front‑controller rules if needed.

If you tell me your CI version (3 or 4) and whether it’s in a subfolder, I’ll tailor the exact file for your setup.

User
require_once(__DIR__ . '/../includes/fossets_db.php');
this in staging/api/api.php
fossets_db.php in staging/includes
{
"status": "error",
"message": "No such file or directory",
"trace": "#0 /home/fossetts/public_html/staging/includes/fossets_db.php(28): mysqli->__construct('localhost', 'fossetts_ct_adm', Object(SensitiveParameterValue), 'fossetts_ticket…', 3306, '/Applications/M…')n#1 /home/fossetts/public_html/staging/includes/fossets_db.php(371): Fossets_db->connect()n#2 /home/fossetts/public_html/staging/api/events.php(23): Fossets_db->getUpcomingEvents()n#3 {main}"
}
Assistant
The include path is fine — `__DIR__ . '/../includes/fossets_db.php'` correctly resolves from `staging/api/*.php` to `staging/includes/fossets_db.php`.

The error is from **MySQL**, not the require. Your stack trace shows mysqli trying to use a **Mac socket path**:

mysqli->__construct('localhost', 'fossetts_ct_adm', ******, 'fossetts_ticket...', 3306, '/Applications/M...')

On cPanel/Linux that socket doesn’t exist → “No such file or directory”.

### Quick fix (most reliable on cPanel)
Force TCP and **remove the socket argument**:

// in fossets_db.php (where you connect)
$host = '127.0.0.1';        // NOT 'localhost' → forces TCP
$user = 'fossetts_ct_adm';
$pass = '...';
$db   = 'fossetts_ticket...';
$port = 3306;

$mysqli = new mysqli($host, $user, $pass, $db, $port); // no socket arg
if ($mysqli->connect_errno) {
    throw new Exception('DB connect failed: ' . $mysqli->connect_error);
}

Why this works: `localhost` makes PHP try the Unix socket; `127.0.0.1` uses TCP and ignores any wrong socket path.

### If you really want to use a socket
Only pass a socket if it exists on the server:

$socket = ini_get('mysqli.default_socket');           // e.g. /var/lib/mysql/mysql.sock or /tmp/mysql.sock
if ($socket && file_exists($socket)) {
    $mysqli = new mysqli('localhost', $user, $pass, $db, 3306, $socket);
} else {
    $mysqli = new mysqli('127.0.0.1', $user, $pass, $db, 3306);
}

### Sanity checks
– Keep your `require_once(__DIR__ . '/../includes/fossets_db.php');`
– Add a quick guard once:
`if (!file_exists(__DIR__ . '/../includes/fossets_db.php')) die('includes missing');`
– Confirm DB creds in cPanel → MySQL® Databases (user, db name, and that the user is added to the DB).
– If your host uses a **remote DB hostname** (not localhost), use that instead of `127.0.0.1`.

Make that one-line change (`localhost` → `127.0.0.1` and drop the socket param) and you should be good.

User
Assistant
You’ve got two separate issues:

1) You’re forcing a **Mac/MAMP socket** on Linux cPanel → MySQL can’t find it.
2) You’re mixing one `connect()` method with many direct `new mysqli(…)` calls that **bypass** your staging/production settings.

Here’s a clean, drop‑in fix you can paste over your class. It:

– Auto‑selects **remote vs local** without ever using the MAMP socket on Linux.
– Forces **TCP** (`127.0.0.1` or remote host) so no bad socket paths.
– Makes **every DB call** go through one `connect()` (no stray `new mysqli`).
– Fixes the stray **PDO** usage in `orderExistsByFingerprint`.

> Replace only credentials/DB names to your real ones (I left placeholders where sensible).

<?php
class Fossets_db {
    /*** === CONFIG (edit these as needed) === ***/
    // Remote (production/staging) – use this on cPanel
    private $remote = [
        'host' => 'server3.thenet.ie',    // or the host your DB uses
        'user' => 'fossetts_ct_adm',
        'pass' => 'YOUR_SECURE_PASSWORD',
        'name' => 'fossetts_circus2025',
        'port' => 3306,
    ];

    // Local dev (MAMP/XAMPP) – optional
    private $local = [
        'host' => '127.0.0.1',            // use TCP, not 'localhost'
        'user' => 'fossetts_ct_adm',
        'pass' => 'YOUR_LOCAL_PASSWORD',
        'name' => 'fossetts_tickets_staging',
        'port' => 3306,
        // 'socket' => '/Applications/MAMP/tmp/mysql/mysql.sock', // we won't force this
    ];

    // Simple environment toggle: if MAMP socket exists, assume local dev; else remote
    private function isLocalDev(): bool {
        return file_exists('/Applications/MAMP/tmp/mysql/mysql.sock');
    }

    private $log_file;

    public function __construct() {
        $this->log_file = __DIR__ . '/orders-debug.log';
    }

    /*** === SINGLE CONNECTION POINT === ***/
    private function connect(): mysqli {
        // Pick config
        $cfg = $this->isLocalDev() ? $this->local : $this->remote;

        // Always use TCP to avoid wrong sockets
        $host = ($cfg['host'] === 'localhost') ? '127.0.0.1' : $cfg['host'];
        $user = $cfg['user'];
        $pass = $cfg['pass'];
        $name = $cfg['name'];
        $port = (int)$cfg['port'];

        $this->log("DB connect to {$host}:{$port} / {$name} (env=" . ($this->isLocalDev() ? 'local' : 'remote') . ")");

        // Optional: try a known socket only if it actually exists
        $socket = ini_get('mysqli.default_socket');
        if ($socket && @file_exists($socket)) {
            $mysqli = @new mysqli($host, $user, $pass, $name, $port, $socket);
        } else {
            $mysqli = @new mysqli($host, $user, $pass, $name, $port);
        }

        if ($mysqli->connect_errno) {
            $this->log("DB connect failed ({$mysqli->connect_errno}): {$mysqli->connect_error}");
            throw new Exception('Database connection failed: ' . $mysqli->connect_error);
        }

        // Use named timezone (safer if MySQL has time zone tables); if not, ignore errors
        @$mysqli->query("SET time_zone = 'Europe/Dublin'");

        return $mysqli;
    }

    /*** === LOG === ***/
    private function log($message) {
        $date = date('Y-m-d H:i:s');
        @file_put_contents($this->log_file, "[$date] $messagen", FILE_APPEND);
    }

    /*** === QUERIES === ***/
    function getEventData($event_id, $event_date_id = null) {
        try {
            $this->log("getEventData: event_id={$event_id}, event_date_id={$event_date_id}");
            $mysqli = $this->connect();

            $stmt_event = $mysqli->prepare("SELECT * FROM events WHERE event_id = ?");
            $stmt_event->bind_param('i', $event_id);
            $stmt_event->execute();
            $res_event = $stmt_event->get_result();

            $data = new stdClass();
            $data->event = $res_event->fetch_assoc();

            if ($event_date_id) {
                $stmt_dates = $mysqli->prepare("SELECT * FROM events_dates WHERE event_date_id = ? AND event_id = ?");
                $stmt_dates->bind_param('ii', $event_date_id, $event_id);
            } else {
                $stmt_dates = $mysqli->prepare("SELECT * FROM events_dates WHERE event_id = ? AND event_datetime > NOW() ORDER BY event_datetime ASC");
                $stmt_dates->bind_param('i', $event_id);
            }
            $stmt_dates->execute();
            $result_dates = $stmt_dates->get_result();
            $data->event_dates = $result_dates->fetch_all(MYSQLI_ASSOC);

            // Per-showtime extra data
            $ist = new DateTimeZone('Europe/Dublin');
            foreach ($data->event_dates as $k => $date) {
                $edid = (int)$date['event_date_id'];
                $showtime_dt = new DateTime($date['event_datetime'], $ist);
                $now_dt = new DateTime('now', $ist);
                $minutes_until_show = ($showtime_dt->getTimestamp() - $now_dt->getTimestamp()) / 60;

                $data->event_dates[$k]['booking_closed']    = $minutes_until_show <= 30 && $minutes_until_show > 0;
                $data->event_dates[$k]['minutes_until_show'] = $minutes_until_show;
                $data->event_dates[$k]['showtime_timestamp'] = $showtime_dt->getTimestamp();

                // capacities
                $stmt_caps = $mysqli->prepare("SELECT seating_area, capacity FROM event_capacities WHERE event_date_id = ?");
                $stmt_caps->bind_param('i', $edid);
                $stmt_caps->execute();
                $data->event_dates[$k]['capacities'] = $stmt_caps->get_result()->fetch_all(MYSQLI_ASSOC);

                // ticket definitions
                $stmt_defs = $mysqli->prepare("SELECT * FROM event_ticket_definitions WHERE event_date_id = ?");
                $stmt_defs->bind_param('i', $edid);
                $stmt_defs->execute();
                $data->event_dates[$k]['ticket_definitions'] = $stmt_defs->get_result()->fetch_all(MYSQLI_ASSOC);
            }

            // vouchers (hashed codes)
            $stmt_v = $mysqli->prepare("SELECT SHA1(voucher_code) AS voucher_code, voucher_discount, voucher_discount_type FROM vouchers WHERE event_id = ? AND voucher_uses != 0");
            $stmt_v->bind_param('i', $event_id);
            $stmt_v->execute();
            $data->event_vouchers = json_encode($stmt_v->get_result()->fetch_all(MYSQLI_ASSOC));

            $mysqli->close();
            return $data;
        } catch (Exception $e) {
            $this->log('getEventData error: ' . $e->getMessage());
            throw $e;
        }
    }

    function getEventDateData($event_date_id, $ticket_def_id, $num_seats) {
        $mysqli = $this->connect();

        $stmt_def = $mysqli->prepare("SELECT * FROM event_ticket_definitions WHERE id = ?");
        $stmt_def->bind_param('i', $ticket_def_id);
        $stmt_def->execute();
        $ticket_def = $stmt_def->get_result()->fetch_assoc();
        if (!$ticket_def) { $mysqli->close(); return false; }

        $seating_area = $ticket_def['seating_area'];
        $seats_required = $num_seats * (int)$ticket_def['seats_per_ticket'];

        $stmt_cap = $mysqli->prepare("SELECT capacity FROM event_capacities WHERE event_date_id = ? AND seating_area = ?");
        $stmt_cap->bind_param('is', $event_date_id, $seating_area);
        $stmt_cap->execute();
        $row_cap = $stmt_cap->get_result()->fetch_assoc();
        $total_capacity = $row_cap ? (int)$row_cap['capacity'] : 0;

        $stmt_sold = $mysqli->prepare("SELECT SUM(o.ticket_num_child + o.ticket_num_adult + o.ticket_num_senior) AS sold_seats FROM orders o WHERE o.event_date_id = ? AND o.ticket_location = ?");
        $stmt_sold->bind_param('is', $event_date_id, $seating_area);
        $stmt_sold->execute();
        $row_sold = $stmt_sold->get_result()->fetch_assoc();
        $seats_sold = $row_sold ? (int)$row_sold['sold_seats'] : 0;

        $mysqli->close();
        return (($total_capacity - $seats_sold) >= $seats_required) ? $ticket_def : false;
    }

    function getVoucherData($event_id, $voucher_code) {
        $mysqli = $this->connect();
        $query = "SELECT * FROM vouchers
                  WHERE event_id = ?
                  AND LOWER(voucher_code) = LOWER(?)
                  AND (voucher_uses > 0 OR voucher_uses = -1)
                  LIMIT 1";
        $stmt = $mysqli->prepare($query);
        $stmt->bind_param('is', $event_id, $voucher_code);
        $stmt->execute();
        $row = $stmt->get_result()->fetch_assoc() ?: [];
        $mysqli->close();
        return $row;
    }

    function insertOrder($order_info) {
        $mysqli = $this->connect();
        $insert_result = false;

        $this->log("insertOrder payload: " . print_r($order_info, true));

        $voucher_data = $order_info->order_voucher ?? null;
        $order_info->order_voucher = (!empty($voucher_data)) ? ($voucher_data['voucher_code'] ?? '') : '';

        try {
            $mysqli->autocommit(false);

            $stmt = $mysqli->prepare("
                INSERT INTO orders
                (order_datetime, order_code, customer_name, customer_email, customer_phone,
                 event_id, event_date_id, event_venue, event_datetime, ticket_location,
                 ticket_num_price_full, ticket_num_price_child, ticket_num_price_senior,
                 ticket_addons, order_voucher, order_total, order_payment, ticket_breakdown,
                 wheelchair_user_count, order_fingerprint)
                VALUES
                (NOW(), ?, ?, ?, ?,
                 ?, ?, ?, ?, ?,
                 ?, ?, ?,
                 ?, ?, ?, ?, ?,
                 ?, ?)
            ");
            $ticket_breakdown = $order_info->ticket_breakdown ?? '';
            $wheelchair = (int)($order_info->wheelchair_user_count ?? 0);

            $stmt->bind_param(
                'ssssiiisssiiisssssiss',
                $order_info->order_code,
                $order_info->customer_name,
                $order_info->customer_email,
                $order_info->customer_phone,
                $order_info->event_id,
                $order_info->event_date_id,
                $order_info->event_venue,
                $order_info->event_datetime,
                $order_info->ticket_location,
                $order_info->ticket_num_price_full,
                $order_info->ticket_num_price_child,
                $order_info->ticket_num_price_senior,
                $order_info->ticket_addons,
                $order_info->order_voucher,
                $order_info->order_total,
                $order_info->order_payment,
                $ticket_breakdown,
                $wheelchair,
                $order_info->order_fingerprint
            );

            if (!$stmt->execute()) {
                throw new Exception("Order insert failed: " . $stmt->error);
            }

            // Voucher decrement
            if (!empty($voucher_data) && !empty($voucher_data['voucher_id']) && (int)$voucher_data['voucher_uses'] > 0) {
                $stmtV = $mysqli->prepare("UPDATE vouchers SET voucher_uses = voucher_uses - 1 WHERE voucher_id = ?");
                $stmtV->bind_param('i', $voucher_data['voucher_id']);
                if (!$stmtV->execute()) {
                    throw new Exception("Voucher update failed: " . $stmtV->error);
                }
            }

            $mysqli->commit();
            $insert_result = true;
        } catch (Exception $e) {
            $this->log("insertOrder exception: " . $e->getMessage());
            $mysqli->rollback();
            throw $e;
        } finally {
            $mysqli->autocommit(true);
            $mysqli->close();
        }

        return $insert_result;
    }

    function getUpcomingEvents($limit = false) {
        try {
            $mysqli = $this->connect();
            $sql = "SELECT e.*, ed.event_date_id, ed.event_datetime, ed.showtime_status
                    FROM events e
                    INNER JOIN events_dates ed ON e.event_id = ed.event_id
                    WHERE e.status = 'published' AND ed.event_datetime > NOW()
                    ORDER BY e.event_id ASC, ed.event_datetime ASC";
            if ($limit && is_numeric($limit)) {
                $sql .= " LIMIT " . intval($limit);
            }

            $res = $mysqli->query($sql);
            if (!$res) throw new Exception('Error fetching upcoming events: ' . $mysqli->error);

            $events = [];
            $ist = new DateTimeZone('Europe/Dublin');

            while ($row = $res->fetch_assoc()) {
                $eid = (int)$row['event_id'];
                if (!isset($events[$eid])) {
                    $events[$eid] = [
                        'event_id'    => $row['event_id'],
                        'venue_title' => $row['venue_title'],
                        'venue_full'  => $row['venue_full'],
                        'area'        => $row['area'],
                        'logo'        => $row['logo'],
                        'dates'       => []
                    ];
                }
                $showtime_dt = new DateTime($row['event_datetime'], $ist);
                $now_dt = new DateTime('now', $ist);
                $minutes_until_show = ($showtime_dt->getTimestamp() - $now_dt->getTimestamp()) / 60;

                $events[$eid]['dates'][] = [
                    'event_date_id'      => $row['event_date_id'],
                    'event_datetime'     => $row['event_datetime'],
                    'showtime_status'    => $row['showtime_status'] ?? 'active',
                    'booking_closed'     => $minutes_until_show <= 30 && $minutes_until_show > 0,
                    'minutes_until_show' => $minutes_until_show,
                    'showtime_timestamp' => $showtime_dt->getTimestamp()
                ];
            }

            $mysqli->close();
            return array_values($events);
        } catch (Exception $e) {
            $this->log("getUpcomingEvents error: " . $e->getMessage());
            throw $e;
        }
    }

    function insertEvent($event_slug, $event_venue, $event_venue_full, $dates) {
        $mysqli = $this->connect();
        $ok = true;

        try {
            $mysqli->autocommit(false);

            $stmtE = $mysqli->prepare("INSERT INTO events (slug, venue_title, venue_full) VALUES (?, ?, ?)");
            $stmtE->bind_param('sss', $event_slug, $event_venue, $event_venue_full);
            if (!$stmtE->execute()) throw new Exception($stmtE->error);

            $event_id = $mysqli->insert_id;
            $stmtD = $mysqli->prepare("INSERT INTO events_dates (event_id, event_datetime) VALUES (?, ?)");
            foreach ($dates as $d) {
                $stmtD->bind_param('is', $event_id, $d);
                if (!$stmtD->execute()) { $ok = false; break; }
            }

            if ($ok) $mysqli->commit(); else $mysqli->rollback();
        } catch (Exception $e) {
            $ok = false;
            $mysqli->rollback();
        }

        $mysqli->autocommit(true);
        $mysqli->close();
        return $ok;
    }

    function getOrders($event_date_id) {
        $mysqli = $this->connect();
        $stmt = $mysqli->prepare("SELECT * FROM orders WHERE event_date_id = ?");
        $stmt->bind_param('i', $event_date_id);
        $stmt->execute();
        $rows = $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
        $mysqli->close();
        return $rows ?: [];
    }

    function getOrder($order_id) {
        $mysqli = $this->connect();
        $stmt = $mysqli->prepare("SELECT * FROM orders WHERE order_id = ?");
        $stmt->bind_param('i', $order_id);
        $stmt->execute();
        $row = $stmt->get_result()->fetch_assoc() ?: [];
        $mysqli->close();
        return $row;
    }

    function getOrderByCode($order_code) {
        $mysqli = $this->connect();
        $stmt = $mysqli->prepare("SELECT * FROM orders WHERE order_code = ?");
        $stmt->bind_param('s', $order_code);
        $stmt->execute();
        $row = $stmt->get_result()->fetch_assoc() ?: [];
        $mysqli->close();
        return $row;
    }

    public function real_escape_string($string) {
        $mysqli = $this->connect();
        $escaped = $mysqli->real_escape_string($string ?? '');
        $mysqli->close();
        return $escaped;
    }

    function get_all_addons() {
        $mysqli = $this->connect();
        $res = $mysqli->query("SELECT * FROM addons ORDER BY id ASC");
        if (!$res) { $err = $mysqli->error; $mysqli->close(); throw new Exception('Failed to get add-ons: ' . $err); }

        $addons = [];
        while ($row = $res->fetch_object()) {
            if (!empty($row->thumb)) {
                $thumb = ltrim($row->thumb, "@ tnrx0B");
                $row->thumb = (strpos($thumb, '/elements/') === 0) ? $thumb : '/elements/' . ltrim($thumb, '/');
            }
            $addons[] = $row;
        }
        $mysqli->close();
        return $addons;
    }

    // Fixed: use mysqli instead of undefined PDO handle
    public function orderExistsByFingerprint(string $fingerprint): bool {
        try {
            $mysqli = $this->connect();
            $stmt = $mysqli->prepare("SELECT 1 FROM orders WHERE order_fingerprint = ? LIMIT 1");
            $stmt->bind_param('s', $fingerprint);
            $stmt->execute();
            $exists = (bool)$stmt->get_result()->fetch_row();
            $mysqli->close();
            return $exists;
        } catch (Exception $e) {
            $this->log("orderExistsByFingerprint error: " . $e->getMessage());
            return false;
        }
    }
}
?>

### Why this fixes your error
– Your trace showed PHP trying to open a **Mac socket path** on Linux. By forcing **TCP** and removing the hardcoded socket, the connection works on cPanel.
– All methods now share the same **env‑aware** connection logic.
– The PDO call is gone (it referenced an undefined `$this->db`).

If you want to keep your current file and only do the **minimal change**, do this in your existing class:

– Change `$db_host = 'localhost'` → `'127.0.0.1'`
– **Remove** the `$db_socket` from the `new mysqli(…)` call in `connect()`.
– Replace every `new mysqli(…)` in other methods with `$this->connect()` (and close when done).
– Fix `orderExistsByFingerprint()` to use `mysqli` as shown above.

That’s it.

User
Error initializing Stripe Express Checkout: VersionError: Unsupported on version [basil]: Can not provide apiVersion to Stripe()
at $_ (stripe.js:1:580766)
at new e (stripe.js:1:583618)
at ah (stripe.js:1:639807)
at initializeStripe (buy-tickets.js?ver=1.10.10:1069:18)
at goToStep (buy-tickets.js?ver=1.10.10:877:13)
at buy-tickets.js?ver=1.10.10:1017:21
Assistant
You’re passing `apiVersion` to the **browser** Stripe initializer. That’s no longer allowed in Stripe.js, hence:

VersionError: ... Can not provide apiVersion to Stripe()

### Fix (front‑end)
Find where you do this (your stack shows `initializeStripe` in `buy-tickets.js:1069`):

// ❌ remove apiVersion here – not allowed in browser
const stripe = Stripe(PUBLISHABLE_KEY, {
  // apiVersion: '2023-10-16',  <-- remove this
  // other allowed options are fine (locale, betas, apiClient, advancedFraudSignals)
});

Use:

const stripe = Stripe(PUBLISHABLE_KEY, {
  locale: 'auto',
  // advancedFraudSignals: true, // optional, allowed
  // betas: ['some_beta']        // only if Stripe doc told you to
});

If you’re using `@stripe/stripe-js`:

import { loadStripe } from '@stripe/stripe-js';
// ❌ don’t pass apiVersion here either
const stripe = await loadStripe(PUBLISHABLE_KEY, { locale: 'auto' });

### Where to set the API version (server‑side only)
Move the version pin to your backend SDK:

**Node:**

import Stripe from 'stripe';
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
  apiVersion: '2024-06-20',
});

**PHP:**

StripeStripe::setApiKey('sk_live_...');
StripeStripe::setApiVersion('2024-06-20');
// or
$stripe = new StripeStripeClient([
    'api_key' => 'sk_live_...',
    'stripe_version' => '2024-06-20',
]);

### Extra checks for “Express Checkout” flows
– Make sure you’re on **HTTPS** and your publishable key is correct.
– For Apple Pay: verify your domain in the Stripe Dashboard.
– For Google Pay: enable it in the Dashboard or via the Payment Element/Payment Request Button config.
– If you’re using the **Payment Element** or **Payment Request Button**, initializing `elements` stays the same after removing `apiVersion`.

Once you remove that one option from `Stripe()` and pin the version on the server, the error will disappear.

User
The Content Security Policy directive 'frame-ancestors' is ignored when delivered via a element.
Assistant
Right—`frame-ancestors` **doesn’t work in a “ tag**. Browsers only honor it when CSP is sent as an **HTTP response header**. That’s why you see the warning.

Here’s how to set it properly (pick your stack):

### What to send

Content-Security-Policy: frame-ancestors 'self' https://partner.example https://*.trusted.com;

– This controls **who can embed your site in an iframe**.
– Use `'none'` to block all; use `'self'` for only same‑origin.

### Apache (.htaccess or vhost)

<IfModule mod_headers.c>
  Header always set Content-Security-Policy "frame-ancestors 'self' https://partner.example"
</IfModule>

### Nginx (server block)

add_header Content-Security-Policy "frame-ancestors 'self' https://partner.example" always;

### PHP (very early, before any output)

header("Content-Security-Policy: frame-ancestors 'self' https://partner.example");

### WordPress (functions.php or a small must‑use plugin)

add_action('send_headers', function () {
    header("Content-Security-Policy: frame-ancestors 'self' https://partner.example");
});

### Cloudflare
Use **Rules → Transform Rules → HTTP Response Header Modification** to add:
– Header name: `Content-Security-Policy`
– Value: `frame-ancestors 'self' https://partner.example`

### Gotchas
– Remove or align old `X-Frame-Options` headers. It’s deprecated and can conflict (e.g., `DENY` will block embedding even if CSP allows it on some browsers).
– Don’t also put CSP in “—it’s fine for some directives, but **`frame-ancestors` is ignored** there.
– Verify in DevTools → Network → (your page) → Response Headers.
– If you need to **test only**, use the report‑only header:
`Content-Security-Policy-Report-Only: frame-ancestors …`

If you tell me your hosting (cPanel/Apache vs Nginx) and whether it’s WordPress, I’ll give you the exact snippet to paste.

User
Header set Content-Security-Policy "frame-ancestors 'self' https://pay.google.com"
Header unset ETag
Header append Vary: Accept-Encoding

alsso add https://js.stripe.com

Assistant
Got it 👍

For **`frame-ancestors`**, remember: it controls **who can embed *your site*** inside an “.
– `https://pay.google.com` = Google Pay embedding your checkout.
– Stripe’s **`https://js.stripe.com`** is just where you load JS from — it does **not** embed your site. You don’t need it in `frame-ancestors`.

👉 If your actual goal is “allow my site to be embedded by Stripe’s Checkout / Google Pay,” then you only whitelist the domains that actually put *your site* inside their iframe. Stripe Checkout doesn’t embed your site — it iframes **the other way around** (you embed Stripe’s iframe). So `frame-ancestors` for Stripe isn’t needed.

But if you still want to allow both (e.g. you know Stripe needs to embed something of yours), the Apache snippet would be:

<IfModule mod_headers.c>
    # Allow only self, Google Pay, and Stripe to embed your pages
    Header set Content-Security-Policy "frame-ancestors 'self' https://pay.google.com https://js.stripe.com"

    # Other headers you had
    Header unset ETag
    Header append Vary "Accept-Encoding"
</IfModule>

✅ **Alternative (cleaner, production-style):**

<IfModule mod_headers.c>
    Header always set Content-Security-Policy "frame-ancestors 'self' https://pay.google.com"
    Header unset ETag
    Header append Vary "Accept-Encoding"
</IfModule>

– Keep only `pay.google.com` in `frame-ancestors`.
– You still need to load Stripe’s JS via **`script-src`** in your CSP, not `frame-ancestors`. Example:

Header always set Content-Security-Policy "default-src 'self'; script-src 'self' https://js.stripe.com; frame-ancestors 'self' https://pay.google.com"

👉 Question for you:
Do you want a **full CSP** (covering `script-src`, `style-src`, etc. with Stripe + Google Pay) or just to patch `frame-ancestors` for iframe embedding?

Leave a Comment

Your email address will not be published. Required fields are marked *