Zmiana operatora komórkowego bez straty numeru

Podczas świadczenia usług abonamentowych podejmujemy się analizy konkurencji, bądź dostajemy bodźce od operatorów. Stara oferta może nam jak najbardziej odpowiadać, często konsultanci przed możliwością przedłużenia umowy proponują różnego rodzaju bonusy, czasem traktowanie abonenta to kpina. Zatem najczęściej wtedy jesteśmy szarpani na smyczy i zastanawiamy się nad zmianą dostawcy usług. Od jakiegoś czasu można przejść do innego operatora sieci telefonii komórkowej, nie tracąc przy tym swojego numeru. Ważnym jest przejść do innego operatora, a nie odstąpić od umowy u operatora macierzystego. Miałem okazję przechodzić ten proces, a że jestem pedantem wypytałem o wszystkie szczegóły i sytuacje krytyczne, zatem podzielę się wiedzą, którą zgromadziłem na temat tej materii. Żeby nikomu się nie zdarzyło popełnić idiotycznego błędu. Potencjalne błędy oznaczyłem na czerwono, kroki pogrubiłem, a istotne rzeczy podkreśliłem.

1. Rejestracja numeru telefonu

W momencie, gdy nie jesteśmy użytkownikami abonamentowymi lub tzw. mix, nie jesteśmy automatycznie właścicielami numeru telefonu. Karty pre-paid (na doładowania na czas nieokreślony) nie wiążą klienta w żaden sposób z operatorem – możemy taką kartę wyrzucić w każdej chwili.

Jeżeli jesteśmy użytkownikami abonamentu, bądź mixa, problem mamy z głowy. W przypadku kart pre-paid’owych, możemy za darmo zarejestrować numer telefonu na nasze nazwisko. Robimy to ostrożnie, bowiem właściciel numeru musi mieć zdolność abonamentową u operatora, do którego przejść. Tj. mieć stałe źródło dochodów lub okazać legitymację studencką. Jeżeli nie masz płynności finansowej, najlepiej zarejestrować numer na rodziców.

2. Aktualizacja danych osobowych u swojego operatora

Pierwszym krokiem, jaki powinien wykonać klient (Ty) to aktualizacja danych osobowych u operatora macierzystego (który w tej chwili świadczy Ci usługi). Najczęstszą przyczyną nieaktualnych danych osobowych jest zmiana miejsca zamieszkania, aktualizacja dowodu osobistego, który wygasł, zgubiliśmy, zniszczył się, etc. W każdym z tych przypadków ulega co najmniej numer i seria dowodu osobistego, o czym koniecznie trzeba poinformować swojego operatora przed podjęciem procedury migracji. Jeżeli jesteś święcie przekonany, że nie nastąpiła zmiana danych w dowodzie – i tak pro forma idź je zaktualizować, nic Cię to nie kosztuje, a zaoszczędzisz stresu i utwierdzisz się w przekonaniu, że wszystko będzie ok.

Proces aktualizacji danych trwa do 24 godzin. Zazwyczaj aktualizacja następuje od razu, ale inni operatorzy, którzy mają wgląd do globalnej bazy, zmiany zaobserwują za maksymalnie 24 godziny. Warto mieć na uwadze te opóźnienie, przed podjęciem kolejnego kroku.

3. Podpisanie umowy

Ważnym jest, żeby nie wypowiadać umowy operatorowi macierzystemu! Tracimy wówczas prawo do swojego numeru telefonu. Umowę „wypowiada” nowy operator, a raczej prosi o przepisanie numeru wraz z końcem świadczonych usług. Idziesz zatem do nowego operatora! U nowego operatora otrzymasz dwa dokumenty: umowę abonamentową oraz pełnomocnictwo wobec wykonanie czynności prawnych dotyczących Twojego numeru telefonu, którego jesteś właścicielem. W skrócie: przenosisz wszystkie obowiązki na nowego operatora. To Cię nic nie kosztuje, a nawet możesz otrzymać bonus, o który warto negocjować.

Kolejną istotną rzeczą jest to, że dane wpisane na nowej umowie i pełnomocnictwie muszą zgadzać się z danymi u starego operatora. W przeciwnym wypadku nowy użytkownik nie ma praw do przejęcia numeru, bo de facto nie jest w jego posiadaniu.

Jeżeli umowę u starego operatora masz podpisaną na rodzica/opiekuna, niech ta sama osoba podpiszę umowę u nowego operatora. Analogicznie: w przypadku karty pre-paid, jeżeli nie masz płynności finansowej, bądź nie jesteś studentem, zarejestruj numer na osobę, która spełnia wymagania, a następnie idź z nią do nowego operatora.

Po całym procesie można (nie trzeba) w każdej chwili wykonać cesję umowy – zmianę danych osobowych wobec której jest ona świadczona. Usługa ta jest bezpłatna, ale czasochłonna, na szczęście nas proszą tylko o zapłacenie pierwszej faktury (choć jedna musi być zapłacona „za kadencji” starego abonenta), a następnie o skan dowodu osobistego. Kolejna faktura przyjdzie już na nowego abonenta.

4. Uregulowanie faktur za ostatnie miesiące u starego operatora

Bez bałaganu, bo mogą nagle wyłączyć nam nowy abonament. Oczywista oczywistość.

Mam nadzieję, że przybliżyłem temat osobom, które biorą pod uwagę zmianę operatora, bądź mieli niewystarczającą wiedzę na temat migracji. Leave feedback if u like it.

 

MySQL DATE() dla pola DATETIME

Oblicza MySQL nie są do końca znane przy tworzeniu aplikacji, a problemy optymalizacyjne stają się nie lada problemem przy funkcjonowaniu wersji produkcyjnej projektu. Nie sposób przewidzieć wszystkich możliwości użycia pól, założenia zarówno wspólnych, jak i pojedynczych indeksów posiadających zakładaną przez nas moc i zajętą pamięć na dysku.

Ostatnimi czasy budowałem dość skomplikowany projekt, jeżeli chodzi o złożoność zapytań i wykonywanych przez nie operacje matematyczne. Pomimo tego, że aplikacja była doskonale przemyślana, a struktury bazy danych perfekcyjnie jej podporządkowane, gdzieś tkwił problem, bowiem jedno z zapytań generowało pozornie prosty (wizualnie) rezultat, baza reagowała na zapytanie dopiero po 2.5 sekundy dla 30k+ rekordów. Patrząc na strukturę kluczy i zapytania, zwłaszcza, że pola, na których operowałem były różnego rodzaju liczbami i datami zacząłem się poważnie martwić i rozkładać zapytanie na czynniki pierwsze, kończąc na warunkach. Wyobraźcie sobie moje zdziwienie, gdy doszedłem do tego, że całe obciążenie (ponad 2.3 sekundy) generował warunek:

WHERE DATE(ticket_date) >= " ... "

Gdzie ticket_date to pole typu DATETIME. Od razu doszedłem do wniosku, że w parze idzie złe przygotowanie danych przez PHP, a angażowana jest w to wszystko baza, na której forsuje się użycie funkcji DATE(). Przynajmniej dla 30k+ rekordów zindeksowanego pola. Prosty zabieg zamiany jednej linijki kodu na drugą przyniósł porządane efekty.

$aTerms[] = 'DATE(ticket_date) >= "' . $sDate . '"'
$aTerms[] = 'ticket_date >= "' . date('Y-m-d H:i:s', strtotime($sDate)) . '"'

Budując aplikację zwracam szczególną uwagę na strukturę bazy, indeksowanie pól, rysuję diagramy przewidujące wykorzystanie danych pod różne zapytania, ale… tak banalny błąd przy przeanalizowanej aplikacji rozłożył mnie na łopatki. Z drugiej strony, zapomniałem o jednej bardzo ważnej rzeczy: maksymalnym odciążeniu bazy danych przy preparowaniu argumentów warunków, skoro warunki te mogą być w odpowiedni i przede wszystkim szybki sposób spreparowane na poziomie modelu (abstrakcyjnie rzecz ujmując, pozbywam się pojęcia PHP), który przygotują zapytanie tylko do wykonania operacji na surowych danych, bez konieczności ich ewentualnego przeliczania. Oczywiście nie zawsze taki efekt da się uzyskać, ale należy to maksymalnie optymalizować.

Jedno jest wiadome: przeliczanie DATE() dla rekordów w warunku jest nieoptymalne dla pola DATETIME.

 

jQuery Animate i Easing

Nie można kwestionować faktu, że jQuery.animate() jest jednym z najbardziej potężnych narzędzi jQuery. Służy on do animowania atrybutów CSS (czyli zmiany ich wartości w czasie od obecnego stanu A do definiowanego stanu B). Najprostszą implementacją jQuery Animate jest podanie zbioru atrybutów CSS, które mają ulec zmianie oraz czasu, w jakim ta zmiana ma nastąpić. Nie będę się zagłębiał w najprostsze przykłady użycia, są one dostępne w oficjalnej dokumentacji jQuery.

Należy pamiętać, że dzięki jQuery jesteśmy w stanie nie tylko płynnie zmieniać kolory, wielkość czcionki, obramowanie, ale także pozycje elementów, nadając stronie dynamicznego kształtu. Domyślnym sposobem animowania (easing) jest płynne przechodzenie. Istnieje natomiast sposób na zmianę adaptera animowania. Robert Penner – autor pluginu jQuery Easing dostarczył nam niewiarygodnie efektowne i proste w implementacji narzędzie. Na oficjalnej stronie pluginu można znaleźć wiele przykładów animacji, które dostarcza nam dodatek. Efekty widoczne są zwłaszcza przy animowaniu pozycji i wymiarów obiektu, ale następują także w przypadku zmiany koloru – czyli są aplikowane do zmiany stanu każdego z atrybutów CSS.

Dziś postaram się pokazać efekty, jakie można uzyskać za pomocą jQuery Animate rozszerzonego o jQuery Interface oraz Easing.

Pierwszym krokiem jest wygenerowanie własnej biblioteki jQuery Interface. Dzięki generatorowi, jesteśmy w stanie ściągnąć tylko te części Interface, które są nam de facto potrzebne, zmniejszając jednocześnie ilość kodu. Klikamy 'deselect all components’, a w sekcji Effects wybieramy efekty, których będziemy używać. Mnie w tej chwili interesuje Bounce i Slide. W paczce otrzymamy wersję deweloperską (z wcięciami) oraz minified, gotową do publikacji na serwerze.

Do wykorzystania efektu slideowania a’la iPhone (elastyczne odbicie od krawędzi ściany ekranu) sprowadza się drobny kawałek kodu, w którym istotnym jest parametr easing:

$('#example').animate({ left: 500 }, { duration: 1000, easing: 'easeOutElastic' })

Na pewno komuś się przyda.

 

How to change iPhone`s SSH password

Jak zmienić hasło SSH w iPhone?

Ostatnimi czasy jest głośno o pierwszym znanym robaku na iPhone. Na szczęście jest on nieszkodliwy, ale pokazuje, na co narażeni są użytkownicy instalujący nieautoryzowane przez Apple aplikacje na swoim telefonie. Jak zabezpieczyć iPhone’a? Dla tych, którzy mieli wcześniej styczność z Linux’em lub OSX to norma, lecz warto przypomnieć pozostałym o zmianie hasła root’a.

Z reguły SSH służy do zdalnego logowania się do urządzenia poprzez sieć. Połączenie jest szyfrowane, a technologia znana i uważana za bezpieczną, lecz nic nie stanie na przeszkodzie hakerowi, który po prostu zna hasło…

Aby spać spokojniej, możemy zmienić hasło dostępu do SSH. Aby to uczynić, wyszukujemy w Cydia paczkę o nazwie MobileTerminal. Po instalacji pojawi nam się dodatkowa ikona reprezentująca terminal. Włączamy aplikację, w której domyślnie jesteśmy zalogowani na użytkownika mobile, zmieniamy hasło:

passwd

System poprosi nas o podanie aktualnego hasła, które brzmi alpine. Wpisujemy nowy kod oraz potwierdzamy jego poprawność.

Wiele ludzi zapomina, że istnieje również użytkownik root. Mu także należy zmienić hasło. Aby przełączyć się na użytkownika root, wpisujemy:

su root

Podajemy hasło alpine oraz analogicznie zmieniamy nasz sekretny kod dla tego użytkownika. Ekran końcowy powinien wyglądać mniej więcej tak:

iPhoneSSHpasswd

 

MySQL: remove duplicate entries/rows

Usuwając coś permanentnie z bazy danych musimy być bardzo ostrożni, bowiem przywrócenie danych jest bardzo trudne, czasem niemożliwe. Podstawową strukturę bazy danych powinno się budować na samym początku tworzenia aplikacji, z biegiem czasu rozbudowywać ją, ale unikać przebudowywania. Niestety są przypadki, gdzie trzeba przebudować jedną rzecz, co powoduje zmianę w wielu warstwach nie tyle aplikacji, co strukturze bazodanowej.

Dziś postaram się opisać, jakie kroki trzeba wykonać, aby bezpiecznie usunąć zdublowane rekordy z bazy danych nie tracąc żadnych danych:

  1. Tworzymy dwie kopie bazy danych lub tabel, na których będziemy pracowali. Najlepiej, aby pracować na drugiej kopii, nigdy na oryginale, a potem wdrożyć zmiany z drugiej kopii na oryginał. Przezorny zawsze ubezpieczony.
  2. Analiza danych w tabeli. Musimy dokładnie wiedzieć jakie są relacje między tabelami, kiedy występują JOIN’y itp. Jeżeli rekordy są zdublowane, a posiadają ustalony ID, do których odwołuje się inny rekord z sąsiedniej tabeli, trzeba będzie w niej zmienić ID rekord zdublowanego na ID „substytuta”, bądź takiego rekordu, który nie spowoduje zmian w serwisie.
  3. Wykonanie operacji usunięcia zdublowanych rekordów.

Po wykonaniu kroku pierwszego zabieramy się za kolejny. Jest to najważniejszy moment naszych operacji. Aby ułatwić zrozumienie problemu, podam przykład z życia. Aplikacja posiadała poważny błąd, który umożliwiał zdublowanie użytkowników, ściślej: można było zdublować username. Za każdym razem, gdy użytkownik się logował i pisał komentarze, był ich właścicielem, ale comment_author posiadały różne ID tego samego użytkownika. Zaraz po skopiowaniu bazy danych spróbowałem przepisać ID autorów komentarzy na pierwszy rekord identyfikujący użytkownika, jaki istnieje w tabeli użytkowników. Skonstruowałem zapytanie:

UPDATE cms_comments
JOIN cms_members AS user_original ON(user_original.user_id = comment_author)
SET comment_author = (
  SELECT user_first.user_id FROM cms_members AS user_first
  WHERE user_first.user_name = user_original.user_name
  ORDER BY user_first.user_id ASC LIMIT 0, 1)

Usunięcie zdublowanych użytkowników było już tylko formalnością. Teraz się okaże, dlaczego zależało mi na wyciągnięciu dokładnie pierwszego rekordu reprezentującego „unikalnego” użytkownika: poniższe zapytanie (ALTER IGNORE TABLE ADD UNIQUE) usunie wszystkie kolejne rekordy oznaczone jako duplicated:

ALTER IGNORE TABLE cms_members ADD UNIQUE INDEX(user_name);

Krótki komentarz z manuala do ALTER TABLE:

IGNORE is a MySQL extension to standard SQL. It controls how ALTER TABLE works if there are duplicates on unique keys in the new table or if warnings occur when strict mode is enabled. If IGNORE is not specified, the copy is aborted and rolled back if duplicate-key errors occur. If IGNORE is specified, only the first row is used of rows with duplicates on a unique key, The other conflicting rows are deleted. Incorrect values are truncated to the closest matching acceptable value.

Pisząc ostatnie posty związane z bazami danych, mam nadzieję, że komuś się przydadzą.

 

MySQL: how to convert NULL to 0 number/int

Im więcej nietypowych rzeczy programuję, tym więcej nietypowych problemów musze pokonać. Co powiecie na sumę 2 liczb, z których jedna jest wartością NULL powstałą w wyniku działania SUM() lub pochodnych, gdzie nie odnaleziono żadnego rekordu.

Badamy:

SELECT 1+2+3
>> 6

SELECT 1+2+NULL
>> NULL

SELECT COALESCE( NULL, 0 )
>> 0

Zatem analogicznie do powyższego przykładu:

UPDATE users SET user_points = user_points + COALESCE((SELECT SUM( ... ) WHERE ...), 0)

Punkty użytkownika już zawsze będą się sumowały poprawnie 🙂

 

MySQL UPDATE JOIN

Ostatnimi czasy potrzebowałem danych z sąsiedniej tabeli przy UPDATE jedngo z pól w bazie danych. Danych do przetworzenia było sporo, więc zwracałem uwagę na wydajność zapytania. Aby zebrać potrzebne informacje, można użyć jednego ze sposobów:

  1. Zebrać potrzebne dane za pomocą SELECT’a, co sprawiłoby, że zajęta zostanie niepotrzebna pamięć w środowisku PHP podczas przypisania rezultatu do zmiennej.
  2. Wykonać SET z podzapytaniem, ale potrzebnych mi było kilka kolumn z sąsiedniej tabeli, podzapytanie może zwrócić tylko jedną określoną wartość.
  3. Wykonać JOIN przy update, czego niestety wówczas nie potrafiłem zrobić.

Kartkując manual nie natrafiłem się w standardowej dokumentacji na nic konkretnego, aż nie spojrzałem na bardzo przydatne komentarze użytkowników. Okazało się, że przy UPDATE można wykonywać dowolne JOIN’y, schemat jest następujący:

UPDATE table JOIN another_table SET ...

W tym momencie mamy do dyspozycji wszystkie pola z dołączonej tabeli. Bardzo przydatne.

Przykład z życia.

Miałem za zadanie odznaczyć typy bukmacherskie na trafione, nietrafione, odwołane z przyczyn odwołania całego meczu piłkarskiego oraz te, które jeszcze nie mogą zostać oznaczone jako trafione lub nie, gdyż mecz się jeszcze nie odbył.

UPDATE typer_tickets_items
LEFT JOIN typer_events ON(event_id = item_event)
SET item_status = (
CASE
WHEN event_status IS NULL THEN NULL # mecz nie zostal rozegrany
WHEN event_status = -1 THEN -1 # mecz anulowany
WHEN event_status = item_bet THEN 1 # typ trafiony
ELSE 0 END # typ nietrafiony
) WHERE item_status IS NULL

Mam nadzieję, że komuś się przyda…

 

Przypadki w MySQL – CASE WHEN THEN ELSE END

Podobnie jak w PHP, baza danych MySQL ma odpowiednik if, czyli przypadków (inaczej serii warunków, instrukcji warunkowych). Różnicą między implementacją CASE’a w MySQL i ifa PHP jest to, że baza danych zwraca konkretną wartość z case’a, a nie wykonuje dowolnej ilości dowolnych akcji.

CASE Syntax:

Najprostsza struktura CASE’aprzedstawia się nastepująco:

CASE WHEN [conditions] THEN ... ELSE ... END

Składnia powinna rozpocząć się słowem kluczowym CASE, a zakończyć END. Pomiędzy znajdują się warunki WHEN oraz operacja zwrócenia odpowiedniej wartości, która po nich następuje THEN (mamy możliwość uwzględnić nieskończenie wiele warunków). Jeżeli żaden warunek nie zostanie spełniony możemy użyć opcjonalnie ELSE.

Przykłady z życia.

Wyobraźmy sobie, że mamy posortować listę aukcji przedmiotów na Allegro od najtańszych, do najdroższych. Należy założyć, że są 2 typy aukcji: kup teraz i licytacja. Pole licytacji w bazie danych zawiera największą zaproponowaną kwotę przez użytkowników w procesie licytacji, a cena kup teraz ustalana jest przez sprzedającego. Są to dwa różne pola w bazie danych, a jedno kryterium sortowania, dlatego trzeba scalić cenę w jedną, wybierając odpowiednią. Musimy przewidzieć sytuację, w której aukcja jest typu kup teraz oraz licytacji, wówczas jeżeli najwyższa oferta jest większa od ceny kup teraz, wówczas wybieramy pole z największa propozycją:

SELECT (
  CASE
    WHEN (auction_type = 'bidding' OR auction_price_bid > auction_price_buynow)
      THEN auction_price_bid
    ELSE auction_price_buynow
  END) AS auction_price

Stworzyliśmy pole auction_price, po którym można sortować aukcje od najtańszej do najdroższej i na odwrót.

Mam nadzieję, że krótki wpis przyda się początkującym. Nic więcej nie trzeba opisywać, temat wydaje się co najmniej trywialny.

 

Samsung T260HD 26″

Na moim biurku programisty pojawił się Samsung T260HD, przekątna 25,5 cala z tunerem TV. Zaraz po rozpakowaniu aż się przeraziłem, jakie to wielkie. Po kilkach godzinach używania, mogę z pełną odpowiedzialnością powiedzieć, że praca na rozdzielczoś›ci 1920¤1200 pikseli jest naprawdę wygodna. Od razu zabrałem się za testy PC i tunera TV. Zaprogramowałem kilka kanałów i skonfigurowałem kontrast dla obu trybów: używania monitora na pececie i jako telewizor.

W poście postaram się wymienić wszystkie plusy i minusy produktu i podzielę się screenami, jak wygląda praca i przeglądanie Internetu na rozdzielczości 1920 pikseli wszerz.

Parametry techniczne.

Przekątna ekranu 25,5″
Wymiary plamki 0,258mm
Maks. rozdzielczość obrazu 1920×1200
Dopuszczalne rozdzielczości pracy 1920×1200
Maksymalna częstotliwość odchylania poziomego 30 ~ 81 kHz
Maksymalna częstotliwość odchylania pionowego 56 ~ 75 Hz
Kąt widzenia pionowy 160 stopni
Kąt widzenia poziomy 170 stopni
Jasność 300 cd/m2
Współczynnik kontrastu 10000:1
Czas reakcji matrycy 5 ms
Wbudowane głośniki Tak
Moc głośników 2 x 3W
Kolor obudowy czarno-bordowy
Pobór mocy tryb normalny/czuwanie/wyłączony 70W / 2W / –
Rodzaj złącza wideo 2 x HDMI, Component, D-Sub, DVI-D, Scart
Tuner TV Tak
Certyfikaty ISO 13406-2, ISO 9241-3,7,8, Vista Premium
Wymiary (szer. x wys. x głeb.) 610.0 x 435.0 x 86.5 mm
Waga [kg] 8,6 kg

T260HD jako monitor PC.

Wszystkie tryby, które oferują domyślne ustawienia monitora nie pozwalają na pracę z monitorem. Aby rozpocząć pracę z tym sprzętem, konieczne jest przejście w tryb custom i dobranie sobie odpowiedniego kontrastu i jasności. Doskonały do oglądania filmów z peceta w rozdzielczości HD. Przeglądając Internet wielu użytkowników może odczuć niepotrzebnie zmarnowanie miejsce na stronach, które mają ustaloną szerokość przystosowaną do 1024 (paski po bokach), natomiast przy stronach, które są rozciągnięte na całą szerokość i podzielone na tabele o sztywno określonych szerokościach kolumn, mogą pojawić się denerwujące pustki (np na forach dyskusyjnych). Matryca TN jest idealna do pracy programisty, który chce poczuć przestrzeń. W sieci spotkałem się z opiniami, że monitora nie monża objąc wzrokiem, czemu muszę zdecydowanie zaprzeczyć. Siedzę od niego na odległość wyciągniętej ręki – jest idealny.

Samsung jako TV.

26 cali to stosunkowo niewiele jeżeli chodzi o standardy telewizora, jednak mi w zupełności wystarcza. Dużym minusem jest to, że sprzęt nie jest wyposażony w pivot, jednakże do niezmienianej w pionie pozycji się w zupełności nadaje. Osobiście, aby korzystać z monitora jako TV, muszę obrócić go o 20 stopni w stronę łóżka i naturalnie oddalić się na przynajmniej trzykrotną odległość, niż pracuję na PC. Manewr ten nie jest denerwujący. Monitor oferuję pełną gamę opcji, jak zwykły TV: teletekst, programowanie kilkuset kanałów. 3W głośniki pozostawiają wiele do życzenia, ale Samsung jest wyposażony w 5 outputów audio, do oglądania telewizji korzystam z moich głośników Logitech X-530.

Outputy i obudowa.

Monitor posiada 2 outputy HDMI, standardowo D-Sub i DVI-D (złącze cyfrowe), Component i Scart (eurozłącze). Jeżeli chodzi o audio, wyposażony jest w 5 outputów standardowo dla konfiguracji 5.1. Obudowa prezentuje wysoką klasę wykonania i estetykę. Błyszcząca, czarno-bordowa (wiśniowa) to ostatnio modny design linii produkcyjnej Samsunga. Bardzo wygodnie ulokowane przyciski na monitorze (niewidoczne, na prawej krawędzi). Wygląd pilota – standardowy, posiada poprzeczne podpórki, co czyni go stabilnym.

Ogólne wrażenie.

Duży 🙂 ale nie za, więc pozytywnie. Na koniec mała galeria screenshootów, jak prezentuje się praca na monitorze:

 

Kubek programatora PHP.pl

Dziś otwarty został sklep.php.pl, którego mały engine miałem okazję pisać. Póki co można w nim nabyć kubek-termos programatora o szczelnym, plastikowym zamknięciu zapobiegającym rozlewaniu się zawartości:

  • Pojemność 400 ml.
  • Nadaje się do spożywania napojów zimnych i gorących.
  • Utrzymuje temperaturę wiele godzin.
  • Dostosowany dla osób prawo i lewo ręcznych.

Cena z przesyłką wynosi 40zł. Mam nadzieję, że z biegiem czasu asortyment znacznie się powiększy. Warto wspomnieć, że z każdym złożonym zamówieniem zasilasz pulę pieniężną przeznaczoną na nagrody w konkursach o 5 zł.