Przegląd skryptów genialnych #3 - xNova

Witajcie, w kontynuacji dawno zaczętej serii, w której przyglądam się największym „perełkom” pośród skryptów PHP. Dzisiaj pod nóż pójdzie coś specjalnego, prawdziwy as wśród asów. Drodzy Państwo, przywitajcie xNovę.

Najpierw krótko o samym skrypcie. xNova to skrypt, który ma umożliwić tworzenie gier MMO via WWW wyglądających dokładnie jak OGame (mówimy tutaj o starej wersji, bo od czasu wydania ocenianej wersji skryptu, OGame doczekał się całkowitej przebudowy interfejsu).

Ciężko jest nawet powiedzieć jaką wersję będę opisywał, bo xNova od wieków nie posiada oficjalnej strony/forum/bloga. Tak więc namnożyło się bez liku wersji (najczęściej po prostu przeróbek prywatnych osób, które połatały 3% bugów i zoptymalizowały kilka zapytań). Wersja którą mam, była opisana jako „xNova 0.3 Multilanguage”, ale zdefiniowana wewnątrz skryptu stała VERSION miała wartość 0.8, więc mamy niezły rozrzut.

W związku z ilością bugów oraz masą amatorsko tworzonych „nowych wersji” najczęstszymi poradami na bugi zamieszczonymi na „profesjonalnych suportach xNovy” nie są wcale poprawki błędnej linijki, tylko zdanie „podmień sobie plik z wersji 1.1A/5.5/x.x by Ktośtam” – to też bardzo skutecznie tworzy chaos w plikach.

No, ale przejdźmy do właściwej części wpisu:

  1. Już patrząc w strukturę katalogów możemy mieć pewne pojęcie o skrypcie. W katalogu głównym umieszczono foldery scripts i js. Oba zawierają skrypty JavaScripts, część z nich jest identyczna i po analizie można dojść do tego, że około 12 z plików nie jest nigdzie używanych.

  2. Wygląd skryptu opiera się na dwóch folderach: templates, w którym leżą pliki dla systemu szablonów i skins, w którym leżą obrazki i pliki CSS. Bardzo piękne założenie, szkoda tylko , że poza tymi folderami w katalogu głównym mamy jeszcze foldery css i images, więc zmiana szablonu nie daje możliwości pełnej modyfikacji wyglądu bez modyfikacji zawartości tych folderów. Ponadto wg mnie pliki JavaScript też powinny być zależne od wybranego theme’u, a więc leżeć w jego katalogu.

  3. Przejdźmy do instalacji. Uruchamiam folder z xNovą a tam bardzo prosty do zdiagnozowania błąd. Skrypt wymaga włączonej dyrektywy short_open_tag Wracamy po szybkim restarcie serwera :)

  4. Po zmianie konfiguracji próbuję jeszcze raz i co widzę? Błąd połączenia z bazą danych zamiast instalatora. W pliku index.php jest odpowiednia instrukcja, która przekierowuje do instalatora jeśli config.php jest pusty. Niestety, ktoś udostępniając tą paczkę ludziom nie pomyślał i zostawił swojego wypełnionego configa. Tak więc poprawmy jego błędy i wyczyśćmy plik config.php. Do trzech razy sztuka…

  5. Próbujemy jeszcze raz i oto naszym oczom ukazuje się przepiękny instalator… po francusku. Szkoda tylko, że silnik posiada możliwość zmiany języka w całej grze, w instalatorze także. Niestety nikt nie pomyślał nad możliwością wyboru języka instalacji.

  6. Lecąc na czuja wypełniamy tabelkę z danymi do DB, standardowy układ jak w każdym skrypcie. Ukazuje nam się podstrona z pociesznym napisem „La base de donnée a bien été installée!”. Szkoda tylko, że ktoś jej pożarł całego CSS-a.

  7. Styl powraca, tworzymy konto admina i mamy rejestrację z głowy. Czas się zalogować.

  8. Bez problemu loguję się do gry, a tam wszystko po… tak, zgadliście, po francusku. Instalator z automatu ustawia adminowi język na francuski. Trzeba go przestawić w opcjach konta/w bazie danych.

  9. Teraz, kiedy gra już działa, przyjrzyjmy się plikom: widać wyraźnie, że każda z podstron najpierw dołącza plik common.php, a więc to jemu przyjrzymy się na początku. Co my tam mamy? Widać, że istnieje możliwość ustawienia własnych ścieżek do kilku folderów oraz domyślnego języka gry. Tylko dlaczego to nie jest w configu? Potem widzimy dołączanie kilku plików, w tym todofleetcontrol.php.

  10. Ten plik jest na tyle wyjątkowy, że zdecydowałem się go pokazać w całości:

    <?php
    /**
    *
    *
    * @version 1
    * @copyright 2008 By Chlorel for XNova
    */
    
    // Fonctions deja 'au propre'
    include($ugamela_root_path . 'includes/functions/FlyingFleetHandler.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/MissionCaseAttack.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/MissionCaseStay.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/MissionCaseStayAlly.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/MissionCaseTransport.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/MissionCaseSpy.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/MissionCaseRecycling.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/MissionCaseDestruction.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/MissionCaseColonisation.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/MissionCaseExpedition.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/SendSimpleMessage.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/SpyTarget.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/RestoreFleetToPlanet.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/StoreGoodsToPlanet.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/CheckPlanetBuildingQueue.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/CheckPlanetUsedFields.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/CreateOneMoonRecord.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/CreateOnePlanetRecord.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/InsertJavaScriptChronoApplet.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/IsTechnologieAccessible.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GetBuildingTime.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GetRestPrice.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GetElementPrice.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GetBuildingPrice.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/IsElementBuyable.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/CheckCookies.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/ChekUser.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/InsertGalaxyScripts.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GalaxyCheckFunctions.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/ShowGalaxyRows.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GetPhalanxRange.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GetMissileRange.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GalaxyRowPos.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GalaxyRowPlanet.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GalaxyRowPlanetName.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GalaxyRowMoon.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GalaxyRowDebris.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GalaxyRowUser.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GalaxyRowava.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GalaxyRowAlly.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GalaxyRowActions.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/ShowGalaxySelector.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/ShowGalaxyMISelector.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/ShowGalaxyTitles.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GalaxyLegendPopup.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/ShowGalaxyFooter.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GetMaxConstructibleElements.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GetElementRessources.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/ElementBuildListBox.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/ElementBuildListQueue.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/FleetBuildingPage.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/DefensesBuildingPage.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/ResearchBuildingPage.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/BatimentBuildingPage.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/CheckLabSettingsInQueue.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/InsertBuildListScript.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/AddBuildingToQueue.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/ShowBuildingQueue.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/HandleTechnologieBuild.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/BuildingSavePlanetRecord.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/BuildingSaveUserRecord.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/RemoveBuildingFromQueue.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/CancelBuildingFromQueue.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/SetNextQueueElementOnTop.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/ShowTopNavigationBar.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/SetSelectedPlanet.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/MessageForm.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/PlanetResourceUpdate.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/BuildFlyingFleetTable.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/SendNewPassword.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/HandleElementBuildingQueue.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/UpdatePlanetBatimentQueueList.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/IsOfficierAccessible.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/CheckInputStrings.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/MipCombatEngine.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/DeleteSelectedUser.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/SortUserPlanets.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/BuildFleetEventTable.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/BuildRessourcePage.'.$phpEx);
    
    ?>
    

    Co my tu mamy? Bezmyślne dołączanie 78 plików za każdym wywołaniem strony. Nawet gdy się logujemy, rejestrujemy, oglądamy podstronę kontakt itd. silnik ładuje 78 plików potrzebnych tylko w grze.

  11. Kolejnym plikiem ładowanym w common.php jest plik /includes/db.php. On też jest w pewien sposób wyjątkowy: jedyne co robi to include’uje kolejny (jeden) plik. Gdzie tu logika?

  12. Wymieniony wyżej plik includes/db.php dołącza plik db/mysql.php. Jego zawartość też jest warta przytoczenia:

    <?
    function doquery($query, $table, $fetch = false){
    global $numqueries,$link,$debug,$ugamela_root_path;
    require($ugamela_root_path.'config.php');if(!$link)
    {$link = mysql_connect($dbsettings["server"], $dbsettings["user"],
    $dbsettings["pass"]) or
    $debug->error(mysql_error()."
    $query","SQL Error");
    
    mysql_select_db($dbsettings["name"]) or $debug->error(mysql_error()."
    $query","SQL Error");
    mysql_query("SET NAMES latin2");
    echo mysql_error();}
    
    $sql = str_replace("{{table}}", $dbsettings["prefix"].$table, $query);
    $sqlquery = mysql_query($sql) or
    $debug->error(mysql_error()."
    $sql
    ","SQL Error");
    
    unset($dbsettings);
    $numqueries++;
    $arr = debug_backtrace();
    $file = end(explode('/',$arr[1]['file']));
    $line = $arr[1]['line'];
    $debug->add("
    Query $numqueries:$query$file($line)$table$fetch
    
    ");
    
    if($fetch)
    {$sqlrow = mysql_fetch_array($sqlquery);
    return $sqlrow;
    }else{return $sqlquery;}}
    ?>
    

    Pierwszą rzeczą jaka rzuciła mi się w oczy poza genialnym układem kodu był fakt, że przy każdym zapytaniu jest dołączany config.php. No ktoś tam nie ma mózgu… nie można po prostu dołączyć go w common.php? Tak samo ewentualne łączenie powinno się odbywać nie funkcji wykonującej zapytania tylko tam.

  13. W folderze includes znajduje się plik databaseinfos.php, który na pierwszy rzut oka jest używany do tworzenia tabel przez instalator, a krótka analiza wykazuje, że używany jest tylko tam. W takim wypadku, czy nie logiczniej byłoby go usunąć automatycznie po zakończeniu instalacji (tak jak zresztą cały skrypt). Ponadto prościej byłoby chyba, gdyby znajdował się po prostu w folderze install.

  14. Samo nazewnictwo plików jest lekko mówiąc dziwne. Czy to mówiąc o sposobie zapisu (add_moon.php, ale już activeplanet.php), czy o języku (errors.php, paneladmina.php, verband.php). Do wyboru, do koloru!

  15. Jeśli chodzi o bazę danych, to widać jak na dłoni, że była dorabiana po kawałku zależnie od konieczności. Jednak nikt nie zadał sobie trudu, aby spojrzeć na nią całościowo, przeprojektować i choćby pousuwać zbędne pola z tabel czy ustawić bardziej optymalne typy dla pól. Jako najlepszy przykład można podać fakt, że informacje o występujących w grze Księżycach są trzymane w dwóch tabelach, przy czym jedna zawiera po prostu część danych z pierwszej tabeli. Niestety, aby to poprawić trzeba dokonać dość mocnej przeróbki skryptu, a mało kto ma tyle zawzięcia.

Powyższy skrypt jest bardzo wymownym przykładem na to jak nie powinno się tworzyć jakichkolwiek aplikacji. Zero organizacji pracy i początkowych założeń daje właśnie takie efekty. Można tylko współczuć ludziom którzy z wielkim zapałem chcą zrobić "super wypasionom grem na xNovie" nie zaglądając nawet do "jej wnętrza".

[PHP] Sprawdzanie poprawności numeru PESEL

Dzisiaj przedstawię prostą funkcję do walidacji numeru PESEL w PHP. Przyjmowany argument i zwracana wartość nie powinny być niespodzianką - bierzemy numer PESEL, a dostajemy wartość logiczną (prawda/fałsz).

Aby sprawdzić poprawność numeru PESEL, należy obliczyć jego tak zwaną cyfrę kontrolną. Jest to ostatnia cyfra numeru PESEL, obliczana na podstawie jego wcześniejszych cyfr i sprawdzana na podstawie wzoru (zakładamy, że a - j to kolejne cyfry PESEL-u licząc od lewej):

a+3b+7c+9d+e+3f+7g+9h+i+3j+k

Teraz należy obliczyć resztę dzielenia powyższej sumy przez 10. Jeżeli ostatnia cyfra otrzymanego wyniku jest równa 0, to numer jest poprawny. W przeciwnym przypadku cyfra kontrolna nie zgadza się z resztą. Przykładowa implementacja w PHP, może wyglądać następująco:

function peselValidate($pesel)
{
    $sum = 0;
    $weights = array(1, 3, 7, 9, 1, 3, 7, 9, 1, 3, 1); // Wagi dla kolejnych cyfr numeru PESEL
    
    foreach (str_split($pesel) as $position => $digit) {
        $sum += $digit * $weights[$position];
    }

    return substr($sum % 10, -1, 1) == 0;
}

Należy jednak zwrócić uwagę na dwie rzeczy:

  • poprawność cyfry kontrolnej nie gwarantuje, że dany numer PESEL istnieje - tylko tyle, że jest zgodny wymogami co do numeru. Faktyczne potwierdzenie istnienia danego numeru jest niemożliwe bez dostępu do ewidencji.
  • w Polsce odnotowano przypadki wydania nieprawidłowych numerów PESEL

Aktualizacja (21.02.2015): aktualizacja kodu funkcji, reorganizacja treści

Przegląd skryptów genialnych

Nie wiem czy ktokolwiek pamięta jeszcze pierwszy wpis z tej serii. Ja w każdym razie o nim zapomniałem. Jednak przy okazji dzisiejszego przeglądania jednego ze skryptów do tworzenia gry MMORPG via WWW przypomniałem sobie o tej serii i postanowiłem co nieco napisać.

Miałem zamiar przetestować skrypt MafiaWarz służący do tworzenia gier MMORPG w klimatach gangsterskich. Oczywiście nie spodziewałem się żadnych rewelacji po darmowym skrypcie tego typu, ale to co tam zobaczyłem dało kilka okazji do śmiechu dlatego postanowiłem się tym podzielić ze światem ;)

Zacznijmy rzecz jasna od początku, bo tak bywa najłatwiej. Instrukcji instalacji/pliku README nigdzie nie było, ale akurat zaimportowanie jednego pliku SQL i uzupełnienie danych w configu (choć dość trudnym do znalezienia) nie należało do bardzo trudnych.

Problem pojawił się podczas gdy chciałem zarejestrować konto. Na szczęście Wujek Google pozwolił szybko dojść do rozwiązania tego problemu - rozbiło się w zasadzie o jedną linijkę, więc na razie idzie dość gładko. Teraz zacznijmy ocenę właściwą:

  1. Błąd uniemożliwiający rejestrację na samym początku. Wielu ludzi może to odstraszyć, ale może i słusznie? :P
  2. Hasło miało zostać wysłane mailem, ale jako że skrypt testowałem na localhoście, to postanowiłem podmienić hasło w bazie danych na nowe. Długo szukałem charakterystycznego zahashowanego hasła w tabeli users, ale go nie znalazłem, bo hasła są trzymane w niezakodowanej postaci.
  3. Kontynuując temat bazy danych - brak prefiksów tabel, a ich nazwy zbyt oryginalne nie są, więc może być mały problem.
  4. Pokręcone typy pól: money jako int, online oraz lastcrime (trzymane jako znacznik czasu) jest polem typu varchar(100) zamiast po prostu int
  5. Jako że skrypt jest po angielsku, postanowiłem go przynajmniej częściowo spolszczyć. Niestety mimo tego, że użytkownicy mają możliwość zmiany języka w profilu to pliki językowe są przygotowane tylko dla dwóch z kilkudziesięciu zakładek. Dodatkowo oba te pliki zawierają dużo nigdzie niewystępujących tekstów, a nie mają tych najważniejszych.
  6. Totalny bajzel w plikach. Dużo zbędnych, po kilka kopii tego samego pliku, pliki do niczego niepotrzebne (przykładowo plik w00t.php zawierający jedynie pustą tabelę w HTML). Takie rzeczy zdecydowanie nie powinny leżeć w publikowanej wersji.
  7. Zero konwencji kodowania dla PHP
  8. Kod HTML na poziomie internetu łupanego. Można by go nazwać HTML-em 2.0 gdyby nie brak doctype. Nazwijmy go więc umownie HTML-em 0
  9. Design oparty na ramkach, znaczniki noframes oczywiście puste…
  10. Postanowiłem znaleźć panel administracyjny. Zajrzałem do jedynego pliku który wydał mi się właściwy (admincp.php), aby poszukać jakie pole w bazie muszę zmienić. Zabezpieczenie tego pliku naprawdę mnie oczarowało.
    if ($userlevel != 3){
        echo "Your not a admin.";
    }
    
    faktycznie - większość użytkowników na pewno przestraszy się tej informacji i czym prędzej ucieknie. Co jednak z pozostałymi niegodziwcami? Czy użycie die() byłoby zbyt trudne?
  11. Używanie " praktycznie wszędzie gdzie to niepotrzebne, np. dla zwyczajnych stringów lub nawet stosowanie echo "$style";. Nie szkoda serwera?
  12. Prawie w każdym pliku występuje podwójne łącznie z bazą. Najpierw dołączany jest plik includes/db_connect.php, a potem includes/functions.php na którego początku jest dołączany includes/db_connect.php
  13. Masa obrazków leżących w katalogu głównym oraz includes mimo istnienia katalogu images
  14. Źle zaprojektowana baza danych - 45 tabel dla tak w sumie nieskomplikowanego skryptu mówi samo za siebie
  15. Totalnie niespójny desing. Wygląda to tak, jakby każda zakładka była robiona przez inną osobę. Poza tym nie ma to jak biały tekst na jasnożółtym tle (no kto tutaj coś widzi?)
  16. Krótka analiza zakładek z menu - kilka to biała strona, jedna to "coming soon!", inna to 404. Zabrakło natomiast linków w menu do tak mało istotnych zakładek jak np. panel admina.
  17. Skrypt wymaga do działania włączonej opcji short_open_tags
  18. Podatność na SQL Injection
  19. CSS rozbite na masę poszczególnych plików
  20. W wielu miejscach widać pozostałości albo podwaliny systemu do aktywacji użytkowników - nie jest on jednak w żaden sposób wykorzystywany.

Skrypt ma na pewno dużo więcej błędów i niedoróbek, których nie wypisałem w tym wpisie. Powyższa lista to tylko jego ogólny przegląd i pewnego rodzaju przestroga dla osób które chciałyby stawiać gry online na takich gotowcach. Szkoda czasu i nerwów na poprawianie takich pseudoskryptów - mówię to także z własnego doświadczenia ;)

Sądzę że w niedługim czasie mogę się pokusić o opisanie większej ilości skryptów tego typu.

Przepraszam też za ewidentną chaotyczność tego wpisu, był on pisany jeszcze podczas "badania" skryptu dlatego nowe rzeczy były dopisywane na bieżąco.

Wstępna zapowiedź IronCMS-a 2.0

Od dość dawna nic nie pisałem na blogu. Przede wszystkim od bardzo dawna nie pisałem nic o IronCMS-ie. A mam w stosunku do niego pewne plany.

Ostatnią wydaną wersją był Iron z numerkiem 1.2.1. Od tamtego czasu nie raz miałem okazję patrzeć na jego kod i zmieniać go. Za każdym razem myślałem o tym samym – „czas na nową wersję z większymi zmianami. Trzeba to w końcu ogarnąć”.

A teraz bez zbędnego gadania przedstawię kilka rzeczy, które chciałbym zmienić w wersji 2.0 skryptu.

Czytaj dalej →

Backupowanie baz danych MySQL z poziomu PHP

Na samym początku zaznaczam, że pierwowzór skryptu przedstawionego poniżej nie jest mojego autorstwa! Skrypt znalazłem tutaj (skrypt na samym dole artykułu). Dokonałem w nim kilku przeróbek i w tym miejscu zostawiam dla potomnych - może się komuś przyda!

{{{ more }}}

Wprowadzone zmiany:

  • Umieszczenie daty wykonania backupu na górze zrzutu bazy
  • Lekkie ogarnięcie kodu PHP. Wiem, że wiele mu brakuje - jak komuś chce się bawić, to proszę bardzo - dla mnie aktualnie liczy się fakt, że skrypt działa
  • Zmiana formatu nazwy pliku (na taki który mi bardziej odpowiada :P). Jakby się komuś nie podobał, to zmiana jest przecież banalna.
  • Możliwość ustawienia katalogu w którym będą lądowały backupy
  • Kilka zmian czysto estetycznych
<?php
header('Content-type: text/html; charset=utf8');

try{
    // KONFIGURACJA - START
    $dbName = 'revival'; // Nazwa bazy danych do zbackupowania
    $dbHost = 'localhost'; // Nazwa serwera baz danych
    $dbUser = 'root'; // Nazwa użytkownika
    $dbPass = ''; // Hasło

    $backupsDir = 'backups'; // Katalog do którego będą zapisywane backupy. Bez końcowego ukośnika!
    // KONFIGURACJA - STOP
    // Dalej lepiej nie ruszać!

    // Stworzenie nowego obiektu klasy PDO
    $pdo = new PDO("mysql:host=$dbHost;dbname=$dbName", $dbUser, $dbPass, array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"));
    $pdo -> setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $sqlResult = $pdo -> query("SHOW tables FROM $dbName");

    // Stworzenie nagłówka informacyjnego
    $sqlData = "-- Data wykonania kopii: ".date('d.m.Y')." r. o godzinie ".date('H:i')."
    -- Baza: $dbName
    SET SQL_MODE=\"NO_AUTO_VALUE_ON_ZERO\";";

    while ($queryTable = $sqlResult -> fetch(PDO::FETCH_ASSOC)){
        $sqlTable = $queryTable['Tables_in_'.$dbName];
        $sqlResultB = $pdo -> query("SHOW CREATE TABLE $sqlTable");
        $queryTableInfo = $sqlResultB -> fetch(PDO::FETCH_ASSOC);

        // Dodanie nagłówków dla konkretnych tabel
        $sqlData .= "\n\n--
        -- Struktura dla tabeli `$sqlTable`
        --\n\n";
        $sqlData .= $queryTableInfo['Create Table'] . ";\n";
        $sqlData .= "\n\n--
        -- Wartości tabeli `$sqlTable`
        --\n\n";

        $sqlResultC = $pdo -> query("SELECT * FROM $sqlTable");

        // Stworzenie INSERT-a dla każdego rekordu
        while ($queryRecord = $sqlResultC -> fetch(PDO::FETCH_ASSOC)) {
            $sqlData .= "INSERT INTO `$sqlTable` VALUES (";
            $sqlRecord = '';
            foreach( $queryRecord as $sqlField => $sqlValue ) {
                $sqlRecord .= "'$sqlValue',";
            }
            $sqlData .= substr($sqlRecord, 0, -1);
            $sqlData .= ");\n";
        }
    }

    // Zapisujemy wynik do pliku
    file_put_contents($backupsDir.'/backup_'.$dbName.'_'.date('d_m_Y').'.sql', $sqlData);
    echo 'Backup został zapisany.';
}

catch(PDOException $e){
    echo 'Połączenie nie mogło zostać utworzone: '.$e->getMessage();
}

Skrypt umieścić u siebie na FTP i uzupełnić sekcję konfiguracyjną. Wywoływać ręcznie (poprzez wpisanie URL-a) albo przez CRON-a.

Musisz koniecznie pamiętać o zabezpieczeniu katalogu z backupami (chociażby przez plik .htaccess). Dobrze by było też zabezpieczyć sam skrypt backupujący, aby nikt nie zajechał nam bazy danych przez wywoływanie w pętli.

Mimo tego, że skrypt testowałem, to nie biorę żadnej odpowiedzialności za jakiekolwiek szkody wynikłe z jego korzystania! Boisz się to nie używaj.

Mam nadzieję, że ten skrypt pomoże Wam w tworzeniu kopii swoich baz danych i sprawi, że będzie się to działo częściej niż raz na rok :P. Bo jak wiadomo:

"Ludzie dzielą się na tych, którzy robią backupy i tych, którzy zaczną je robić".

Pozdrawiam.

PS: Jakby ktoś zdecydował się rozbudować ten skrypt np. o wysyłanie backupów na e-mail czy ich uploadowanie na zewnętrzny serwer FTP, to niech go udostępni, może innym też się przyda.

Dwa nowe skrypty w Dev Center

Żeby moje nowe Dev Center (opublikowane wczoraj) nie świeciło pustkami toteż napisałem dwa nowe skrypty, które w dniu dzisiejszym dochodzą do jego skromnych zasobów.

Postanowiłem odpicować dwie z klas, których używanych przeze mnie w IronCMS-ie. Upiekłem przy tym dwie pieczenie na jednym ogniu. Po pierwsze: zyskałem nowe skrypty do kolekcji, a po drugie uzyskałem mocno poprawione klasy do użytku w kolejnych wersjach CMS-a (a te się powoli budują…).

Nie ma sensu się rozpisywać. Po prostu prezentuję:

Klasa do obsługi MySQL oraz powiązana z nią klasa loggera zdarzeń.

Wszelkie komentarze mile widziane ;)

Iron 1.2.1 i trochę informacji o nim

Wydałem lekko poprawioną wersję Irona. Nowa wersja usuwa przede wszystkim błędy bezpieczeństwa (za wyłapanie kilku z nich serdecznie dziękuję DeXTeD'owi z forumweb.pl :)), ale poprawia też np. nieaktualizowany od prawie pół roku, skrypt sprawdzania aktualizacji dla skryptu. Zarówno ten wbudowany jak i jego wersję online, dla osób których serwery nie pozwalają na otwieranie zewnętrznych plików funkcją file_get_contents.

Ponadto postanowiłem w końcu ogarnąć jakiekolwiek strony informacyjne, download etc dla Irona. Znajdują się one w nowym podmenu w menu "Projekty" na górze strony. Będzie toto stopniowo rozbudowywane.

Pozdrawiam :)

IronCMS 1.2

Wreszcie, po naprawdę sporym opóźnieniu, udało mi się skończyć wersję 1.2 mojego CMS-a. Skrypt miał być wprawdzie gotowy ponad 2 tygodnie temu, ale z nawału wykrytych w nim błędów dopadło mnie chwilowe lenistwo. Dopiero dziś udało mi się wziąć w garść i skończyć zaczętą robotę.

Najwięcej zmian w tej wersji można znaleźć "pod maską", oto najważniejsze z nowości:

  • przepisanie części skryptu na programowanie obiektowe
  • znaczne uproszczenie struktury bazy danych
  • możliwość włączenia komentowania dla osób niezalogowanych
  • możliwość wygenerowania kanału ATOM dla artykułów
  • skórkę dla telefonów komórkowych przywrócono do używalności - wprawdzie była ona w Ironie już od dłuugiego czasu, jednak była dość mocno niedopracowana i od dawna nieaktualizowana. W tej wersji wprowadzono w niej naprawdę sporo zmian.
  • w związku z powyższym nadszedł też czas na zrobienie łatwego przełączania się między motywami
  • pasek dla administratora, na górze strony, zawierający najważniejsze odnośniki
  • więcej zmian w readme…

Pobierz Irona

IronCMS 1.1

Zaledwie osiem dni minęło od premiery pierwszej publicznej wersji mojego CMS-a, a już prezentuję Wam jego kolejną wersję. Nie jest to jednak aż tak wielki powód do dumy jak pozornie mogłoby się wydawać, bo wersja ta spowodowana jest wieloma błędami i spostrzeżeniami do wersji 1.0. W tym miejscu dziękuję wszystkim za konstruktywną krytykę i wszelką pomoc.

W tej wersji skupiłem się głównie na poprawie wytkniętych mi błędów. Tak więc z najważniejszych zmian wymienić można:

  • działające BBCode - szukałem troszkę czegoś odpowiedniego i w sumie oparłem bbcode w Ironie na klasie NBBC. Banalna edycja i dodawanie znaczników, skompresowany kod (pozbawiony wcięć i enterów) zajmuje ok. 2500 linii (64 KB) (w Ironie rozmiar obcięty do 1900 linii). Do tego genialna dokumentacja. Polecam ;)
  • nowe menu w panelu administracyjnym - było na nie wiele narzekań (sam się w nim gubiłem), więc przysiadłem na chwilę i mam nadzieję, że jest łatwiej się w nim połapać.
  • wiele poprawek w systemie komentarzy - możliwość korzystania z bbcode, komentarze adminów nie potrzebują akceptacji (wiem, absurd, przeoczyłem to), tekst domyślny znika po kliknięciu na pole, możliwość dodania komentarzy do artykułów, podniesione zabezpieczenia itd.
  • uporządkowano praktycznie wszystkie formularze w skrypcie - ujednolicono wygląd i poprawiono opisy
  • wszystkie funkcje mysql_* zastąpiono funkcjami własnymi (odniesienie do sugestii Kwpolska) - kto wie? Może kiedyś zobaczymy Irona np. na PostgreSQL? :P
  • dużo, dużo więcej…

Teraz czas na informacje nieco z innej beczki. Otóż w dniu dzisiejszym, grupa tworząca Irona została z powrotem zredukowana do jednoosobowego zespołu szaleńców (czytaj mnie…). M4tx przez cały czas swego udziału włożył do projektu około 20 linii kodu (a po wersji 1.0, która została napisana od nowa ten udział wyniósł równe zero), tak więc stwierdziłem, że nie będę go więcej męczył pytaniem, czy coś zrobi. I został (jak na razie!) wyrzucony z projektu.

Mam nadzieję, że zaprezentowane powyżej zmiany  uważacie za dobre.

IRON CMS 1.1 (*.zip)

A na koniec: Wesołych Świąt Wam życzę :)

Premiera Iron CMS-a!

Z nieukrywaną dumą informuję, że skrypt który zacząłem tworzyć dokładnie 114 dni temu (20.12.10) został w końcu doprowadzony do stanu używalności. Dziś, o godzinie 14:40 nadszedł czas zaprezentować go światu.

Premiera odbyła by się chwilę wcześniej, gdyby nie to, że nie mogłem się dostać do phpMyAdmina na sobak.pl i tym samym zdebugować pewnego zapytania (na localhoście działało, tu nie). Przez około 5 godzin męczyłem się z instalatorem. Dodatkowo ponad godzinę straciłem, czekając aż panel administracyjny 1&1 raczy się załadować., ale, tak, udało się! :) Iron CMS 1.0 jest gotowy!

O tym, że jego przepisywanie od nowa zbliża się do końca informowałem tutaj. Zajęło mi to dokładnie 20 dni (z małymi przerwami). Podczas tego "procesu" skupiłem się głównie na poprawie logiki, wydajności i błędów ze starszych wydań (były 4 wydania prezentowane tylko znajomym).

  • poprawiłem dziesiątki nieoptymalnych miejsc. Trzykrotne łączenie się z bazą w jednym pliku, czy pobieranie tej samej wartości kilkanaście razy pod rząd? Takie rzeczy tylko w starych wersjach Irona (tak, ochrzciłem CMS-a mianem żelazka :D (joke)) :)
  • poprawiłem stare funkcjonalności. Np. f-cja kosza dostępna od wersji 0.3 została rozszerzona o możliwość wyrzucania artykułów. Wcześniej były to tylko podstrony.
  • nowa wersja starego szablonu. Jak już napisałem w zapowiedzi skryptu, użyłem gotowego szablonu. Podczas przepisywania CMS-a na nowo, znalazłem jego nowszą wersję. Został mocno poprawiony HTML i CSS i doszło m. in. górne menu. Dodatkowo dorzuciłem lekką modyfikację szablonu od siebie.
  • możliwość ustawienia oddzielnego theme'u dla panelu administracyjnego
  • zalążek rang użytkowników
  • znaczna poprawa bezpieczeństwa
  • dodany instalator
  • poprawa przejrzystości panelu administracyjnego
  • zmiana wielu rozwiązań pod względem logistycznym
  • setki, naprawdę setki rzeczy o których zapomniałem bądź pisać nie warto…

Skrypt został napisany z użyciem możliwości z PHP5 (wersja czwarta musi odejść do lamusa…) i bazy danych MySQL.

Trochę statystyki

Wielkość skryptu (zdekompresowane)

  • 0.1 - 26.4 kB
  • 0.2 - 2.87 MB
  • 0.3 - 1.48 MB
  • 0.4 - 2.32 MB
  • 1.0 - 344 KB

Skąd tak duża różnica? Od wersji 0.2 w skład skryptu wszedł JS-owy edytor WYSIWYG - TinyMCE. W 0.3 go "lekko" odchudziliśmy (z m4tx'em) - stąd taki spadek rozmiaru. W 0.4 doszło troszkę nowych rzeczy, a IronCMS 1.0 został pozbawiony edytora WYSIWYG, zastąpiono go BBCodem. Jego rozmiar jest podwójnym sukcesem, bo poprzez rozbicie theme'u na theme dla strony i oddzielny dla panelu, ilość obrazków de facto nam się podwoiła.

Dlaczego nie WYSIWYG lecz BBCode? Otóż stwierdziłem, że skryptów idiotoodpornych powstało już mnóstwo. Zdaję sobie sprawę, że to może zawęzić ilość użytkowników Irona (ale spójrzmy obiektywnie - ile ludzi tak czy siak będzie go używać? :P). Poza tym - znajomość BBCode, to nie jest jakaś magiczna sztuka.

Linie kodu (kliknij aby powiększyć)
Statystyki linii w IronCMS

Podziękowania

  • dla Rhina za rozwiązanie niezliczonej ilości moich błędów i przeoczeń
  • dla Pawła1503 i m4tx'a za betatesty
  • dla CapaciousCore'a, za naprawdę fachowe porady dotyczące skryptu.

DOWNLOAD IRON CMS 1.0