Sortowanie wyników z użyciem funkcji w Solarium

To będzie zwięzły wpis dokumentujący małe zwycięstwo w pojedynku z Solarium. Zadanie polegało na dodaniu nowego sposobu sortowania produktów: w pierwszej kolejności według dostępności produktu (bez względu na ilość, tylko binarne tak/nie), w drugiej według ceny. W bazie Solr mamy tylko ilość sztuk danego produktu, należało ją więc jakoś zrzutować na wartość binarną. Na szczęście Solr umożliwia użycie funkcji do porządkowania znalezionych dokumentów, Solarium od drugiej wersji także to obsługuje. Końcowy efekt moich starań jest następujący:

$query->addSort($query->getHelper()->functionCall('min', array('stock', 1)), Solarium_Query_Select::SORT_DESC);

Dla celów dydaktycznych dodam, że najpierw próbowałem zrobić to w ten sposób:

$query->addSort($query->getHelper()->functionCall('if', array('stock', 1, 0)), Solarium_Query_Select::SORT_DESC);

jednak kończyło się to błędem:

Solr HTTP error: sort param could not be parsed as a query, and is not a field that exists in the index: if(stock,1,0) (400)

Przyczyna okazała się prozaiczna, ale odkrycie jej zajęło mi dłuższą chwilę. Otóż funkcja if dostępna jest dopiero od wersji 4.0 Solr, a my używamy 3.6.

Generowanie losowych kodów rabatowych

Wczoraj pisałem moduł obsługujący kody rabatowe w sklepie internetowym. Wedle specyfikacji kody mogą być jedno- lub wielokrotnego użytku. Te pierwsze mogą być hurtowo generowane w panelu administracyjnym. Na chłopski rozum taki jednorazowy kod powinien być:

  • losowy, żeby pomysłowi klienci nie mogli zgadywać kodów
  • w miarę krótki, żeby wpisywanie go nie było kłopotliwe
  • pozbawiony podobnych do siebie znaków takich jak O i 0, ze względów wspomnianych w poprzednim punkcie

Mój pomysł na generowanie takich kodów wygląda następująco:

public static function generateRandom()
{
  return str_pad(strtr(strtoupper(base_convert(rand(1000, 2176782335), 10, 36)), '0O', '1A'), 6, 'X');
}

Powyższa metoda generuje losowe kody złożone z 6 alfanumerycznych znaków (wielkie litery i cyfry) z pominięciem zera i litery O. Przykładowy kod: 3LFPHP. Tytułem wyjaśnienia: 2176782335 to ZZZZZZ w systemie trzydziestoszóstkowym.

Nie robiłem żadnych testów, ale przeczucie mówi mi, że takie rozwiązanie jest szybsze niż losowanie po jednym znaku z tablicy dozwolonych znaków.

Kody powinny być także unikalne, a powyższa metoda tego nie zapewnia. Gdyby tych kodów trzeba było generować tysiące na minutę pewnie pokusiłbym się o jakiś bardziej wyszukany algorytm, np. w jednej transakcji wygenerować tablicę z liczbą kodów ciut większą niż zadana, jednym zapytaniem sprawdzić które z nich już występują w bazie, odfiltrować je z tablicy, a resztę wstawić drugim zapytaniem. W tym konkretnym sklepie nie ma takiej potrzeby, więc wybrałem prostsze rozwiązanie.

public static function generateCodes(Voucher $voucher, $number)
{
  for ($i = 1; $i <= $number; $i++)
  {
    do
    {
      try
      {
        $code = new VoucherCode();
        $code->setVoucher($voucher)
          ->setCode(VoucherCode::generateRandom())
          ->save();
      }
      catch (Doctrine_Connection_Mysql_Exception $e)
      {
      }
    } while ($code->isNew())
  }
}

Kolumna code w tabeli ma założony unikalny indeks (przydaje się do wyszukiwania), więc próba wstawienia takiej samej wartości powoduje wyjątek, który łapię i generuję nowe wartości dopóki wstawianie rekordu do tablicy nie zakończy się powodzeniem.

PLAIN stupid czyli jak zostałem spamerem

Dzisiaj będzie o głupim błędzie w konfiguracji Exima, który wynikł z bezrefleksyjnego kopiowania i wklejania opublikowanych w Internecie przykładowych konfiguracji.

Ze zdziwieniem przeczytałem maila z serwerowni informującego, że z mojego serwera jest wysyłany spam. Dołączony w załączniku mail wyglądał na wysłany z mojego serwera, a w logach Exima znalazłem informację o wysłaniu inkryminowanej wiadomości, więc o pomyłce serwerowni nie było mowy.

2012-06-22 09:34:16 1ShyNj-0000KH-Cx <= harun@digit0.com H=server177288-1.santrex.net [37.59.45.10] P=esmtpa A=plain_pgsql:sales S=3593 id=73D566338398i-49A3A971B01x025-29B991E@PHRSBS
2012-06-22 09:34:18 1ShyNj-0000KH-Cx => altairhosttest@hotmail.com R=dnslookup T=remote_smtp H=mx2.hotmail.com [65.54.188.94]
2012-06-22 09:34:18 1ShyNj-0000KH-Cx Completed

Z powyższego wycinka logów wynika, że spamer z adresu 37.59.45.10 z powodzeniem autoryzował się jako użytkownik sales przy użyciu autoryzacji plain_pgsql. Stosowna regułka wyglądała tak:

plain_pgsql:
   driver = plaintext
   public_name = PLAIN
   server_condition = ${if eq{$auth3}{${lookup pgsql{SELECT password FROM exim_virtual_users WHERE login = '${quote_pgsql:$auth2}'}}}{yes}{no}}
   server_set_id = $auth2
   server_prompts = :

Powyższy mechanizm wyszukuje w bazie dane użytkownika o podanym loginie ($auth2) i uzyskane w ten sposób hasło porównuje z hasłem podanym przez użytkownika ($auth3). Gdzie tkwi problem? Otóż powyższy mechanizm działa świetnie dla użytkowników, którzy istnieją w bazie. Jeśli jednak sprytny spamer poda nieistniejącego użytkownika i puste hasło to autoryzacja przebiega pomyślnie! Dzieje się tak ponieważ dla nieistniejącego użytkownika zwracane jest puste hasło, a puste hasło = puste hasło.

Dokładnie ten przypadek został opisany w dokumentacji Exima, więc sobie ku pamięci i wszystkim zainteresowanym powtarzam: RTFM!

Dla porządku podaję jeszcze poprawną wersję regułki autoryzującej:

plain_pgsql:
   driver = plaintext
   public_name = PLAIN
   server_condition = ${if and{{!eq{$auth2}{}} {!eq{$auth3}{}} {eq{$auth3}{${lookup pgsql{SELECT password FROM exim_virtual_users WHERE login = '${quote_pgsql:$auth2}'}}}}} {yes}{no}}
   server_set_id = $auth2
   server_prompts = :

Ciasteczka w mod_rewrite

Dotychczas pisząc regułki dla mod_rewrite operowałem tylko na URL-ach. Ostatnio w pracy przy okazji wdrażania nowej wersji strony musiałem przekierować adresy starych stron na nowe. Trudność polegała na tym, że w starej wersji jeden URL generował trzy wersje językowe, a język jest przechowywany w ciasteczku. Na szczęście mod_rewrite może operować na wielu elementach żądania HTTP, w tym na ciasteczkach. Po kilku minutach czytania doszedłem do takiego wyniku:

RewriteCond %{QUERY_STRING} ^event=testAccount$ [NC]
RewriteCond %{HTTP_COOKIE} LANG=fr [OR]
RewriteCond %{HTTP_COOKIE} !^.*LANG.*$
RewriteRule ^/5/index\.cfm$ /5/tester-gratuitement? [NC,R=301,L]

RewriteCond %{QUERY_STRING} ^event=testAccount$ [NC]
RewriteCond %{HTTP_COOKIE} LANG=nl
RewriteRule ^/5/index\.cfm$ /5/test-gratis? [NC,R=301,L]

RewriteCond %{QUERY_STRING} ^event=testAccount$ [NC]
RewriteCond %{HTTP_COOKIE} LANG=en
RewriteRule ^/5/index\.cfm$ /5/test-for-free? [NC,R=301,L]

Pierwszy zestaw dotyczy francuskiej a zarazem domyślnej wersji strony. Istotne są druga i trzecia instrukcja RewriteCond, które odpowiednio sprawdzają czy ciasteczko LANG ma wartość fr lub czy w ogóle nie jest ustawione. Następująca po nich instrukcja RewriteRule przekierowuje do nowej francuskiej strony. Tu warto dodać, że mod_rewrite operuje na ciasteczkach w takiej postaci w jakiej są przesyłane w nagłówku HTTP Cookie czyli napisu rozdzielonego średnikami.

T-Mobile mnie zaskoczył #not

Najwyraźniej tęgie głowy w T-Mobile w końcu zrozumiały, że określanie taryfy zawierającej 100 MB transferu mianem multimedialnej to kiepski żart albo pobożne życzenie. Z tego co widzę, obecnie pakiety internetowe w taryfach multimedialnych zaczynają się od 250 MB, co można uznać za rozsądne minimum. Ta zmiana to nic dziwnego, konkurencja już od dawna oferowała pakiety tej wielkości. O ile mnie pamięć nie zwodzi, to ostatnimi czasy Era/T-Mobile miesiąc w miesiąc traciła najwięcej abonentów z wielkiej trójki. Widocznie powoli do nich dociera, że eksodus abonentów nie wynika z za małej sumy pieniędzy wydanych na czerstwe reklamy z Nowickim a po prostu z kiepskiej oferty. W tym kontekście takiej zmiany spodziewałem się prędzej czy później. Zaskoczeniem było dla mnie jednak znalezisko, które odkryłem w IBOA:

Promocyjny pakiet internet w telefonie 250 MB

Promocyjny pakiet internet w telefonie 250 MB brzmi obiecująco, w szczególności z abonamentem 0 zł. Czyżby T-Mobile dla odmiany pomyślał o stałych abonentach i wraz z ulepszeniem oferty dla nowych klientów dał dotychczasowym możliwość skorzystania z tych samych warunków? Chciałbym w to wierzyć, ale wrodzony pesymizm kazał mi to zweryfikować. W odpowiedzi na mail do BOA zadzwonił do mnie konsultant #1, który poinformował mnie, że pakiet mogę aktywować, ale będą za niego naliczane opłaty. Mając doświadczenie z infoliniami wszelkiej maści zadzwoniłem do konsultanta #2, który z kolei orzekł, że usługa w ogóle nie zostanie aktywowana, bo nie mam takiej w umowie. Tak czy owak wspólny mianownik obu odpowiedzi jest następujący: nie dla psa kiełbasa. Czyli tak jak zwykle, o klienta już uwiązanego umową nie warto zabiegać. T-Mobile zmądrzał, ale tylko troszeczkę i dlatego wciąż będzie tracił klientów, bo nie czują się związani z siecią, a konkurencja w większości wypadków ma lepszą ofertę.

Festiwal makaronów w Pizza Hut

Ne festiwal makaronów w Pizza Hut wybrałem się z odrobiną niepewności. W internetach piszczało, że mogę spodziewać się mikroskopijnych porcji podawanych w nieznośnie długich odstępach czasu. Dzikość serca i żądza małych miejskich przygód jednak wzięły górę i tak trafiłem do Grubej Kaśki.

festiwal makaronów

Pierwszą porcję otrzymałem w niespełna 10 minut od przyjścia, kolejną po dwóch minutach, a następną też dość szybko. Z moich obserwacji wynika, że kolejne potrawy pojawiały się falami: w jednej fali kuchnia przyrządza trzy rodzaje pasty, które trafiają na talerz klienta w kilkuminutowych odstępach. Choć zdarzało się, że miałem pusty talerz to nie mogę narzekać na szybkość obsługi. Przeplatając jedzenie rozmową miło spędziłem czas.

Największą zaletą festiwalu jest to, że można spróbować za jednym zamachem wielu rodzajów pasty i odkryć nowe smaki. Dla mnie odkryciem wieczoru było farfalle di concretto (makaron farfalle, kurczak, kiełbasa pepperoni, boczek oraz cebulka zapieczone z żółtym serem i sosem pomidorowym z bazylią) – pycha! Z nieznanych mi wcześniej smaków zasmakowało mi też tagliatelle molto bene (wstążki makaronu tagliatelle w sosie czosnkowo-beszamelowym z szynką, suszonymi pomidorami, czarnymi oliwkami i rucolą). Lasagne i penne pesto jak zwykle smaczne.

Choć wedle zasad festiwalu konsument nie ma prawa głosu w kwestii wyboru konsumowanych potraw, obsługująca nas kelnerka była ponadprzeciętnie miła i pytała nas na co mamy ochotę.

Z uczestnictwa w festiwalu makaronów jestem zadowolony. Wydaje mi się, że rozczarowany może być tylko ktoś, kto nastawia się na nażarcie się pod korek w 20 minut. Jeśli jednak potraktować wizytę w Pizza Hut jako okazję do pogadania przy jedzeniu to nikt nie powinien mieć powodów do narzekań. A przy okazji można się nażreć pod korek.

Hurtowe acz selektywne skalowanie zdjęć

Hurtowe skalowanie zdjęć z konsoli to pikuś – convert albo mogrify z pakietu ImageMagick nadaje się do tego bardzo dobrze. Dzisiaj musiałem sobie poradzić z nieco bardziej skomplikowanym przypadkiem. Otóż w drzewiastej strukturze katalogów miałem kilka tysięcy zdjęć, a w każdym z podkatalogów znajdowały się kopie danego zdjęcia w kilku rozmiarach. Przykład:

katalog/12/345.jpg
katalog/12/345d.jpg
katalog/12/345m.jpg
katalog/12/345s.jpg
katalog/12/346.jpg
katalog/12/346d.jpg
katalog/12/346m.jpg
katalog/12/346s.jpg

Utrudnienie polegało na tym, że chciałem zmienić rozmiar tylko zdjęć z przyrostkiem „m” (małe) wykorzystując do tego zdjęcia bez przyrostka (oryginały), tzn. dla uzyskania jak najlepszej jakości zmniejszyć oryginały.

O ile wyłuskanie oryginałów przez find nie sprawiło mi problemów, o tyle zapisanie pomniejszonej kopii pliku pod nazwą z przyrostkiem nie było dla mnie takie oczywiste. Po kilku minutach lektury podręcznika polecenia convert udało mi się osiągnąć cel dzięki takiemu poleceniu:

find katalog/ze/zdjeciami/ -type f -regex '.*/[0-9]+.jpg' -exec convert {} -resize 640x640 -set filename:f '%d/%[base]m.%e' '%[filename:f]' ;

Zapisuję je przyszły użytek mój i potomnych.

Moje wielkie bułgarskie wakacje

W zeszłym tygodniu wróciłem z wakacji w Bułgarii. Domyślam się, że grono osób zainteresowanych tym co robiłem na urlopie jest cokolwiek skromne, ale liczę, że komuś, kto zamierza skorzystać z usług serwisu wakacje.pl, biura podróży Summerelse lub po prostu wybiera się do hotelu Les Magnolias w Primorsku, kilka słów opinii na temat tychże może wydać się interesujące.

Czytaj dalej „Moje wielkie bułgarskie wakacje”

DotPay czy Don’t Pay?

Niemal tydzień temu, dokładnie w piątek 18 czerwca, natknąłem się na krótki artykuł, w którym redakcja Dziennika Internautów odpytuje prezesa DotPay S.A. na okoliczność problemów z ichnią usługą płatności online. Jednym z głównych zarzutów były opóźnienia w wypłatach pieniędzy. Niektórym klientom firma tak zalazła za skórę, że posunęli się do tworzenia brzydkich i smutnych filmików kanalizujących ich niezadowolenie.

Podczas lektury przypomniałem sobie, że nie tak dawno zlecałem wypłatę środków z konta DotPay i chyba ich jeszcze nie otrzymałem. Sprawdziłem historię wypłat i faktycznie – 1 czerwca zleciłem wypłatę, która jeszcze nie została zrealizowana. Wedle regulaminu wypłata środków dla osoby fizycznej powinna zostać wykonana w ciągu 10 dni roboczych czyli najpóźniej do 15 czerwca. Przez DotPay zarabiam raczej niewiele, te pieniądze nie są dla mnie kwestią życia i śmierci, więc w ramach reakcji ograniczyłem się do wysłania maila z prośbą o wyjaśnienie zaistniałej sytuacji.

Odpowiedzi na maila nie doczekałem się ani w piątek, ani w następny poniedziałek (20 czerwca), toteż postanowiłem wyjaśnić sprawę telefonicznie. Wbrew temu, na co skarżyli się niezadowoleni klienci DotPaya, udało mi się dodzwonić bez problemów. Miła pani potwierdziła, że moja wypłata jest opóźniona, ale niestety nie potrafiła powiedzieć dlaczego. Obiecała skierować sprawę do księgowości do wyjaśnienia.

Księgowość najwyraźniej nie kwapiła się do pracy, bo żadnych wyjaśnień nie otrzymałem. Dlatego w środę 22 czerwca zadzwoniłem do firmy po raz kolejny. Tym razem przez pierwsze kilka prób „wszyscy konsultanci byli zajęci”, ale w końcu się udało. Poinformowano mnie, że wypłata już została wykonana. Faktycznie, po ostatniej sesji przychodzącej w mBanku na koncie znalazłem przelew z DotPaya.

Dane przelewu w mBanku

Co ciekawe, w historii wypłat w DotPayu zlecenie wypłaty miało datę wykonania 15 czerwca.

historia wypłat

Problem rozwiązany, pieniądze są, ale choć daleki jestem od wieszania psów na DotPayu i na pewno nie będę tworzył szkalujących go filmików, to postawa firmy nie podoba mi się. Błędy i przeoczenia się zdarzają, nie byłoby problemu gdybym po pierwszym mailu otrzymał satysfakcjonującą odpowiedź. Niestety nie dość, że do dziś jej nie otrzymałem, to jeszcze dwukrotnie musiałem do nich dzwonić. Nie podoba mi się antydatowanie zlecenia wypłaty, na pewno byłby to problem przy składaniu reklamacji („no ale ococho, przecież w systemie wszystko się zgadza”). Nie podoba mi się, że pan prezes mówi, że wszystko jest ok, kiedy nie jest. W ten sposób DotPay podkopuje fundament swojego biznesu jakim jest zaufanie. Bez zaufania nikt nie zechce powierzyć im swoich pieniędzy.