 <?php
// ============================================================
// HTACCESS FORCE REMOVER v3
// - Scan recursive dari folder script
// - Hapus satu / semua .htaccess
// - Aman jika chown / POSIX disabled
// - Restore permission directory setelah percobaan
// - Tampilkan detail file yang gagal
// ============================================================

@set_time_limit(0);
@ini_set('max_execution_time', '0');

$start_directory = __DIR__;


// ============================================================
// SCAN RECURSIVE
// ============================================================

function scanRecursive($dir, &$htaccess_files, &$skipped_dirs)
{
    $items = @scandir($dir);

    if ($items === false) {
        $skipped_dirs[] = $dir;
        return;
    }

    foreach ($items as $item) {

        if ($item === '.' || $item === '..') {
            continue;
        }

        $path = $dir . DIRECTORY_SEPARATOR . $item;

        // Jangan ikuti symbolic link.
        if (is_link($path)) {
            continue;
        }

        if (is_dir($path)) {

            scanRecursive(
                $path,
                $htaccess_files,
                $skipped_dirs
            );

        } elseif ($item === '.htaccess') {

            $perms = @fileperms($path);

            $htaccess_files[] = [
                'path' => $path,

                'size' => (int) @filesize($path),

                'mtime' => (int) @filemtime($path),

                'perms' => $perms !== false
                    ? substr(sprintf('%o', $perms), -4)
                    : '????',

                'dir_writable' => is_writable(dirname($path))
            ];
        }
    }
}


// ============================================================
// AMBIL ERROR TERAKHIR
// ============================================================

function getLastPhpError($default = 'Unknown error')
{
    $error = error_get_last();

    if (
        is_array($error) &&
        isset($error['message']) &&
        $error['message'] !== ''
    ) {
        return $error['message'];
    }

    return $default;
}


// ============================================================
// FORCE DELETE
// ============================================================

function forceDelete($file_path)
{
    $result = [
        'success' => false,
        'method'  => 'none',
        'error'   => ''
    ];


    // --------------------------------------------------------
    // VALIDASI FILE
    // --------------------------------------------------------

    if (
        !is_file($file_path) &&
        !is_link($file_path)
    ) {
        $result['error'] = 'File tidak ditemukan.';
        return $result;
    }


    $parent = dirname($file_path);


    // --------------------------------------------------------
    // SIMPAN PERMISSION AWAL
    // --------------------------------------------------------

    $filePermRaw = @fileperms($file_path);
    $dirPermRaw  = @fileperms($parent);


    $originalFilePerm =
        ($filePermRaw !== false)
            ? ($filePermRaw & 0777)
            : null;


    $originalDirPerm =
        ($dirPermRaw !== false)
            ? ($dirPermRaw & 0777)
            : null;


    // --------------------------------------------------------
    // METODE 1
    // UNLINK LANGSUNG
    // --------------------------------------------------------

    if (@unlink($file_path)) {

        $result['success'] = true;
        $result['method']  = 'direct';

        return $result;
    }


    $result['error'] =
        getLastPhpError('Direct unlink gagal');


    // --------------------------------------------------------
    // METODE 2
    // CHMOD FILE 0644
    // --------------------------------------------------------

    if (function_exists('chmod')) {

        @chmod($file_path, 0644);

        clearstatcache(true, $file_path);

        if (@unlink($file_path)) {

            $result['success'] = true;
            $result['method']  = 'chmod 0644';

            return $result;
        }
    }


    // --------------------------------------------------------
    // METODE 3
    // CHMOD FILE 0777
    // --------------------------------------------------------

    if (function_exists('chmod')) {

        @chmod($file_path, 0777);

        clearstatcache(true, $file_path);

        if (@unlink($file_path)) {

            $result['success'] = true;
            $result['method']  = 'chmod 0777';

            return $result;
        }
    }


    // --------------------------------------------------------
    // METODE 4
    // CHOWN
    //
    // HANYA dijalankan kalau:
    // - chown tersedia
    // - posix_geteuid tersedia
    // - posix_getpwuid tersedia
    //
    // try/catch supaya tidak membuat seluruh script mati.
    // --------------------------------------------------------

    if (
        function_exists('chown') &&
        function_exists('posix_geteuid') &&
        function_exists('posix_getpwuid')
    ) {

        try {

            $euid = @posix_geteuid();

            $user = @posix_getpwuid($euid);


            if (
                is_array($user) &&
                isset($user['name']) &&
                $user['name'] !== ''
            ) {

                @chown(
                    $file_path,
                    $user['name']
                );


                if (function_exists('chmod')) {
                    @chmod($file_path, 0644);
                }


                clearstatcache(
                    true,
                    $file_path
                );


                if (@unlink($file_path)) {

                    $result['success'] = true;

                    $result['method'] =
                        'chown + chmod';

                    return $result;
                }
            }

        } catch (Throwable $e) {

            // Abaikan.
            // Lanjut ke metode berikutnya.
        }
    }


    // --------------------------------------------------------
    // METODE 5
    // BUAT PARENT DIRECTORY WRITABLE SEMENTARA
    // --------------------------------------------------------

    if (function_exists('chmod')) {

        @chmod(
            $parent,
            0777
        );


        @chmod(
            $file_path,
            0644
        );


        clearstatcache(
            true,
            $file_path
        );


        $deleted =
            @unlink($file_path);


        // PENTING:
        // restore permission parent.
        if ($originalDirPerm !== null) {

            @chmod(
                $parent,
                $originalDirPerm
            );
        }


        if ($deleted) {

            $result['success'] = true;

            $result['method'] =
                'temporary parent chmod';

            return $result;
        }
    }


    // --------------------------------------------------------
    // GAGAL
    // RESTORE PERMISSION FILE
    // --------------------------------------------------------

    if (
        file_exists($file_path) &&
        $originalFilePerm !== null &&
        function_exists('chmod')
    ) {

        @chmod(
            $file_path,
            $originalFilePerm
        );
    }


    // Restore parent juga untuk memastikan.
    if (
        $originalDirPerm !== null &&
        function_exists('chmod')
    ) {

        @chmod(
            $parent,
            $originalDirPerm
        );
    }


    clearstatcache(
        true,
        $file_path
    );


    $result['error'] =
        getLastPhpError(
            'Tidak dapat menghapus file. Kemungkinan ownership, ACL, permission parent, atau immutable.'
        );


    return $result;
}


// ============================================================
// HANDLE ACTION
// ============================================================

$message = '';


if (
    isset($_SERVER['REQUEST_METHOD']) &&
    $_SERVER['REQUEST_METHOD'] === 'POST'
) {

    $base = realpath($start_directory);


    // --------------------------------------------------------
    // DELETE ALL
    // --------------------------------------------------------

    if (
        isset($_POST['delete_all']) &&
        $_POST['delete_all'] === '1'
    ) {

        $list = [];
        $skipped = [];


        scanRecursive(
            $start_directory,
            $list,
            $skipped
        );


        $ok = 0;
        $fail = 0;
        $details = [];


        foreach ($list as $f) {

            $r = forceDelete(
                $f['path']
            );


            $details[] = [
                'path'   => $f['path'],
                'result' => $r
            ];


            if ($r['success']) {
                $ok++;
            } else {
                $fail++;
            }
        }


        $message =
            '<div class="msg ' .
            ($fail === 0 ? 'success' : 'warning') .
            '">';


        $message .=
            '<strong>Hapus semua selesai:</strong> ' .
            $ok .
            ' berhasil';


        if ($fail > 0) {

            $message .=
                ', ' .
                $fail .
                ' gagal';
        }


        $message .= '</div>';


        // Detail gagal
        if ($fail > 0) {

            $message .=
                '<div class="msg error">' .
                '<strong>Detail kegagalan:</strong><br>';


            foreach ($details as $d) {

                if (!$d['result']['success']) {

                    $message .=
                        '✗ <code>' .
                        htmlspecialchars(
                            $d['path'],
                            ENT_QUOTES,
                            'UTF-8'
                        ) .
                        '</code> — ' .
                        htmlspecialchars(
                            $d['result']['error'],
                            ENT_QUOTES,
                            'UTF-8'
                        ) .
                        '<br>';
                }
            }


            $message .= '</div>';
        }


    // --------------------------------------------------------
    // DELETE SINGLE
    // --------------------------------------------------------

    } elseif (
        !empty($_POST['delete_single'])
    ) {

        $target =
            $_POST['delete_single'];


        $targetParent =
            realpath(dirname($target));


        $valid = false;


        if (
            $target &&
            $base &&
            $targetParent &&
            basename($target) === '.htaccess'
        ) {

            $basePrefix =
                rtrim(
                    $base,
                    DIRECTORY_SEPARATOR
                ) .
                DIRECTORY_SEPARATOR;


            $parentPrefix =
                rtrim(
                    $targetParent,
                    DIRECTORY_SEPARATOR
                ) .
                DIRECTORY_SEPARATOR;


            if (
                $targetParent === $base ||
                strpos(
                    $parentPrefix,
                    $basePrefix
                ) === 0
            ) {

                $valid = true;
            }
        }


        if ($valid) {

            $r =
                forceDelete($target);


            if ($r['success']) {

                $message =
                    '<div class="msg success">' .
                    '✅ <code>' .
                    htmlspecialchars(
                        $target,
                        ENT_QUOTES,
                        'UTF-8'
                    ) .
                    '</code> — dihapus (' .
                    htmlspecialchars(
                        $r['method'],
                        ENT_QUOTES,
                        'UTF-8'
                    ) .
                    ')' .
                    '</div>';

            } else {

                $message =
                    '<div class="msg error">' .
                    '❌ <code>' .
                    htmlspecialchars(
                        $target,
                        ENT_QUOTES,
                        'UTF-8'
                    ) .
                    '</code><br>' .

                    'Error: ' .
                    htmlspecialchars(
                        $r['error'],
                        ENT_QUOTES,
                        'UTF-8'
                    ) .

                    '<br><small>' .
                    'Kemungkinan file/folder dimiliki user lain, ACL membatasi akses, atau file memiliki immutable flag.' .
                    '</small>' .

                    '</div>';
            }

        } else {

            $message =
                '<div class="msg error">' .
                '⚠️ Path tidak valid.' .
                '</div>';
        }
    }
}


// ============================================================
// SCAN UNTUK DISPLAY
// ============================================================

$htaccess_files = [];
$skipped_dirs = [];


scanRecursive(
    $start_directory,
    $htaccess_files,
    $skipped_dirs
);


usort(
    $htaccess_files,
    function ($a, $b) {

        return strcmp(
            $a['path'],
            $b['path']
        );
    }
);


// ============================================================
// STATISTIK
// ============================================================

$count_444 = 0;
$count_555 = 0;
$count_dirlock = 0;


foreach ($htaccess_files as $f) {

    if ($f['perms'] === '0444') {
        $count_444++;
    }

    if ($f['perms'] === '0555') {
        $count_555++;
    }

    if (!$f['dir_writable']) {
        $count_dirlock++;
    }
}

?>
<!DOCTYPE html>
<html lang="id">

<head>

<meta charset="UTF-8">

<meta
    name="viewport"
    content="width=device-width, initial-scale=1"
>

<title>
HTACCESS Force Remover v3
</title>


<style>

:root {
    --bg: #0f1117;
    --card: #1a1d27;
    --border: #2a2d39;
    --text: #cdd6f4;
    --muted: #6c7086;
    --red: #f38ba8;
    --green: #a6e3a1;
    --yellow: #f9e2af;
    --accent: #cba6f7;
}

* {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

body {

    font-family:
        Inter,
        "Segoe UI",
        Arial,
        sans-serif;

    background: var(--bg);
    color: var(--text);

    min-height: 100vh;

    padding: 24px;
}

.container {

    max-width: 1200px;

    margin: 0 auto;
}

.header {

    display: flex;

    align-items: center;

    justify-content:
        space-between;

    flex-wrap: wrap;

    gap: 16px;

    margin-bottom: 24px;

    padding: 20px 24px;

    background: var(--card);

    border:
        1px solid
        var(--border);

    border-radius: 12px;
}

.header h1 {

    font-size: 1.4rem;

    font-weight: 700;

    display: flex;

    align-items: center;

    gap: 10px;
}

.header .subtitle {

    color: var(--muted);

    font-size: .85rem;

    margin-top: 4px;

    word-break: break-all;
}

.msg {

    padding: 14px 18px;

    border-radius: 8px;

    margin-bottom: 16px;

    font-weight: 600;

    font-size: .9rem;

    line-height: 1.6;

    word-break: break-word;
}

.msg.success {

    background: #1a3620;

    border:
        1px solid
        #2d5a35;

    color: var(--green);
}

.msg.error {

    background: #3a1a20;

    border:
        1px solid
        #5a2d35;

    color: var(--red);
}

.msg.warning {

    background: #3a3015;

    border:
        1px solid
        #5a4a20;

    color: var(--yellow);
}

.stats {

    display: grid;

    grid-template-columns:
        repeat(
            auto-fit,
            minmax(150px, 1fr)
        );

    gap: 12px;

    margin-bottom: 20px;
}

.stat-card {

    background: var(--card);

    border:
        1px solid
        var(--border);

    border-radius: 10px;

    padding: 16px;

    text-align: center;
}

.stat-card .number {

    font-size: 2rem;

    font-weight: 800;

    line-height: 1;
}

.stat-card .label {

    font-size: .75rem;

    color: var(--muted);

    margin-top: 6px;

    text-transform:
        uppercase;

    letter-spacing: .05em;
}

.stat-card.danger .number {
    color: var(--red);
}

.stat-card.warning .number {
    color: var(--yellow);
}

.stat-card.info .number {
    color: var(--accent);
}

.actions-row {

    display: flex;

    gap: 10px;

    flex-wrap: wrap;

    margin-bottom: 20px;
}

.btn {

    padding: 9px 18px;

    border: none;

    border-radius: 6px;

    font-weight: 700;

    font-size: .8rem;

    cursor: pointer;

    text-transform:
        uppercase;

    letter-spacing: .04em;
}

.btn-danger {

    background: var(--red);

    color: #11111b;
}

.btn-danger:hover {
    filter: brightness(1.15);
}

.btn-outline {

    background: transparent;

    border:
        1px solid
        var(--border);

    color: var(--text);
}

.btn-outline:hover {
    background: var(--border);
}

.btn-sm {

    padding: 5px 12px;

    font-size: .7rem;
}

.table-wrap {

    background: var(--card);

    border:
        1px solid
        var(--border);

    border-radius: 12px;

    overflow-x: auto;
}

table {

    width: 100%;

    border-collapse:
        collapse;

    font-size: .85rem;
}

thead th {

    background: #11131c;

    color: var(--muted);

    font-weight: 600;

    font-size: .72rem;

    text-transform:
        uppercase;

    letter-spacing: .06em;

    padding: 12px 14px;

    text-align: left;

    border-bottom:
        1px solid
        var(--border);
}

tbody td {

    padding: 10px 14px;

    border-bottom:
        1px solid
        var(--border);

    vertical-align:
        middle;
}

tbody tr:last-child td {
    border-bottom: none;
}

tbody tr:hover {
    background:
        rgba(255,255,255,.02);
}

.path-cell {

    font-family:
        monospace;

    font-size: .8rem;

    word-break: break-all;

    color: var(--accent);
}

.perm-digits {

    font-family:
        monospace;

    font-weight: 700;

    letter-spacing: .1em;
}

.badge {

    display: inline-block;

    padding: 3px 10px;

    border-radius: 20px;

    font-size: .7rem;

    font-weight: 700;

    text-transform:
        uppercase;
}

.badge-ok {

    background: #1a3620;

    color: var(--green);
}

.badge-warn {

    background: #3a3015;

    color: var(--yellow);
}

.empty {

    padding: 60px 20px;

    text-align: center;

    color: var(--muted);
}

.empty .big-icon {

    font-size: 3rem;

    margin-bottom: 12px;
}

.note-box {

    margin-top: 16px;

    padding: 12px 16px;

    background: #1a1d2e;

    border-left:
        3px solid
        var(--yellow);

    border-radius:
        0 6px 6px 0;

    font-size: .8rem;

    color: var(--muted);

    line-height: 1.6;

    word-break: break-word;
}

code {
    color: var(--text);
}

</style>

</head>


<body>

<div class="container">


<div class="header">

    <div>

        <h1>
            🛡️ HTACCESS Force Remover
            <span
                style="
                    color:var(--muted);
                    font-size:.8rem
                "
            >
                v3
            </span>
        </h1>

        <p class="subtitle">
            📁
            <?php
            echo htmlspecialchars(
                $start_directory,
                ENT_QUOTES,
                'UTF-8'
            );
            ?>
        </p>

    </div>


    <div
        style="
            color:var(--muted);
            font-size:.8rem
        "
    >

        Total:

        <strong
            style="color:var(--text)"
        >
            <?php
            echo count(
                $htaccess_files
            );
            ?>
        </strong>

        .htaccess

    </div>

</div>


<?php echo $message; ?>


<div class="stats">

    <div class="stat-card info">

        <div class="number">
            <?php
            echo count(
                $htaccess_files
            );
            ?>
        </div>

        <div class="label">
            Total Ditemukan
        </div>

    </div>


    <div class="stat-card danger">

        <div class="number">
            <?php
            echo
                $count_444 +
                $count_555;
            ?>
        </div>

        <div class="label">
            Read-Only File
        </div>

    </div>


    <div class="stat-card warning">

        <div class="number">
            <?php
            echo $count_dirlock;
            ?>
        </div>

        <div class="label">
            Dir Locked
        </div>

    </div>

</div>


<?php if (count($htaccess_files) > 0): ?>

<div class="actions-row">

    <form
        method="POST"
        onsubmit="
            return confirm(
                'HAPUS SEMUA .htaccess?\n\nTindakan ini tidak bisa dibatalkan.'
            );
        "
    >

        <input
            type="hidden"
            name="delete_all"
            value="1"
        >

        <button
            type="submit"
            class="btn btn-danger"
        >
            🗑 Hapus Semua
            (<?php
                echo count(
                    $htaccess_files
                );
            ?>)
        </button>

    </form>


    <button
        type="button"
        class="btn btn-outline"
        onclick="location.reload();"
    >
        🔄 Refresh
    </button>

</div>

<?php endif; ?>


<div class="table-wrap">

<table>

<thead>

<tr>

    <th>#</th>

    <th>Path</th>

    <th>Izin</th>

    <th>Ukuran</th>

    <th>Status</th>

    <th>Aksi</th>

</tr>

</thead>


<tbody>


<?php if (empty($htaccess_files)): ?>

<tr>

<td colspan="6">

    <div class="empty">

        <div class="big-icon">
            ✅
        </div>

        <p>
            Tidak ada file .htaccess ditemukan.
        </p>

    </div>

</td>

</tr>


<?php else: ?>


<?php
$no = 1;

foreach ($htaccess_files as $f):
?>

<tr>

<td>
    <?php echo $no++; ?>
</td>


<td class="path-cell">

<?php
echo htmlspecialchars(
    $f['path'],
    ENT_QUOTES,
    'UTF-8'
);
?>

</td>


<td>

<?php

$permColor =
    in_array(
        $f['perms'],
        ['0444', '0555'],
        true
    )
        ? 'var(--red)'
        : 'var(--green)';

?>

<span
    class="perm-digits"
    style="
        color:
        <?php echo $permColor; ?>
    "
>
    <?php
    echo htmlspecialchars(
        $f['perms'],
        ENT_QUOTES,
        'UTF-8'
    );
    ?>
</span>

</td>


<td>

<?php

if ($f['size'] > 0) {

    echo number_format(
        $f['size'] / 1024,
        1
    ) . ' KB';

} else {

    echo '0 KB';
}

?>

</td>


<td>

<?php if ($f['dir_writable']): ?>

<span class="badge badge-ok">
    Hapus Aman
</span>

<?php else: ?>

<span class="badge badge-warn">
    Coba Paksa
</span>

<?php endif; ?>

</td>


<td>

<form
    method="POST"
    style="display:inline"
>

<input
    type="hidden"
    name="delete_single"
    value="<?php
        echo htmlspecialchars(
            $f['path'],
            ENT_QUOTES,
            'UTF-8'
        );
    ?>"
>


<button
    type="submit"
    class="btn btn-danger btn-sm"
    onclick="
        return confirm(
            'Hapus file .htaccess ini?'
        );
    "
>
    🗑 Hapus
</button>

</form>

</td>

</tr>


<?php endforeach; ?>


<?php endif; ?>


</tbody>

</table>

</div>


<?php if (!empty($skipped_dirs)): ?>

<div class="note-box">

<strong>
⚠️
<?php echo count($skipped_dirs); ?>
direktori dilewati karena tidak dapat dibaca:
</strong>

<br>


<?php

foreach (
    array_slice(
        $skipped_dirs,
        0,
        20
    )
    as $d
):

?>

↳
<code>
<?php
echo htmlspecialchars(
    $d,
    ENT_QUOTES,
    'UTF-8'
);
?>
</code>

<br>

<?php endforeach; ?>


<?php if (count($skipped_dirs) > 20): ?>

...

dan

<?php
echo
    count($skipped_dirs) -
    20;
?>

lainnya.

<?php endif; ?>

</div>

<?php endif; ?>


<div class="note-box">

<strong>📌 Catatan:</strong>

<br>

• Script hanya mencari file bernama
<code>.htaccess</code>.

<br>

• Scan dimulai dari:
<code>
<?php
echo htmlspecialchars(
    $start_directory,
    ENT_QUOTES,
    'UTF-8'
);
?>
</code>

<br>

• Tidak mengikuti symbolic link.

<br>

• Jika permission parent diubah sementara,
script mencoba mengembalikannya ke permission semula.

<br>

• Jika tetap gagal meskipun chmod berhasil,
periksa ownership, ACL, atau immutable flag dari SSH/root.

</div>


</div>

</body>

</html>