Seite 3 von 3
Re: Zeitlich begrenzter Zugang zu einem Download
Verfasst: Sa 6. Sep 2025, 18:49
von Volker
Ne lass mal DU bist meine KI
DU findest die Fehler
DU fragst dann mal Tante KI für mich
so machen wir das auch in Zukunft
Ich brauch die KI nur um hübscher zu machen und ab und zu um Fehler zu finden.
Den Rest mach ich, sonst wär es ja auch Langweilig
Re: Zeitlich begrenzter Zugang zu einem Download
Verfasst: Sa 6. Sep 2025, 18:53
von Tommy Herrmann
Ich verwende ChatGPT in erster Linie um etwas zu lernen. 90% meiner Fragen sind zum Lernen, der Rest ist eher zum Spaß.
Re: Zeitlich begrenzter Zugang zu einem Download
Verfasst: Sa 6. Sep 2025, 20:02
von Volker
Ups
Kleiner Fehler - aber behoben . Datum fehlte.
Tommy einfach diese Zeile oben nach <?php in die beiden Dateien admin.php und download.php einfügen
sonst stimmt die Zeit ja nicht
im zip erledigt
Re: Zeitlich begrenzter Zugang zu einem Download
Verfasst: So 7. Sep 2025, 05:55
von Tommy Herrmann
Moin,
ich habe eben noch das
limitierte Download-Skript von Dir mit in meinen
Mobirise-Tutorials aufgenommen:
https://www.mobirise-tutorials.com/Tuto ... imited.php
Alles klappt wunderbar
Die HTML-Codes sehen zwar so aus, als hätten sie einen tropischen Taifun fast nicht überstanden – aber na ja, das ist ja nur optisch
Es fehlt übrigens noch das Meta-Tag fürs Handy, in beiden Dateien:
Code: Alles auswählen
<meta name="viewport" content="width=device-width, initial-scale=1.0">
Zum Testen habe ich das Token jetzt auf 2 Jahre und 5.000 Downloads gesetzt

Re: Zeitlich begrenzter Zugang zu einem Download
Verfasst: So 7. Sep 2025, 12:52
von Tommy Herrmann
Volker,
das mit dem Download klappt nicht
Jetzt wollte ich das einsetzen und es wird immer der erste Token mit den Bildern geöffnet, auch wenn ich den neuen Token mit den NOF Installationsdateien verwende:
https://www.mobirise-tutorials.com/down ... 7f376cead3
Re: Zeitlich begrenzter Zugang zu einem Download
Verfasst: So 7. Sep 2025, 13:34
von Volker
Den Fehler mit den Token kann ich bei mir nicht feststellen. Wenn ich
2 unterschiedliche Token habe und die jeweils öffne, dann sind das auch die, die ich angelegt hatte.
Wenn ich die Links kopiere, habe ich auch 2 unterschiedliche Token
Was genau geht nicht bei Dir ?
Token1:
https://www.niederastroth.de/downloadve ... cd42cb3fb6
Token2:
https://www.niederastroth.de/downloadve ... cd4b5672a3
Mal testen ob da immer der selbe aufgeht
Wie Du siehst sind das 2 verschiedene Token und laufen auch
Lass mal das Script stand alone laufen und teste das mal
Re: Zeitlich begrenzter Zugang zu einem Download
Verfasst: So 7. Sep 2025, 13:46
von Tommy Herrmann
Wie Du an dem Screenshot (oben) schon sehen kannst wurden zwei unterschiedliche Tokens erstellt.
1) Mein Token zu dem Standard-Demo für meine Webseite - 5000 Downloads und 2 Jahre diese Bilder:
Code: Alles auswählen
https://www.mobirise-tutorials.com/download-limited/download.php?token=0fe48becdb3ad63ba06cf294a71f306a
Dann habe ich vorhin für ein Forum Mitglied NOF 15 und dessen Update 1 angehakt und dieser Token wurde erstellt:
2) Mein Token für 3 Downloads und 24 Stunden für NOF 15 und dessen Update 1:
Code: Alles auswählen
https://www.mobirise-tutorials.com/download-limited/download.php?token=c5f095df695698589c49f37f376cead3
Wie du überprüfen kannst wird bei beiden Links - mit unterschiedlichen Token -
immer der erste Token geladen.
Re: Zeitlich begrenzter Zugang zu einem Download
Verfasst: So 7. Sep 2025, 13:49
von Tommy Herrmann
warte mal - ich habe eine Vermutung ...
Re: Zeitlich begrenzter Zugang zu einem Download
Verfasst: So 7. Sep 2025, 13:51
von Tommy Herrmann
Ja - das war es - meine Schuld. Ich habe für einen Aufruf ohne iFrame eine Umleitung eingebaut. Das geht in diesem Fall nicht.
Habe diese entfernt und nun geht es

Re: Zeitlich begrenzter Zugang zu einem Download
Verfasst: Di 9. Sep 2025, 05:34
von Tommy Herrmann
Moin,
ich habe den Code der Datei "download.php" noch etwas umgeschrieben. Nun kann man den Container irgendwo am <li> Tag öffnen/schließen (nicht nur am Text). Der Download ist nur am Button "Download" möglich. Weiterhin gibt es einen zusätzlichen Button um alle Download-Links gleichzeitig zu öffnen oder zu schließen.
https://www.mobirise-tutorials.com/Tuto ... imited.php
Falls Du das übernehmen wolltest, hier der neue Code:
Code: Alles auswählen
<?php
date_default_timezone_set('Europe/Berlin');
$storageFile = __DIR__ . "/tokens.json";
$protectedDir = __DIR__ . "/schutz";
$logFile = __DIR__ . "/download.log";
$blacklist = ['.htaccess', '.gitignore'];
// Tokens laden
$tokens = file_exists($storageFile) ? json_decode(file_get_contents($storageFile), true) : [];
// Abgelaufene Tokens automatisch bereinigen
foreach ($tokens as $tk => $entry) {
if (time() > $entry['expires']) {
unset($tokens[$tk]);
}
}
file_put_contents($storageFile, json_encode($tokens), LOCK_EX);
// Token prüfen
if (!isset($_GET['token'])) die("❌ Ungültiger Zugriff");
$token = $_GET['token'];
if (!isset($tokens[$token])) die("❌ Ungültiger oder abgelaufener Token");
$entry = $tokens[$token];
if (time() > $entry['expires']) {
unset($tokens[$token]);
file_put_contents($storageFile, json_encode($tokens), LOCK_EX);
die("⏰ Der Link ist abgelaufen");
}
// Funktion: Download-Log
function writeLog($token, $file)
{
global $logFile;
$entryLog = date("Y-m-d H:i:s") . " | Token: $token | Datei: $file | IP: " . $_SERVER['REMOTE_ADDR'] . "\n";
file_put_contents($logFile, $entryLog, FILE_APPEND);
}
// Einzeldatei ausliefern
if (isset($_GET['file'])) {
$file = $_GET['file']; // kann auch unterordner/datei.txt sein
if (!in_array($file, $entry['files']) || in_array(basename($file), $blacklist))
die("❌ Datei nicht freigegeben");
$downloads = $tokens[$token]['downloads'][$file] ?? 0;
if ($downloads >= $entry['maxDownloads']) die("⚠️ Max Downloads für diese Datei erreicht");
$tokens[$token]['downloads'][$file] = $downloads + 1;
file_put_contents($storageFile, json_encode($tokens), LOCK_EX);
writeLog($token, $file);
$filePath = $protectedDir . "/" . $file;
if (!file_exists($filePath)) die("❌ Datei nicht gefunden");
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . basename($filePath) . '"');
header('Content-Length: ' . filesize($filePath));
readfile($filePath);
exit;
}
// ZIP-Download
if(isset($_GET['zip'])){
$zipFile = tempnam(sys_get_temp_dir(), 'dlzip') . ".zip";
$zip = new ZipArchive();
if($zip->open($zipFile, ZipArchive::CREATE) !== TRUE) die("❌ Konnte ZIP nicht erstellen");
$added = false; // Prüfen, ob überhaupt Dateien hinzugefügt wurden
foreach($entry['files'] as $f){
$filePath = $protectedDir . "/" . $f;
if(!file_exists($filePath)) continue;
// Max-Downloads prüfen
$downloads = $tokens[$token]['downloads'][$f] ?? 0;
if($downloads >= $entry['maxDownloads']) continue;
$zip->addFile($filePath, $f);
$tokens[$token]['downloads'][$f] = $downloads + 1;
writeLog($token, $f);
$added = true;
}
if(!$added){
$zip->close();
unlink($zipFile);
die("⚠️ Alle Dateien haben das Max-Download-Limit erreicht");
}
file_put_contents($storageFile, json_encode($tokens), LOCK_EX);
$zip->close();
header('Content-Type: application/zip');
header('Content-Disposition: attachment; filename="download_bundle.zip"');
header('Content-Length: ' . filesize($zipFile));
readfile($zipFile);
unlink($zipFile);
exit;
}
// Bootstrap-Ausgabe: Liste der freigegebenen Dateien
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Freigegebene Dateien</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body {max-width: 800px; margin: 0 auto;}
.subfolder { margin-left: 20px; display: none; }
.folder { font-weight: bold; }
/* Visuelles Feedback für klickbare Ordner-LIs */
li.folder-item { cursor: pointer; user-select: none; }
/* Buttons bleiben klickbar */
a.btn { cursor: pointer; }
/* Innerhalb der geöffneten Subfolder-Zeile Standardcursor, außer auf dem Button */
.subfolder .list-group-item { cursor: default; }
.subfolder .list-group-item a.btn { cursor: pointer; }
/* ✅ Fix für Smartphones: Zeilen umbrechen statt Overflow */
@media (max-width: 767px) {
.list-group-item.d-flex {
flex-wrap: wrap !important;
text-align: left;
}
.list-group-item.d-flex span,
.list-group-item.d-flex a.btn {
margin-top: 6px;
max-width: 100%;
word-break: break-word;
}
}
/* Umbrechen des Hinweistexts "(klicken zum Öffnen/Schließen)" innerhalb der Ordnerzeile */
@media (max-width: 767px) {
/* Die Kopfzeile des Ordners darf umbrechen */
.folder.d-flex {
flex-wrap: wrap !important;
align-items: flex-start;
}
/* Erster <span> (Ordnername) darf schrumpfen statt Overflow zu erzwingen */
.folder.d-flex > span:first-child {
min-width: 0;
}
/* Zweiter <span> (Hinweistext) springt in eigene Zeile und darf umbrechen */
.folder.d-flex > span + span {
flex: 0 0 100%;
margin-top: 4px;
white-space: normal;
overflow-wrap: anywhere; /* bricht auch lange/zusammenhängende Strings */
word-break: break-word; /* Fallback */
}
}
</style>
</head>
<body class="bg-light">
<div class="container py-4">
<!-- deaktiviert - eventueller Link zur Homepage
<h3> ↗️ <a href="https://www.Deine-Domain.de/" target="_blank">Deine-Domain.de</a></h3><br>
-->
<h2>📂 Freigegebene Dateien</h2>
<p>Gültig bis: <?= date("d.m.Y H:i", $entry['expires']) ?></p>
<?php
// Dateien nach Ordner sortieren
$folders = [];
$rootFiles = [];
foreach ($entry['files'] as $f) {
if (strpos($f, "/") !== false) {
$folders[$f] = $f;
} else {
$rootFiles[] = $f;
}
}
// Root-Dateien (nur Button lädt herunter, <li> selbst ist NICHT klickbar)
if ($rootFiles) {
echo "<ul class='list-group mb-3'>";
foreach ($rootFiles as $f) {
$downloads = $entry['downloads'][$f] ?? 0;
$max = $entry['maxDownloads'];
$disabled = $downloads >= $max ? "disabled" : "";
$downloadUrl = "?token=$token&file=" . urlencode($f);
echo "<li class='list-group-item d-flex justify-content-between align-items-center'>";
echo htmlspecialchars($f) . " ($downloads/$max)";
if (!$disabled) {
echo " <a href='" . htmlspecialchars($downloadUrl, ENT_QUOTES) . "' class='btn btn-sm btn-primary ms-3'>📥 Download</a>";
}
echo "</li>";
}
echo "</ul>";
}
// Unterordner (ganzer <li> toggelt, Download NUR über Button im Subfolder)
if ($folders) {
// Button: Alles auf-/zuklappen
echo "<div class='d-flex justify-content-end mb-2'>";
echo "<button type='button' id='toggleAllBtn' class='btn btn-outline-secondary btn-sm'>Alles aufklappen</button>";
echo "</div>";
echo "<ul class='list-group' id='foldersList'>";
foreach ($folders as $f) {
$downloads = $entry['downloads'][$f] ?? 0;
$max = $entry['maxDownloads'];
$disabled = $downloads >= $max ? "disabled" : "";
$downloadUrl = "?token=$token&file=" . urlencode($f);
$folderId = md5($f);
echo "<li class='list-group-item folder-item' data-folder-id='" . $folderId . "'>";
// Titelzeile
echo "<div class='folder d-flex justify-content-between align-items-center'>";
echo "<span>📁 " . htmlspecialchars($f) . "</span>";
echo "<span class='text-muted small ms-2'>(klicken zum Öffnen/Schließen)</span>";
echo "</div>";
// Subfolder-Inhalt: eigener Eintrag mit ausschließlich Button-Download
echo "<div class='subfolder list-group mt-1' id='" . $folderId . "'>";
echo "<div class='list-group-item d-flex justify-content-between align-items-center'>";
echo "<span>" . htmlspecialchars($f) . " ($downloads/$max)</span>";
if (!$disabled) {
echo "<a href='" . htmlspecialchars($downloadUrl, ENT_QUOTES) . "' class='btn btn-sm btn-primary'>📥 Download</a>";
}
echo "</div>";
echo "</div>";
echo "</li>";
}
echo "</ul>";
}
?>
<p class="mt-3">
<a href='?token=<?= $token ?>&zip=1' class="btn btn-success">📦 Alles als ZIP herunterladen</a>
</p>
<script>
// Toggle-Funktion (beibehalten für direkte Aufrufe)
function toggleFolder(id){
const el = document.getElementById(id);
if (!el) return;
el.style.display = (el.style.display==='none' || el.style.display==='') ? 'block' : 'none';
}
// GANZES LI eines Ordners toggelt auf/zu.
// – Klicks auf Buttons/Links und innerhalb des Subfolder-Inhalts lösen KEIN Toggle aus.
document.addEventListener('click', function(e){
if (e.target.closest('a, button')) return; // Buttons/Links: nichts toggeln
if (e.target.closest('.subfolder')) return; // Klick im Subfolder-Inhalt: nicht toggeln
const li = e.target.closest('li.folder-item');
if (!li) return;
const id = li.getAttribute('data-folder-id');
if (!id) return;
toggleFolder(id);
}, {passive:true});
// --- Alles auf-/zuklappen ---
(function(){
const btn = document.getElementById('toggleAllBtn');
if (!btn) return;
function anyClosed(){
const subs = document.querySelectorAll('.subfolder');
for (const el of subs){
if (el.style.display === '' || el.style.display === 'none') return true;
}
return false;
}
function setAll(open){
const subs = document.querySelectorAll('.subfolder');
subs.forEach(el => { el.style.display = open ? 'block' : 'none'; });
btn.textContent = open ? 'Alles zuklappen' : 'Alles aufklappen';
}
// Initialer Button-Text je nach Zustand
btn.textContent = anyClosed() ? 'Alles aufklappen' : 'Alles zuklappen';
btn.addEventListener('click', function(){
const open = anyClosed(); // wenn irgendeiner zu ist -> alle öffnen, sonst alle schließen
setAll(open);
});
})();
</script>
</div>
</body>
</html>
Außerdem (ganz wichtig)
ein Fix der CSS für das Smartphone (schon oben im Code mit dabei), damit kein Overflow entsteht:
Re: Zeitlich begrenzter Zugang zu einem Download
Verfasst: Di 9. Sep 2025, 12:55
von Volker
Moin Tommy,
hab ich in den Download gepackt.
Da ich das Teil ja ursprünglich für mich gemacht hatte, bin ich natürlich nicht von Handy Nutzung ausgegangen

Re: Zeitlich begrenzter Zugang zu einem Download
Verfasst: Di 9. Sep 2025, 13:30
von Tommy Herrmann
Ich glaube, man muss heutzutage immer auch mit der Nutzung über das Smartphone rechnen, denn die Hälfte der Menschen benutzt ohnehin keinen Computer mehr, sondern erledigt alles mit dem Handy. Der Computer wird bald, ähnlich wie das Faxgerät, zu den Relikten gehören
