Witam! Z pewnością część z Was prowadzi, prowadziła lub będzie prowadzić w przyszłości jakieś forum internetowe. Jednym z popularnych dzisiaj CMSów jest MyBB. Goły skrypt forum nie zawsze dostarcza nam wszystkiego, co chcielibyśmy widzieć na naszej stronie. Jednym z rozwiązań jest znalezienie odpowiedniego pluginu w internecie, lecz nie zawsze znajdzie się wtyczka, która zaspokoiłby nasze potrzeby. Co wtedy? Ano wtedy napiszemy sobie ją sami ;)
Sam miałem często taki problem jak opisany powyżej. Nie było wtyczki do MyBB, która spełniałaby wymagane przeze mnie funkcje. Postanowiłem napisać ją sobie samemu, lecz jak tylko zobaczyłem dokumentację MyBB, a właściwie jej brak to ręce mi opadły. Niedawno jednak postanowiłem się przemóc i spróbować. Dobrą metodą poznania zasady działania systemu rozszerzeń w MyBB okazało się otworzenie kodu źródłowego pluginu realizującego podobne zadanie do tego, który sam chciałem napisać i przeanalizowanie linijka po linijce co tam się właściwie dzieje. Jako, że naprawdę ciężko znaleźć w google jakichś sensownych informacji na temat pisania rozszerzeń do tego popularnego skryptu forum postanowiłem napisać ten poradnik i podzielić się z Wami moją wiedzą. Nie ukrywam, że dobrze by było, gdybyście znali przynajmniej podstawy PHP lub umieli programować w jakimś innym języku, aczkolwiek postaram się napisać ten artykuł tak, aby nawet laik zrozumiał w czym rzecz i może nawet zainteresował się programowaniem ;) Zaczynajmy więc!
Na początek…
Aby móc pisać plugin i jakoś go testować wypadałoby postawić sobie jakieś czyste forum na serwerze lub lokalnym komputerze z zainstalowanym np. XAMPPem.
XAMPP to darmowe oprogramowanie pozwalające postawić sobie w szybki i prosty sposób serwer na domowym komputerze. W celu uruchomienia takiego serwera wchodzimy na https://www.apachefriends.org/pl/index.html , a następnie ściągamy paczkę instalacyjną dla naszego systemu operacyjnego. Instalator jest bardzo prosty, konfigurować nic nie trzeba. Po zainstalowaniu uruchamiamy sobie panel kontrolny XAMPPa i uruchamiamy Apache oraz MySQL. Pliki instalacyjne wrzucamy do folderu htdocs znajdującego się tam, gdzie zainstalowaliśmy XAMPPa. Teraz możemy wejść na http://localhost/ i zainstalować sobie MyBB jak na normalnym serwerze www.
Jak już mamy działające forum to wchodzimy do folderu ./inc/plugins/ i w nim tworzymy plik *.php z nazwą naszego pluginu. W moim przypadku będzie to “mojplugin.php”.
Wymagane podstawy
Jedną z pierwszych rzeczy, które musimy zrobić to zabezpieczenie przed bezpośrednim wykonywaniem naszego skryptu przez użytkownika. W tym celu w naszym nowym pustym pliku wpisujemy następujący kod:
1 2 3 |
<?php if(!defined("IN_MYBB")) die("Direct initialization of this file is not allowed.<br /><br />Please make sure IN_MYBB is defined."); ?> |
Co robi powyższy kod? Otóż w każdym głównym pliku MyBB (np. index.php, showthread.php) zdefiniowana jest stała IN_MYBB. Każdy załączany później plik (np. plugin) ma dostęp do owej stałej. Ten kod sprawdza właśnie, czy stała IN_MYBB jest zdefiniowana w kodzie. Znak ! oznacza negację, tak więc jeśli IN_MYBB jest niezdefiniowane to interpreter zakończy wykonywanie skryptu z napisem “Direct initialization […]”.
Teraz musimy wpisać w plik podstawowe informacje o naszym pluginie oraz funkcje, które MyBB powinno wykonać w celu zainstalowania, odinstalowania, aktywowania lub dezaktywowania naszego pluginu. Służą do tego następujące funkcje:
- nazwaPluginu_info() – wymagana
- nazwaPluginu_install() – opcjonalna
- nazwaPluginu_uninstall() – opcjonalna
- nazwaPluginu_activate() – opcjonalna
- nazwaPluginu_deactivate() – opcjonalna
MyBB rozpoznaje również funkcję nazwaPluginu_is_installed(), która sprawdza, czy nasz plugin jest zainstalowany (zwraca wartość logiczną np. prawdę, kiedy znajdzie w bazie danych tabelę, która została stworzona przez nasz plugin przy instalacji).
Oczywiście zamiast nazwaPluginu musimy wpisać nazwę naszego pliku *.php. W moim przypadku będzie to np. mojplugin_info()
Teraz musimy uzupełnić informacje o naszym pluginie. Funkcja nazwaPluginu_info() powinna zwracać tablicę asocjacyjną z następującymi kluczami:
- name – nazwa pluginu,
- description – opis pluginu,
- website – strona www pluginu,
- author – autor pluginu,
- authorsite – strona www autora,
- version – wersja pluginu,
- guid – ang. Globally Unique Identifiers – Unikalny identyfikator pluginu, który owtrzymasz, jeśli wstawisz swoje dzieło na oficjalną stornę z modami do MyBB. Pozwala to użytkownikom sprawdzać, czy mają najnowszą wersję pluginu. Jeśli nie chcesz wysyłąć pluginu na mods.mybb.com, zostaw w tym polu pusty ciąg.
- compatibility – kompatybilność naszego pluginu z odpowiednimi wersjami MyBB (np. jeśli wpiszemy tam “16*” to nasz plugin będzie kompatybilny z wszystkimi wersjami MyBB z numerkiem 1.6.x)
Po dopisaniu do naszego kodu funkcji odpowiedzialnej za przekazanie informacji o pluginie powinien on wyglądać tak:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<?php if(!defined("IN_MYBB")) die("Direct initialization of this file is not allowed.<br /><br />Please make sure IN_MYBB is defined."); function mojplugin_info() { return array( "name" => "Mój plugin", "description" => "To jest mój pierwszy plugin do MyBB.", "website" => "http://krupson.eu/podstronamojegopluginu/", "author" => "Krupson", "authorsite" => "http://krupson.eu/", "version" => "1.0", "guid" => "", "compatibility" => "1*" ); } ?> |
Instalacja / deinstalacja pluginu
Niektóre pluginy mogą działać bez instalacji. Wystarczy je tylko aktywować z panelu admina i plugin już działa. Inne z kolei wymagają instalacji. Z czego to wynika? Załóżmy, że mamy plugin, który do każdego postu w MyBB dokleja na sztywno zdefiniowany ciąg oraz plugin realizujący funkcję czatu. Ten drugi musi wgrać do bazy danych swoje tabele, w których będzie przechowywał dane takie jak wpisy na czacie, lista zbanowanych użytkowników itp. Ten pierwszy zaś nie musi trzymać w bazie żadnych danych. Oddzielenie instalacji/deinstalacji od aktywacji/deaktywacji jest bardzo pomocne w sytuacji, kiedy chcemy np. na jakiś czas wyłączyć czat nie tracąc przez to wszystkich napisanych na nim do tej pory wpisów. Poniżej przedstawię przykładową realizację kodu do instalacji/deinstalacji pluginu.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
function mojplugin_install() { global $db; // Normalnie z poziomu funkcji nie mamy dostępu do zmiennej $db, musimy uczynić ją widoczną wewnątrz funkcji, aby móc jej używać. // Teraz tworzymy naszą tabelę prostym zapytaniem SQL. $db -> write_query("CREATE TABLE `".TABLE_PREFIX."mojatabela` ( `kolumna1` int(11) NOT NULL, `kolumna2` tinyint(1) NOT NULL, `kolumna3` int(11) NOT NULL ) ENGINE=MyISAM"); } function mojplugin_uninstall() { global $db; $db -> drop_table('mojatabela'); // Usuwamy naszą tabelę } function mojplugin_is_installed() { global $db; return $db -> table_exists('mojatabela'); // Sprawdzamy, czy nasza tabela istnieje. Jeśli tak - zwróci true, jeśli nie - zwróci false. } |
W czym tkwi haczyk?
Nasz plugin potrafi się już przedstawić, zainstalować oraz odinstalować z poziomu ACP. Najwyższa pora, aby zaczął spełniać pewne funkcje. Niech napisany przez nas plugin zacznie pokazywać 10 najnowszych użytkowników na naszym forum. Pierwsze co musimy zrobić to zdefiniować sobie funkcję, która pobierze z bazy 10 najnowszych użytkowników, sformatuje ich nicki, wygeneruje linki, sformatuje datę rejestracji, a następnie zapisze wynik swojej pracy do zmiennej $nowiUzytkownicy. Niech nasza funkcja nazywa się mojplugin_get_new_users(). Jednak skrypt forum wciąż nie wie co z nią zrobić. W MyBB istnieje coś takiego jak system haków (ang. hooks). Pozwala to podczepić samemu napisaną funkcję pod wybrany fragment kodu całego forum, w którym ma się ona wykonać. Pełną listę hooków można zobaczyć tutaj: http://docs.mybb.com/1.8/development/plugins/hooks/
W kolumnie “Params” niektóre hooki mają podaną jakąś zmienną. Jest to zmienna, która jest przekazywana jako argument przez MyBB do naszej funkcji.
Ok, wiemy już co to są hooki, ale jak ich używać? Jest to bardzo proste. Wybieramy sobie w którym miejscu ma wykonywać się nasz kod (np. niech będzie na początku każdej strony w MyBB). W tym celu wybieramy sobie hooka o nazwie “global_start” i podpinamy pod niego naszą funkcję w następujący sposób:
1 |
$plugins -> add_hook('global_start', 'mojplugin_get_new_users'); |
Teraz, żeby nie przeciągać i niepotrzebnie się rozpisywać przedstawie kod funkcji realizującej te zadania z wyczerpującymi komentarzami :)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
$plugins -> add_hook('global_start', 'mojplugin_get_new_users'); // Zaczepiamy naszą funkcję w global_start. function mojplugin_get_new_users() { global $db, $nowiUzytkownicy; // Tak jak poprzednio zapewniamy sobie dostęp do potrzebnych nam zmiennych. Dodatkowo zmienna $nowiUzytkownicy jest defiuoniowana jako globalna, aby była ona dostępna poza naszą funkcją (w całym MyBB). Dzięki temu będziemy mogli wpisać w szablonie {$nowiUzytkownicy} w celu pokazania treści tej zmiennej w wybranym miejscu naszego forum. $q = $db -> simple_select('users', 'uid, username, usergroup, displaygroup, regdate', "", array( 'order_by' => 'regdate', 'order_dir' => 'DESC', 'limit' => 10) ); // Zapytanie do bazy danych o 10 najnowszych użytkowników. simple_select() przyjmuje parametry kolejno: tabela, nazwy kolumn, [warunki], [opcje np. sortowanie] $nowiUzytkownicy = "<h1>Najnowsi użytkownicy: </h1><ol>"; // Definiujemy naszą zmienną, w której umieścimy wynik dziłania naszego pluginu. while($r = $db -> fetch_array($q)) { // Sprawdzamy po jednym wyniku (tutaj: użytkowniku) na raz dopóki się nie skończą. Metoda fetch_array() dla danego zapytania zwraca po jednym wierszu z wyniku tego zapytania umieszczając dane w tablicy asocjacyjnej z kluczami o takich nazwach, jak nazwy kolumn. //$nowiUzytkownicy .= "<li>" . . "</li>"; $nowiUzytkownicy .= "<li>" . build_profile_link(format_name($r['username'], $r['usergroup'], $r['displaygroup']), $r['uid']); // W tym przypadku używamy 2 funkcji zdefiniowanych w MyBB - format_name($nazwa_uzytkownika, $grupa_uzytkownika, $wyswietlana_grupa) oraz build_profile_link($nazwa_uzytkownika, $id_uzywkownika, $target, $onclick) $nowiUzytkownicy .= " (" . date("d.m.Y H:i:s", $r['regdate']) . ")</li>"; // Dodajemy sformatowaną date rejestracji. Funkcja date() jest opisana w manualu PHP. } $nowiUzytkownicy .= "</ol>"; // Domykamy tag <ol> } |
Pora trochę poustawiać ;)
Byłoby świetnie, gdyby w naszym pluginie dało się zmieniać ustawienia z poziomu ACP. Możemy oddać użytkownikowi możliwość zmiany np. liczby wyświetlanych najnowszych użytkowników z 10 na jakąś inną wartość. W tym celu musimy w funkcjach odpowiedzialnych za instalację, deinstalację oraz sprawdzenie, czy plugin jest zainstalowany umieścić następujący kod:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
function mojplugin_install() { global $db; // Normalnie z poziomu funkcji nie mamy dostępu do zmiennej $db, musimy uczynić ją widoczną wewnątrz funkcji, aby móc jej używac. $db -> insert_query('settinggroups', array( "gid" => "NULL", // To pole jest 'auto increment', więc musimy podać tutaj NULL "name" => "mojplugin", // Nazwa "title" => "Ustawienia mojego pluginu", // Tytuł "description" => "Tu możesz zmieniać ustawienia mojego pluginu np. liczbę wyświetlanych najnowszych użytkowników.", // Opis "disporder" => "1", // Kolejność wyświetlania "isdefault" => "1" )); // Dodajemy do bazy grupę ustawień dla naszego pluginu. $id = $db -> insert_id(); // Zapisujemy do zmiennej $id identyfikator naszej grupy. $db -> insert_query('settings', array( // Dodajemy do bazy poszczególne ustawienia. "sid" => "NULL", // To pole jest 'auto increment', więc musimy podać tutaj NULL "name" => "mojplugin_liczba_uzytkownikow", // Unikalny identyfikator ustawienia "title" => "Liczba wyśw. użytkowników", // Tytuł "description" => "Liczba najnowszych użytkowników wyświetlanych przez plugin (zalecane 10).", // Opis "optionscode" => "text", // Typ pola "value" => "10", // Wartość "disporder" => "1", // Kolejnośc wyświetlania "gid" => $id, // ID grupy ustawień "isdefault" => "1" )); rebuild_settings(); // Teraz musimy odświeżyć ustawienia. } function mojplugin_uninstall() { global $db; $id = $db -> simple_select('settinggroups', 'gid', "name = 'mojplugin'"); $id = $db -> fetch_field($id, 'gid'); // Pobieramy identyfikator grupy ustawień $db -> delete_query('settinggroups', "name = 'mojplugin'"); // Usuwamy grupę ustawień $db -> delete_query('settings', "gid = {$id}"); // Usuwamy ustawienia z naszej grupy } function mojplugin_is_installed() { global $db; $q = $db -> simple_select('settinggroups', '*', "name = 'mojplugin'"); if($db -> num_rows($q)) return true; else return false; } |
Następnie musimy odrobinę przerobić kod naszej głównej funkcji. Po pierwsze musimy dodać do zmiennych globalnych funkcji zmienną $mybb:
1 |
global $db, $mybb, $nowiUzytkownicy; |
Po drugie zamieniamy linijkę, w której określamy ilość wyników zwracanych z bazy na:
1 |
'limit' => (int) $mybb -> settings['mojplugin_liczba_uzytkownikow']) |
Właśnie dlatego musieliśmy dodać $mybb do zmiennych globalnych. Obiekt ten zawiera w sobie tablicę ‘settings’, w której znajdują się wszystkie ustawienia.
Cały kod
Po tym wszystkim cały kod naszego pluginu powinien wyglądać tak:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
<?php if(!defined("IN_MYBB")) die("Direct initialization of this file is not allowed.<br /><br />Please make sure IN_MYBB is defined."); function mojplugin_info() { return array( "name" => "Mój plugin", "description" => "To jest mój pierwszy plugin do MyBB.", "website" => "http://mojastrona.pl/podstronamojegopluginu/", "author" => "Krupson", "authorsite" => "http://mojastrona.pl/", "version" => "1.0", "guid" => "", "compatibility" => "1*" ); } function mojplugin_install() { global $db; // Normalnie z poziomu funkcji nie mamy dostępu do zmiennej $db, musimy uczynić ją widoczną wewnątrz funkcji, aby móc jej używac. $db -> insert_query('settinggroups', array( "gid" => "NULL", // To pole jest 'auto increment', więc musimy podać tutaj NULL "name" => "mojplugin", // Nazwa "title" => "Ustawienia mojego pluginu", // Tytuł "description" => "Tu możesz zmieniać ustawienia mojego pluginu np. liczbę wyświetlanych najnowszych użytkowników.", // Opis "disporder" => "1", // Kolejność wyświetlania "isdefault" => "1" )); // Dodajemy do bazy grupę ustawień dla naszego pluginu. $id = $db -> insert_id(); // Zapisujemy do zmiennej $id identyfikator naszej grupy. $db -> insert_query('settings', array( // Dodajemy do bazy poszczególne ustawienia. "sid" => "NULL", // To pole jest 'auto increment', więc musimy podać tutaj NULL "name" => "mojplugin_liczba_uzytkownikow", // Unikalny identyfikator ustawienia "title" => "Liczba wyśw. użytkowników", // Tytuł "description" => "Liczba najnowszych użytkowników wyświetlanych przez plugin (zalecane 10).", // Opis "optionscode" => "text", // Typ pola "value" => "10", // Wartość "disporder" => "1", // Kolejnośc wyświetlania "gid" => $id, // ID grupy ustawień "isdefault" => "1" )); rebuild_settings(); // Teraz musimy odświeżyć ustawienia. } function mojplugin_uninstall() { global $db; $id = $db -> simple_select('settinggroups', 'gid', "name = 'mojplugin'"); $id = $db -> fetch_field($id, 'gid'); // Pobieramy identyfikator grupy ustawień $db -> delete_query('settinggroups', "name = 'mojplugin'"); // Usuwamy grupę ustawień $db -> delete_query('settings', "gid = {$id}"); // Usuwamy ustawienia z naszej grupy } function mojplugin_is_installed() { global $db; $q = $db -> simple_select('settinggroups', '*', "name = 'mojplugin'"); if($db -> num_rows($q)) return true; else return false; } $plugins -> add_hook('global_start', 'mojplugin_get_new_users'); // Zaczepiamy naszą funkcję w global_start. function mojplugin_get_new_users() { global $db, $mybb, $nowiUzytkownicy; // Tak jak poprzednio zapewniamy sobie dostęp do potrzebnych nam zmiennych. Dodatkowo zmienna $nowiUzytkownicy jest defiuoniowana jako globalna, aby była ona dostępna poza naszą funkcją (w całym MyBB). Dzięki temu będziemy mogli wpisać w szablonie {$nowiUzytkownicy} w celu pokazania treści tej zmiennej w wybranym miejscu naszego forum. $q = $db -> simple_select('users', 'uid, username, usergroup, displaygroup, regdate', "", array( 'order_by' => 'regdate', 'order_dir' => 'DESC', 'limit' => (int) $mybb -> settings['mojplugin_liczba_uzytkownikow']) ); // Zapytanie do bazy danych o 10 najnowszych użytkowników. simple_select() przyjmuje parametry kolejno: tabela, nazwy kolumn, [warunki], [opcje np. sortowanie] $nowiUzytkownicy = "<h1>Najnowsi użytkownicy: </h1><ol>"; // Definiujemy naszą zmienną, w której umieścimy wynik dziłania naszego pluginu. while($r = $db -> fetch_array($q)) { // Sprawdzamy po jednym wyniku (tutaj: użytkowniku) na raz dopóki się nie skończą. Metoda fetch_array() dla danego zapytania zwraca po jednym wierszu z wyniku tego zapytania umieszczając dane w tablicy asocjacyjnej z kluczami o takich nazwach, jak nazwy kolumn. //$nowiUzytkownicy .= "<li>" . . "</li>"; $nowiUzytkownicy .= "<li>" . build_profile_link(format_name($r['username'], $r['usergroup'], $r['displaygroup']), $r['uid']); // W tym przypadku używamy 2 funkcji zdefiniowanych w MyBB - format_name($nazwa_uzytkownika, $grupa_uzytkownika, $wyswietlana_grupa) oraz build_profile_link($nazwa_uzytkownika, $id_uzywkownika, $target, $onclick) $nowiUzytkownicy .= " (" . date("d.m.Y H:i:s", $r['regdate']) . ")</li>"; // Dodajemy sformatowaną date rejestracji. Funkcja date() jest opisana w manualu PHP. } $nowiUzytkownicy .= "</ol>"; // Domykamy tag <ol> } ?> |
Kilka słów na koniec
Po pierwsze to gratulacje, jeżeli udało Ci się przebrnąć przez cały ten artykuł. Mam nadzieję, że pomoże Ci on w pisaniu własnych modów do MyBB oraz zmotywuje do dalszego rozwijania swoich umiejętności na własną rękę. Jeśli wyrazicie taką chęć w komentarzach, to w przyszłości postaram się napisać drugą część tego poradnika, a w niej zamieścić informacje bardziej zaawansowane takie jak np. obsługa języków, dodawanie własnych zakładek w ACP itp.
Dodam tutaj jeszcze listę ciekawych linków, które są bardzo pomocne przy pisaniu pluginów do MyBB:
- http://www.mybbsecurity.net/docs/index.html – Lista funkcji oraz klas w MyBB,
- http://docs.mybb.com/1.8/development/plugins/database-methods/ – Metody obiektu $db,
- http://docs.mybb.com/ – Oficjalna ‘dokumentacja’ MyBB.
Jeśli poznanie systemu pisania pluginów do MyBB nie sprawiło Ci żadnego problemu mimo, że wcześniej nie wiedziałeś jak to robić to polecam w przyszłości zainteresować się również pisaniem pluginów dla WordPressa. Jest to bardzo podobne do pisania dla MyBB z tą różnicą, że WordPress ma lepszą dokumentację ;)
PS. W tym okienku do wyświetlania kodu PHP nie wyświetlają się wcięcia. Mam nadzieję, że nie spowoduje to ogromnego spadku czytelności kodu.
Dzięki! Na pewno się przyda. xaxa
Fajne
Dokumentację należy umieć czytać. Dokumentacja nie jest z reguły zbiorem przykładów i opisów działania funkcji.
MyBB jest raczej CMS’em dla początkujących. Wymagające fora na MyBB długo nie pociągną.
“Z pewnością część z Was prowadzi, prowadziła lub będzie prowadzić w przyszłości jakieś forum internetowe.” – ee. wróżką jesteś, czy co?
Mimo to jakieś minimalne informacje na oficjalnej stronie o liście dostępnych funkcji oraz tym jakie parametry przyjmują by się przydały. Czytałeś dokumentację C++, Pythona lub PHP? Zobacz, że tam to wszystko jest i to jest dobra dokumentacja, bo po co programista ma szukać Bóg wie gdzie przydatnych informacji, jak powinien mieć to na wyciągnięcie ręki w dokumentacji, w końcu po to ona jest. MyBB daje radę też na nieco większych forach (np. http://www.gta-five.pl).
PS. Tak, jestem wróżką, skąd wiedziałeś?
I jeszcze jedno: “MyBB jest raczej CMS’em dla początkujących. Wymagające fora na MyBB długo nie pociągną.” – co Ci się tu nie zgadza z artykułem? Jest napisany dla początkujących, którzy chcą rozszerzyć możliwości MyBB. Ja osobiście uważam MyBB za bardzo dobry CMS.
Programista powinien umieć czytać każdą dokumentację.
Poza tym w dokumentacji mybb jest bardzo ładnie opisane tworzenie własnego pluginu.
Oczywiście uważam, że początkującym na pewno przyda się twój artykuł. Ale lepszym miejscem dla niego były by fora dla PHP’owców.
Nie pamiętam czy czytałem oficjalną dokumentację C++ – nie wiem nawet czy takowa istnieje.
No, ale jeśli programista narzeka na dokumentację zamiast cieszyć się, że w ogóle jakaś jest, to kiepsko. Nie zawsze będziesz miał wszystko podane jak na tacy.
MyBB jest słabym CMS’em, bardzo kulawo napisanym, chociaż fakt, że ciężko za darmo o coś lepszego.
“Z pewnością część z Was prowadzi, prowadziła lub będzie prowadzić w przyszłości jakieś forum internetowe.”
Też nie jestem pewien czy większość majsterkowiczów poprowadzi kiedykolwiek własne forum ;)
To majsterkowo czy programowo?