<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Caught in a Web &#187; Tutorials</title>
	<atom:link href="http://www.dinke.net/blog/category/tutorials/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.dinke.net/blog</link>
	<description>Dinke&#039;s Personal Blog</description>
	<lastBuildDate>Thu, 12 Aug 2010 18:10:39 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.1</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>MySQL &#8211; NULL polja i sortiranje</title>
		<link>http://www.dinke.net/blog/2009/10/04/mysql-null-polja-i-sortiranje/</link>
		<comments>http://www.dinke.net/blog/2009/10/04/mysql-null-polja-i-sortiranje/#comments</comments>
		<pubDate>Sun, 04 Oct 2009 11:51:35 +0000</pubDate>
		<dc:creator>dinke</dc:creator>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[Tutorials]]></category>

		<guid isPermaLink="false">http://www.dinke.net/blog/?p=551</guid>
		<description><![CDATA[Vrlo često imamo situaciju da želimo sortiranje po nekom određenom polju u tebeli ali tako da se NULL polja nikada ne pojavljuju na početku. Tipičan primer je recimo frontend koji prikazuje podatke iz neke tabele, gde se klikom na header kolone obavlja sortiranje po rastućem (asc) ili opadajućem (desc) poretku.
Obzirom da se ja u poslednje [...]]]></description>
			<content:encoded><![CDATA[<p>Vrlo često imamo situaciju da želimo sortiranje po nekom određenom polju u tebeli ali tako da se NULL polja nikada ne pojavljuju na početku. Tipičan primer je recimo frontend koji prikazuje podatke iz neke tabele, gde se klikom na header kolone obavlja sortiranje po rastućem (asc) ili opadajućem (desc) poretku.</p>
<p>Obzirom da se ja u poslednje vreme dosta bavim domenima, kreirao sam jednu tabelu sa par svojih domena, čisto kao demonstraciju koncepta.</p>
<pre>
mysql> select * from domains;
+----+-------------------+-------------+
| id | domain            | expire_date |
+----+-------------------+-------------+
|  1 | dinke.net         | 2010-01-17  |
|  2 | lampix.net        | 2009-12-26  |
|  3 | blogodak.com      | 2010-09-08  |
|  4 | maestrodesert.com | 2009-09-11  |
|  5 | nepostojeci.com   | NULL        |
+----+-------------------+-------------+
5 rows in set (0.00 sec)
</pre>
<p>Dakle problem, želim sortiranje po expire_date polju ali tako da se NULL polje (recimo domen koji još nije regovan ili je istekao) uvek pojavljuje na kraju. Po defaultu NULL se javlja na početku ako sortiramo u rastućem (ASC) orderu odnosno na kraju ako sortiramo po opadajućem (desc) orderu.</p>
<pre>
mysql> select * from domains
order by expire_date asc;
+----+-------------------+-------------+
| id | domain            | expire_date |
+----+-------------------+-------------+
|  5 | nepostojeci.com   | NULL        |
|  4 | maestrodesert.com | 2009-09-11  |
|  2 | lampix.net        | 2009-12-26  |
|  1 | dinke.net         | 2010-01-17  |
|  3 | blogodak.com      | 2010-09-08  |
+----+-------------------+-------------+
5 rows in set (0.00 sec)

mysql> select * from domains
order by expire_date desc;
+----+-------------------+-------------+
| id | domain            | expire_date |
+----+-------------------+-------------+
|  3 | blogodak.com      | 2010-09-08  |
|  1 | dinke.net         | 2010-01-17  |
|  2 | lampix.net        | 2009-12-26  |
|  4 | maestrodesert.com | 2009-09-11  |
|  5 | nepostojeci.com   | NULL        |
+----+-------------------+-------------+
5 rows in set (0.00 sec)
</pre>
<p>Problem sortiranja ćemo rešiti korišćenjem MySQL-ove IF f-je, a rešenje je:</p>
<pre>
mysql> select * from domains
order by if(expire_date is null, 1, 0), expire_date asc;
+----+-------------------+-------------+
| id | domain            | expire_date |
+----+-------------------+-------------+
|  4 | maestrodesert.com | 2009-09-11  |
|  2 | lampix.net        | 2009-12-26  |
|  1 | dinke.net         | 2010-01-17  |
|  3 | blogodak.com      | 2010-09-08  |
|  5 | nepostojeci.com   | NULL        |
+----+-------------------+-------------+
5 rows in set (0.00 sec)
</pre>
<p>MySQL-ova IF f-ja slična je ternarnom operatoru, tj. vraća prvi argument ako je iskaz tačan odnosno drugi u slučaju da nije, dakle u ovom slučaju vraća 1 za null vrednosti odnosno 0 za ostale, čime dobijamo upravo prikaz koji želimo tj. NULL polje na kraju liste. </p>
<p>Znam da ovo može delovati pomalo konfuzno pa ću otići još jedan korak dalje i dodati još jedno polje u našoj tabeli čisto radi razjašnjenja šta se ovde tačno događa:</p>
<pre>
mysql> alter table domains
add column nullorder tinyint not null;
Query OK, 5 rows affected (0.01 sec)
Records: 5  Duplicates: 0  Warnings: 0
</pre>
<p>a zatim i update-ovati vrednosti nullorder polja tako da sadrže vrednost IF iskaza odozgo:</p>
<pre>
mysql> update domains
set nullorder = if(expire_date is null, 1, 0);
Query OK, 1 row affected (0.00 sec)
Rows matched: 5  Changed: 1  Warnings: 0

mysql> select * from domains;
+----+-------------------+-------------+-----------+
| id | domain            | expire_date | nullorder |
+----+-------------------+-------------+-----------+
|  1 | dinke.net         | 2010-01-17  |         0 |
|  2 | lampix.net        | 2009-12-26  |         0 |
|  3 | blogodak.com      | 2010-09-08  |         0 |
|  4 | maestrodesert.com | 2009-09-11  |         0 |
|  5 | nepostojeci.com   | NULL        |         1 |
+----+-------------------+-------------+-----------+
5 rows in set (0.00 sec)
</pre>
<p>Sve u svemu naš gornji query iz rešenja problema:</p>
<pre>
select * from domains
order by if(expire_date is null, 1, 0), expire_date asc;
</pre>
<p>Potpuno je isto što i ovaj query:</p>
<pre>
mysql> select * from domains
order by nullorder, expire_date;
+----+-------------------+-------------+-----------+
| id | domain            | expire_date | nullorder |
+----+-------------------+-------------+-----------+
|  4 | maestrodesert.com | 2009-09-11  |         0 |
|  2 | lampix.net        | 2009-12-26  |         0 |
|  1 | dinke.net         | 2010-01-17  |         0 |
|  3 | blogodak.com      | 2010-09-08  |         0 |
|  5 | nepostojeci.com   | NULL        |         1 |
+----+-------------------+-------------+-----------+
5 rows in set (0.00 sec)
</pre>
<p>osim što naravno nullorder polje nismo morali da kreiramo.</p>
<p>Naravno na sličan način možemo dobiti NULL polja na početku u desc prikazu (za slučaj da je to ikome potrebno).</p>
]]></content:encoded>
			<wfw:commentRss>http://www.dinke.net/blog/2009/10/04/mysql-null-polja-i-sortiranje/feed/</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>Client size resizovanje slika</title>
		<link>http://www.dinke.net/blog/2007/03/12/client-size-resizovanje-slika/</link>
		<comments>http://www.dinke.net/blog/2007/03/12/client-size-resizovanje-slika/#comments</comments>
		<pubDate>Mon, 12 Mar 2007 13:06:37 +0000</pubDate>
		<dc:creator>dinke</dc:creator>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[Tutorials]]></category>

		<guid isPermaLink="false">http://www.dinke.net/blog/2007/03/12/client-size-resizovanje-slika/sr/</guid>
		<description><![CDATA[Radeći na servisu Blogodak, nedavno sam se susreo sa problemom kod slika velikih dimenzija koje neki korisnici uključuju u svojim feedovima. Naime, kako Blogodak &#8220;vuče&#8221; feed-ove sa raznih domaćih blogova, a sa njima i slike koje se u njima nalaze, dešavalo se da one dimenzija većih od 560px uredno &#8220;skrljaju&#8221; layout blogotka, obzirom da to [...]]]></description>
			<content:encoded><![CDATA[<p>Radeći na servisu <a href="http://www.blogodak.com">Blogodak</a>, nedavno sam se susreo sa problemom kod slika velikih dimenzija koje neki korisnici uključuju u svojim feedovima. Naime, kako Blogodak &#8220;vuče&#8221; feed-ove sa raznih domaćih blogova, a sa njima i slike koje se u njima nalaze, dešavalo se da one dimenzija većih od 560px uredno &#8220;skrljaju&#8221; layout blogotka, obzirom da to e == &#8220;Microsoft Internet Explorer&#8221;)<br />
    {prevazilazi predviđenu veličinu containera, tako da u najboljem slučaju dolazi do pojave horizonatalnog skrol bara. Kako nemamo nikakav uticaj na slike koje se vuku sa servera gde su hostovane, server side varijante(npr. resize korišćenjem GD liba ili Image Magicka) nisu primenjive, jedino rešenje je da se dimenzije slike smanje direktno u browseru &#8211; Client Side.</p>
<p><strong>CSS Rešenje</strong><br />
Za browsere koji imaju potpunu podršku za css2, dovoljno je staviti nešto tipa:</p>
<pre>
.container img{
  max-width:560px;
}
</pre>
<p>u stil strane, tako da će sve slike koje se nalaze unutar nekog &lt;div<del datetime="2007-03-12T17:11:15+00:00"> id</del>class=&#8221;container&#8221;&gt;&#8230;&lt;/div&gt; elementa biti ograničene na max 560 piksela, tj. biće automatski resizovane na odgovarajuću veličinu. Naravno, ovo ne radi u IE-u (verzije < 7) tako da je potreban hack :)</p>
<p><strong>IE Hack (CSS verzija)</strong></p>
<pre>
.container img{
  max-width:560px;
  /*hack for IE*/
  width: expression(this.width > 560 ? 560: true);
}
</pre>
<p>Ovaj css hack je validan samo u IE-u, koristi se neka vrsta &#8220;ternarnog operatora&#8221;, a sam hack skinut je sa <a href="http://phydeaux3.blogspot.com/2006/01/max-width-and-faking-it-for-ie.html">ovog bloga</a>. Rado bih vam rekao da više informacija potražite tamo, ali i sam autor priznaje da na razume mnogo oko toga &#8220;kako to radi&#8221;, ali jednostavno radi. Za ljubitelje standardnijih rešenja, sledi JS verzija.</p>
<p><strong>IE Hack (JS verzija)</strong></p>
<p>Obzirom da mi se nije svidela ideja da koristim nevalidan css kod koji uz to i ne razumem u potpunosti(na stranu što je pravio i neke nekoegzistentne probleme sa IE-om za koji je i namenjen), odlučio sam se za JavaScript rešenje koje sledi.</p>
<pre>
function fixImages()
{
  //fix images for ie only
  if(navigator.appName == "Microsoft Internet Explorer")
  {
    for(i=0; i&lt;document.images.length; i++)
    {
      //if image is bigger than 560
      if(document.images[i].width &gt; 560)
      {
        imgRatio = document.images[i].width/document.images[i].height;
        document.images[i].width = 560;
        document.images[i].height = 560 / imgRatio;

        //hack neophodan da bi se uklonio skroler zbog prvobitne velicine slike
        //mainContent je ime div kontejnera koji drzi sadrzaj strane
        divid = document.getElementById('mainContent');
        content = divid.innerHTML;
        divid.innerHTML = '';
        divid.innerHTML = content;
      }
    }
  }
}
</pre>
<p>Ova funkcija se poziva nakon učitavanja stane(onLoad). U slučaju da je u pitanju IE, proveravaju se dimenzije svih slika na strani(parsuje se niz document.images), i u slučaju da dimenzije prevazilaze 560 piksela, setuju se na manje, uz očuvanje proporcija slike. Naravno, ovaj metod ima manu, jer je neophodno da se sve slike prvo učitaju, pa tek onda dolazi do resizovanja. Kod IE-a i pored resizovanja slike na &#8220;prihvatljive&#8221; dimenzije, bilo je potrebno nekako mu i staviti do znanja da je došlo do promene dimenzija(samo resizovanje nije dovoljno da nestane horizontalni skroler), tako da je bilo neophodno ručno ili skriptabilno rezisovati i prozor browsera. Srećom posle manjeg &#8220;prčkanja&#8221; sa alternativama prošlo je i jednostavnije rešenje sa setovanjem kontejnera cele strane na prazan string i vraćanjem na prvobitno stanje korišćenjem innerHTML-a. </p>
<p>Nadam se da će ovo nekome biti od koristi :)</p>
]]></content:encoded>
			<wfw:commentRss>http://www.dinke.net/blog/2007/03/12/client-size-resizovanje-slika/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Adsense &#8211; izmena javnih reklama</title>
		<link>http://www.dinke.net/blog/2007/01/30/adsense-izmena-javnih-reklama/</link>
		<comments>http://www.dinke.net/blog/2007/01/30/adsense-izmena-javnih-reklama/#comments</comments>
		<pubDate>Tue, 30 Jan 2007 11:53:43 +0000</pubDate>
		<dc:creator>dinke</dc:creator>
				<category><![CDATA[Software]]></category>
		<category><![CDATA[Tutorials]]></category>

		<guid isPermaLink="false">http://www.dinke.net/blog/2007/01/30/adsense-izmena-javnih-reklama/sr/</guid>
		<description><![CDATA[Kao što verovatno već znate, adsense za sadržaj odnedavno podržava i hrvatski jezik, što je mnoge ovdašnje Webmastere(rekao bih ponovo) zainteresovalo za adsense. Naravno, i pored činjenice da su srpski i hrvatski veoma slični jezici, nakon što na vaš sajt postavite adsense, veoma često bićete u prilici da gledate takozvane &#8220;javne reklame&#8221; (public ads), koje [...]]]></description>
			<content:encoded><![CDATA[<p>Kao što verovatno već znate, <a href="http://www.google.com/adsense/">adsense za sadržaj</a> odnedavno <a href="http://www.personalmag.co.yu/blog/?postid=1204">podržava i hrvatski jezik</a>, što je mnoge ovdašnje Webmastere(rekao bih ponovo) zainteresovalo za adsense. Naravno, i pored činjenice da su srpski i hrvatski veoma slični jezici, nakon što na vaš sajt postavite adsense, veoma često bićete u prilici da gledate takozvane &#8220;javne reklame&#8221; (public ads), koje ne donose nikav prihod, a uz to i njihov izgled često značajno odstupa od boja koje ste odabrali za adsense reklame, što dodatno kvari vizuelni identitet vašeg sajta.</p>
<p>Srećom, gornji problem se može jednostavno rešiti postavljanjem alternativnog url-a za public ads, a što je najlepše od svega, u pitanju je sasvim legalan &#8220;hack&#8221;, dakle google vas neće banovati zbog toga. Sve što treba da uradite je da kreirate posebnu html stranu koja će prikazivati vaše banere, i da google-u stavite do znanja kako da do nje dođe. Pored html strane, možete staviti i link do slike, ali imajte u vidu da ona neće biti linkovana(što imho i nema mnogo smisla), a alternativno možete staviti i boju koja će jednostavno popuniti prostor predviđen za reklamu.</p>
<p>Što se same html strane tiče, tu nema nikakvih trikova, jednostavno kreirajte najobičniju html stranu koja sadrži baner (slika ili flash) koji se opciono može dinamički generisati iz php skripta. Jedina bitna stvar je da se u stilu za stranu setuju margine i padding na 0, i da naravno baner bude identičan dimezijama reklama koju menja. Na primer za reklamu dimenzija 468&#215;60 trebalo da i dimenzije banera budu identične. Takođe, poželjno je da urlovi na objekte koje linkujete budu absolutni.</p>
<p>Evo kako to recimo izgleda na servisu <a href="http://www.blogodak.com">blogodak</a> za prikaz flash banera <a href="http://humanost.org">humanost.org</a>:</p>
<pre>
&lt;html&gt;
&lt;head&gt;
	&lt;title&gt;Banner&lt;/title&gt;

	&lt;style type="text/css"&gt;
		body
		{
			margin:0px;
			padding:0px;
			background:#ffffff;
		}
	&lt;/style&gt;

&lt;/head&gt;
&lt;body&gt;
 &lt;object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=7,0,19,0" width="468" height="60"&gt;
    &lt;param name="movie" value="http://www.blogodak.com/images/banners/humanost468.swf" /&gt;
    &lt;param name="quality" value="high" /&gt;

    &lt;embed src="http://www.blogodak.com/images/banners/humanost468.swf" quality="high" pluginspage="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash" width="468" height="60"&gt;&lt;/embed&gt;
  &lt;/object&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre>
<p>Naravno, stranu možete generisati potpuno dinamički, rotirati banere, pratiti statistiku itd. Nakon što postavite vašu stranu na server, neophodno je da google-u stavite do znanja kako da do nje dođe. To možete učiniti tako što ćete proći kroz adsense setup, a zatim u delu sa dodatnim opcijama unesete url do vaše strane.</p>
<p><img id="image205" src="http://www.dinke.net/blog/wp-content/uploads/2007/01/adsense.gif" alt="adsense.gif" /><br />
Adsense Setup &#8211; Setovanje alternativnog url-a za public ads</p>
<p>Alternativno, možete u već generisani adsense kod, odmah ispod google_ad_client linije dodati google_alternate_ad_url liniju, kao na primer:</p>
<pre>
google_ad_client = "pub-0981372496796058";
google_alternate_ad_url = "http://www.blogodak.com/misc/display_banner.php";
google_ad_width = 468;
...
</pre>
<p>Dodatne informacije o celoj proceduri možete naći na google adsense help stranama, <a href="https://www.google.com/adsense/alternateads">ovde</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.dinke.net/blog/2007/01/30/adsense-izmena-javnih-reklama/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Uvod u GeoIP</title>
		<link>http://www.dinke.net/blog/2006/11/27/uvod-u-geoip/</link>
		<comments>http://www.dinke.net/blog/2006/11/27/uvod-u-geoip/#comments</comments>
		<pubDate>Mon, 27 Nov 2006 15:50:05 +0000</pubDate>
		<dc:creator>dinke</dc:creator>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[Tutorials]]></category>

		<guid isPermaLink="false">http://www.dinke.net/blog/2006/11/27/uvod-u-geoip/sr/</guid>
		<description><![CDATA[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 [...]]]></description>
			<content:encoded><![CDATA[<p>Verovatno ste već bili u prilici da koristite <a href="https://www.google.com/analytics/">Google Analytics</a> 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 &#8220;lociranja&#8221; korisnika opšte je poznata pod nazivom GeoIP.</p>
<p>Danas ćemo pričati o tome kako &#8220;locirati&#8221; posetioca pomoću PHP-a i <a href="http://www.maxmind.com/app/ip-location">Max Mind-ove</a> 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.</p>
<p>MaxMind obezbeđuje API za nekoliko popularnih programskih jezika, (kompletna lista dostupna je <a href="http://www.maxmind.com/app/api">ovde</a>), a detalji o PHP API-u dostupni su <a href="http://www.maxmind.com/app/php">ovde</a>. Pored takozvanog &#8220;Pure PHP API-a&#8221; koji ćemo ovde koristiti, postoje i PECL ektstenzija kao i apache modul(mod_geoip), koji pružaju bolje perfomanse ali i komplikovaniji setup. </p>
<p>Za početak neophodno je da skinete sve fajlove koji se nalaze na <a href="http://www.maxmind.com/download/geoip/api/php/">http://www.maxmind.com/download/geoip/api/php/</a> i snimite ih negde unutar vašeg Web stabla(recimo /htdocs/geoip). Za korišćenje GeoIP Country treba skinuti lite bazu <a href="http://www.maxmind.com/download/geoip/database/GeoIP.dat.gz ">odavde</a>, a za city GeoLiteCity bazu <a href="http://www.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz">odavde</a>. Radi jednostavnosti korišćenja, obe baze ćemo takođe raspakovati u isti direktorijum gde smo i snimili fajlove iz PHP API-a (/htdocs/geoip).</p>
<p><strong>GeoIP Country</strong><br />
&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;</p>
<p>Idemo sa primerom detekcije zemlje posetioca:</p>
<pre>
&lt;?php
/**
 * Primer Koriscenja GeoIP Country Baze
 *
 * @version $Id$
 * @package geoip
 * @copyright &copy; 2006 Lampix.net
 * @author Dragan Dinic &lt;dinke@lampix.net&gt;
 */

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 &lt;br /&gt;";
	echo "Skracena Oznaka: $country_code &lt;br /&gt;";
}
else
{
	echo "Nazalost, nismo bili u mogucnosti da vas lociramo.";
}

geoip_close($gi);
?&gt;
</pre>
<p>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']). </p>
<p>Kao izlaz skripta, trebalo bi da dobijemo nešto poput:</p>
<pre>
Zemlja iz koje nas posecujete je: Serbia and Montenegro
Skracena Oznaka: CS
</pre>
<p>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.</p>
<p><strong>GeoIP City</strong><br />
&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-</p>
<p>A sada da proširimo podatke o zemlji sa tačnom lokacijom (grad, poštanski kod itd).</p>
<pre>
&lt;?php
/**
 * Primer Koriscenja GeoIP City Baze
 *
 * @version $Id$
 * @package geoip
 * @copyright &copy; 2006 Lampix.net
 * @author Dragan Dinic &lt;dinke@lampix.net&gt;
 */

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 . "&lt;br /&gt;";
	echo "Skracena Oznaka: " . $record->country_code . "&lt;br /&gt;";
	echo "Skracena Oznaka2: " . $record->country_code3 . "&lt;br /&gt;";
	echo "Region: " .$record->region . "&lt;br /&gt;";
	echo "Grad: " .$record->city . "&lt;br /&gt;";
	echo "Postanski Kod: " .$record->postal_code . "&lt;br /&gt;";
	echo "Geog. Sirina: " .$record->latitude . "&lt;br /&gt;";
	echo "Geog. Duzina: " .$record->longitude . "&lt;br /&gt;";
}

geoip_close($gi);
?&gt;
</pre>
<p>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 &#8216;geoiprecord&#8217; koja sadrži kao promenljive(osobine) podatke o lokaciji koje koristimo u gornjem kodu. Nakon pokretanja skripta trebalo bi da dobijemo nešto poput:</p>
<pre>
Zemlja: Serbia and Montenegro
Skracena Oznaka: CS
Skracena Oznaka2: SCG
Region: 02
Grad: Beograd
Postanski Kod:
Geog. Sirina: 44.8186
Geog. Duzina: 20.4681
</pre>
<p>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 &#8220;egzotike&#8221; u koju nažalost spada i Srbija.</p>
<p><strong>CaseStudy &#8211; Redirekcija na osnovu IP adrese</strong><br />
&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br />
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:</p>
<pre>
&lt;?php
/**
 * Case Study - Redirekcija na osnovu lokacije
 *
 * @version $Id$
 * @package geoip
 * @copyright &copy; 2006 Lampix.net
 * @author Dragan Dinic &lt;dinke@lampix.net&gt;
 */

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/');
}
?&gt;
</pre>
<p>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.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.dinke.net/blog/2006/11/27/uvod-u-geoip/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Hacking Webalizer</title>
		<link>http://www.dinke.net/blog/2006/09/20/hacking-webalizer-2/</link>
		<comments>http://www.dinke.net/blog/2006/09/20/hacking-webalizer-2/#comments</comments>
		<pubDate>Wed, 20 Sep 2006 09:14:01 +0000</pubDate>
		<dc:creator>dinke</dc:creator>
				<category><![CDATA[Software]]></category>
		<category><![CDATA[Tutorials]]></category>

		<guid isPermaLink="false">http://www.dinke.net/blog/2006/09/20/hacking-webalizer-2/</guid>
		<description><![CDATA[Few weeks ago I found a way to install Webalizer on DreamHost. Webalizer is a little bit of an obsolete utility (comparing to Google Analytics Webalizer stats look rude), but still has some advantages. One of the biggest is that it is still used by many hosting solutions, so you don&#8217;t have to change all [...]]]></description>
			<content:encoded><![CDATA[<p>Few weeks ago I found a way to <a href="http://wiki.dreamhost.com/index.php/Webalizer">install Webalizer</a> on DreamHost. Webalizer is a little bit of an obsolete utility (comparing to <a href="http://www.google.com/analytics/">Google Analytics</a> Webalizer stats look rude), but still has some advantages. One of the biggest is that it is still used by many hosting solutions, so you don&#8217;t have to change all of your site(blog) pages just to make sure Google will track your stats.<br />
<span id="more-145"></span><br />
<a href="http://www.mrunix.net/webalizer/">Webalizer</a> is *nix based command line utility which generate html stats pages by parsing log files. Although it is able to parse many types of log files, in practice it is mostly used for parsing Apache Web logs. Usually it is set to run by cron job, by setting it to run soon after log rotate.</p>
<p>Like any other *nix command line utility, you can pass dozens of arguments to webalizer (full argument list is available in manual which can be accessed with <em>man webalizer</em>). However, most of options can be set in webalizer.conf file, which is usually located in /etc/webalizer.conf.</p>
<p>Webalizer will probably work even if you don&#8217;t change anything in webalizer.conf file, but generated statistics will not be quite accurate. For example, big number of visits/hits on your site is actually generated by bots (msn, google, yahoo &#8230;) which is not something you would like to count in your stats. Also, by default referrers includes your site pages, which means in &#8220;Top 10 Referrers&#8221; only pages from your site will be listed. So, lets hack webalizer.conf in order to make stats more usefull to us :)</p>
<p>First off, lets turn off your own site from referrers, by adding it to <em>HideReferrer</em>. For example:</p>
<p><em>HideReferrer    dinke.net/</em></p>
<p>Now, lets ignore spiders, bots etc:<br />
<em><br />
# with option IgnoreSite we ignore well known bots sites<br />
IgnoreSite      msnbot.msn.com</p>
<p># with IgnoreAgent we ignore based on useragent<br />
IgnoreAgent     msnbot<br />
IgnoreAgent     Googlebot<br />
IgnoreAgent     lmspider<br />
IgnoreAgent     Yahoo<br />
IgnoreAgent     ZyBorg<br />
IgnoreAgent     Jeeves/Teoma<br />
</em></p>
<p>Bear in mind that during setting above options for let say &#8220;www.yourmama.com&#8221;, all strings like &#8220;your&#8221;, &#8220;*mama.com&#8221; and &#8220;www.your*&#8221; will match.</p>
<p>In order to hide visits to particular part of your site, you should use IgnoreURL option. For example, on my own Word Press blog I don&#8217;t want to count any access to /wp-admin/ part so I added something like:<br />
<em><br />
# ignore visits to particular part of the site<br />
IgnoreURL       /wp-admin/*</em></p>
<p>And last but not least, user agent list. In order to group your browsers by user agent (MSIE, Mozilla) you could use following code:<br />
<em><br />
GroupAgent     MSIE           Internet Exploder<br />
HideAgent      MSIE<br />
GroupAgent     Mozilla        Mozilla Based<br />
HideAgent      Mozilla<br />
</em><br />
GroupAgent will group the same useragents, so you would have something like &#8220;MSIE &#8211; 156 hits&#8221; if you put MSIE on list. If you don&#8217;t add HideAgent option, all agents with string &#8220;MSIE&#8221; in it will be listed. I really don&#8217;t know why, but nothing except MSIE and Mozilla didn&#8217;t work for me (like opera, gecko etc) so this option turned out to be useless.</p>
<p>And this is it. With only few &#8220;tweaks&#8221; your stats became much more accurate, but with one drawback. Now when you turned off bots etc. number of visits/hits might be much lower than before :)</p>
]]></content:encoded>
			<wfw:commentRss>http://www.dinke.net/blog/2006/09/20/hacking-webalizer-2/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>MySQL Full-Text Searches</title>
		<link>http://www.dinke.net/blog/2006/01/20/mysql-full-text-searches/</link>
		<comments>http://www.dinke.net/blog/2006/01/20/mysql-full-text-searches/#comments</comments>
		<pubDate>Sat, 21 Jan 2006 06:08:04 +0000</pubDate>
		<dc:creator>dinke</dc:creator>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[Tutorials]]></category>

		<guid isPermaLink="false">http://www.dinke.net/blog/?p=67</guid>
		<description><![CDATA[Danas vam predstavljam treci, ujedno i poslednji zapis iz serije tutorijala o &#8220;Mogucnostima MySQL-a koje developeri retko koriste&#8221;.
U prethodna dva pricali smo transakcijama i referencijalnom integritetu &#8211; mogucnostima koje su dostupne samo u InnoDB i BDB tabelama. Danas vam predstavljam &#8220;Full-Text Search&#8221;, koji je za razliku od gore opisanih feature-a dostupan samo u MyISAM tabelama [...]]]></description>
			<content:encoded><![CDATA[<p>Danas vam predstavljam treci, ujedno i poslednji zapis iz serije tutorijala o &#8220;Mogucnostima MySQL-a koje developeri retko koriste&#8221;.</p>
<p>U prethodna dva pricali smo <a href="http://www.dinke.net/blog/archives/2006/01/mysql_i_transak.html"><strong>transakcijama</strong></a> i <a href="http://www.dinke.net/blog/archives/2006/01/mysqlspoljni_kl.html"><strong>referencijalnom integritetu</strong></a> &#8211; mogucnostima koje su dostupne samo u InnoDB i BDB tabelama. Danas vam predstavljam &#8220;Full-Text Search&#8221;, koji je za razliku od gore opisanih feature-a dostupan samo u MyISAM tabelama (što je MySQL-ov default).</p>
<p>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:</p>
<pre>
select * from moja_tabela
where textpolje like '%text_iz_search_polja%'
</pre>
<p>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š.</p>
<p><span id="more-67"></span><br />
Query koji smo predstavili gore ima jednu veliku manu &#8211; nije u mogucnosti da koristi indekse! Zahvaljujuci cinjenici da search pattern pocinje sa džokerom &#8216;%&#8217;, upit ne može koristiti indekse, cak ni onda kada su oni definisani za polja koje pretražujete! U praksi, ovo znaci znatno sporije vreme izvršavanja upita (koji mora proci kroz sve slogove u tabeli jer ne koristi indekse), a ako na to dodate sve veci broj sadržaja na sajtu i nekoliko korisnika koji mogu istovremenu vršiti pretragu &#8211; imate ozbiljan problem. Srecom rešenje je tu na dohvat ruke i zove se <strong>Full-Text Search</strong>.</p>
<p>Full-Text Search omogucava vam efikasnije pretraživanje unutar text polja (CHAR, VARCHAR, TEXT), korišcenjem takozvanih Full-Text Indeksa.</p>
<p>Postoje tri vrste Full-Text Search-a:<br />
- <strong>Natural language search</strong><br />
String koji tražite je podeljen u reci, tako da kao rezulutat dobijate one slogove koji sadrže date reci.<br />
- <strong>Bolean mode seach</strong><br />
String koji tražite je podeljen u reci, ali svakoj reci dodaje se logicki operator koji omogucava pretragu onih reci koje su prisutne ili pak onih koje ne postoje u traženom tekstu<br />
-<strong> Expansion Search</strong><br />
Pretraga u dve faze. Prva faza je identicna &#8220;natural language search-u&#8221;. U drugoj fazi obavlja se pretraga tako što se prvobitni string spaja sa najrelevantnijim slogovima dobijenim kao rezultat prve faze.</p>
<p>Prilikom pretrage ignorišu se &#8220;uobicajene reci&#8221;, tj. one koje postoje u minimum 50% slogova. Takode, ignorišu se takozvane &#8220;stopwords&#8221; reci kao što su &#8220;the&#8221;, &#8220;and&#8221; i sl. kao i reci krace od 4 karaktera. Spisak svih ovih reci možete naci <a href="http://dev.mysql.com/doc/refman/4.1/en/fulltext-stopwords.html">u odgovarajucoj sekciji MySQL manuala</a> uz napomenu da ih možete promeniti (recimo prilagodavanjem srpskom jeziku) tako što cete promeniti putanju do fajla sa ignorisanim recima. Upustvo za to možete naci <a href="http://dev.mysql.com/doc/refman/4.1/en/fulltext-fine-tuning.html">ovde</a>.</p>
<p>A sada da vidimo nekoliko primera. Koristicu se bazom najpoznatijih pogrešnih predvidanja, koju sam kreirao specijalno za ovu priliku. (inspiraciju sam pronašao na ES-u &#8211; http://www.elitesecurity.org/tema/156409-Pogresna-predvidjanja)</p>
<p>Za pocetak odradite copy/paste teksta koji sledi i snimite ga na vaš kompjuter kao fajl wrong_prediction.txt.</p>
<pre>Albert Einstein, 1974|There is not the slightest indication that nuclear energy will ever be obtainable. It would mean that the atom would have to be shattered at will
Margaret Thatcher, 1974|It will be years - not in my time - before a woman will become a Prime Minister.
Alexander Graham Bell, c.1880.|One day there will be a telephone in every major city in the USA.
Popular Mechanics, 1949|Computers in the future may weigh no more than 1.5 tons.
Thomas Watson, IBM Computers,1943|I think there is a world market for as many as 5 computers.
Marshal Foch, France, 1912|Aircraft are interesting toys, but of no military value.</pre>
<p>Zatim cemo kreirati odgovarajucu tabelu, importovati podatke i na kraju dodati text indekse. Startujte MySQL klijent iz istog direktorijuma gde ste snimili gornji fajl, a zatim otkucaje sledece:</p>
<pre>create table wrong_predictions(
who varchar(100) not null,
prediction text not null
) engine = MyISAM;

load data local infile 'wrong_predictions.txt' into table wrong_predictions
fields terminated by '|' lines terminated by 'rn';

ALTER TABLE wrong_predictions
ADD FULLTEXT (who),
ADD FULLTEXT (prediction),
ADD FULLTEXT (who, prediction);</pre>
<p>Kao što vidite, prvo smo importovali sve podatke u tabelu, a tek onda kreirali indekse. Razlog zašto smo to baš tako uradili su perfomanse &#8211; insert je uvek znacajno sporiji ako tabela vec ima kreirane indekse.</p>
<p>A sada nekoliko primera &#8220;Natural Search&#8221;-a:</p>
<pre>mysql&gt; SELECT * FROM wrong_predictions WHERE MATCH(who) AGAINST('Thatcher');
+-------------------------+---------------------------------------------------------------------------------+
| who                     | prediction                                                                      |
+-------------------------+---------------------------------------------------------------------------------+
| Margaret Thatcher, 1974 | It will be years - not in my time - before a womanill become a Prime Minister.  |
+-------------------------+---------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql&gt;  SELECT * FROM wrong_predictions WHERE MATCH(who,prediction) AGAINST('computers');
+-------------------------+-------------------------------------------------------------+
| who                     | prediction                                                  |
+-------------------------+-------------------------------------------------------------+
| Thomas Watson, IBM,1943 | I think there is a world market for as many as 5 computers. |
| Popular Mechanics, 1949 | Computers in the future may weigh no more than 1.5 tons.    |
+-------------------------+-------------------------------------------------------------+
2 rows in set (0.00 sec)

mysql&gt;</pre>
<p>Kada koristimo MATCH u where klauzi, redosled kolona je odreden relevantnošcu (eng. relevance) pronadenih slogova. To možemo videti na sledecem primeru:</p>
<pre>mysql&gt; SELECT prediction, MATCH(prediction) AGAINST('computers') AS relevance FROM wrong_predictions;
+---------------------------------------------------------------------------------------------------------------------------------------------------+------------------+
| prediction                                                                                                                                        | relevance        |
+---------------------------------------------------------------------------------------------------------------------------------------------------+------------------+
| There is not the slightest indication that nuclear energy will ever be obtainable. It would mean that the atom would have to be shattered at will | 0                |
| It will be years - not in my time - before a woman will become a Prime Minister.                                                                  | 0                |
| One day there will be a telephone in every major city in the USA.                                                                                 | 0                |
| Computers in the future may weigh no more than 1.5 tons.                                                                                          | 0.66266459031789 |
| I think there is a world market for as many as 5 computers.                                                                                       | 0.67003110026735 |
| Aircraft are interesting toys, but of no military value.                                                                                          | 0                |
+---------------------------------------------------------------------------------------------------------------------------------------------------+------------------+
6 rows in set (0.00 sec)</pre>
<p>Boolean mode omogucava pretragu slicnu nekim pretraživacima na Internetu u smislu da je moguce zadati reci koje moraju postojati (+rec) kao i reci koje ne smeju postojati (-rec). Na primer:</p>
<pre>mysql&gt; SELECT * FROM wrong_predictions WHERE MATCH(who,prediction)
-&gt; AGAINST('+computers -popular' IN BOOLEAN MODE);
+-------------------------+-------------------------------------------------------------+
| who                     | prediction                                                  |
+-------------------------+-------------------------------------------------------------+
| Thomas Watson, IBM,1943 | I think there is a world market for as many as 5 computers. |
+-------------------------+-------------------------------------------------------------+
1 row in set (0.00 sec)</pre>
<p>Pored toga, mogu se koristiti džokeri kao na primer:</p>
<pre>mysql&gt; SELECT * FROM wrong_predictions WHERE MATCH(who,prediction)
-&gt; AGAINST('comp*' IN BOOLEAN MODE);
+-------------------------+-------------------------------------------------------------+
| who                     | prediction                                                  |
+-------------------------+-------------------------------------------------------------+
| Popular Mechanics, 1949 | Computers in the future may weigh no more than 1.5 tons.    |
| Thomas Watson, IBM,1943 | I think there is a world market for as many as 5 computers. |
+-------------------------+-------------------------------------------------------------+
2 rows in set (0.00 sec)</pre>
<p>Za pun opis mogucnosti BOOLEAN MODE -a posetite <a href="http://dev.mysql.com/doc/refman/4.1/en/fulltext-boolean.html">odgovarajucu sekciju MySQL manuala</a>.</p>
<p>I za kraj primer &#8220;Expansion Search&#8221;-a:</p>
<pre>mysql&gt; SELECT * FROM wrong_predictions WHERE MATCH(who,prediction)
-&gt; AGAINST('market');
+-------------------------+-------------------------------------------------------------+
| who                     | prediction                                                  |
+-------------------------+-------------------------------------------------------------+
| Thomas Watson, IBM,1943 | I think there is a world market for as many as 5 computers. |
+-------------------------+-------------------------------------------------------------+
1 row in set (0.00 sec)

mysql&gt; SELECT * FROM wrong_predictions WHERE MATCH(who,prediction)
-&gt; AGAINST('market' WITH QUERY EXPANSION);
+-------------------------+-------------------------------------------------------------+
| who                     | prediction                                                  |
+-------------------------+-------------------------------------------------------------+
| Thomas Watson, IBM,1943 | I think there is a world market for as many as 5 computers. |
| Popular Mechanics, 1949 | Computers in the future may weigh no more than 1.5 tons.    |
+-------------------------+-------------------------------------------------------------+
2 rows in set (0.00 sec)</pre>
<p>Kod prvog upita tražili smo rec market obicnim (natural) searchom. Kod drugog upita koristili smo &#8220;QUERY EXPANSION&#8221; koji prvobitni string spaja sa najrelevantnijim slogovima dobijenim kao rezultat prve faze (natural searcha) cime smo dobili još jedan slog. Više o Expansion Searchu možete pronaci u <a href="http://dev.mysql.com/doc/refman/4.1/en/fulltext-query-expansion.html">MySQL Manualu</a>.</p>
<p>Eto toliko o FullText Searchu. Nadam se da ce primeri koje sam kreirao biti od pomoci da vas zainteresuju za njegovo korišcenje. Kao i uvek, za više informacija možete konsultovati MySQL manual.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.dinke.net/blog/2006/01/20/mysql-full-text-searches/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>MySQL::Spoljni Ključevi i Referencijalni Integritet</title>
		<link>http://www.dinke.net/blog/2006/01/15/mysqlspoljni-kljucevi-i-referencijalni-integritet/</link>
		<comments>http://www.dinke.net/blog/2006/01/15/mysqlspoljni-kljucevi-i-referencijalni-integritet/#comments</comments>
		<pubDate>Mon, 16 Jan 2006 05:59:20 +0000</pubDate>
		<dc:creator>dinke</dc:creator>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[Tutorials]]></category>

		<guid isPermaLink="false">http://www.dinke.net/blog/?p=66</guid>
		<description><![CDATA[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 &#8220;Spoljnih Ključeva i Referencijalnog Integriteta&#8221;.
Kao i kod transakcija, podrška za referencijalni integritet nije dostupna u standardnim MyISAM tabelama, već se moraju koristiti InnoDB tabele. Malo developera [...]]]></description>
			<content:encoded><![CDATA[<p>Danas nastavljamo sa započetom serijom tutorijala o retko korišćenim mogućnostima MySQL-a. U prošlom <a href="http://www.dinke.net/blog/archives/2006/01/mysql_i_transak.html">zapisu</a> detaljno sam predstavio kako se u MySQL-u koriste transakcije. Danas ćemo se usresrediti na korišćenje &#8220;Spoljnih Ključeva i Referencijalnog Integriteta&#8221;.</p>
<p>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.</p>
<p><span id="more-66"></span><br />
U najkraćem, pod spoljnim ključem podrazumevamo polje u jednoj tabeli koje je u direktnoj vezi sa poljem (primarnim ključem) u drugoj tabeli. Pravilo &#8220;Referencijalnog Integriteta&#8221; kaže da spoljni ključ <strong>ne sme</strong> imati vrednost koja ne postoji u tabeli na koju referencira. Bez namere da mnogo teoretišem ovde (koga interesuje može da googla za više informacija), evo odmah i konkretnog primera.</p>
<p>Zamislite bazu mobilnih telefona, koja između ostalog sadrži tabelu sa proizvođačima i modelima telefona. Recimo nešto ovako:</p>
<pre>
Proizvodjaci
---------------------
ID     Proizvodjac
1.     Nokia
2.     Sony-Ericson
3.     Samsung
4.     Siemens

Telefoni
-------------------------------
ID.     Model     PID
1.      6600       1
2.      6630       1
3.      3650       1
4.      p800       2
5.      p900       2
...</pre>
<p>
PID kolona u tabeli &#8216;Telefoni&#8217; je u relaciji sa ID kolonom u tabeli &#8216;Proizvodjaci&#8217;, te kolona PID predstavlja tzv. &#8220;spoljni ključ&#8221; (foreign key). PID vrednosti u našem primeru odnose se na proizvođače smart phone-ova Nokia (6600, 6630 i 3650) kao i Sony-Ericson (p800, p900). <br />
Da bi se održao takozvani &#8220;referencijalni integritet&#8221; baze, moramo voditi računa da vrednosti PID kolone tabele <em>Telefoni</em> sadrže samo one vrednosti koje postoje u ID koloni tabele <em>Proizvodjaci</em>. U slučaju brisanja proizvođača u prvoj tabeli, svi slogovi koji referenciraju na njih u tabeli telefona moraju takođe biti obrisani (tzv. <em>cascade delete</em>) ili setovani na NULL. Slično važi i za update i insert tj. ako update-ujemo ID u prvoj tabeli, spoljni ključ PID mora takođe biti updejtovan. Isto tako, prilikom inserta vrednosti u drugu tabelu moramo voditi računa da PID u drugoj već ima odgovarajući ID u prvoj tabeli. Ovo su stvari o kojima morate sami da brinete ako podrška za referencijalni integrtitet nije uključena u vaš RDBMS. Srećom, korišćenjem InnoDB tabela, brigu o njegovom održavanju prepuštamo samom MySQL-u.</p>
<p>Spoljni ključ se zadaje prilikom kreiranja same tabele. Sintaksa izgleda ovako:</p>
<pre>
[CONSTRAINT symbol]
FOREIGN KEY [id] (index_col_name, ...)
REFERENCES tbl_name (index_col_name, ...)
[ON DELETE {RESTRICT | CASCADE | SET NULL | NO ACTION}]
[ON UPDATE {RESTRICT | CASCADE | SET NULL | NO ACTION}]
</pre>
<p>- <strong>CONSTRAINT</strong> symbol je opciono ime za ograničenje (constraint) za ovaj spoljni ključ. <br />
- <strong>FOREIGN KEY</strong> označava kolonu koja predstavlja spoljni ključ<br />
- <strong>REFERENCES tbl_name (index_col_name, &#8230;)</strong> je tabela i tačna kolona na koju spoljni ključ referencira<br />
ON DELETE i ON UPDATE  definišu akcije koje će biti preduzete od strane baze u slučaju da se obriše/updatuje neki slog u glavnoj tabeli. Moguće vrednosti su:<br />
- <strong>RESTRICT</strong> brisanje slogova u glavnoj tabeli će biti odbijeno <strong>ako postoje</strong> spoljni ključevi koji refereniraju na njih<br />
- <strong>CASCADE</strong> brisanje slogova u glavnoj tabeli će rezultovati brisanjem slogova koji referenciraju na njih u tabli sa spoljnim ključem<br />
- <strong>SET NULL</strong> slično <strong>CASCADE</strong>-u osim što slogovi u child tabeli neće biti obrisani već će vrednost spoljnih ključeva biti setovana na NULL</p>
<p>Da bih demonstrirao ceo koncept, kreiraću bazu mobilnih telefona koju sam pominjao gore.</p>
<pre>create table proizvodjaci(
	id int unsigned primary key not null auto_increment,
	proizvodjac varchar(255) not null unique
	) engine = InnoDB;

create table telefoni(
	id int unsigned primary key not null auto_increment,
	model varchar(255) not null,
	pid int unsigned not null,
	foreign key(pid) references proizvodjaci(id)
		on delete cascade
		on update cascade
	) engine = InnoDB;

insert into proizvodjaci(proizvodjac) values ('Nokia'),('Sony-Ericson'),('Samsung'),('Siemens');
insert into telefoni (model,pid) values ('6600',1),('6630',1),('3650',1),('p800',2),('p900',2);
</pre>
<p>U primeru gore koristili smo opciju <strong>cascade</strong>, što znači da će brisanje slogova u tabeli <em>proizvodjači</em> rezultovati brisanjem slogova koji referenciraju na njih u tabeli <em>telefoni</em>. A sada, hajde da probamo da &#8220;prevarimo&#8221; MySQL tako što ćemo ubaciti telefon sa nepostojećim ID-jem proizvođača:</p>
<pre>mysql&gt; select * from proizvodjaci;
+----+--------------+
| id | proizvodjac  |
+----+--------------+
|  1 | Nokia        |
|  3 | Samsung      |
|  4 | Siemens      |
|  2 | Sony-Ericson |
+----+--------------+
4 rows in set (0.00 sec)

mysql&gt; select * from telefoni;
+----+-------+-----+
| id | model | pid |
+----+-------+-----+
|  1 | 6600  |   1 |
|  2 | 6630  |   1 |
|  3 | 3650  |   1 |
|  4 | p800  |   2 |
|  5 | p900  |   2 |
+----+-------+-----+
5 rows in set (0.02 sec)

mysql&gt; insert into telefoni(model,pid) values ('nepostoji',5);
ERROR 1216 (23000): Cannot add or update a child row: a foreign key constraint fails
mysql&gt;</pre>
<p>Kao što vidite, nije nam uspelo. Pokušali smo da unesemo novi model telefona sa proizvođačem čiji ID je 5 koji ne postoji u tabeli proizvodjaci i MySQL nam to nije dozvolio, jer bi time narušili referencijalni integritet baze. </p>
<p>A sada da vidimo kako funkcioniše <strong>cascade delete/update</strong> .</p>
<pre>
mysql&gt; update proizvodjaci set id=5 where proizvodjac='sony-ericson';
Query OK, 1 row affected (0.05 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql&gt; select * from proizvodjaci;
+----+--------------+
| id | proizvodjac  |
+----+--------------+
|  1 | Nokia        |
|  3 | Samsung      |
|  4 | Siemens      |
|  5 | Sony-Ericson |
+----+--------------+
4 rows in set (0.00 sec)

mysql&gt; select * from telefoni;
+----+-------+-----+
| id | model | pid |
+----+-------+-----+
|  1 | 6600  |   1 |
|  2 | 6630  |   1 |
|  3 | 3650  |   1 |
|  4 | p800  |   5 |
|  5 | p900  |   5 |
+----+-------+-----+
5 rows in set (0.00 sec)
mysql&gt;
</pre>
<p>Dakle, update-ovali smo ID proizvođača sony-ericsson telefona na vrednost 5. Zahvaljujući setovanoj opciji <strong>on update cascade</strong> vrednosti pid polja su updateovane i u tabeli telefoni. Slično važi i prilikom brisanja. Ako obrišemo proizvođača Nokia telefona evo šta će se desiti:</p>
<pre>
mysql&gt; select * from proizvodjaci;
+----+--------------+
| id | proizvodjac  |
+----+--------------+
|  1 | Nokia        |
|  3 | Samsung      |
|  4 | Siemens      |
|  5 | Sony-Ericson |
+----+--------------+
4 rows in set (0.00 sec)

mysql&gt; select * from telefoni;
+----+-------+-----+
| id | model | pid |
+----+-------+-----+
|  1 | 6600  |   1 |
|  2 | 6630  |   1 |
|  3 | 3650  |   1 |
|  4 | p800  |   5 |
|  5 | p900  |   5 |
+----+-------+-----+
5 rows in set (0.00 sec)

mysql&gt; delete from proizvodjaci where proizvodjac='Nokia';
Query OK, 1 row affected (0.03 sec)

mysql&gt; select * from proizvodjaci;
+----+--------------+
| id | proizvodjac  |
+----+--------------+
|  3 | Samsung      |
|  4 | Siemens      |
|  5 | Sony-Ericson |
+----+--------------+
3 rows in set (0.00 sec)

mysql&gt; select * from telefoni;
+----+-------+-----+
| id | model | pid |
+----+-------+-----+
|  4 | p800  |   5 |
|  5 | p900  |   5 |
+----+-------+-----+
2 rows in set (0.00 sec)
mysql&gt;
</pre>
<p>Kao što vidite modeli marke Nokia su nestali i iz tabele telefoni.</p>
<p>Toliko za ovaj tutorijal. Smatram da je sasvim dovoljan kao uvod, a koga interesuje više, kao i uvek sve potrebne informacije možete naći u <a href="http://dev.mysql.com/doc/refman/4.1/en/innodb-foreign-key-constraints.html">odgovarajućoj sekciji MySQL manuala.</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.dinke.net/blog/2006/01/15/mysqlspoljni-kljucevi-i-referencijalni-integritet/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>MySQL i Transakcije</title>
		<link>http://www.dinke.net/blog/2006/01/10/mysql-i-transakcije/</link>
		<comments>http://www.dinke.net/blog/2006/01/10/mysql-i-transakcije/#comments</comments>
		<pubDate>Tue, 10 Jan 2006 20:20:03 +0000</pubDate>
		<dc:creator>dinke</dc:creator>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[Tutorials]]></category>

		<guid isPermaLink="false">http://www.dinke.net/blog/?p=65</guid>
		<description><![CDATA[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 [...]]]></description>
			<content:encoded><![CDATA[<p>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 &#8220;ne podržava&#8221; 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.</p>
<p>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.</p>
<p>Danas krećemo sa opisom transakcija.</p>
<p><span id="more-65"></span><br />
Transakcija SQL rečnikom označava skup upita koji se izvršavaju kao celina. Ako su svi upiti uspešno izvršeni onda je transakcija prošla, u suprotnom vraćamo se u pređašnje stanje (rollback). Obzirom da većina ljudi sam pojam transakcija vezuje za novac, iskoristiću upravo jedan takav primer kako bi cela stvar bila još jasnija.</p>
<p>Zamislite transakciju gde Pera svom klijentu Mikici treba da isplati 1000 dinara za neku učinjenu uslugu. Evo kako bi tipičnim SQL rečnikom to mogli da predstavimo:</p>
<pre>
update bank_accounts set amount = amount - 1000
where user = 'pera';
update bank_accounts set amount = amount + 1000
where user = 'mikica';
</pre>
<p>
Šta ako se jedan od gornja dva upita ne izvrši korektno ? Imali bi situaciju da Pera ostane kratak za 1000 dinara bez da je uplatio novac Mikici, ili da Mikica dobije svoj novac od same banke u slučaju da prvi upit nije izvršen. Korišćenjem transakcija u slučaju da dođe do greške sistem se vraća na stanje pre startovanja transakcije, tako da će u ovom konkretnom slučaju biti izvršena oba upita ili nijedan od njih.</p>
<p>Transakcione baze tipično moraju obezbediti 4 osobine koje se skraćeno nazivaju <strong>ACID</strong>:<br />
<strong>Atomicity</strong><br />
Iskaz se sastoji od nekoliko logičnih celina &#8211; skupa upita. Ili su svi upiti izvršeni uspešno ili nijedan od njih.<br />
<strong>Consistency</strong><br />
Baza je u koezistentnom stanju pre i posle transakcije.<br />
<strong>Isolation</strong><br />
Transakcija nema efekat na druge procese. To znači da efekti pojedinačnih upita u transakciji nisu vidljivi drugim klijentima sve dok se transakcija uspešno ne izvrši (commit iskazom).<br />
<strong>Durability</strong><br />
Kada je transakcija uspešno obavljena njeni efekti su permanentni. </p>
<p>A sada dosta teorije, vreme je da pređemo i na konkretnu upotrebu. Sintaksa koja sledi prilagođena je MySQL-u 4.1.x. U zavisnosti od verzije vašeg MySQL servera biće potrebne izvesne izmene. Da bi ste saznali koju verziju servera koristitite u mysql klijent programu ukucajte <em>select version();</em>.<br />
Korišćenje transakcija moguće je samo sa InnoDB i BDB tabelama. Ako niste sigurni da li ih vaš server podržava koristite <em>show engines</em> query koji će izlistati podržane tipove tabela. Ako je potrebno omogućiti neki od tipova tabela, konsultujte MySQL manual kako da to učinite (ili kontaktirajte support ako je u pitanju MySQL server na vašem hostingu). U našem primeru koristićemo se InnoDB tabelama, potpuno isto važi i za BDB tabele.</p>
<p>Za početak kreirajmo jednu test tabelu:</p>
<pre>create table test(
ime char(20) not null unique)
engine = innodb;</pre>
<p>A sada da upotrebimo jednu transakciju:</p>
<pre>
mysql&gt; start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql&gt; insert into test(ime) values('Pera');
Query OK, 1 row affected (0.01 sec)

mysql&gt; insert into test(ime) values('Mikica');
Query OK, 1 row affected (0.00 sec)

mysql&gt; commit;
Query OK, 0 rows affected (0.03 sec)

mysql&gt; select * from test;
+--------+
| ime    |
+--------+
| Mikica |
| Pera   |
+--------+
2 rows in set (0.02 sec)</pre>
<p>Kao što vidite, <em>start transaction</em> koristimo za početak starta transakcije. Nakon uspešno izvršena 2 upita koristili smo <em>commit</em> za kraj transakcije, koja je u ovom slučaju uspešno obavljena. Važna stvar je da nijedna od promena tokom transakcije (recimo unošenje novog imena <em>Pera</em>) nije vidljiva drugim klijentima sve dok se ne izvrši commit. To je osobina Izolacije (I u ACID) koju smo pominjali ranije. A sada da prikažemo jednu neuspelu transakciju:</p>
<pre>mysql&gt; select * from test;
+--------+
| ime    |
+--------+
| Mikica |
| Pera   |
+--------+
2 rows in set (0.02 sec)

mysql&gt; start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql&gt; insert into test(ime) values('Laza');
Query OK, 1 row affected (0.00 sec)

mysql&gt; insert into test(ime) values('Mikica');
ERROR 1062 (23000): Duplicate entry 'Mikica' for key 1
mysql&gt; rollback;
Query OK, 0 rows affected (0.03 sec)

mysql&gt; select * from test;
+--------+
| ime    |
+--------+
| Mikica |
| Pera   |
+--------+
2 rows in set (0.00 sec)
</pre>
<p>U slučaju gore tokom transakcije došlo je do greške, jer smo pokušali da unesemo već postojeću vrednost (Mikica) u polje definisano kao jedinstveno (unique). Kao rezultat došlo je do greške, pa smo izvršili <em>rollback</em> iskaz. Kao što vidite tabela sadrži iste slogove kao i pre početka transakcije, tj. ime Laza koje je prvo uneto na početku transakcije nije ostalo upisano jer transakcija u celini nije uspela.</p>
<p>Počev od verzije MySQL 4.1.1 moguće je snimiti tačku u transakciji (savepoint) na koju se možemo vratiti rollback k-dom. Evo primera:</p>
<pre>
mysql&gt; select * from test;
+--------+
| ime    |
+--------+
| Mikica |
| Pera   |
+--------+
2 rows in set (0.00 sec)

mysql&gt; start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql&gt; insert into test(ime) values('Laza');
Query OK, 1 row affected (0.00 sec)

mysql&gt; savepoint tacka1;
Query OK, 0 rows affected (0.00 sec)

mysql&gt; insert into test(ime) values('Mirko');
Query OK, 1 row affected (0.00 sec)

mysql&gt; rollback to savepoint tacka1;
Query OK, 0 rows affected (0.00 sec)

mysql&gt; insert into test(ime) values('Dragan');
Query OK, 1 row affected (0.00 sec)

mysql&gt; commit;
Query OK, 0 rows affected (0.03 sec)

mysql&gt; select * from test;
+--------+
| ime    |
+--------+
| Dragan |
| Laza   |
| Mikica |
| Pera   |
+--------+
4 rows in set (0.00 sec)
</pre>
<p>Kao što vidite, nakon unešenog prvog imena (Laza) snimili smo poziciju u transakciji kao <em>tacka1</em>. Unos imena <em>Mirko</em> je poništen vraćanjem na <em>tačku1</em>, nakon koje smo uneli uspešno ime Dragan i odradili commit. Na kraju transakcije, vidi se da ime Mirko nije uneto jer smo se vratili na tačku u transakciji gde to ime nije postojalo.</p>
<p>Eto, toliko o transakcijama u ovom kratkom tutorijalu, nadam da će biti dovoljno da vas zainteresuje za njihovo korišćenje. Za više informacija o transakcijama pogledajte <a href="http://dev.mysql.com/doc/">MySQL Manual</a> ili konsultujte odgovarajuću literaturu. Moja preporuka, <a href="http://www.kitebird.com/mysql-book/">MySQL 3rd Edition</a> by Paul DuBois.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.dinke.net/blog/2006/01/10/mysql-i-transakcije/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Hacking Webalizer</title>
		<link>http://www.dinke.net/blog/2005/08/31/hacking-webalizer/</link>
		<comments>http://www.dinke.net/blog/2005/08/31/hacking-webalizer/#comments</comments>
		<pubDate>Wed, 31 Aug 2005 07:07:35 +0000</pubDate>
		<dc:creator>dinke</dc:creator>
				<category><![CDATA[Software]]></category>
		<category><![CDATA[Tutorials]]></category>

		<guid isPermaLink="false">http://www.dinke.net/blog/?p=37</guid>
		<description><![CDATA[Konačno sam našao dovoljno vremena da podesim Webalizer kako bi prilagodio statistike posećenosti sajta svojim potrebama. Obzirom da je Webalizer iako delimično zastareo još uvek prisutan na velikom broju Web hostinga, evo nekoliko saveta kako da ga učinite preciznijim i prilagodite prikaz svojim potrebama.

Prvo malo o Webalizeru, ali zaista kratko. Webalizer je &#8220;command line utility&#8221; [...]]]></description>
			<content:encoded><![CDATA[<p>Konačno sam našao dovoljno vremena da podesim <a href="http://www.mrunix.net/webalizer/">Webalizer</a> kako bi prilagodio statistike posećenosti sajta svojim potrebama. Obzirom da je Webalizer iako delimično zastareo još uvek prisutan na velikom broju Web hostinga, evo nekoliko saveta kako da ga učinite preciznijim i prilagodite prikaz svojim potrebama.<br />
<span id="more-37"></span><br />
Prvo malo o Webalizeru, ali zaista kratko. Webalizer je &#8220;command line utility&#8221; koji generiše statistiku parsovanjem logova. U praksi najčešće se koristi za parsovanje Apache-ovih logova kako bi generisao statistike posećenosti sajta, ali može parsovati i razne druge log fajlove. Kod generisanja Web statistike obično se startuje kao cron job koji se uobičajeno obavlja odmah nakon rotiranja apache logova (log rotate).</p>
<p>Kao i kod ostalih unix-oidnih programa, webalizeru se prilikom startovanja može proslediti brdo argumenata što je sve lepo dokumentovano (koga interesuje &#8211; <em>man webalizer</em>). Međutim, najvažnije od svega je podešavanje webalizer.conf fajla koji se po defaultu nalazi u /etc/webalizer.conf.</p>
<p>Default verzija webalizer.conf fajla će i bez finog podešavanja raditi. No, statistika koju ona prikazuje nije precizna. Naime, veliki broj hitova dolazi od strane raznih botova (msn, pogodak, google &#8230;), što nije nešto što se računa kao poseta. Pored toga refereri uključuju i sam sajt, pa je jako teško nakon šume linkova sa sopstvenog sajta na listi top 10 Referera videti odakle ljudi zaista dolaze. Vreme je za promene.</p>
<p>Da bi ste isključili sopstveni sajt sa liste referera, dodajte ga u <em>HideReferrer</em>. Primer:</p>
<p><em>HideReferrer    dinke.net/</em></p>
<p>A sada da isključimo sve ove spidere, botove i sl. Evo kako:<br />
<em><br />
# opcijom IgnoreSite ignorisemo sajtove sa koga dolaze botovi<br />
IgnoreSite      spider.pogodak.co.yu<br />
IgnoreSite      msnbot.msn.com<br />
#ignorisemo i planetoid<br />
IgnoreSite      supa.sekjur.com</p>
<p># opcijom IgnoreAgent ignorisemo recorde<br />
# na osnovu useragenta bota<br />
IgnoreAgent     Pogodak.co.yu<br />
IgnoreAgent     msnbot<br />
IgnoreAgent     Googlebot<br />
IgnoreAgent     lmspider<br />
IgnoreAgent     Yahoo<br />
IgnoreAgent     ZyBorg<br />
IgnoreAgent     Jeeves/Teoma</em></p>
<p>Imajte u vidu da prilikom zadavanja stringa u gornjim opcijama važi da za recimo &#8220;www.yourmama.com&#8221;, svi stringovi poput &#8220;your&#8221;, &#8220;*mama.com&#8221; and &#8220;www.your*&#8221; zadovoljavaju upit.</p>
<p>Da bi se sakrile posete određenom delu sajta koristite IgnoreURL. Na primer, ja želim da se na mom sajtu ne vidi statistika poseta /test delu sajta, gde uobičajeno vršim testiranja pa sam zato dodao i :<br />
<em><br />
#ignorisemo posete test delu saja<br />
IgnoreURL       /test*</em></p>
<p>I poslednje ali ne i najmanje bitno, lista user agenta. Radi grupisanja istih browsera (MSIE i Mozilla recimo) može se iskoristiti sledeće :<br />
<em><br />
GroupAgent     MSIE           Interent Exploder<br />
HideAgent      MSIE<br />
GroupAgent     Mozilla        Mozilla Based<br />
HideAgent      Mozilla<br />
</em><br />
GroupAgent će grupisati broj istih &#8220;user agenata&#8221;, tako da će recimo pisati nešto tipa &#8220;MSIE &#8211; 156 hits&#8221; ako stavite MSIE na listu. Ako ne dodate i HideAgent opciju, svi agenti koji sadrže string &#8220;MSIE&#8221; će svejedno biti izlistani. Iz nekog čudnog razloga, ništa drugo osim MSIE i Mozilla nije radilo (recimo gecko ili opera) tako da sam odustao od korišćenja ove opcije.</p>
<p>Nakon samo par &#8220;tweakova&#8221;, statistika je mnogo preciznija uz doduše jednu manu. Sada kada smo isključili računanje search engine-ova u posetioce sajta, značajno se smanjio se broj hitova koje su očigledno generisali spideri.</p>
<p>Eto toliko. Ovo su naravno samo najinteresantnije od mnogobrojnih opcija koje možete koristiti prilikom podešavanja webalizera. Za pun opis svih opcija konsultujte dokumentaciju.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.dinke.net/blog/2005/08/31/hacking-webalizer/feed/</wfw:commentRss>
		<slash:comments>10</slash:comments>
		</item>
	</channel>
</rss>
