AmfPHP ile Flash Remoting'e Giriş (Flash ile veri tabanı arasında XML olmadan veri alışverişi yapacağız)
Ahmet Erkan ÇELİK Tarih: 2/07/2008 Yorum: 18 adet
Okunma : 3459 Tutanlar: Bu yazıyı 5 kişi tuttu.
Flash ile ActionScript kullananların şu ana kadarki en büyük korkulu rüyası veri tabanı ile iletişim kurmaktı herhalde. Çünkü Flash doğrudan veri tabanından veri alamaz. Doğrudan veri tabanına veri yazamaz.
Adobe Flex Data Services ürününü çıkardığında işte bu ve benzeri sorunları çözme yoluna gitti. Flex Data Services, Java Programlama Dili ile server taraflı uygulamalaları, class olarak yazdıktan sonra, Flash uygulamalarınızla bu classlardan nesne yaratma olanağı sunuyor idi.
XML ile iletişimde farklı olarak Adobe Flex Data Services ürünü flash ile Binary iletişim kuruyordu. Bu da XML için kullandığınız Tag 'ları kullanmamanız böylece daha yalın veriler ile alışveriş yapmanız anlamına geliyordu. Adobe iletişimde kullandığı bu mesaj formatına Adobe Messaging Format ismini verdi. Bu şekilde AMF0 ortaya çıktı. Bu gün bu format revize edildi ve AMF3 oldu. Ancak siz haberleşmenizde hala AMF0 ve AMF3 tercihi yapabiliyorsunuz.
Daha sonra adobe firması bu ürünün adını Adobe Live Cycle Data Services olarak değiştirdi. Açık kaynak kod cephesinde de bu ürünün yerini doldurmak amacıyla çeşitli ürünler çıktı. Bu ürünlerden en popüleri olarak RED5 'i burada örnek vermek yerinde olur.
Adobe bir boşluğu doldurmuştu ama bu ürünle birlikte önemli bir boşluk daha ortaya çıkmıştı. PHP, ASP.NET dillerini kullanarak server taraflı kod yazan ve Flash'ı bu şekilde veri tabanı ile haberleştiren kişiler için de bu teknikleri kullanabilecekleri bir ürün gerekliydi. Bu amaçla 3. nesil program olarak birkaç çözüm geldi. Bu çözümlerden en popüler ikisinin ismi AmfPHP ve WebOrb.
WebOrb, php,.NET,Java ve Ruby on Rails için 4 ayrı sürüme sahiptir ve .NET için olan sürümü RTMP dahil Adobe Live Cycle Data Services ürününün Java ile yapabildiği herşeyi yapabilir.
AmfPHP ise sadece yazdığınız PHP class dosyasını uzaktan çalıştırmanızı sağlar.
AmfPHP'yi www.amfphp.org adresinden indirebilirsiniz. Yada buraya tıklayarak doğrudan indirebilirsiniz. Şu anki kararlı son sürüm amfphp 1.2 ve son sürüm ise amfphp 1.9 betadır.
AmfPHP 'yi indirdikten sonra web server'ınızdaki document root klasörüne açınız. yani http://localhost/amfphp
şeklinde AmfPHP'ye erişebilmeliyiz. Eğer herşey yolunda ise
http://localhost/amfphp/gateway.php
adresinden aşağıdaki gibi bir görüntü alırsınız:
eğer Load the service browser linkine tıklarsanız. Oluşturduğunuz servisleri görmek ve olarla test sürüşü yapma şansına sahip olursunuz. Ancak ilk kez service browser'ı açıyorsanız o zaman AmfPHP size hangi iletişim kuralını kullanacağınızı sorar.
AmfPHP'yi başka bir klasöre kopyalarsanız yada uzak sunucuya atarsanız yine aynı soru ile karşılaşırsınız. Bu soruya bir kez yanıtvermeden de flash yada flex ile AmfPHP'ye bağlantı kuramazsınız
AmfPHP ile bir web servisi oluşturmak için tek yapmanız gereken servisinizi bir class olarak hazırlayıp amfphp klasörü içindeki services klasörüne atmak olacaktır.
Ancak buna geçmeden önce AmfPHP ile yapılacak son bir işlemimiz daha var. AmfPHP standart olarak ilk kurulduğunda ISO-8859-1 karakter setiyle çalışmaktadır. Oysaki bu karakter seti ile türkçe karakterleri kullanmamız çok zor olur. Bu karakter seti ayarlamasını UTF-8 'e dönüştürmeliyiz. Bunun için amfphp kök dizinindeki gateway.php dosyasını açıp şu satırı bulun:
//Read above large note for explanation of charset handling
//The main contributor (Patrick Mineault) is French,
//so don't be afraid if he forgot to turn off iconv by default!
$gateway->setCharsetHandler("utf8_decode", "ISO-8859-1", "ISO-8859-1");
üstteki üç satır açıklama satırı. Alttaki satır ise karakter setinin belirlendiği satır. Bu satırı şu şekilde yeniden düzenleyin:
$gateway->setCharsetHandler("utf8_decode", "utf-8", "utf-8");
Şimdi Flash Remoting 'e en basit girişi yapabileceğimiz AmfPHP 'yi kullanarak, bir veri tabanına kayıt yapalım. Öncelikle MySQL ile bir veri tabanı ve tablo oluşturalım:
CREATE DATABASE `flash_test_db` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
CREATE TABLE `flash_test_db`.`kisiler` (
`id` INT NOT NULL AUTO_INCREMENT ,
`name` VARCHAR( 20 ) NOT NULL ,
`tel` VARCHAR( 11 ) NOT NULL ,
PRIMARY KEY ( `id` )
) ENGINE = InnoDB
veri tabanımızın adı : flashtestdb ve kişi isimleri ve telefon numaralarını tutacağımız tablonun ismi ise kisiler oldu.
Bu veritabanında yetkili kullanıcıyı oluşturalım ve bu veri tabanı üzerinde yetkilendirelim
CREATE USER 'amfphpuser'@'localhost' IDENTIFIED BY 'amfphppass';
GRANT USAGE ON * . * TO 'amfphpuser'@'localhost' IDENTIFIED BY 'amfphppass' WITH MAX_QUERIES_PER_HOUR 0 MAX_CONNECTIONS_PER_HOUR 0 MAX_UPDATES_PER_HOUR 0 MAX_USER_CONNECTIONS 0 ;
GRANT ALL PRIVILEGES ON `flash\_test\_db` . * TO 'amfphpuser'@'localhost' WITH GRANT OPTION ;
bu veri tabanına ulaşacak php class'ını oluşturalım:
<?php
define('DATABASE_SERVER', "localhost");
define('DATABASE_USERNAME', "amfphpuser");
define('DATABASE_PASSWORD', "amfphppass");
define('DATABASE_NAME', "flash_test_db");
class connect_to_mysql {
private $dbObj;
public function connect_to_mysql(){
$this->dbObj = mysql_pconnect(DATABASE_SERVER, DATABASE_USERNAME, DATABASE_PASSWORD) or (trigger_error(mysql_error(),E_USER_ERROR));
mysql_select_db(DATABASE_NAME, $this->dbObj);
}
private function SQL($sql){
$queryReturn= mysql_query($sql,$this->dbObj) or trigger_error("AMFPHP şu hatayı döndürdü hata oluşan class: 'connect_to_mysql' hata veri tabanında SELECT cümlesi işlenirken oldu. mysql tarafından döndürlen hata :\nError:" . mysql_error());
return $queryReturn;
}
private function insertSQL($table,$fields){
$insertSQL="INSERT INTO `".$this->dbPrepend.$table."` (";
$fieldSQL="";
$valueSQL="";
foreach ($fields as $field=>$value){
if($fieldSQL!="") $fieldSQL.=",";
$fieldSQL.="`".$field."`";
if($valueSQL!="") $valueSQL.=",";
$valueSQL.="'".$value."'";
}
$insertSQL.=$fieldSQL.") VALUES (";
$insertSQL.=$valueSQL.");";
mysql_query($insertSQL,$this->dbObj) or trigger_error("AMFPHP şu hatayı döndürdü hata oluşan class: 'connect_to_mysql' hata veri tabanında INSERT cümlesi işlenirken oldu. mysql tarafından döndürlen hata :\nError:" . mysql_error());
return true;
}
public function insertPerson($person){
$values= array(
'name' =>$person['name'],
'tel' =>$person['tel']
);
$this->insertSQL('kisiler',$values);
}
public function getAllPersons(){
$personSQL="SELECT * FROM `kisiler`";
return $this->SQL($personSQL);
}
}
?>
bu php class'ı PHP5 standarlarına göre yazıldı. Ancak PHP4'e de uygun yazıldı. eğer public ve private bildirimlerini silerseniz PHP4 ile de çalışacaktır. Nesne Yönelimli Programlamanın en temel unsurlarından olan public ve private bildirimleri ile çalışmak çok önemlidir. Yazdığımız php cllass'ındaki private yordamlara (fonksiyon) flashRemoting ile erişmemiz mümkün değil. Bu yolla veri tabanını ve uygulamamızı korumuş oluyoruz.
Şimdi de flash dosyamızı oluşturalım:
ve action script kodları da şöyle:
btnGonder.addEventListener(MouseEvent.CLICK,btnGonderClicked);
function btnGonderClicked(event:MouseEvent){
var postObj:Object={name:txtAdSoyad.text,tel:txtTelefon.text}
var nc:NetConnection= new NetConnection();
var resp:Responder = new Responder(postSuccessHandler,faultHandler);
nc.connect("http://localhost/amfphp/gateway.php");
nc.call("connect_to_mysql.insertPerson",resp,postObj);
}
function postSuccessHandler(result:Object){
trace("Kayıt Girişi başarılı");
}
function faultHandler(fault:Object){
trace("Bağlantı Hatası");
}
burada yapılan işlem kabaca şöyle:
http://localhost/amfphp/gateway.php adresine bir bağlantı oluşturuluyor. Bu bağlantıyı oluşturan bağlantı nesnesinin türü NetConnection, ismi ise nc. Bu nesnenin call yordamı bizim yazdığımız php class'ına erişmemizi sağlıyor. sınıfadı.yordam_adı şekilnde hangi yordama bağlanacağımızı ilk parametre ile anltoyoruz.
ikinci parametre bu işlem gerçekleştiğinde dönen bilgileri yakalamak için kullanılacak Responder tipinde resp nesnesi. Bu nesne ise iki türlü sonucun olabilirliği ile çalışıyor. Birisi fonksiyonun (yordamın) başarıyla çalışması ve geriye bir değer döndürmesi. Diğeri ise bir hata oluşması.
üçüncü parametre isteğe bağlıdır. Son parametre değildir. Üçüncü parametreden itibaren istediğimiz kadar parametre yazabiliriz. Bu parametreler çağırdığımız yordama geçirilecektir. Bizim yazdığımız insertPerson yordamı bir parametre aldığı için bizde bir parametre göndermeyi tercih ettik. Gönderdiğimiz parametre bir Object idi. Bu türden parametreler göndermeye mecbur değiliz. Sadece öreneğimizin zengin olması bakımından, nesneleri de parametre olarak geçirbileceğimizi göstermek ve geçirdiğimiz nesne parametreleri PHP ile nasıl çözeceğimizi de göstermek amacıyla Object geçirdik.
flash'ı çalıştıralım ve bir veri girişi yapalım :
bu işlem sonucunda veri tabanına veri girişi yapılıp yapılmadığını phpmyadmin ile kontrol edelim :
service borwser'ı tekrar çalıştıralım:
yazdığımız class soltarafta görünüyor. Sağda ise class'daki public fonksiyonlar görünüyor. getAllPersons fonksiyonunu seçip call butonuna bastığımda alt kısımda fonksiyon çaıştığında geriye döndürdüğü değerleri görüyoruz. Eğer Flex ile çalışıyorsanız o zaman aşağıdaki kodu kullanabilirsiniz.
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
<mx:Script>
<![CDATA[
public function btnGonderClicked(event:MouseEvent):void{
var postObj:Object={name:txtAdSoyad.text,tel:txtTelefon.text}
var nc:NetConnection= new NetConnection();
var resp:Responder = new Responder(postSuccessHandler,faultHandler);
nc.connect("http://localhost/amfphp/gateway.php");
nc.call("connect_to_mysql.insertPerson",resp,postObj);
}
private function postSuccessHandler(result:Object):void{
trace("Kayıt Girişi başarılı");
}
private function faultHandler(fault:Object):void{
trace("Bağlantı Hatası");
}
]]>
</mx:Script>
<mx:Panel x="10" y="10" width="370" height="238" layout="absolute">
<mx:Form right="0" left="0" bottom="50" top="0">
<mx:FormItem label="Adı Soyadı">
<mx:TextInput id="txtAdSoyad"/>
</mx:FormItem>
<mx:FormItem label="Telefonu">
<mx:TextInput id="txtTelefon"/>
</mx:FormItem>
</mx:Form>
<mx:Button x="187" y="156" label="Gönder" id="btnGonder" click="btnGonderClicked(event)"/>
</mx:Panel>
</mx:Application>
php class'ındaki fonksiyondan geriye dönen değerler Responder clasından tanımladığımız nesnenin result olayına atadığımız fonksiyona parametre olarak döner. Yani yukarıdaki iki örnekte de eğer fonksiyon bir değer döndürüyorsa
private function postSuccessHandler(result:Object):void{
fonksiyonundaki result bu dönen değeri alabilir. result ismi ile tanımladığımız nesne illaki Object olmak zorunda değildir. php class'ından hangi türde değer dönüyorsa ona göre ihtiyaç duyduğunuz tipte tanımlayabilirsiniz.
rica ederim.
Bu arada bir arakadaşımın uyarısı üzerine farkettim bende: Yaılan php class dosyası amfphp içinde services klasörüne kaydedilmelidir.
WebOrb ve AmfPHP yi karşılaştıran bir makalede yolda.
Gerçekten çok güzel bir paylaşım. Çok makbule geçti. İngilizce olarak çok okumuştum ama biri anlatınca daha iyi öğrenebilen biri olunca çok kıymetli oluyor böyle yazılar... :)
Devamını bekliyorum, verdiğiniz emekler için çok teşekkürler...
Eksiksiz hazirlanmis bi egitim makalesi olmus. Tebrikler, elinize saglik.
gerçekten çok işe yarıyor. teşekkürler. özellikle 20 ayrı tablodan epey kalabalık satırlar halinde bilgi çekerken.
eğer tek bir tablonuz varsa bu kadar zahmete girmeden variables kullanarak halletmek daha kolay olacaktır.
denerken farkettiğim birkaç şey:
veri tabanına elle giriş yaparsanız türkçe karakterler encode olmuyor. yani "ç" yi "ç" olarak alıyor phpmyadmin ve flash okurken bunu "?" olarak görüyor. veri girişini yine burada anlatılan şekilde yapınca encode ediyor ve alırken düzgün görünüyor.
verilere ulaşmak için sadece result i trace edince sadece object döndürüyor. result.serverInfo.initialData tüm objeyi döndürüyor. bunu result.serverInfo.initialData[0][2] yazınca 1. satırın 3. sütununu döndürüyor.
gerçekten çok güzel bi ders hocam ellerinize sağlık...
yalnız bu veri alışveirş işini biraz daha kompleks hale getirdiğimizde işler epey karışıcak bununla ilgili bi yazınız fikriniz yada örnek dosyanız vamrı?...
bu dediğim şeyi şöyle açıklayayım... örneğin bi maillist yapıyosunuz;
isim email kaydet çıkart... kaydet e bastığınızda üyeyi db ye kaydedicek ve girilen e-mail adresine kaydınız başarılı olmuştur bu email grudundan çıkmak için şu linke tıklayın gibi bir mail gidicek; bunun yanı sıra ekranda kaydınız başarılı olmuştur mail box ınızı kontrol ediniz.. şeklinde bi yazı görüntülenecek... çıkart komutundada aynen böyle olmalı..
ve bu değişiklikleri admine bildiricek ; her yeni biri eklendiğinde mail gelicek...
:) şimdi işler bu kadar uzun ver karışık olunca .. "aslında php için karşık değil lakin flashla birleşince garipleşiyo..." işin içindne nasıl çıkıcaz :)
:) şimdi işler bu kadar uzun ver karışık olunca .. "aslında php için karşık değil lakin flashla birleşince garipleşiyo…" işin içindne nasıl çıkıcaz :)
aslında çözümü kendiniz söylemişsiniz. Bu işleri PHP yapar. Flash'ı bir arayüz olarak kullanırsınız. PHP ile yazdığınız fonksiyonlara veri gönderen, fonksiyondan dönen değerleri de yorumlayan, akllı bir arayüz.
Bakın bu örnekteki
insertPerson()
php fonksiyonu geriye bir değer döndürüyor olsaydı. O zaman dönen bu değer flash'ta :
function postSuccessHandler(result:Object){
}
result isimli değişken de olacaktı.
merak ettiğim bir konu var.
php kodlarının üye girişini kontrol etmesi yeni üye oluşturması üye bilgilerini güncellemesi üyeye mail atması filmler tablosundan kullanıcının filmlerini görüntülemesi buna ekleme yapması bunlar arasından seçim yapması vb. gerekiyor.
veri transverinin hızlı olması için bu işlemleri birbirleri ile ilişkili olanlar yeni class dosyaları mı oluşturmak gerekir. yoksa tek bir dosyada yeni public functionlar şeklinde mi bu işlemlerin yapılması gerekir.
bir konu daha var. şöyle açıklayayım.
flash kadum şöyle:
userInfo = {u_name:login_mc.userName_txt.text, password:login_mc.password_txt.text};
resp = new Responder(loginDone,unDone);
preload();
nc.call("connect.login",resp, userInfo, "accounts");
php kodum şöyle:
public function login($bilgi, $tabela)
{
$u_name = $bilgi['u_name'];
$password = sha1($bilgi['password']);
if(($this->controlUserName($u_name, $password)) == 1)
{
return 1;
}
else
{
return "User name and/or password is incorrect.";
}
}
private function controlUserName($name, $pass)
{
$personSQL="SELECT * from accounts where u_name = '{$name}' and password = '{$pass}'";
if(mysql_num_rows($this->SQL($personSQL)) != 0)
{
return 1;
}
else
{
return 0;
}
}
flash eğer kullanıcı adı ve parola doğru ise 1 alıyor ve kullnıcının bilgileri isteminde bulunuyor. bu onaylama kısmında eğer controlUserName fonksiyonu 1 gönderdiyse flasha doğrudan bu kullanıcının bilgilerini göndermeyi denediğimde yani şöyle ki:
if(($this->controlUserName($u_name, null, $password)) == 0)
{
$personSQL="SELECT * from $tabela";
return $this->SQL($personSQL);
}
incorrect number of arguments diye bir uyarı alıyorum.
Bunun bir açıklaması var mı acaba onu merak ediyorum.
Şimdiden teşekkür ederim.
Localhost kurulu indirdiğim dosyayı C:\AppServ\www içine atıyorum http://localhost/amfphp/gateway.php adresini çalıştırıyorum Load the service browser linkine tıklıyorum açılan pencereden AMF3 ve Results seçenekleri işaretli save butonuna basınca alttaki hatayı alıyorum .
(mx.rpc::Fault)#0 errorID = 0 faultCode = "Client.Error.MessageSend" faultDetail = "Channel.Connect.Failed error NetConnection.Call.BadVersion: " faultString = "Send failed" message = "faultCode:Client.Error.MessageSend faultString:'Send failed' faultDetail:'Channel.Connect.Failed error NetConnection.Call.BadVersion: '" name = "Error" rootCause = (Object)#1 code = "NetConnection.Call.BadVersion" description = "" details = "" level = "error"
Ydığınız classta syntax hatası var
Class'la alakası yok kurulumda hata veriyor. Evdeki pc'de sorunsuz şekilde kuruldu.
O halde AmfPHP nin kendi dosyalarında bir arıza yada eksiklik olabilir. Bence temiz bir tane indirip onu deneyin. Belki indirme esnasında CRC hatası oluşmuştur. Bu hata AMF mesajlarını karşılayan PHP dosyasında bozukluk olduğunda çıkıyor. Bu bozukluk kendi class'ınızda olmadığına göre AMFPHP dosyalarında olmalı
Veritabanı oluşturdum , yetkileri ayarladım Flash dosyasını oluşturdum Bağlantı hatası aldım. php class nasıl kullanılır biraz daha açıklayabilirmisiniz .
Bir şey sormak istiyorum. Yazdığınız tutorial üzerinden ben de Flex ile database kayıt edilen kişi bilgilerini tekrar Flex içinde görüntülemeye çalışıyorum. Datagrid i kullandım ama bir yerlerde hata var. PHP tarafında diye düşünüyorum. Ancak anlayamadım hala. Yazmış olduğunuz PHP kodunu hiç editlemedim. Flex'ten 'getAllPersons' fonksiyonunu çağırıyorum kayıt edilen datayı görebilmek için. Bunun için de bir arraycollection değişkeni tanımladım. Bu konu da bir fikri olan var mı?
php kodlarını amfphp'nin panelinden test edin. Eğer çalışıyorsa flex kodlaryla ilgilidir. Verdiğim örnekteki kodlr çalışıyor. Zaten bu da ekran görüntülerinden anlaşılıyor. Php den dönen veriler ArrayCollection tipinde olamaz. Array olabilir. Örneğin:
public function gotAllPersons(persons:Array):void{
}
şekindeki bir fonksiyon ancak bu verileri alabilir. Ama bu verileri ArrayCollection'a dönüştürecekseniz:
public function gotAllPersons(persons:Array):void{
var acPersons:ArrayCollection = new ArrayCollection(persons);
}
şeklinde bir kod kullanabilirsiniz.
Çok teşekkür ederim. Sanırım, birşeyler karıştı gözümden kaçtı. 'getAllPersons' ı bu şekilde terar yazdım. Şimdi sorun çözüldü. İstediğim oldu. Datagrid'e yazdırdım. Ancak hala mecburen bunu yapmak zorunda mıyım bilemiyorum?
public function getAllPersons()
{
@mysql_connect("localhost", "root", "root");
@mysql_select_db("flash_test_db");
//return mysql_query("select name,tel from kisiler");
$query = "SELECT * from kisiler";
$result = mysql_query($query);
$menuNames = array();
while ( $row = mysql_fetch_object($result))
{
$tmp = new Sentence();
$tmp->name = $row->name ;
$tmp->tel = $row->tel ;
array_push($menuNames,$tmp);
}
mysql_free_result($result);
return $menuNames;
}
Ben de Flex tarafını dediğiniz gibi yaptım. AMFPHP browser'da aldığım çıktı yine aynı. Değişen birşey yok. Fakat php kodunu yukarıdaki gibi yapmadığım zaman yazdığım veriyi çağıramıyorum.
Merhabalar. Yazınız için öncelikle teşekkürler
AmfPhp ilk deneme olarak bir sendmailde kullandım. Ama tükçe karakter sorunu yaşıyorum. Sizin yazdığınız şekilde gateway.php dosyasında değişiklik yaptım ama mail gmail,yahoo veya mynet gibi adreslere sorunsuz giderken. SQWebMai, IMP, Horde gibi maillere Türkçe karakter sorunlu gidiyor. Bu sorun kullandığım text editörün kodlamasondan kaynaklana bilirmi? Notepad++ kullanıyorum normalda kodlama utf8 olur ama amfphp dosyalarını bu kodlama kaydedince çalışmıyo ANSI olmak zorunda qaliba.
Şimdiden teşekkürler
Flash utf-8 ile çalışıyor ve karakterleri utf-8 ile kodluyor, ancak php dosyaları (ve amfphp) dosyaları ansi. Bundan kaynaklı olarak flash tarafından gelen karakterler ilk anda sanki ansi ile kodlanmış gibi algılanıyor ve yanlış karakterlerle ifade ediliyor.
Bunu çözmek için php'nin iconv fonksiyonu ile gelen veriyi farklı bir karakter kodlamasına dönüştürün yada mail göndermeden önce mail metninizin header kısmında karakter kodlamasının utf-8 olarak ayarlayın.
Makale
Haber
Etkinlikler
Toplantı
Özgür Yazılım ve Linux Günleri '10
Özgür Yazılım ve Linux Günleri '10
İstanbul Bilgi Üniversitesi Bilgisayar Bilimleri Bölümü ve Linux Kullanıcıları Derneği'nin 9 yıldır düzenlemekte oldukları etkinlikler bu yıl `Özgür Y...
Kategori:
Toplantı
MMIstanbul Blog'undan
Blog Bölümü Blogevi.com'a Taşınıyor
Selam arkadaşlar MMIstanbul'da , tasarımcı ve programcıların blog yazılarını "feedleyerek" MMIstanbul okurlarını MMIstanbul dışın ...
7.500'üncü üyemiz Cem Koç!
Neler Yapılabilir?
500 Hatası Hakkında!
Reklam, MMIstanbul ve Yeni Projeler (Durumumuz Bu Tarzında)











güzel bi paylaşım emeğinize sağlık çok teşekkür ederim