# Drupal 6 – duże obrazy, lokalizacja tematów i bezpieczna optymalizacja Rozbudowana wersja `compare_filesize_drupal6`. Działa **poza Drupalem**, w Pythonie 3.9+; potrzebuje dostępu do MySQL/MariaDB i katalogu plików. ## Co robi - Wyszukuje największe pliki i największe rozdzielczości (JPG, PNG, GIF – GIF wyłącznie do analizy). - Wskazuje, **w jakim temacie, poście lub komentarzu** pojawia się obraz. Raport pokazuje `nid`, `cid`, tytuł, typ zawartości, URL do wpisu, URL do edycji i źródło powiązania. - Szuka połączeń przez `upload.fid` i pola `*_fid` w tabelach CCK/ImageField/FileField; obsługuje aktualne rewizje (`vid`) i opcjonalne tabele załączników komentarzy (`cid` + `fid`). - Szuka obrazów w `node_revisions.body` i `node_revisions.teaser`, `comments.comment`, opcjonalnie też w tekstowych polach CCK. Rozróżnia **pewne** dopasowania identyfikatora `fid` lub pełnej ścieżki od **możliwych** trafień po samej nazwie pliku. - Generuje link do oryginalnego pliku (jeżeli publiczny) i pomocniczy link do Drupal Search (jeżeli moduł Search jest aktywny). - Generuje CSV (polskie kolumny, Excel, UTF-8 BOM) i JSON. - Symuluje dokładną optymalizację w **dry-run**, bez zapisu źródłowego pliku i bazy. - Przy `--apply` zachowuje oryginał w oddzielnym katalogu, przygotowuje nowy obraz do atomowej podmiany, zapisuje `files.filesize`, a przy błędzie próbuje odtworzyć oryginał i wartości bazy. - Wykrywa duplikaty SHA-256 i pliki na dysku, których nie ma w analizowanym zestawieniu z bazy. ## Instalacja ```bash unzip compare_filesize_drupal6-extended.zip cd compare_filesize_drupal6 python3 -m venv .venv source .venv/bin/activate pip install -r requirements.txt cp config.example.py config.py ``` W `config.py` ustaw: ```python DB_CONFIG = { 'host': 'localhost', 'port': 3306, 'database': 'drupal6_db', 'user': 'drupal_user', 'password': 'twoje_haslo', 'charset': 'utf8', } FILES_BASE_PATH = '/var/www/drupal/sites/default/files' PUBLIC_FILES_PATH = 'sites/default/files' DRUPAL_ROOT = '/var/www/drupal' DRUPAL_BASE_URL = 'https://forum.example.org' DB_TABLE_PREFIX = '' ``` **Ważne:** `FILES_BASE_PATH` to lokalizacja rzeczywistych plików, a `PUBLIC_FILES_PATH` to fragment zapisany zwykle w `files.filepath`. Jeśli pliki są np. w `/var/www/drupal/files`, zmień obie wartości. Można też podać je w CLI: `--public-files-path`, `--drupal-root`. Gdy Drupal pracuje bez clean URLs, dodaj `--no-clean-urls`. ## 1. Znajdź tematy i komentarze z największymi obrazami ```bash python main.py --find-usage --top 50 --report-csv najwieksze-z-linkami.csv ``` Przykładowe wiersze (fikcyjne): ```text 1. 18.42 MB 4200x5900 fid=1234 scans/mapa.jpg -> Archiwalne skany (forum, nid=812, pewne) LINK: https://forum.example.org/node/812 EDYCJA: https://forum.example.org/node/812/edit SKAD: upload.fid, tresc wpisu (sciezka) -> Zdjęcie w komentarzu (forum, nid=920, cid=42, pewne) LINK: https://forum.example.org/node/920#comment-42 ``` CSV zawiera jedną linię dla każdego znalezionego miejsca. Gdy powiązania brak, w CSV zostaje plik, jego `fid`, rozmiar i propozycja wyszukiwania nazwą w Drupal Search. ### Tylko skany lub wybrane pliki ```bash python main.py --find-usage --top 100 --path-contains skan --report-csv skany.csv python main.py --find-usage --fid 1234 --report-csv jeden-obraz.csv python main.py --find-usage --min-size-mb 5 --usage-scope all --report-csv wieksze-niz-5mb.csv ``` `--usage-scope top` szuka tylko w `--top N` największych obrazach; `optimizable` – w obrazach przekraczających limity; `all` – w całym filtrowanym zestawieniu. Przy bardzo dużych bazach `all` może oznaczać długi skan treści. `--no-text-scan` wyłącza szukanie w treściach i zostawia szybsze powiązania `fid`. `--scan-cck-text` rozszerza skan na tekstowe pola CCK. ## 2. Dokładny dry-run optymalizacji ```bash python main.py \ --find-usage --usage-scope optimizable \ --optimize --dry-run --optimize-top 20 \ --max-width 2400 --max-height 3200 --max-file-mb 2 \ --report-csv plan.csv --report-json plan.json ``` Dostaniesz rzeczywisty przewidywany rozmiar po rekompresji (test wykonany na pliku tymczasowym). **Nie zmieni to oryginałów ani SQL.** `--top` dotyczy wyświetlania; `--optimize-top` ogranicza liczbę obrazów poddawanych testowi/zmniejszaniu. ### Duze skany: DecompressionBombWarning / DecompressionBombError Pillow standardowo ostrzega powyzej ok. **89,5 mln pikseli (89,5 MP)**, a powyzej ok. **179 mln** przerywa otwieranie z bledem `DecompressionBombError`. To limit liczby pikseli, **nie rozmiaru pliku w MB**. Uczciwe skany dokumentow moga miec 100-200 MP. W tej wersji prog jest konfigurowalny: domyslnie `--max-image-mpixels 220`, dzieki czemu skany o **199 756 800 pikselach** z podanego logu nie wywoluja juz ostrzezen ani automatycznego bledu Pillow. Narzedzie **nie wylacza globalnie ochrony**: otwiera naglowek, sprawdza liczbe pikseli **przed dekodowaniem**, a przekraczajace limit obrazy wypisuje jako `[SKIP-LIMIT]` z przyczyna. Pozostale rodzaje ostrzezen nie sa filtrowane. Zbyt duzy obraz nie zostaje zmieniony na dysku ani w bazie. Uruchom ponownie swoj test: ```bash python main.py \ --min-size-mb 5 --optimize --dry-run \ --max-width 4000 --max-height 6000 --max-file-mb 3 \ --max-image-mpixels 220 --report-csv plan.csv ``` Jesli serwer ma malo RAM, mozna obnizyc limit, np. do `--max-image-mpixels 120`: obrazy powyzej 120 MP zostana **pominiete**, a nie bezwarunkowo dekodowane. Skany JPEG wymagajace skalowania korzystaja z `draft()` do zmniejszenia pamieci przy dekodowaniu. Bardzo duze PNG moga nadal potrzebowac **ponad 1 GB RAM** na dekodowanie i skalowanie; domyslny limit MP nie jest gwarancja bezpiecznego zuzycia pamieci. Uruchamiaj najpierw `--dry-run`, a `--apply` testuj na pojedynczych plikach z backupem. Nie ustawiaj `Image.MAX_IMAGE_PIXELS = None` ani nie ignoruj wszystkich ostrzezen Pillow: takie ustawienia usuwaja mechanizm ostrzegania przed niebezpiecznymi obrazami. ## 3. Zmniejsz np. pierwsze 10 obrazów i aktualizuj bazę **Najpierw zrób pełen backup bazy danych i katalogu plików** oraz przetestuj na kopii. Następnie: ```bash python main.py \ --optimize --apply \ --optimize-top 10 --max-changes 10 \ --backup-dir /srv/backups/drupal6-images \ --max-width 2400 --max-height 3200 --max-file-mb 2 \ --report-csv wykonane.csv --report-json wykonane.json ``` Każda udana zmiana: 1. Zapisuje oryginał w osobnym katalogu (automatycznie tworzony podfolder uruchomienia). 2. Zastępuje obraz tylko jeżeli nowy plik jest mniejszy o co najmniej `--min-gain-percent` (domyślnie 5%). 3. Aktualizuje `files.filesize` dla wszystkich `fid` wskazujących na tę samą ścieżkę. 4. W przypadku błędu SQL próbuje odtworzyć oryginalny plik i rozmiar w DB (także przy MyISAM – kompensacja aktualizacji). `--apply` jest wymagane do każdego zapisu. Samo `--optimize` lub `--update-db` nie wykonuje zmian. **Realna optymalizacja zawsze aktualizuje rozmiar w DB**, także bez `--update-db`. Jeżeli chcesz wyłącznie zsynchronizować rozmiary plików w istniejącym `files`: ```bash python main.py --update-db --dry-run python main.py --update-db --apply ``` `--update-db` w połączeniu z `--optimize --apply` dodatkowo koryguje rozmiary tych plików, które *nie zostały* zoptymalizowane. ## Zasady jakości i bezpieczeństwa - **JPEG:** zachowuje rozszerzenie i ścieżkę, skaluje proporcjonalnie filtrem LANCZOS, zapisuje z wysoką jakością 92/89/86/85 (`--jpeg-quality`, `--min-jpeg-quality`). Nie schodzi poniżej skonfigurowanej jakości, nawet gdy cel 2 MB nie zostanie osiągnięty. JPEG jest formatem stratnym – każda ponowna kompresja może zmienić piksele. - **PNG:** zachowuje format oraz kanał przezroczystości, używa kompresji **bezstratnej**. Nie konwertuje PNG do JPEG i nie zmienia rozszerzeń, bo mogłoby to popsuć stare linki w Drupalu. Duży PNG ze skanem może pozostać duży, jeśli kompresja bezstratna nic nie da. Konwersja do JPEG wymagałaby osobnej, kontrolowanej migracji i aktualizacji odnośników. - **Skan tekstowy:** domyślny limit **2400 × 3200 px** zamiast starego 1920 × 1080. Przed decyzją sprawdź czytelność drobnego druku w próbce i w razie potrzeby zwiększ limity lub pomiń plik. - **GIF/animacje, nieobsługiwane formaty, niezgodności formatu i rozszerzenia:** pomijane przy zapisie. Symlinki i hardlinki też są pomijane. - Kopia oryginału wymagana jest przy `--optimize --apply`, a lokalizacja kopii **musi być poza** `FILES_BASE_PATH` i poza katalogiem WWW Drupala (jeśli ustawiono `DRUPAL_ROOT`). - Weryfikacja czasu modyfikacji i inode chroni przed zmianą pliku w trakcie skanowania; zalecane jest wykonywanie pracy w oknie serwisowym. - Zachowuje ICC, DPI, EXIF (z prawidłową obsługą orientacji) i tekstowe metadane PNG w obsługiwanych formatach, ale nie gwarantuje zachowania każdego niestandardowego segmentu metadanych. - Aktualizowany jest tylko `files.filesize` – nie są zmieniane ścieżki, `fid`, treści postów ani załączniki. Zmiana dużego obrazka może wymagać ręcznego odświeżenia wygenerowanych miniatur przez ImageCache. - Kopia wykonana przez samo narzędzie obejmuje **obraz**, nie całą bazę. MyISAM nie zapewnia transakcji; procedura przywracania jest best-effort. Pełny backup DB i plików przed zmianami jest obowiązkowy. ## Pozostałe opcje ```bash python main.py --find-duplicates python main.py --show-deleted --exclude-folders imagecache styles python main.py --find-usage --no-clean-urls --site-url https://example.org/drupal python main.py --help ``` Uwaga do `--show-deleted`: pokazuje pliki na dysku **bez pasującego wpisu `files` w analizowanym zestawieniu**, nie zaś potwierdzenie, że plik naprawdę nie występuje w treści. Przy filtrach (`--path-contains`, `--min-size-mb`, `--fid`) plik odfiltrowany może zostać pokazany jako „spoza zestawienia”. Nic nie jest usuwane. ### Ograniczenia wyszukiwania - Powiązania po `fid` są najbardziej jednoznaczne; wyszukiwanie po samej nazwie pliku bywa niejednoznaczne – w raporcie `pewnosc=mozliwe`. - Tekst przeszukiwany jest w **aktualnych rewizjach** węzłów i komentarzach. Nie szuka w archiwalnych rewizjach ani w niestandardowych zewnętrznych modułach przechowywania treści bez włączonego skanu CCK. - Link do nieopublikowanego wpisu lub komentarza może wymagać konta z prawami administracyjnymi. Raport to zaznacza. - **Drupal 6 stronicuje komentarze**: `node/NID#comment-CID` może nie przewinąć do komentarza z dalszej strony. Jeżeli masz zainstalowany moduł [Permalink dla Drupal 6](https://www.drupal.org/project/permalink), dodaj `--comment-permalinks`, aby generować `comment/CID#comment-CID`, które kieruje na właściwą stronę komentarzy. Bez tego modułu nie używaj tej flagi. Niezależnie od typu linku raport pokazuje `nid` i `cid`. - Nie ma gwarancji odnalezienia obrazów w niestandardowych relacjach tworzonych przez własne moduły, w CSS i w HTML poza bazą. - W przypadku prywatnego systemu plików adres bezpośredni do pliku może nie działać; odnośnik do tematu nadal powinien wskazać miejsce. ## Testy ```bash python -m unittest discover -s tests -v ``` Testy z lokalną, symulowaną bazą weryfikują powiązania Upload/CCK, obecne rewizje, komentarze, brak zmian w dry-run, aktualizację `files.filesize`, backup, przywracanie po błędzie SQL, format PNG i ścieżki katalogów. **Nie zastępują próby na kopii konkretnej instalacji Drupal 6**. ### Źródła struktury bazy Drupal 6 - [Oficjalny migrator Drupal 6 Upload](https://api.drupal.org/api/drupal/core%21modules%21file%21src%21Plugin%21migrate%21source%21d6%21Upload.php/8.9.x) – `upload.fid`, `upload.nid`, `upload.vid`. - [Oficjalny migrator komentarzy D6](https://api.drupal.org/api/drupal/core%21modules%21comment%21src%21Plugin%21migrate%21source%21d6%21Comment.php/class/Comment/main) – tabela `comments`, `cid`, `nid`, status komentarza. - [CCK 6](https://api.druphelp.com/api/cck/content.module/6) – `content_type_*`, `content_field_*`.