includes/modules/update.php

<?php
declare(strict_types=1);

/*
|--------------------------------------------------------------------------
| Update manifest
|--------------------------------------------------------------------------
|
| Checks whether a newer Brivacia release is available by fetching a small
| JSON manifest from the project's own site. Only handles the "is an update
| available, and what does it look like" question — actually downloading,
| verifying and installing an update is handled by api/update.php.
|
*/

const BRIVACIA_UPDATE_JSON = 'https://breat.fr/static/json/brivacia-version.json';

function brivaciaUpdateManifest(): array {
    static $memo = null;
    if ($memo !== null) return $memo;

    $cacheFile = updateDir() . '/update-check.json';
    $cached = is_file($cacheFile) ? json_decode((string)file_get_contents($cacheFile), true) : null;

    if (is_array($cached) && ($cached['checked_at'] ?? 0) > time() - 3600) {
        return $memo = $cached['manifest'] ?? [];
    }

    $json = @file_get_contents(
        BRIVACIA_UPDATE_JSON . '?t=' . time(),
        false,
        stream_context_create([
            'http' => [
                'timeout' => 3,
                'ignore_errors' => true,
                'header' => "Cache-Control: no-cache\r\nPragma: no-cache\r\n",
            ],
        ])
    );
    $manifest = is_string($json) && $json !== '' ? (json_decode($json, true) ?: []) : ($cached['manifest'] ?? []);

    file_put_contents($cacheFile, json_encode(['checked_at' => time(), 'manifest' => $manifest]), LOCK_EX);
    return $memo = $manifest;
}

function brivaciaCurrentVersion(): string {
    return function_exists('brivacia_version') ? brivacia_version() : '0.0.0';
}

// Lets an admin force the "update available" UI/flow to appear locally
// (for testing the update process itself) by dropping a file named
// update/test-update — no manifest fetch needed to exercise that path.
function brivaciaTestUpdate(): bool
{
    return is_file(brivacia_root_path('update/test-update'));
}

function brivaciaUpdateAvailable(): bool {
    if (brivaciaTestUpdate()) {
        return true;
    }

    $manifest = brivaciaUpdateManifest();

    if (empty($manifest['version'])) {
        return false;
    }

    return version_compare((string)$manifest['version'], brivaciaCurrentVersion(), '>');
}

function brivaciaUpdateInfo(): array {
    $manifest = brivaciaUpdateManifest();

    return [
        'available' => brivaciaTestUpdate() || (!empty($manifest['version']) && version_compare((string)$manifest['version'], brivaciaCurrentVersion(), '>')),
        'current' => brivaciaCurrentVersion(),
        'latest' => (string)($manifest['version'] ?? ''),
        'download' => (string)($manifest['download'] ?? ''),
        'changelog' => (string)($manifest['changelog'] ?? ''),
        'minimum_php' => (string)($manifest['minimum_php'] ?? ''),
        // Expected SHA-256 of the downloadable zip, lowercase hex, published
        // alongside the version/download fields in the same JSON manifest.
        // Checked against the actual downloaded bytes in api/update.php
        // before anything gets extracted or installed.
        'sha256' => strtolower(trim((string)($manifest['sha256'] ?? ''))),
    ];
}

function brivaciaLatestVersion(): string
{
    return brivaciaUpdateInfo()['latest'] ?? brivacia_version();
}

Contribute