Zaczynając swoją przygodę z PHP nie miałem pojęcia jak wykonać paginację newsów, która załamywałaby łańcuchy liczb w momencie, w których chce. Na dzień dzisiejszy postanowiłem napisać swój nowy pager, gdyż ten, który dotychczas używałem przez ostatnie 2 lata nie odpowiadał mi pod trzema względami:
1, 2 ... 6, 7, 8 ... 12, 13.Ostatnio potrzebowałem zwiększyć limit liczb “z przodu” i “tyłu” oraz “w środku”:
1, 2, 3 ... 5, 6, 7, 8, 9 ... 11, 12, 13
Dla tych, którzy ciągle szukają komponentu obsługującego paginację prezentuję Vframe_Pagination.
Implementacja od strony kontrolera (metoda krótka):
$oPager = new Vframe_Pagination($iItemsCount, $iLimit, $iCurrentPage);
Metoda długa:
$oPager = new Vframe_Pagination();
$oPager->limit($iLimit);
$oPager->items($iItemsCount);
$oPager->page($iPage);
Metody limit, items oraz page zwracają liczby odpowiadające ich nazwą niezależnie od tego, czy została podana nowa wartość w argumencie, czy nie, co jest absolutnie wygodnym (dla mnie) rozwiązaniem.
Aby wyświetlić oczekiwane rekordy, wykorzystujemy pagera:
$aData = $oModel->GetList($iUser, $oPager->start(), $oPager->limit());
Lub bezpośrednio w zapytaniu do bazy dancyh:
$sSql = "SELECT news_id FROM news LIMIT " . $oPager->start() . ", " . $oPager->limit();
Od strony widoku, prezentacja pagincaji prezentuje się w bardzo prosty sposób:
<?php echo $oPager->Render(true); ?>
Metoda render przyjmuje kolejno:
Możemy sami ostylować linki generowane przez pager. Wystarczy że w widoku dodamy swój apperance:
$this->oPager->PatternPage('<a href="?[$]">[$]</a>');
$this->oPager->PatternPageCurrent('<strong>[$]</strong>');
$this->oPager->PatternPageNavigation('<a href="?[$]" rel="nofollow">[$$]</a>', array('« poprzednia', 'następna »'));
$this->oPager->PatternSeparator('<span>...</span>');
Końcowy efekt, możemy nie wyświetlać pagera, gdy jest tylko jedna strona elementów:
<?php if($this->oPager->Render('pages') > 1) {
// wyswietl pager...
} ?>
Projektując serwisy zaczynam ostrożnie podchodzić do usability. Temat jest bardzo wrażliwy, każdy webmaster to inne zdanie. Dziś chciałbym przedstawić problem logowania do serwisu. Oprócz hasła, przy logowaniu używamy:
W trosce o użytkowników w kilku moich serwisach zastosowałem możliwość logowania się na adres email lub nazwę użytkownika. Wszystko po to, aby ułatwić dostęp do ukrytej części witryny, aby nikt nie “zwątpił” bo nazwy użytkownika, lub adresu email, który podał przy zakładaniu profilu. Sam używam w sieci kilku adresów email oraz kilku prefixów i suffixów do nicka Athlan. Mam problem z zalogowaniem, gdy danie nie pasują, a potrzebuję dostęp tylko na chwilę.
Przygotujmy zatem tok myślenia programu, który pobierze dane, w zależności od tego, jakie dane podał użytkownik. Pomijam walidację hasła etc:
$aUser.$aUser.Jak możemy zauważyć, w obu przypadkach dane zapisujemy do tej samej zmiennej $aUser, więc możemy je dalej tak samo wykorzystywać. Różni się tylko pobieranie, dlateg nie trzeba w żadnym wypadku powielać kodu.
Jak słusznie zauważył devnull, należy wykluczyć możliwość użycia znaku małpy w loginie przy rejestracji użytkownika. Wyjaśnienie znajdziecie w komentarzach.
Dla programisty nie jest wiele pracy, a warto ułatwić dostęp użytkownikowi do serwisu. Usability na pierwszym miejscu ;-)
Dla niektórych wygasanie sesji jest zabezpieczeniem (banki, etc.). Realizując jeden z projektów oczekiwałem od systemu tego, aby użytkownik nigdy nie gubił sesji, gdy ma otwarte okno w przeglądarce. Dlaczego? Może dodaje posta, być może uzupełnia dość obszerny tekst na stronie. Gdy klika zapisz, przerzuca go do strony logowania, a cały tekst zniknął za sprawą tego, że jego przeglądarka nie zapisuje wartości pól formularza. Skąd to znamy.
Jak użytkownik gubi sesję?
Rozwiązania:
Rozmyślając nad podtrzymaniem sesji, próbowałem znaleźć wszystkie metody oraz wybrać najlepszą. Wszystkie sprowadzają się do “odświeżenia” strony lub jej fragmentu tak, aby nasz silnik wykonał tylko potrzebne session_start(); czyli podtrzymanie aktywności sesji. Jest kilka mniej lub bardziej zadowalających sposobów:
Sposób 3 wydaje mi się najlepszy. Można go ulepszyć w ten sposób, aby ramka nie wysyłała żądania zaraz po załadowaniu strony. Powodowałoby to podwójne requesty do serwera.
ping.php wygląda wówczas następująco: aplikacje nie używające frameworków ingerujących w standardowe działanie sesjisession_start(); header('Refresh: 60');Zapewne znajdą się osoby, które powiedzą: a co z użytkownikami, którzy mają wyłączone ramki, lub ich przeglądarki w ogóle ich nie obsługują. Zapytam wówczas: a co z użytkownikami, którzy nie akceptują cisteczek (wówczas sesje nie są dla nich użyteczne, chyba, że użyjemy przesłyki jej identyfikatora w adresie url). Dopytam również: a co z użytkownikami, którzy mają wyłączony Javascript? Patologiczne przepadki się po prostu pomija ;)
Przeglądając forum.php.pl często widziałem, jak użytkownicy przechowują hasła w swoich bazach danych. Najczęściej używają funkcji hashujących md5, sha1 i sha2. Wszystko wygląda bardzo dobrze, hasła są przechowywanie bezpiecznie. No właśnie… na ile bezpiecznie.
Nie będę tutaj rozwodził się nad zabezpieczeniem baz danych, w których owa baza haseł się znajduje, ale nad samym zahashowanym ciągu. Wszyscy doskonale wiemy, że istnieją bazy md5 (sha1, sha2 również).
Przezorny zawsze ubezpieczony. Wiadomo, że nigdy nic nie wiadomo.
Pokażę, jak dodatkowo zabezpieczyć nasze hasła. Będą przechowywane w tej samej bazie danych, używając tych samych metod hashowania, a jednak szansa na “złamanie” hasła (wyszukania w bazie) będzie niemożliwa. Posłużymy się ciągiem znaków zwanym przez programistów solą (salt). Przykład implementacji możemy znaleźć w forum IPB, natomiast phpBB pozbawione jest tego fjuczuru ficzera. Cała sprawa sprowadza się do wygenerowania dowolnego kawałka ciągu znaków i doklejenia go do hasła. Sól potrzebna nam będzie również przy porównaniu hasła, więc trzeba ją zapisać w bazie danych obok hasła.
Poniżej zamieszczam przykładową klasę, która obsługuje solenie haseł. Doklejanie soli może być napisane w dowolny sposób, zależy to od Waszej wyobraźni. Ja dodatkowo dodałem element “losowy” w postaci doklejenia do soli wyniku działania funkcji microtime().
Denerwujące jest, że na niektórych portalach:
Ostatnio programując sklep internetowy zastosowałem najprostszą technikę, która uwzględni oba powyższe punkty. W moim przypadku, gdy kontroler wymaga zalogowania się, warstwa ACL wskazuje na kontroler logowania (w zależności od konfiguracji). Natomiast URL zostaje, tj:
http://example.com/konto/dodaj-produkt.html
Pierwsze co trzeba zrobić, to sprawdzić, gdzie jest użytkownik. Jeżeli wywołujemy kontroler logowania pod powyższym adresem, trzeba porównać aktualny url do adresu logowania. W moim przypadku adresem logwania jest:
http://example.com/zaloguj.html
Aktualną pozycję użytkownika możemy sprawdizć w zmiennej $_SERVER['REQUEST_URI'], a adres logowania mamy z góry ustalony w naszej aplikacji. U mnie za adresu odpowiada router, stąd poniższy przykład przekierowania do strony logowania:
if(($sReq = $_SERVER['REQUEST_URI']) != ($sUrl = Vframe_Router::Route('auth_login')))
{
$this->_oUser->login_backlink = $sReq;
return $this->_redirect($sUrl);
}
UWAGA! Jeżeli przekierowujemy użytkownika do strony logowania, zamiast $_SERVER['REQUEST_URI'] sprawdzamy $_SERVER['HTTP_REFERER'].
Metoda _redirect to nic innego, jak wysłanie header Location. Dodatkowo do sesji użytkownika zapisuję adres, z którego użytkownik został przekierowany do strony logowania. Bardzo ważne jest to, żeby zapisać lokalizację tylko wtedy, gdy adres nie wskazuje na URL logowania, bo użytkownik po zalogowaniu zamiast powrócić do przegladanej strony, wróci do podstrony logowania.
Tuż po autoryzacji ustawiamy w sesji login_backlink na null, aby w przyszłości, gdy użytkownik wejdzie bezpośrednio na podstronę logowania, nie został odesłany do zapamiętanej wcześniej lokalizacji.
Mała modyfikacja aplikacji i nie denerwujemy użytkownika.
Od niedawna na moim blogu można dostrzec nową zakładkę: GoogleAnalytics plugin.
Wtyczkę stworzyłem dla siebie, ma ona na celu dodanie kodu Javascript generowanego przez Google Analytics do kodu HTML, bezpośrednio przed tagiem </body>. Po aktywacji pluginu w dziale ustawień pojawi się nowa podstrona umożliwiająca wpisanie ID statystyk. I wszystko sprowadza się do prostoty.
Szczerze powiedziawszy wtyczkę napisałem z lenistwa (paradoks) – nie chciało mi się szukać gotowego rozwiązania (których istnieje zapewne milion), a przy zmianie szablonu umknął mi fakt, że kod JS został wklejony “na sztywno” do pliku szablonu footer.php. Gdy chciałem sprawdzić statystyki, wyświetlił mi się pusty wykres.
Napisanie jej zajęło mi 10 minut (sic! z zegarkiem w ręku) i dużą satysfakcję z wykorzystania API wordpressa. GoogleAnalytics plugin został dodany do oficjalnego katalogu pluginów oraz otrzymał repozytorium SVN (które jest puste – wtyczka nie będzie dalej rozwiaja, bo ma być taka prosta, jak jest teraz).