Files
2026-11-01 11:15:37 +00:00

138 lines
7.0 KiB
PHP

<?php
declare(strict_types=1);
/**
* Download der Windows- und Android-App für Mitarbeiter.
*
* Der Admin legt die Dateien per FTP in app_downloads/ (per .htaccess nicht direkt aufrufbar) ab:
* DeskHub-Setup.exe (Windows) und DeskHub.apk (Android). Ausgeliefert werden sie nur über staff/app_download.php
* an angemeldete Mitarbeiter.
*/
/** Unterstützte Plattformen: Dateiendung, feste Dateinamen auf dem Server, Dateikopf-Prüfung, Anzeigename. */
function app_platforms(): array
{
return [
'windows' => ['label' => 'Windows', 'ext' => 'exe', 'stored' => 'DeskHub-Setup.exe', 'download' => 'DeskHub-Setup.exe', 'magic' => 'MZ', 'mime' => 'application/vnd.microsoft.portable-executable'],
'android' => ['label' => 'Android', 'ext' => 'apk', 'stored' => 'DeskHub.apk', 'download' => 'DeskHub.apk', 'magic' => 'PK', 'mime' => 'application/vnd.android.package-archive'],
'minecraft' => ['label' => 'Minecraft', 'ext' => 'jar', 'stored' => 'DeskHub-Minecraft.jar', 'download' => 'DeskHub-Minecraft.jar', 'magic' => 'PK', 'mime' => 'application/java-archive'],
'wordpress' => ['label' => 'WordPress', 'ext' => 'zip', 'stored' => 'deskhub-tickets-wordpress.zip', 'download' => 'deskhub-tickets-wordpress.zip', 'magic' => 'PK', 'mime' => 'application/zip'],
'chrome' => ['label' => 'Chrome', 'ext' => 'zip', 'stored' => 'deskhub-chrome-erweiterung.zip', 'download' => 'deskhub-chrome-erweiterung.zip', 'magic' => 'PK', 'mime' => 'application/zip'],
];
}
function app_downloads_dir(): string
{
return rtrim((string) (getenv('DESKHUB_APPS_DIR') ?: dirname(__DIR__) . '/app_downloads'), '/\\');
}
/** Official publisher release endpoint; the access key stays on the server. */
function app_publisher_endpoint(string $platform, bool $download = false): string
{
if (!isset(app_platforms()[$platform]) || !function_exists('catalog_defaults')) return '';
$base = (string) (catalog_defaults()['plugins_url'] ?? '');
$parts = parse_url($base);
if (!$parts || ($parts['scheme'] ?? '') !== 'https' || empty($parts['host'])) return '';
$path = rtrim(str_replace('\\', '/', dirname((string) ($parts['path'] ?? '/'))), '/');
return 'https://' . $parts['host'] . ($parts['port'] ?? null ? ':' . (int) $parts['port'] : '') . $path . '/app-release.php?platform=' . rawurlencode($platform) . ($download ? '&download=1' : '');
}
function app_publisher_request(string $platform, bool $download = false)
{
$url = app_publisher_endpoint($platform, $download);
if ($url === '') return false;
$headers = function_exists('catalog_access_headers') ? catalog_access_headers($url) : [];
if (function_exists('curl_init')) {
$ch = curl_init($url);
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => false, CURLOPT_CONNECTTIMEOUT => 3, CURLOPT_TIMEOUT => $download ? 180 : 4, CURLOPT_HTTPHEADER => $headers]);
$body = curl_exec($ch);
$code = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
return $code === 200 ? $body : false;
}
$ctx = stream_context_create(['http' => ['method' => 'GET', 'timeout' => $download ? 180 : 4, 'ignore_errors' => true, 'header' => implode("\r\n", $headers)]]);
$body = @file_get_contents($url, false, $ctx);
$status = $http_response_header[0] ?? '';
return is_string($body) && str_contains($status, ' 200 ') ? $body : false;
}
function app_publisher_info(string $platform): ?array
{
$cacheKey = 'app_publisher_' . $platform . '_cache';
$cached = json_decode((string) get_setting($cacheKey, ''), true);
if (is_array($cached) && (int) ($cached['at'] ?? 0) > time() - 60) return is_array($cached['info'] ?? null) ? $cached['info'] : null;
$json = app_publisher_request($platform);
$data = is_string($json) ? json_decode($json, true) : null;
if (!is_array($data) || empty($data['version']) || empty($data['sha256'])) {
if (is_array($cached) && is_array($cached['info'] ?? null)) return $cached['info'];
set_setting($cacheKey, json_encode(['at' => time(), 'info' => null]));
return null;
}
$p = app_platforms()[$platform] ?? null;
if (!$p) return null;
$info = ['platform' => $platform, 'label' => $p['label'], 'size' => (int) ($data['size'] ?? 0), 'sha256' => (string) $data['sha256'], 'modified_at' => (string) ($data['created_at'] ?? ''), 'filename' => $p['download'], 'mime' => $p['mime'], 'version' => (string) $data['version'], 'notes' => (string) ($data['notes'] ?? ''), 'source' => 'publisher'];
set_setting($cacheKey, json_encode(['at' => time(), 'info' => $info], JSON_UNESCAPED_UNICODE));
return $info;
}
/** Legt den Ordner an und sperrt den Direktzugriff. */
function app_downloads_prepare_dir(): bool
{
$dir = app_downloads_dir();
if (!is_dir($dir) && !@mkdir($dir, 0755, true)) return false;
$htaccess = $dir . '/.htaccess';
if (!is_file($htaccess)) {
@file_put_contents($htaccess, "# Nur über staff/app_download.php (mit Anmeldung) abrufbar\n<IfModule mod_authz_core.c>\n Require all denied\n</IfModule>\n<IfModule !mod_authz_core.c>\n Order deny,allow\n Deny from all\n</IfModule>\nOptions -Indexes\n");
}
if (!is_file($dir . '/index.html')) @file_put_contents($dir . '/index.html', '');
return is_writable($dir);
}
/**
* Angaben zu einer App oder null, wenn die Datei nicht im Ordner liegt. Version gibt es nicht: Die Dateien werden
* per FTP abgelegt, angezeigt werden Datum, Größe und eine berechnete SHA-256-Prüfsumme (zwischengespeichert,
* solange sich Änderungsdatum und Größe nicht ändern).
*/
function app_download_info(string $platform): ?array
{
$platforms = app_platforms();
if (!isset($platforms[$platform])) return null;
$p = $platforms[$platform];
$path = app_downloads_dir() . '/' . $p['stored'];
clearstatcache(true, $path);
if (!is_file($path) || (int) filesize($path) < 1024) return null;
$size = (int) filesize($path);
$mtime = (int) filemtime($path);
$cacheKey = "app_{$platform}_hash_cache";
$cached = explode(':', (string) get_setting($cacheKey, ''), 3);
if (count($cached) === 3 && (int) $cached[0] === $mtime && (int) $cached[1] === $size) {
$sha = $cached[2];
} else {
$sha = (string) hash_file('sha256', $path);
set_setting($cacheKey, "{$mtime}:{$size}:{$sha}");
}
return [
'platform' => $platform,
'label' => $p['label'],
'size' => $size,
'sha256' => $sha,
'modified_at' => date('Y-m-d H:i:s', $mtime),
'path' => $path,
'filename' => $p['download'],
'mime' => $p['mime'],
];
}
function app_format_size(int $bytes): string
{
if ($bytes >= 1048576) return number_format($bytes / 1048576, 1, ',', '.') . ' MB';
return number_format(max(1, $bytes / 1024), 0, ',', '.') . ' KB';
}
/** Minecraft-Plugin auf der Apps-Seite zeigen? Einstellung unter Einstellungen → System (Standard: aus). */
function app_minecraft_enabled(): bool
{
return get_setting('minecraft_enabled', '0') === '1';
}