# Releases Module Implementation Plan > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** Wdrożyć dwukanałowy system aktualizacji (beta/stable) z zarządzaniem licencjami w panelu admina. **Architecture:** Dwie nowe tabele MySQL (`pp_update_licenses`, `pp_update_versions`) dostępne tylko na serwerze dewelopera. `updates/versions.php` czyta z DB przez Medoo. Nowy moduł `Releases` w panelu admina zarządza wersjami i licencjami. Całość wykluczona z paczek klientów przez `.updateignore`. **Tech Stack:** PHP 8.x, Medoo 1.x (`$mdb` global), jQuery UI (dialog), Bootstrap 3 (klasy CSS), `\Tpl` (admin templates z `admin/templates/`), `\S::get()` (POST→GET params). --- ### Task 1: Dodaj wykluczenia do `.updateignore` **Files:** - Modify: `.updateignore` **Step 1: Dopisz nowe wykluczenia** Otwórz `.updateignore` i dopisz na końcu: ``` # Moduł zarządzania releaseami (tylko serwer dewelopera) autoload/admin/controls/class.Releases.php autoload/admin/factory/class.Releases.php autoload/admin/view/class.Releases.php admin/templates/releases/ # Menu dewelopera templates/additional-menu.php ``` **Step 2: Zweryfikuj manualnie** Uruchom build w trybie dry-run i sprawdź, że powyższe pliki NIE pojawiają się na liście: ```powershell ./build-update.ps1 -ToTag v9.999 -DryRun ``` **Step 3: Commit** ```bash git add .updateignore git commit -m "chore: wyklucz modul Releases i menu dewelopera z paczek klientow" ``` --- ### Task 2: Utwórz tabele DB (jednorazowo na serwerze) **Files:** - Create: `_db_releases_setup.sql` (uruchom raz w phpMyAdmin, nie commituj) **Step 1: Utwórz plik SQL** ```sql CREATE TABLE pp_update_licenses ( id INT AUTO_INCREMENT PRIMARY KEY, `key` VARCHAR(64) NOT NULL UNIQUE, domain VARCHAR(255) NOT NULL, valid_to_date DATE NULL, valid_to_version VARCHAR(10) NULL, beta TINYINT(1) NOT NULL DEFAULT 0, note TEXT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE pp_update_versions ( id INT AUTO_INCREMENT PRIMARY KEY, version VARCHAR(10) NOT NULL UNIQUE, channel ENUM('beta','stable') NOT NULL DEFAULT 'beta', created_at DATETIME NULL, promoted_at DATETIME NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ``` **Step 2: Uruchom w phpMyAdmin na serwerze `cmspro.project-dc.pl`** Wklej SQL do phpMyAdmin → Execute. Obie tabele muszą być widoczne. **Step 3: Nie commituj** — ten plik jest tylko pomocniczy, możesz go usunąć po wykonaniu. --- ### Task 3: Utwórz `admin\factory\Releases` **Files:** - Create: `autoload/admin/factory/class.Releases.php` **Step 1: Napisz klasę** ```php select('pp_update_versions', '*', ['ORDER' => ['version' => 'DESC']]); if (!$rows) return []; foreach ($rows as &$row) $row['zip_exists'] = file_exists('../updates/' . self::zip_dir($row['version']) . '/ver_' . $row['version'] . '.zip'); return $rows; } public static function promote(string $version): void { global $mdb; $mdb->update('pp_update_versions', ['channel' => 'stable', 'promoted_at' => date('Y-m-d H:i:s')], ['version' => $version] ); } public static function demote(string $version): void { global $mdb; $mdb->update('pp_update_versions', ['channel' => 'beta', 'promoted_at' => null], ['version' => $version] ); } public static function get_licenses(): array { global $mdb; return $mdb->select('pp_update_licenses', '*', ['ORDER' => ['domain' => 'ASC']]) ?: []; } public static function get_license(int $id): array { global $mdb; return $mdb->get('pp_update_licenses', '*', ['id' => $id]) ?: []; } public static function save_license(array $data): void { global $mdb; $row = [ 'key' => trim($data['key'] ?? ''), 'domain' => trim($data['domain'] ?? ''), 'valid_to_date' => $data['valid_to_date'] ?: null, 'valid_to_version' => $data['valid_to_version'] ?: null, 'beta' => (int)(bool)($data['beta'] ?? 0), 'note' => trim($data['note'] ?? ''), ]; if (!empty($data['id'])) $mdb->update('pp_update_licenses', $row, ['id' => (int)$data['id']]); else $mdb->insert('pp_update_licenses', $row); } public static function delete_license(int $id): void { global $mdb; $mdb->delete('pp_update_licenses', ['id' => $id]); } public static function toggle_beta(int $id): void { global $mdb; $license = $mdb->get('pp_update_licenses', ['id', 'beta'], ['id' => $id]); if ($license) $mdb->update('pp_update_licenses', ['beta' => $license['beta'] ? 0 : 1], ['id' => $id]); } private static function zip_dir(string $version): string { return substr($version, 0, strlen($version) - (strlen($version) == 5 ? 2 : 1)) . '0'; } } ``` **Step 2: Weryfikacja składni** ```bash php -l autoload/admin/factory/class.Releases.php ``` Oczekiwane: `No syntax errors detected` --- ### Task 4: Utwórz `admin\controls\Releases` **Files:** - Create: `autoload/admin/controls/class.Releases.php` **Step 1: Napisz klasę** ```php versions = \admin\factory\Releases::get_versions(); $tpl->licenses = \admin\factory\Releases::get_licenses(); return $tpl->render('releases/main-view'); } } ``` **Step 2: Weryfikacja składni** ```bash php -l autoload/admin/view/class.Releases.php ``` --- ### Task 6: Utwórz szablon `admin/templates/releases/main-view.php` **Files:** - Create: `admin/templates/releases/main-view.php` **Step 1: Utwórz katalog i plik** ```php

Wersje

versions as $v): ?> versions): ?>
WersjaKanałDodana Promocja do stableZIPAkcje
stable beta ✓' : '' ?> Promuj →stable Cofnij →beta
Brak wersji w bazie. Wersje będą rejestrowane automatycznie przy pierwszym odpytaniu versions.php.
``` **Step 2: Zweryfikuj, że szablon jest poprawny** Otwórz w przeglądarce: `https://cmspro.project-dc.pl/admin/releases/main_view/` Oczekiwane: strona ładuje się bez błędów PHP, widoczne dwa taby, tabela wersji pusta lub z danymi. --- ### Task 7: Dodaj pozycję menu dla dewelopera **Files:** - Create: `templates/additional-menu.php` (ten plik nie trafia do klientów — dodany do `.updateignore` w Task 1) **Step 1: Utwórz plik** ```php
Developer
``` **Step 2: Sprawdź w przeglądarce** Po odświeżeniu panelu admina powinna pojawić się sekcja "Developer" z linkiem "Releases & Licencje" w menu bocznym. --- ### Task 8: Migracja danych licencji do DB Licencje z hardkodowanej tablicy `$license` w `updates/versions.php` muszą trafić do `pp_update_licenses` **przed** przejściem na nową wersję `versions.php`. **Files:** - Create: `_migrate_licenses.php` (tymczasowy, uruchom raz przez przeglądarkę, potem usuń) **Step 1: Utwórz skrypt migracji** ```php 'mysql', 'database_name' => $database['name'], 'server' => $database['host'], 'username' => $database['user'], 'password' => $database['password'], 'charset' => 'utf8' ]); // Wyodrębnij $license z pliku versions.php bez uruchamiania die() $source = file_get_contents('updates/versions.php'); // Wyciągnij wszystkie wpisy $license['key']['domain'] itd. preg_match_all('/\$license\[\'([^\']*)\'\]\[\'domain\'\]\s*=\s*\'([^\']*)\';/', $source, $m_domain); preg_match_all('/\$license\[\'([^\']*)\'\]\[\'valid_to_date\'\]\s*=\s*\'([^\']*)\';/', $source, $m_date); preg_match_all('/\$license\[\'([^\']*)\'\]\[\'valid_to_version\'\]\s*=\s*\'([^\']*)\';/', $source, $m_ver); // Zbuduj mapę po kluczu $dates = array_combine($m_date[1], $m_date[2]); $vers = array_combine($m_ver[1], $m_ver[2]); $count = 0; foreach ($m_domain[1] as $i => $key) { $domain = $m_domain[2][$i]; $row = [ 'key' => $key, 'domain' => $domain, 'valid_to_date' => ($dates[$key] ?? '') ?: null, 'valid_to_version' => ($vers[$key] ?? '') ?: null, 'beta' => 0, ]; // Pomiń jeśli już istnieje if ($mdb->has('pp_update_licenses', ['key' => $key])) { echo "SKIP (już istnieje): $domain ($key)
"; continue; } $mdb->insert('pp_update_licenses', $row); echo "OK: $domain ($key)
"; $count++; } echo "
Zmigrowano $count licencji."; echo "
USUŃ ten plik z serwera!"; ``` **Step 2: Wgraj na serwer i uruchom** ``` https://cmspro.project-dc.pl/_migrate_licenses.php ``` Oczekiwane: lista "OK: domena (klucz)" dla każdej licencji, na końcu podsumowanie. **Step 3: Usuń skrypt z serwera** Skasuj `_migrate_licenses.php` — nie commituj go do repozytorium. **Step 4: Ustaw flagę beta dla swoich testowych stron** W panelu admina `/admin/releases/main_view/` → zakładka Licencje → przy swoich testowych domenach kliknij "stable" aby przełączyć na "beta". --- ### Task 9: Przebuduj `updates/versions.php` **Files:** - Modify: `updates/versions.php` > **WAŻNE:** Wykonaj ten krok dopiero po Task 8 (migracja danych do DB). **Step 1: Zastąp całą zawartość pliku** ```php 'mysql', 'database_name' => $database['name'], 'server' => $database['host'], 'username' => $database['user'], 'password' => $database['password'], 'charset' => 'utf8' ] ); $current_ver = 1691; // aktualizowane automatycznie przez build-update.ps1 // 1. Skan filesystem — lista istniejących ZIPów $versions = []; for ( $i = 1; $i <= $current_ver; $i++ ) { $dir = substr( number_format( $i / 1000, 3 ), 0, strlen( number_format( $i / 1000, 3 ) ) - 2 ) . '0'; $version_old = number_format( $i / 1000, 2 ); $version_new = number_format( $i / 1000, 3 ); if ( file_exists( '../updates/' . $dir . '/ver_' . $version_old . '.zip' ) ) $versions[] = $version_old; if ( file_exists( '../updates/' . $dir . '/ver_' . $version_new . '.zip' ) ) $versions[] = $version_new; } $versions = array_unique( $versions ); // 2. Walidacja klucza licencji $license = $mdb->get( 'pp_update_licenses', '*', [ 'key' => ( $_GET['key'] ?? '' ) ] ); if ( !$license ) die(); // 3. Sprawdź ważność daty if ( $license['valid_to_date'] && $license['valid_to_date'] < date( 'Y-m-d' ) ) die(); // 4. Auto-discovery: rejestruj nowe ZIPy jako beta $known = array_flip( $mdb->select( 'pp_update_versions', 'version', [] ) ?: [] ); foreach ( $versions as $ver ) { if ( !isset( $known[$ver] ) ) { @$mdb->insert( 'pp_update_versions', [ 'version' => $ver, 'channel' => 'beta', 'created_at' => date( 'Y-m-d H:i:s' ) ] ); $known[$ver] = true; } } // 5. Filtruj wersje wg kanału (beta widzi beta+stable, reszta tylko stable) $channels = $license['beta'] ? [ 'beta', 'stable' ] : [ 'stable' ]; $allowed = array_flip( $mdb->select( 'pp_update_versions', 'version', [ 'channel' => $channels ] ) ?: [] ); // 6. Wypisz dostępne wersje $valid_to_version = $license['valid_to_version']; foreach ( $versions as $ver ) { if ( !isset( $allowed[$ver] ) ) continue; if ( $valid_to_version && $ver > $valid_to_version ) continue; echo $ver . PHP_EOL; } ``` **Step 2: Weryfikacja składni** ```bash php -l updates/versions.php ``` **Step 3: Test ręczny** Otwórz w przeglądarce (podaj klucz jednej z migrowanych licencji): ``` https://cmspro.project-dc.pl/updates/versions.php?key=TWOJ_KLUCZ ``` Oczekiwane: - Dla klucza z `beta=0`: zwraca TYLKO wersje `channel='stable'` (pusta lista jeśli żadna jeszcze niepromowana) - Dla klucza z `beta=1`: zwraca wersje `channel='beta'` i `'stable'` - Dla nieprawidłowego klucza: brak odpowiedzi (die()) **Step 4: Sprawdź panel aktualizacji u klienta** Na swojej testowej stronie (beta=1) otwórz `/admin/update/main_view/` — powinien widzieć dostępne wersje. --- ### Task 10: Commit końcowy **Step 1: Sprawdź status** ```bash git status git diff updates/versions.php ``` **Step 2: Commit** ```bash git add \ .updateignore \ autoload/admin/factory/class.Releases.php \ autoload/admin/controls/class.Releases.php \ autoload/admin/view/class.Releases.php \ admin/templates/releases/main-view.php \ templates/additional-menu.php \ updates/versions.php git commit -m "$(cat <<'EOF' feat: dwukanalowy system aktualizacji (beta/stable) + zarzadzanie licencjami - Nowy modul admin\Releases: lista wersji z promocja beta→stable, CRUD licencji z flaga beta - versions.php czyta z DB (pp_update_licenses, pp_update_versions) zamiast hardkodowanej tablicy $license - Auto-discovery: nowe ZIPy automatycznie rejestrowane jako 'beta' - Calosc wykluczona z paczek klientow przez .updateignore Co-Authored-By: Claude Sonnet 4.6 EOF )" ``` --- ## Weryfikacja end-to-end Po wdrożeniu sprawdź ręcznie: 1. **Nowy ZIP (beta):** Wrzuć nowy ZIP na serwer → odpytaj `versions.php` kluczem beta → pojawia się nowa wersja → w panelu admina widoczna jako `beta` 2. **Promocja:** Kliknij "Promuj →stable" → odpytaj kluczem `beta=0` → wersja pojawia się na liście 3. **Cofnięcie:** Kliknij "Cofnij →beta" → klient `beta=0` nie widzi wersji 4. **Licencja:** Dodaj nową licencję przez formularz → weryfikuj w phpMyAdmin 5. **Toggle beta:** Kliknij "stable" przy licencji → zmienia się na "beta" 6. **Zabezpieczenie:** Wgraj nową wersję CMS do klienta → sprawdź czy `admin/templates/releases/` i `autoload/admin/*/class.Releases.php` NIE znalazły się w ZIPie (build dry-run)