Provozujeme systém nabízející letenky a umožňující jejich nákup… zjednodušeně řečeno eshop. Jenže ho neprovozujeme pro sebe, ale pro řádově stovky jiných společností a každá z nich by se pochopitelně ráda odlišila od konkurence. Naštěstí většinou stačí nastavit pár barev, nahradit pár obrázků a voilá - nový osobitý design je na světě. Někteří zákazníci (rozuměj společnosti prodávající letenky) by rádi začlenili „eshop“ přímo do svého firemního webu. Naštěstí ne celý, stačí tzv. první krok - vstupní formulář, kde lze zadat odkud a kam kolik osob kdy chce letět - to je taková fasáda naší aplikace (nabízí se srovnání s pověstnou špičkou ledovce, ale nebuďme malicherní). Protože si přání našich zákazníků vážíme nade vše, máme hned dva způsoby jak jim vyhovět:
první krok aplikace lze vložit do iframe - celý formulář se pěkně přerovná podle rozměrů rámce, jen se občas musí trochu scrollovat
nabízíme ke stažení zip s html, css a javascriptem určený k vložení přímo do stránky zákazníka
Zjistili jsme, že nemalé procento zákazníků provozuje weby na redakčním systému WordPress a tak jsme se jim rozhodli vyjít vstříc - vytvoříme plugin do WordPressu, který si prodeje chtivý obchodník stáhne z pluginstóru, nastaví pár triviálních parametrů a bude hotovo. Pochopitelně jsme se do takového dobrodružství nepustili sami - obrátili jsme se na společnost, která se implementací WordPressu zabývá na plný úvazek (nebo aspoň víc než my). Zodpovědným pracovníkům z vedení i programátorům jsme předvedli stávající formulář (ten co nabízíme ke stažení co zip) a zahrnuli je záplavou požadavků nový plugin:
musí podporovat všechny jazyky, které podporuje náš systém (asi patnáct)
musí denně stahovat a cachovat konfiguraci příslušného prodejce
musí podporovat konfiguraci barev některých textů (včetně pozadí)
Na oplátku nám sdělili, že:
plugin nesmí obsahovat žádné obrázky a tak musíme všechny ikonky převést do inline css
Odborníci se bez meškání pustili do práce a brzy nám zaslali plod svého snažení - zip, který po načtení do instalace WordPressu přidal do hlavního menu stránku s konfigurací (nastavení barev některých prvků, průhlednost celého formuláře, kódem jazyka a url webu, na který se odešlou získaná data) a stručnou poznámkou, že pokud do publikované stránky vložíme magický řetězec tzv. shortcode, tak se na jeho místě objeví kýžený formulář. A opravdu, když jsme do vzorové stránky uvedený shortcode vložili, formulář se zjevil a fungoval! Drobný potíž se projevil, když jsme formulář vložili do prvku pro zobrazování obrázků na pozadí - využíval absolutního pozicování, s kterým se špatně vyrovnával použitý modul jQuery-ui, ale to se nám povedlo opravit úpravou daného modulu (kontrolní otázka: všiml si laskavý čtenář, že takové řešení je ideově pochybené a nemůže dlouho vydržet?).
Aktualizaci pluginu v pluginstóru jsme chtěli provádět automaticky, aby zapadala do našeho vývojového procesu a tak jsme registraci pluginu nesvěřili odborníkům, ale hrdě oznámili, že to zařídíme sami. První krok byl jasný - přečetli jsme si, jak se to dělá. WordPress spravuje úložiště pluginů v systému správy verzí Subversion (známém též pod zkratkou „svn“)... to pro nás není žádná výzva - svn jsme již na jednom projektu používali (takové mezipřistání na cestě od cvs do gitu). Je to verzovací systém, bylo by tedy pěkné mít celou historii - požádali jsme tedy odborníky o dump projektu pluginu z jejich verzovacího systému. Přiznávám, těšil jsem se s dychtivostí voyera, že uvidím genezi díla krok po kroku… obdrželi jsme tentýž zip jako při předchozím předávání. Nevadí, stejně jsme se dočetli, že svn repozitář nemá sloužit k vývoji, ale jen k distribuci pluginu k uživatelům, neboť se při každém commitu přegenerovávají instalační balíčky a aktivnější vývojářský tým by brzy přetížil infrastrukturu úložiště.
Jenže abychom získali přístup k úložišti, musíme náš balíček nahrát přes jednoduché webové rozhraní k manuální kontrole… zřejmě ověření zda neobsahuje nějaký backdoor, těžbu kryptoměn či co já vím - v našem případě jistě pouhá formalita… omyl - sotva jsem klikl na tlačítko „Odeslat“, vyskočila na mne hláška „každý plugin musí obsahovat readme.txt“ (pochopitelně v angličtině, ale screenshot jsem si neudělal a má anglická paměť je ještě děravější než česká). Zrovna jsem řešil několik jiných úkolů, tak jsem o tvorbu readme.txt požádal kolegu, který akci pokrýval z organizačního hlediska - ostatně, co tam tak může napsat… k čemu plugin slouží, co je nutné nastavit a něco o naší firmě. Po pár dnech kolega dodal sofistikovaný dokument čítající několik kapitol a odkazy na screenshoty. Doprovodný dopis obsahoval i odkazy na pokyny ze strany WordPressu, jak má readme.txt vypadat, a na online nástroj k jeho validaci! Z uvedeného vyplynulo, že screenshoty mají být součástí balíčku (v adresáři assets), stejně jako všechny ostatní obrázky… hleďme, součástí pluginu mohou být obrázky… no, teď už to předělávat nebudeme, času je málo a už se chceme věnovat něčemu jinému. Znovu nahrávám plugin. Úspěch! Dostává se mi informace, že plugin byl přijat do fronty čekajících k manuální kontrole a že je druhý v pořadí.
Uběhly asi tři dny napjatého očekávání, když dorazil obsáhlý email vypočítávající nedostatky našeho drobečka:
Do Not Combine JS Files
DO NOT include your own copy of javascript files that are already included in WordPress Core
Please sanitize, escape, and validate your POST calls
Allowing Direct File Access to plugin files
Not using Nonces and/or checking permissions
Asks users to edit/writes to plugin
Generic function (and/or define) names
To vše doplněné zdrcujícími důkazy ve formě ukázek z našeho kódu - za všechny příklad snitizace a validace vstupních dat:
Some examples from your plugin:
GOL-IBE-Search-Form/GOL-IBE-Search-Form-settings-handler.php - the way you're sanitizing there isn't correct
if (isset($_POST['button_color'])) {
$regex = "/^#(?:[0-9a-fA-F]{3}){1,2}$/m";
if (preg_match($regex, $_POST['button_color'])) {
$settings['button_color'] = $_POST['button_color'];
}
}
That should be using sanitize_hex_color()
Your validations are great, but you also need to remember to sanitize properly just in case.
Zvážili jsme eventualitu předání připomínek původním tvůrcům našeho miláčka (glum, glum), ale z různých důvodů jsme se rozhodli popsané excesy napravit vlastními silami.
Vzali jsme to pěkně z gruntu:
použité javascripty jsme rozsekali na jednotlivé moduly - nebylo to tak těžké, protože v naší aplikaci je také udržujeme separátně a spojujeme je až před uložením do cache pro odeslání do browseru
vynechali jsme moduly obsažené ve WordPressu - poskytuje jejich přehledný seznam (laskavý čtenář si všimne, že zde jsme přišli o naši skvělou úpravu jQuery-ui)
totéž jsme udělali s css soubory
vrátili jsme zpět css soubory jQuery-ui - kupodivu WordPress vkládá jejich javascript, css nikoliv
nahradili jsme všechny validace regulárními výrazy příslušnými funkcemi WordPressu a doplnili escapovací funkce na všechny výstupy
vyřešili jsme ukládání konfigurace… to bude na samostatný odstavec
celý kód sestávající z asi desítky globálních funkcí jsme přepsali do tří tříd s prefixem „GOL_IBE“ - na použití namespace jsme si netroufli
Výtky týkající se přímého přístupu k souborům pluginu, nepoužívání Nonces a editace pluginu uživatelem se vztahovaly k práci s konfigurací. Původní řešení vypadalo zhruba takto:
do hlavního menu přidáme stránku s konfigurací
formulář této stránky bude v action odkazovat na soubor save.php v pluginu
soubor save.php v pluginu přijme POST, převede ho na json a zapíše do souboru config.json
hlavní script pluginu json ze souboru načte a použije v něm obsažené nastavení
hlavní script pluginu zaregistruje jako zdroj css soubor css.php v pluginu
soubor css.php načte config.json a vygeneruje podle něj konfigurovatelné styly
Problém tkvěl jednak v tom, že plugin zapisoval sám do sebe (config.json), druhak v tom, že soubory save.php a css.php stály zcela mimo WordPress - žádná kontrola práv, žádná možnost přístupu k validačním a escapovacím funkcím WordPressu.
Řešení spočívalo v převodu nastavení na api nabízená WordPressem k tomuto účelu:
settings api k zobrazení formuláře s nastavením
options api k uložení hodnot parametrů
Tím odpadla nutnost kontrolovat oprávnění a používat nonces ve formuláři konfigurace, protože to vše api vyřešilo za nás - soubor save.php pochopitelně zmizel. Následně i soubor css.php, protože z něj nebylo dostupné options api potřebné k načtení nastavení - zkusili jsme jej ještě zachránit předáváním parametrů přes GET, ale neprošlo to a konfigurovatelné styly nakonec vkládáme v tagu <style> přímo v html (mimochodem, věděli jste, že takto vložené selektory mají menší prioritu než stejné vložené v externím stylesheetu?).
Options api se nakonec hodilo i na ukládání nastavení načítané z naší aplikace.
Po uvedených úpravách jsme šťastně prošli kontrolou ze strany WordPressu, žel s poněkud nefunkčním produktem - nefungovaly kalendáře pro zadávání data odletů jednotlivých fází cesty a nastavování barev také nešlo jak bychom si přáli. Stáli jsme tak před dilematem, zda použít získaný přístup k repozitáři a publikovat plugin tak jak je, nebo dále ladit a vychytávat mouchy. Demokraticky jsme si jednohlasně odhlasovali vydání v aktuálním stavu. Plugin se v nabídce dostupných objevil prakticky ihned, instalace a aktivace byly dílem okamžiku, velmi příjemný pocit. Žel, práce zdaleka není hotova a je třeba neprodleně zahájit práce na nové verzi… prostě opravit ty kalendáře.
Výchozí teorie byla, že dialogy pro výběr data jsou kvůli chybějící úpravě jQuery-ui špatně napozicované a mizí někde mimo obraz. Prověřili jsme i možnost opatchovat příslušnou metodu za běhu… ale nevypadalo to schůdně - měla asi 100 řádek značně nesrozumitelného kódu. Pak jsme si všimli, že primární problém je možná jinde - stránka při načítání vyhazovala do konzole poněkud záhadnou hlášku že „itemList is undefined“. Nezachycená výjimka padala odkudsi z hlubin minifikovaného jQuery-ui s několika vrstvami anonymních funkcí na zásobníku volání. Naštěstí nás intuice a mravenčí práce s debuggerem brzy přivedla k zakopanému psu: pro rozšíření možností stylování tagu <select>, nahrazuje jej jQuery-ui konstrukcí z divů a spanů, která emuluje funkci původního prvku. Některé selecty našeho formuláře mají v době inicializace prázdný seznam položek (tzv. <option>) a s tím se verze jQuery-ui distribuovaná s WordPressem nedokázala vyrovnat - námi používaná neměla námitek. Stačilo do každého prázdného selectu v html vložit jednu prázdnou option a hleďme, kalendáře jsou na světě. Tedy… pokud je formulář umístěn na „frai plac“ v jednoduché stránce. Při vložení do prvku s obrázkem na pozadí se formulář zdeformuje, z kalendářů jsou vidět jen části a modální dialog pro zadávání počtů cestujících vypadá jak po křivici. Náš krásný formulář je zřejmě utiskován a dokonce i ořezáván!
Padl návrh umístit kalendáře doprostřed formuláře, aby nebyly ořezány, ale neprošel.
Místo toho jsme zkusili celý formulář před inicializací vyjmout z omezujícího elementu a znovu vložit do stránky za něj. Vyžádalo si to další refaktoring javascriptových modulů, protože jejich inicializaci bylo třeba odložit až za přesun, ale výsledek stál za to. Přestože to je strašná řezničina… přeháním, jeden $.detach() a jeden $.after() není zas tak hrozný. Navíc se provádí jen když je detekována přítomnost omezujícího prvku a původnímu obalujícímu elementu je vnucen rozměr, jako by v něm formulář stále byl.
Zbývalo už jen vyřešit potíže s barvami - původní návrh umožňoval nastavit barvu:
písma formuláře
pozadí hlavní plochy formuláře
pozadí méně významných prvků formuláře
pozadí tlačítek
Z toho plynuly hned dvě úzce provázané potíže:
tlačítka mívají z logiky věci kontrastní barvu k ostatnímu pozadí a tak bylo třeba přidat možnost nastavit samostatně barvu textu tlačítek
tlačítka pro výběr typu letu (jednosměrný/zpáteční/vícefázový) jsou realizována průhledným svg obrázkem vloženým pomocí css jako pozadí tlačítka - přesto že svg vložené jako obrázek se stylovat dá, pokud je vloženo via css, tak se stylovat nedá
Naštěstí jsme se už dozvěděli, že plugin vlastní obrázky obsahovat smí, odstranili jsme ze stylesheetu dataurl ikonek, vytvořili jsme černou a bílou verzi svg obrázků a na ty jsme odkázali konfiguračním stylesheetem vkládaným do html. To umožnilo nastavovat barvu popisku tlačítek v mezích černá/bílá, což odpovídá možnostem nastavování barvy textu formuláře. Někdo by navrhl vkládat ikonky jako img do html a tím umožnit jejich stylování… jenže tou dobou už jsme měli pluginu plné zuby a celí šťastní jsme vydali verzi 1.1.0, která už je snad použitelná.
Udělat to lépe… to jde vždy - důležité je odhadnout tu hranici, kdy je třeba práci na projektu uzavřít a posunout se dál, k dalším výzvám :-).
tl;dr: Provozujeme weby na prodej letenek, někteří zákazníci mají web na wordpressu, publikovali jsme vstupní formulář eshopu jako plugin do wordpressu.