Kako Prepoznati Srbina

Sećate li se mog prijatelja Manu-a, amerikanca koji prati isključivo fudbal(i to ne američki) ?

Manu       15.12.20 20:34 usa stole a serb footballer

Manu       15.12.20 20:35 http://chivas.usa.mlsnet.com/players/bio.jsp?team
                               t120&player=kljestan_s&playerId=kle326299&statTyp
                               =current

dinke      15.12.20 20:35 Sacha Kljestan ? Serb ?

Manu       15.12.20 20:36 lol

Manu       15.12.20 20:36 you should know

Manu       15.12.20 20:36 that is serb name

Manu       15.12.20 20:36 that is not serb name

Manu       15.12.20 20:36 that is serb name

Manu       15.12.20 20:36 that is not serb name

Manu       15.12.20 20:36 to be or not to be a serb

Manu       15.12.20 20:36 that is the question

Nakon što mi je sinoć po n-ti put(gde n–>00) servirao vest o nekom mom “zemljaku”, morao sam da potrošim vreme i objasnim mu kako da prepozna Srbina u vestima(tim pre što u našem multinacionalnom timu imamo dvocifren broj ovdašnjih programera):

function isSerb($lastname)
{
	$found = preg_match("/^[\pL]+ić$/u", $lastname);

	if($found)
	{
		return true;
	}
	else
	{
		return false;
	}
}

Uvod u GeoIP

Verovatno ste već bili u prilici da koristite Google Analytics alat, gde između ostalog možete na mapi sveta videti odakle tačno dolaze posetioci vašeg sajta, ili ste tu i tamo posetili sajt koji bi Vam između ostalog izbacio podatke o Vašoj trenutnoj lokaciji. Naravno, nije u pitanju nikakva magija, tačna lokacija posetioca definisana je na osnovu njegove IP adrese, a tehnologija koja se koristi prilikom “lociranja” korisnika opšte je poznata pod nazivom GeoIP.

Danas ćemo pričati o tome kako “locirati” posetioca pomoću PHP-a i Max Mind-ove GeoIP baze. U primerima koji slede koristicemo besplatne(lite) verzije GeoIP baza, obzirom da se za pune verzije plaća $50USD + $12USD za update (GeoIP Country baza) i $370USD + $90USD za update (GeoIP City baza). Mana lite verzija je što nisu uvek 100% ažurne, ali će odlično poslužiti za naš tutorijal, a iz ličnog iskustva tvrdim da su upotrebljive i u većini live projekata.

MaxMind obezbeđuje API za nekoliko popularnih programskih jezika, (kompletna lista dostupna je ovde), a detalji o PHP API-u dostupni su ovde. Pored takozvanog “Pure PHP API-a” koji ćemo ovde koristiti, postoje i PECL ektstenzija kao i apache modul(mod_geoip), koji pružaju bolje perfomanse ali i komplikovaniji setup.

Za početak neophodno je da skinete sve fajlove koji se nalaze na http://www.maxmind.com/download/geoip/api/php/ i snimite ih negde unutar vašeg Web stabla(recimo /htdocs/geoip). Za korišćenje GeoIP Country treba skinuti lite bazu odavde, a za city GeoLiteCity bazu odavde. Radi jednostavnosti korišćenja, obe baze ćemo takođe raspakovati u isti direktorijum gde smo i snimili fajlove iz PHP API-a (/htdocs/geoip).

GeoIP Country
——————————–

Idemo sa primerom detekcije zemlje posetioca:

<?php
/**
 * Primer Koriscenja GeoIP Country Baze
 *
 * @version $Id$
 * @package geoip
 * @copyright © 2006 Lampix.net
 * @author Dragan Dinic <dinke@lampix.net>
 */

require_once("geoip.inc");

$gi = geoip_open("GeoIP.dat", GEOIP_STANDARD);

$ip = $_SERVER['REMOTE_ADDR'];
//ako testirate u lokalu koristite ovaj ip radi testa
//posto ce $_SERVER['SERVER_ADDR'] biti 127.0.0.1
//$ip = "89.216.226.174";

$country_name = geoip_country_name_by_addr($gi, $ip);
$country_code = geoip_country_code_by_addr($gi, $ip);
if($country_name)
{
	echo "Zemlja iz koje nas posecujete je: $country_name <br />";
	echo "Skracena Oznaka: $country_code <br />";
}
else
{
	echo "Nazalost, nismo bili u mogucnosti da vas lociramo.";
}

geoip_close($gi);
?>

Dakle, na početku uključujemo geoip.inc koji sadrži sve f-je potrebne za korišćenje GeoIP County baze, zatim kreiramo novu instancu GeoIP klase pomoću geoip_open f-je, i na kraju pozivamo odgovarajuće f-je (geoip_country_name_by_addr i geoip_country_code_by_addr) da bi smo dobili ime/kod zemlje u kojoj se nalazi ip adresa posetioca(u slučaju da testirate u lokalu nemojte koristiti $_SERVER['REMOTE_ADDR']).

Kao izlaz skripta, trebalo bi da dobijemo nešto poput:

Zemlja iz koje nas posecujete je: Serbia and Montenegro
Skracena Oznaka: CS

F-je koje smo koristili da bi dobili podatke o zemlji posetioca, samo su neke od f-ja koje su dostupne u API-u. Ostatak možete i sami pronaći jednostavnom analizom PHP sourca geoip.inc fajla.

GeoIP City
—————————-

A sada da proširimo podatke o zemlji sa tačnom lokacijom (grad, poštanski kod itd).

<?php
/**
 * Primer Koriscenja GeoIP City Baze
 *
 * @version $Id$
 * @package geoip
 * @copyright © 2006 Lampix.net
 * @author Dragan Dinic <dinke@lampix.net>
 */

require_once("geoipcity.inc");

$gi = geoip_open("GeoLiteCity.dat", GEOIP_STANDARD);

$ip = $_SERVER['REMOTE_ADDR'];
//ako testirate u lokalu koristite ovaj ip radi testa
//posto ce $_SERVER['SERVER_ADDR'] biti 127.0.0.1
//$ip = "89.216.226.174";

$record = geoip_record_by_addr($gi, $ip);

if(!$record)
{
	echo "Nazalost, nismo bili u mogucnosti da vas lociramo.";
}
else
{
	echo "Zemlja: " .$record->country_name . "<br />";
	echo "Skracena Oznaka: " . $record->country_code . "<br />";
	echo "Skracena Oznaka2: " . $record->country_code3 . "<br />";
	echo "Region: " .$record->region . "<br />";
	echo "Grad: " .$record->city . "<br />";
	echo "Postanski Kod: " .$record->postal_code . "<br />";
	echo "Geog. Sirina: " .$record->latitude . "<br />";
	echo "Geog. Duzina: " .$record->longitude . "<br />";
}

geoip_close($gi);
?>

Kao što vidite, PHP kod je sličan kodu za detekciju zemlje, s tim što smo koristili geoipcity.inc kao i GeoLiteCity.dat bazu. F-ja geoip_record_by_addr($gi, $ip) vraća instancu klase ‘geoiprecord’ koja sadrži kao promenljive(osobine) podatke o lokaciji koje koristimo u gornjem kodu. Nakon pokretanja skripta trebalo bi da dobijemo nešto poput:

Zemlja: Serbia and Montenegro
Skracena Oznaka: CS
Skracena Oznaka2: SCG
Region: 02
Grad: Beograd
Postanski Kod:
Geog. Sirina: 44.8186
Geog. Duzina: 20.4681

Napominjem da je GeoIP baza najažurnija kada su u pitanju gradovi sa severnoameričkog dela planete, dok je njena preciznost znatno manja kada se dođe do “egzotike” u koju nažalost spada i Srbija.

CaseStudy - Redirekcija na osnovu IP adrese
————————————————————–
Za kraj znanje stečeno ovde iskoristićemo u jednom pravom projektu. Naime cilj je da se na dvojezičnom sajtu(blogu) korisnici koji dolaze iz Srbije usmere na srpsku verziju sajta, dok će se svi ostali usmeriti na englesku verziju. Evo kako to izgleda:

<?php
/**
 * Case Study - Redirekcija na osnovu lokacije
 *
 * @version $Id$
 * @package geoip
 * @copyright © 2006 Lampix.net
 * @author Dragan Dinic <dinke@lampix.net>
 */

require_once("geoip/geoip.inc");

$gi = geoip_open("geoip/GeoIP.dat",GEOIP_STANDARD);

$country_code = geoip_country_code_by_addr($gi, $_SERVER['REMOTE_ADDR']);

geoip_close($gi);

if($country_code == 'CS')
{
        header("HTTP/1.1 301 Moved Permanently");
        header('Location: http://www.dinke.net/blog/sr/');
}
else
{
        header("HTTP/1.1 301 Moved Permanently");
        header('Location: http://www.dinke.net/blog/en/');
}
?>

Primer koji vidite gore koristi se upravo na ovom blogu, kako bi sve korisnike koji ne dolaze iz Srbije automatski preusmerio na englesku verziju bloga. Slanje custom 301 redirection headera je važno kako bi botovi (Google i sl.) indeksirali strane na odgovarajući način.

Izašao PHP 5.2.0

Nova verzija PHP-a, 5.2.0 od juče je dostupna za download.

Pored gomile ispravljenih bagova, nova verzija donosi i neke interesantne mogućnosti od kojih su meni najinteresantnije podrška za “progres bar” prilikom uploada, kao i podrška za zip fajlove. Više informacija o tome šta nam novo donosi PHP 5.2 možete pronaći ovde.

MySQL - Kako ukloniti duplikate

Ovaj post je donekle inspirisan problemom koji je bluesman opisao na svom blogu. Dva problema koji nemaju veze jedan sa drugim, ali odlično pokazuju zašto poslodavci insistiraju na x godina iskustva (gde x > 2) :)

Naime, danas sam “u minut do dvanaest” dobio fajl sa par stotina hiljada keyworda koje treba procesirati ASAP (oh kako volim ovu reč). Naravno, u žurbi nisam mnogo gledao u fajl, provalio sam da je format uobičajen (1 keyword po liniji), importovao u bazu sa load data local infile … , startovao procesiranje i to bi bilo to.

Par minuta nakon toga startuje me kolega koji je zaboravio da mi kaže da se kod nekih keyworda “potkralo” par domena tipa “foo.eu” gde ono “.eu” samo treba izbaciti. Sve je to lepo, ali svi su već importovani u tabelu i samo što nisu pokupljeni. Rešen da brzo delam, odlučim da je najjednostavnije odraditi “search/replace hack” direktno u tabeli, ali …

mysql> update odm_master_keywords
       set keyword = replace(keyword, '.eu','');
ERROR 1062 (23000): Duplicate entry 'academia' for key 2

Shit. Posto je keyword polje unique, neki od tih “.eu” je nakon search/replace-a napravio problem sa istim identičnim keywordom. Ok, obrisaću ručno te duplikate, valjda ih nema mnogo …

mysql> delete from odm_master_keywords_cl where keyword='academia';
Query OK, 1 row affected (0.02 sec)

mysql> update odm_master_keywords set keyword = replace(keyword, '.eu','');
ERROR 1062 (23000): Duplicate entry 'academic' for key 2
mysql> delete from odm_master_keywords_cl where keyword='academic';
Query OK, 1 row affected (0.02 sec)

mysql> update odm_master_keywords set keyword = replace(keyword, '.eu','');
ERROR 1062 (23000): Duplicate entry 'actriz' for key 2
mysql> delete from odm_master_keywords where keyword='actriz';
Query OK, 1 row affected (0.02 sec)

mysql> update odm_master_keywords set keyword = replace(keyword, '.eu','');
ERROR 1062 (23000): Duplicate entry 'foo' for key 2
mysql> delete from odm_master_keywords where keyword='foo';
Query OK, 1 row affected (0.48 sec)
...

I tako, mogao bih ovako do prekosutra … Mora da postoji nešto pametnije :)

Nakon kraćeg razmišljanja, došao sam do ovog rešenja. Privremeno skloniti “unique” index, odraditi update i vratiti index natrag, ali sa ignore opcijom koja će “ubiti” višak duplikata. Dakle:

mysql> alter table odm_master_keywords drop key keyword;
Query OK, 338565 rows affected (13.79 sec)
Records: 338565  Duplicates: 0  Warnings: 0

mysql> update odm_master_keywords set keyword = replace(keyword, '.eu','');
Query OK, 378 rows affected (4.95 sec)
Rows matched: 338565  Changed: 378  Warnings: 0

mysql> alter ignore table odm_master_keywords add unique keyword(keyword);
Query OK, 338565 rows affected (46.45 sec)
Records: 338565  Duplicates: 233  Warnings: 0

Obratite pažnju na ignore deo u poslednjem alteru. On je ključan ovde, jer da ga nismo koristili ponovo bi došlo do greške(duplicate entry …) prilikom update-a.

Inače, poslednja dva query-a su uobičajen “pattern” kada se želite osloboditi duplikata u nekom polju - jednostavno dodajte unique index na polje i višak će biti obrisan iz tabele.

Curl HTTP Client

Vrlo često u svojim svakodnevnim poslovima imam potrebu da parsujem podatke sa raznih sajtova (tipa lista expired domena, lista blogova na planetoidu i sl.) i to najčešće tamo gde nemam RSS na raspolaganju. Dugo vremena sam za takve zadatke koristio sopstvenu HTTP klasu baziranu na socketima, ali nisam nešto preterano bio zadovoljan perfomansama, plus sam bio suočen sa gomilom drugih problema tipa kompatibilnost između linuxa i bsd-a i sl.

U međuvremenu sam tu i tamo koristio Curl cli program (uglavnom za dibagovanje), ali mi se nikada nije preterano sviđao njegov PHP API, način na koji se setuju opcije i sl. Zbog svega gore navedenog, napravio sam sopstveni curl “oop wrapper” - jednostavnu klasu koja bi trebala da znatno olakša uobičajene zadatke kao što su slanje GET/POST zahteva, snimanje kukija i sl.

Obzirom da je još nekoliko kolega ovu klasu smatralo vrlo korisnom (neki su čak implementirali i dodatne mogućnosti), pre nekoliko nedelja odlučio sam da je submitujem na phpclasses.org kao Open Source projekat (BSD Licenca).

Evo nekoliko primera korišćenja. Klasa (kao i fajl sa primerima) može se downloadovati ovde.

<?php
/**
 * @version $Id$
 * @package dinke.net
 * @copyright © 2005 Dinke.net
 * @author Dragan Dinic
 */

require_once("curl_http_client.php");

$curl = &new Curl_HTTP_Client();

//pretend to be IE6 on windows
$useragent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)";
$curl->set_user_agent($useragent);

//uncomment next two lines if you want to manage cookies
//$cookies_file = "/tmp/cookies.txt";
//$curl->store_cookies($cookies_file);

//Uncomment next line if you want to set credentials
//$curl->set_credentials($username, $password);

//Uncomment next line if you want to set specific referrer
//$curl->set_referrer("http://my.referrer.url");

//if you want to send some post data
//form post data array like this one
$post_data = array('login' => 'pera', 'password' => 'joe', 'other_foo_field' => 'foo_value');
//and send request to http://www.foo.com/login.php. Result page is stored in $html_data string
$html_data = $curl->send_post_data("http://www.foo.com/login.php", $post_data);

//You can also fetch data from somewhere using get method!
//Fetch html from url
$html_data = $curl->fetch_url("http://www.foo.com/foobar.php?login=pera&password=joe&other_foo_field=foo_value");

//if you have more than one IP on your server,
//you can also bind to specific IP address like ...
//$bind_ip = "192.168.0.1";
//$curl->fetch_url("http://www.foo.com/login.php", $bind_ip);
//$html_data = $curl->send_post_data("http://www.foo.com/login.php", $post_data, $bind_ip);
?>

Stigle knjizice

Da vam malo pravim zazubice :)

MySQL Full-Text Searches

Danas vam predstavljam treci, ujedno i poslednji zapis iz serije tutorijala o “Mogucnostima MySQL-a koje developeri retko koriste”.

U prethodna dva pricali smo transakcijama i referencijalnom integritetu - mogucnostima koje su dostupne samo u InnoDB i BDB tabelama. Danas vam predstavljam “Full-Text Search”, koji je za razliku od gore opisanih feature-a dostupan samo u MyISAM tabelama (što je MySQL-ov default).

Upravo ste kreirali još jedan database driven sajt (Forum, Blog, CMS, šta vec) i došao je red na pretragu. Bez mnogo razmišljanja, dolazite do uobicajenog rešenja:

select * from moja_tabela
where textpolje like '%text_iz_search_polja%'

koje po svoj prilici završava posao. Malo varijacije na temu ako je potrebno ukljuciti više polja u pretragu, par logickih operatora koje dinamicki generišete u vašem scriptu i to je to? Hmmm ne baš.

(more…)

MySQL::Spoljni Ključevi i Referencijalni Integritet

Danas nastavljamo sa započetom serijom tutorijala o retko korišćenim mogućnostima MySQL-a. U prošlom zapisu detaljno sam predstavio kako se u MySQL-u koriste transakcije. Danas ćemo se usresrediti na korišćenje “Spoljnih Ključeva i Referencijalnog Integriteta”.

Kao i kod transakcija, podrška za referencijalni integritet nije dostupna u standardnim MyISAM tabelama, već se moraju koristiti InnoDB tabele. Malo developera zna da je ova mogućnost dostupna još od verzije 3.23.44, koja se pojavila sada već davne 2001 godine. Za početak idemo malo sa teorijom, a posle ćemo preći na praktične primere.

(more…)

MySQL i Transakcije

Kao što sam u prvom ovogodišnjem zapisu i najavio, od danas krećem sa serijom tutorijala o retko korišćenim mogućnostima MySQL-a kao što su transakcije, referencijalni integritet, fulltext search itd. Developeri ove mogućnosti MySQL-a retko koriste. Mnogi od njih čak i ne znaju da one postoje, pa se uobičajeno, bez puno argumenata, na raznim advocacy raspravama MySQL naziva nekompletnom bazom jer “ne podržava” ništa od gore navedenih mogućnosti. Malo ljudi zna da MySQL recimo podržava transakcije još od verzije 3.23.15 (izašla još maja sada već davne 2000-te godine). Slična je priča i sa ostalim mogućnostima.

Ovaj tekst kao i nastavci koji slede ima za cilj da razbije neke predrasude o MySQL-u kao i da podstakne developere da počnu sa korišćenjem naprednijih mogućnosti koje im njihova baza pruža.

Danas krećemo sa opisom transakcija.

(more…)

MySQL - Prebacivanja tabele iz jedne baze u drugu

Pre neko veče sam morao da prebacim nekoliko tabela iz jedne baze (na istom MySQL serveru) u drugu. Pošto nisam imao vremena da se smaram sa dump-om, posle kraćeg razmišljanja došao sam do sledećeg rešenja:

use prvabaza;
create table foo like drugabaza.foo;
insert into foo select * from drugabaza.foo;

gde je prvabaza baza u koju prebacujemo tabele iz drugebaze. create table like … kreira tabelu identično originalnoj uključujući i indexe, a drugi iskaz kopira podatke iz jedne u drugu. I tako za svaku tabelu posebno (srećom nije ih bilo mnogo).

Danas sam listajući MySQL knjigu došao do znatno jednostavnijeg rešenja :)

alter table drugabaza.foo rename prvabaza.foo;

Jednostavno, brzo, efektno! Nadam se da će nekome ovo koristiti.

Newer Entries » « Older Entries