# ============================================================================
#  PPPHP — project-root .htaccess
#
#  WHY THIS FILE EXISTS
#  Some hosts (many cPanel / shared plans) will NOT let you point the document
#  root at the app's /public folder. On those hosts the document root is the
#  PROJECT ROOT, so this file does two jobs:
#     1. Funnel every web request into /public/ so the front controller runs
#        (this is what makes simply typing the bare domain land the app).
#     2. Hard-block direct web access to everything private at the project root.
#
#  When the document root already IS /public (the recommended setup), this file
#  sits ABOVE the document root and Apache never reads it — so it is completely
#  inert there, and safe to ship everywhere.
#
#  It hard-codes NO domain, so the identical file works on every host, including
#  every secondary / backup server. Do NOT add a RewriteCond on %{HTTP_HOST}.
#
#  NOTE: this file is NOT overwritten by platform updates (it is intentionally
#  excluded from release bundles), so your cPanel PHP block and any local edits
#  survive upgrades — but it also means a fix to it must be applied by hand.
# ============================================================================

# No directory listings; no content-negotiation guessing of extensions.
Options -Indexes -MultiViews
DirectoryIndex index.php

# ---------------------------------------------------------------------------
# 1) Route all traffic into /public/
#
#    Targets are RELATIVE ("public/…") so they resolve against THIS directory —
#    the rules work whether the project root is the document root or a subfolder.
#
#    WHY THREE RULES (and not one blanket "rewrite to public/$1"):
#      • The BARE DOMAIN must reach the front controller directly. Rewriting the
#        empty root to the public/ *directory* and hoping DirectoryIndex then
#        finds index.php loses to any leftover host placeholder page (the classic
#        "webserver is functioning normally" index.html) — because mod_dir runs
#        after mod_rewrite. Sending routes straight to public/index.php avoids it.
#      • A route whose NAME matches a real private folder at the project root
#        (e.g. the /projects page vs the projects/ storage folder) cannot be
#        rewritten past that folder: its deny-all .htaccess returns 403 in an
#        EARLIER Apache phase (authorization) than the funnel (fixup), and root
#        rewrite rules are not inherited into sub-folders. Routing everything to
#        the front controller sidesteps the collision entirely, and the private
#        folder can never be served in place of a page.
#
#    The app keeps its base URL at /public, so its own links stay under /public
#    and never address a bare, colliding path. Net effect: typing the bare domain
#    lands you on the app (it redirects to /public/… for the actual pages).
# ---------------------------------------------------------------------------
<IfModule mod_rewrite.c>
    RewriteEngine On

    # 1a) Requests already inside /public are served as-is (direct access still works).
    RewriteRule ^public/ - [L]

    # 1b) Genuine STATIC files that physically live under /public → serve the real
    #     file (assets, service worker, icons, web manifest, robots, the installer,
    #     and ACME / SSL validation). Add any other truly-static top-level file here.
    RewriteRule ^(assets/.*|sw\.js|favicon\.ico|manifest\.webmanifest|apple-touch-icon\.png|robots\.txt|install\.php|\.well-known/.*)$ public/$1 [L,NC]

    # 1c) EVERYTHING ELSE — the bare domain and every application route — goes to the
    #     front controller. The app reads the ORIGINAL request URL to route, so
    #     dropping the path here is fine. The already-rewritten /public/index.php is
    #     caught by rule 1a on the next pass, so there is no loop.
    RewriteRule ^.*$ public/index.php [L]
</IfModule>

# ---------------------------------------------------------------------------
# 2) Defense-in-depth for the case where mod_rewrite is UNAVAILABLE.
#    When mod_rewrite is on, rule 1 already routes everything through /public, so
#    this never triggers. The PRIMARY, rewrite-independent guard for secrets is
#    the deny-all .htaccess the platform keeps inside config/, storage/, projects/,
#    bin/ and bootstrap/ (self-healed on every boot).
#
#    These filename patterns never occur under /public, so denying them here is
#    safe even though the rule is inherited by /public.
# ---------------------------------------------------------------------------
<FilesMatch "(?i)(^\.env|^\.git|\.(sql|sqlite|sqlite3|db|log|bak|old|orig|swp|key|pem|p12|pfx|crt)$|^server-key.*\.json$|^composer\.(json|lock)$|^package(-lock)?\.json$)">
    <IfModule mod_authz_core.c>
        Require all denied
    </IfModule>
    <IfModule !mod_authz_core.c>
        Order allow,deny
        Deny from all
    </IfModule>
</FilesMatch>

# ---------------------------------------------------------------------------
#  NOTE for cPanel users: cPanel's MultiPHP Manager appends its own
#  "# php -- BEGIN cPanel-generated handler" block below this line. Leave that
#  block in place — it selects your PHP version and is managed by cPanel. The
#  platform never ships or overwrites it.
# ---------------------------------------------------------------------------
