Remembering the user language choice

Recently, I had to develop a small web portal for a client. Since the portal consisted mostly of static pages which the client wanted to update himself, and I had limited time for development, WordPress platform was an obvious choice. We agreed to derive the design from the standard “Responsive” theme (WordPress Codex has a great article on how to do that).

The portal had to be multi-lingual, most pages would be in English, but some would have translations in Latvian. I found WordPress qTranslate plugin the best solution for my needs. It changes the way WordPress dashboard forms work, now a blog post has two fields “Title (Latvian)” and “Title (English)” as well as “Post content (Latvian)” and “Post content (English)”. If you don’t fill the fields for a particular language, the page would be treated as not translated and may be hidden in the navigation as well. You have to pick the default language of the site – what content to show if the user has not picked any language.

qTranslate provides several ways how you can do localization:

  • using a subdomain, e.g. lv.yourdomain.com and en.yourdomain.com
  • by adding a GET parameter to each page, eg. yourdomain.com/article_title/?lang=lv
  • using a directory prefix, e.g. yourdomain.com/lv/articletitle, yourdomain.com/en/articletitle

I picked the latter, as it seemed most rational of the three. Now every page had two addresses and everything seemed to be running just fine. However, I found it a bit annoying that a returning visitor always sees the default language, regardless which language he had chosen in the previous visit. It would seem logical that qTranslate could just set a cookie “last language” or something like that. I found out, it does not. Moreover, qTranslate authors are not going to implement it, they say “it’s a wanted behavior”.

Not wanting to mess with plugin’s PHP code, I decided to fix this at the web server level (as you may know, Apache Mod_Rewrite can both read and set cookies).

So, I needed two parts of the algorithm:

— when the user requests a page where the URL contains language code, save that language in a cookie. In “.htaccess language” it is

    #Only for requests which start with "/lv/" or "/en/". 
    RewriteCond %{REQUEST_URI} ^/(lv|en)/.*$ [NC]

    #no need to set a cookie if you already have one with the same value
    RewriteCond %{HTTP_COOKIE} !lang=%1 [NC] 

    #rewrite to the same address, but set a cookie to the value captured in first RewriteCond
    RewriteRule . - [cookie=lang:%1:.%{HTTP_HOST}:144000:/]

— when the user requests the “root” of the portal and there is a “lang” cookie set, do a redirect to the particular language root page

  #only for the root page /
  RewriteCond %{REQUEST_URI} ^/$ [NC]

  #only if you have a "lang" cookie
  RewriteCond %{HTTP_COOKIE} lang=(en|lv) [NC] \

  #redirect (not rewrite!) to either /en/ or /lv/
  RewriteRule ^(.*)$ /%1/ [R=302,L]

So, after all modifications my .htaccess file looks like this:

# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /

RewriteCond %{REQUEST_URI} ^/$ [NC]
RewriteCond %{HTTP_COOKIE} lang=(lv|en) [NC] 
RewriteRule ^(.*)$ /%1/ [R=302,L]

RewriteCond %{REQUEST_URI} ^/(lv|en)/.*$ [NC] 
RewriteCond %{HTTP_COOKIE} !lang=%1 [NC] 
RewriteRule . - [cookie=lang:%1:.%{HTTP_HOST}:144000:/]

RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress

String concatenation versus array.join() in JavaScript

To answer a question on StackOverflow, I made an experiment which actually qualifies for a separate blog post.

So, I compared the speed of str1+str2 concatenation and array.push(str1, str2).join() methods. The code I used was quite simple:

var iIterations =800000;
var d1 = (new Date()).valueOf();
str1 = "";
for (var i = 0; i<iIterations; i++) str1 = str1 + Math.random().toString();
var d2 = (new Date()).valueOf();
log("Time (strings): " + (d2-d1));

var d3 = (new Date()).valueOf();
arr1 = [];
for (var i = 0; i<iIterations; i++)arr1.push(Math.random().toString());
var str2 = arr1.join("");
var d4 = (new Date()).valueOf();
log("Time (arrays): " + (d4-d3));

I tested it in IE8 and FireFox 3.5.5, both on a Windows 7 x64.

In the beginning I tested on small number of iterations (some hundred, some thousand items). The results were unpredictable (sometimes string concatenation took 0 milliseconds, sometimes it took 16 milliseconds, the same for array joining).

When I increased the count to 50’000, the results were different in different browsers – in IE the string concatenation was faster (94 milliseconds) and join was slower(125 milliseconds), while in Firefox the array join was faster (113 milliseconds) than string joining (117 milliseconds).

Then I increased the count to 500’000. Now the array.join() was slower than string concatenation in both browsers: string concat 937ms in IE, 1155 ms in Firefox, array join 1265 in IE, 1207 in Firefox.

Maximum iteration count I could test in IE without having “the script is taking too long to execute” was 850’000. Then IE was 1593 for string concatenation and 2046 for array join, Firefox had 2101 for string concatenation and 2249 for array join.

Results – if the number of iterations is small, you can try to use array.join(), as it might be faster in Firefox. When the number increases, the string1+string2 method is faster.

UPDATE
I performed the test on IE6 (WindowsXP). The process stopped to respond immediately and never ended, if I tried the test on more than 100’000 iterations. On 40’000 iterations the results were

Time (strings): 59175 ms
Time (arrays): 220 ms

This means – if you need to support IE6, choose array.join() which is way faster than string concatenation.

“_vti_bin” folder defined

Ever wondered why SharePoint “lists web service” (i.e., lists.asmx) lives in an interesting address of http://mossserver/sites/somesite/_vti_bin/lists.asmx ?

This is actually not the only place where you can see the magic _vti_something. I remember myself deleting such directories from “wwwroot” quite often back in the days “everyone” used MS FrontPage. FrontPage used to create the folders “_vti_bin”, “_vti_cnf” on your computer locally as you developed a website .. and also on the server, if you used “Frontpage Extensions” for publishing. The _bin folder would normally contain executables required for FP server components (as far as I understand, something like cgi_bin).

So, even though FP is announced dead, guys from the SharePoint team apparently like the idea of puting executables in the _vti_bin folder which was used by FrontPage Extensions.

Anyway, the question is, what does the acronym “VTI” mean. Apparently, Microsoft did not develop MS FrontPage from scratch, they acquiredVermeer Technologies Inc”. Thus the name of “bin” folder in FrontPage. Thus the URL for lists.asmx in SharePoint.

Ko pasaule zina par Storm Worm?

Ievadam – apnika lasīt par jauniem gadždetiem. Jāuzlabo LV blogu kultūra reportējot arī par citiem jaunumiem.

IEEE izdotā žurnāla “Computerfebruāra numurā ir ļoti interesants raksts par Storm Worm. IEEE bibliotēkas lasītājiem šis raksts ir pieejams arī tiešsaistē: “A Storm (Worm) Is Brewing”.

Īsumā – kopš 2006. gada rudens IT drošības pasaule ir iepazinusi jaunu ienaidnieku, kurš ticis pie vārda Storm, pirmo reizi īsti par sevi liekot manīt 2007. gada 19. janvārī, kad dienas laikā tika izsūtīts apmēram 20 reižu lielāks surogātpasta daudzums nekā citās dienās. Citi šī ienaidnieka nosaukumi ir Nuwar, Peacomm, Zhelatin. Pēdējais nosaukums vislabāk parāda šī kiber-ienaidnieka dabu, jo tieši mainīguma dēļ IT drošības kompānijām ir tik grūti izstrādāt pretlīdzekļus Storm-am.

Storm ir milzīgs attālināti kontrolējamu (“zombiju“) datoru tīkls, ar kura palīdzību tiek veikti kibernoziegumi, vienlaicīgi arī inficējot jaunus datorus un tos piesaistot storm tīklam. Drošības ekspertiem ir izdevies noteikt, ka tīkla darbības kontrolei tiek izmantoti austrumeiropā (tātad – arī Latvijā?) un Krievijā esošas IP adreses. Krievijas valdība atsakās sadarboties ar ASV spēkiem, lai likvidētu Storm tīklu.

Ir izteikti minējumi, ka 2007. gada pavasara DDoS uzbrukumi Igaunijas serveriem tikuši veikti no Storm tīkla. Tāpat Storm tiek pielietots naudas pelnīšanai, izsūtot surogātpastu, kas reklamē organizācijas, kuru akcijas pieder Storm īpašniekiem. Joe Stewart, SecureWorks pētnieks norāda, ka pastāv aizdomas, ka kāda Kanādas kompānija savulaik izīrējusi daļu tīkla resursu, lai izsūtītu surogātpastu.

Daži fakti par Storm:

  • Izplatās, izmantojot sociālās inženierijas ceļus. Izsūta e-pasta vēstules, ar tēmām “Milzīga vētra Eiropā”, “Nogalinājis 11 gados, 21 gada vecumā atkal brīvs un dodas nogalināt”, “Britu genocīds pret musulmaņiem”, “Krievu raķete notriekusi ASV satelītu”. Tāpat tiek izsūtītas e-pasta vēstules, kas satur saiti uz it kā Youtube esošām filmām. Vēl vienu klikšķi tālāk lietotāja neprasmīgi aizsargātais dators tiek inficēts ar Trojas zirgu.
  • Pēc datora inficēšanas sistēma var uzstādīt klaviatūras signālu pārķeršanas draiverus un šo informāciju pārsūtīt tīkla īpašniekiem. Protams, tiek izveidota “lūka”, caur kuru Storm īpašnieki var nodot komandas datoram
  • Programmatūra slēpjas. Tā instalē rootkit-us, lai izpildāmie faili nebūtu redzami datora lietotājam, tā modificē API, kas parāda aktīvos procesus, tā dzēš rīkus, kas ļautu identificēt Storm klātesamību.
  • Inficētie datori uzvedas gluži normāli un ir lietojami ikdienas darbam.
  • Decentralizēts – savstarpējai saziņai un izplatīšanai izmanto P2P tīklus, izmantojot savus protokolus un arī standarta rīkus kā ICQ un IRC
  • Izmanto t.s. fast flux principu, lai paslēptu web vietnes, kas satur inficējošo kodu. DNS ieraksti tiek mainīti ik pa dažām minūtēm, kas sevišķi apgrūtina izsekošanu.
  • Izplatīšanai vienlaicīgi izmanto tikai nelielu daļu no tīklā esošajiem datoriem, bet šī daļa regulāri mainās
  • Šifrē visu saziņu starp tīklā esošajiem datoriem, izmantojot vismaz 40 bitu atslēgas (simetriskās kriptosistēmās tas ir pietiekami daudz)
  • Izplatīšanās mehānisms tiek regulāri mainīts, tai skaitā mainot arī modificējot izpildāmo kodu līdz pat 10 reizēm stundā

UTF8 adreses

Lietojot wikipediju, vienmēr meklēto vārdu rakstu uzreiz adresē (http://en.wikipedia.org/wiki/<vajadzīgais vārds>).

Līdzīgu pieeju mēģināju lietot arī latviešu wikipēdijā, bet secināju, ka vārdos ar latviešu diakritiskajiem simboliem tas nedarbojas. Piemēram, mēģinot atrast skaidrojumu vārdam “Māra”, atveras lapa “Mâra” (kur, protams, nekāda satura nav).

Kā noskaidroju, Firefox lietotāji to var labot ar slēdža Network.standard-url.encode-utf8 palīdzību. Uzstādot šo slēdzi uz “true”, Firefox sāk darboties atbilstoši RFC 3987 un visi nestandarta burti tiek kodēti ar URLencode.

Tas gan man nedarīja saprotamu, kāpēc pirmajā gadījumā teksts nokodējās uz
http://lv.wikipedia.org/wiki/M%C3%A2ra (Mâra)
bet otrajā uz
http://lv.wikipedia.org/wiki/M%C4%81ra (Māra)

Upd: Izskatās, ka šeit aprakstītā problēma nepastāv citos datoros kā tikai man mājās pieejamajos.

Fonti

Calibri” fonts, kas nāk līdzi Vistai/MSOffice 2007, izskatās briesmīgi, ja to skata ar izslēgtu anti-aliasing.

Šis ir tāds kā kliedziens visiem tiem, kas ieinstalējuši IE7 vai citādā veidā ieguvuši apaļos, izplūdušos burteļus un priecājas par “jauno, webam piemēroto fontu” – nē, neder! Gaidiet, kamēr visa pasaule iešaus sev galvā un ieslēgs to acu mocītāju.

Sharepoint, MOSS un WSS – 2007. versija

Šķiet, esmu viens no retajiem LV, kuram šīs lietas vispār interesē, tomēr pastāstīšu – lai jau saglabājas nākotnei.

Kopš šī gada sākuma tīmeklī it bieži tiek pieminēts “jaunais Sharepoint” – visvairāk kā apjūsmojoši ieraksti dažādos blogos, cik ērts gan tas būs. Runa ir par Sharepoint 2007, precīzāk, diviem Sharepoint saimes produktiem – Windows Sharepoint Services (WSS) 3.0 un Microsoft Office Sharepoint Server (MOSS) 2007. Ar ko tie atšķiras? Apmēram ar to pašu, ar ko Notepad atšķiras no Wordpad – ar iespēju bagātību. Ja WSS iedomājamies kā skudrupūzni, tad MOSS ir skudrupūznis ar centrālapkuri, četriem skursteņiem un peldbaseinu pagrabā. Vārdu sakot, MOSS ir daudz papildus lietu – unificēta meklēšanas sistēma, lietotāju individuālie saiti, papildus saitu templeiti, papildus darba plūsmu realizācijas, biznesam svarīgo indikatoru (KPI, key performance indicators) monitorēšana, Excel web servisi utt utt. Vēl viena atšķirība – WSS ir “par velti”, tas ir, kopā ar Windows Server 2003, bet MOSS ir maksas produkts… un dārgs maksas produkts.

Atpakaļ pie pamatstāsta. Solīja jau kādu laiku, šī gada sākumā iznāca pirmā beta versija, kas bija patiešām ar īstu “betas” garšu, šī gada maijā – otrā Beta, augustā – Beta2TR versija. Pagājušajā nedēļā Microsoft paziņoja par galaversijas iznākšanu, pašlaik jebkuram ir pieejama 180 dienu trial versija MOSS un, protams, jebkuram Windows Server 2003 īpašniekam – WSS 3.0 RTW Instalēšanai būs noderīgs .NET Framework 3.0, kas arī tika izziņots pagājušajā nedēļā.

Par jaunumiem šajā versijā. Vēl joprojām Sharepoint kā datu glabātuvi izmanto Microsoft SQL Server, tagad varam lietot gan 2000., gan 2005. versiju. Joprojām tas darbojas kā IIS webservera process (bet, atšķirībā no iepriekšējās versijas, tagad kā normāla ASP.Net aplikācija, nevis ISAPI filtrs). Ir krietni uzlabota Active Directory integrācija, tagad varam precīzāk definēt attiecību “domēna lietotājs:portāla lietotājs”, piemēram, nosakot, kurus portāla lietotāja atribūtus nolasīt no kuriem domēna lietotāja konta atribūtiem (šī atkal laikam tikai mani interesējoša nianse).

Piekļuves tiesību granularitātes līmenis (ak, svešvārdi…). 2003. versijā nebija daudz iespēju – piekļuvi varēja definēt vai nu visam saitam kopā, vai katrai dokumentu bibliotēkai vai sarakstam atsevišķi. Un viss. Jaunajā variantā piekļuves tiesības varam definēt līdz pat ieraksta līmenim, kas ir daudz loģiskāk, ja nepieciešams izveidot koplietošanas dokumentu glabātvi. Šī iemesla dēļ gan sanācis tā, ka tiesību administrēšana ir kļuvusi ķēpīgāka. Ja līdz šim šķita, ka normālā prakse būtu – 1) Sharepoint saita līmenī sadefinēt loģiskas lietotāju grupas 2) Šīm grupām piešķirt tiesības skatīt to vai citu dokumentu bibliotēku, sarakstu, darīt to vai citu darbību 3) Šajās grupās ievietot cilvēkus vai grupas no domēna … tad tagad rodas sajūta, ka “ai, vai tad nu es šī viena dokumenta dēļ taisīšu speciālu grupu?”, tātad tiesības tiek izmētātas pa visu saitu.

Audiences (šķiet, šī ir MOSS, nevis WSS iespēja). Katram ierakstam varam norādīt mērķauditoriju (target audiences), t.i., grupas, kam tas varētu būt interesants. Tāpat – lapā ievietotam webpart-am var norādīt auditoriju. Ja pareizi saprotu, piekļuve šādā veidā netiek aizliegta, bet attiecīgais ieraksts vai webparts tiek aizvākts no to lietotāju acīm, kam tas nebūs vajadzīgs. Un tas ir ērti.

Navigācijas sistēma. Jau noklusētajā shēmā tā ir stipri sakarīga. 1) katram saitam var veidot savu augšējo navigācijas paneli, sastāvošu no atsevišķām cilnēm (tabs). Ir izvēle, vai mantot “parent” saita tabus, vai izmantot savējos. 2) stipri labāks ‘quick launch’ menu saita kreisajā malā. Ir izvēle, vai nu automātiski ģenerēt no visām dokumentu bibliotēkām un sarakstiem, vai veidot pašam savējo 3) breadcrumbs pasākums, kas parāda vertikālo navigāciju saitu struktūrās. Visumā ir sajūta, ka apmaldīties nevarēs.

Darba plūsmu programmēšana. Kopā ar Windows Workflow Foundation ir atnākusi iespēja strādāt ar darba plūsmām arī no Sharepoint. Ir sajūta, ka te ir savāktas vienkāršākās un nepieciešamākās lietas no Biztalk Server un palaistas plašākās tautās. Līdz ar to tādi procesi kā dažādu veidu vīzēšana, atsauksmju savākšana, atbilžu sagatavošana un citi darbiņi, ko cilvēki veic nelielās komandās, ir aprakstāmi un programmējami arī šajā vidē.

Multiple lookups. Iepriekšējā Sharepoint versijā sarakstam bija iespējams izveidot lauku, kurš norāda uz cita tajā pašā saitā esoša saraksta konkrētu ierakstu. Piemēram, cilvēku tabulā varētu būt lauks, kas ir norāde uz struktūrvienību sarakstu. Līdz šim nebija iespējams apskatīt situāciju, kad cilvēks strādātu vairākās struktūrvienībās vienlaicīgi. Tagad ir.

Custom field types. Šis ir interesants. Pēc noklusējuma Sharepoint sarakstiem var pievienot laukus ar tipiem kā “text”, “multiline text”, “number”, “date”, “choice”, “lookup”… tagad varam veidot savus tipus. Nu, kaut vai tas pats iecienītais piemērs ar personas kodu – zināms teksta formāts, zināms garums. Atliek tikai izstrādāt savu klasi, kas mantota no SPField tipa, izveidot rediģēšanas kontroli savam lauka tipam (Jo Sharepoint taču automātiski ģenerē datu ievades un parādīšanas formas katram sarakstam), varbūt vēl neliela čupiņa koda… un tam ir jādarbojas. Pagaidām neesmu izmēģinājis, bet jau ticu, ka būs labi.

Wiki un Blog saitu templeiti. Nezinu, pagaidām nešķiet, ka jaunā spēļmantiņa būs tik vērta, cik viņi paši sola. Wiki sintakse ne tuvu nelīdzinās wikipedia iespējām, blogi it kā ok, pat komentēt var… bet kaut kā nav ērti, vismaz ne priekš tiem, kas paši tajos raksta.

Overall – labais! Uzrakstīšu citreiz vēl, šoreiz jau tā par garu sanāca.

Par “conditional comments”

Ir tāda lieta HTMLā kā “conditional comments“, kas ir parasti SGML komentāri, kurus Internet Explorer 5+ tomēr “lasa” un mēģina saprast. Un ja tur atrodas [if kautkas] tad tas tiek ņemts vērā.

Parasti tiek lietots, lai speciāli priekš IE ielādētu papildus CSS failu vai “pielabotu” atsevišķas CSS definīcijas, bet tikpat labi, lūk, arī alternatīvs pielietojums nosacījumam “ja tiek lietots IE”.

<!--[if IE]>
<a href="http://www.getfirefox.com/" title="Browse happy">Go get firefox</a>
<![endif]-->

Web izstrādātātāju bookmarklets

Izstrādātājot lietas tīmeklim, reizēm gribas pārliecību, ka lapa tiek tiešām pieprasīta par jaunu no servera, nevis izmantota IExplorer (vai citam pārlūkam pēc izvēles) pieejama kešota versija.

Parasti ar šo vēlmi tieku galā, pierakstot URLim galā kaut ko līdzīgu “&bubu”.
Nupat uzrakstīju bookmarkletu, kurš dara ko līdzīgu – ja URLī nav parametra TmpPart, tad to pieliek, ja tāds jau ir, tad tam piekabina galā kādus papildus burtus.

Bookmarkleta teksts (salauzts pa rindām, savelciet kopā, lai darbotos):

javascript:(function(){
    var sL = document.location+'';
    var s="";
        if (sL.indexOf('?')==-1) 
            s = sL + "?TmpPart=x"; 
        else 
            if (sL.indexOf('TmpPart=')==-1) 
                s=sL+"&TmpPart=x"; 
            else 
                s = sL.replace(/(TmpPart=)(x+)/g,"$1$2x");
    document.location=s;
    }())