Erhan is typing…

WebDevelopment , PHP, Javascript, CakePHP, ExtJS

GOOGLE TÜRKÇE ÖĞRENDİ (GİBİ)

| Comments

Internet’te Türkçe içerik anlamında her zaman sıkıntı vardır. Gerçi bu yazıyı okurken siteye şöyle bir bakarsanız söylene de bakın diyebilirsiniz. Kişisel alanım olduğu için tercih hakkımı kullandığımı söyleyebilirim fakat asıl konumuz farklı: Google Translate.

Google çeviri servisi Google Translate dilleri arasına Türkçe de eklenmiş durumda. Farkettiğim gibi denemek maksadıyla Google Translate sayfasına hemen bir ziyaret gerçekleştirdim. Bugüne kadar ki çalışmalardan verimli bir sonuç alınmadığına birçok kere şahit olduğum düşünüldüğünde önüme çıkan her yeni denemeye daha bir özenle yaklaştığımı söyleyebilirim. Google Translate için de aynısı oldu. Kendimi önyargılarımdan arındırarak ilk denememi yapmak istedim fakat sonuç neredeyse hüsran:

Şanssız olduğumu düşünüp birkaç deneme daha yaptığımda fikrim değişmeye başlamıştı. Fakat uzun, yan cümlecikli, eklerin birbirini takip ederek bağlandığı kelimelerin sayısının da arttığı durumlarda sonuç ekran görüntüsünü verdiğimden örnekten daha da kötüydü.

Beta konusunda çok hassas olan Google madem ki Türkçe’ye de el atıyor en azından bizi başarı yüzdelerine yönlendirecek açıklamaların yer aldığı bir “beta” simgesiyle uyarsaydı da beklentilerimiz konusunda bizi yanıltmasaydı. İyi tarafından bakıp en azından kör topal da olsa çevirebiliyor, biraz anlasak gerisini biz de tamamlarız diyenlere bir yardım olarak görebiliriz. Yine de Google’ın öneri gönderim aracından gelecek yardımlara çok ihtiyacı olacak.

Makine çevirisi yeni bir kavram değil tabii ki fakat Türkçe bu alanın üvey evlatlarından. Birçok dilin geldiği konuma bakınca Türkçe’deki başarısızlığı yapılan çalışmaların nispeten azlığına bağlayabilirsiniz. Yani çalışsak Türkçe’yi biraz okumuş kitlenin arkasını çok da aramadan hemen kabullendiği, aidiyet duygusuyla daha çok sarıldığı Türkçe aslında matematiksel bir dil, Bilgisayar için en uygun dil Türkçe gibi laflardan kurtarabiliriz. Kendimi ayrı bir yere koyduğum anlaşılmasın. Sonuçta karşılaştırma yapabilecek kadar araştırmaya ömrümün yeteceğini sanmıyorum.

Image Rotator For Your SilverStripe Application

| Comments

I used SilverStripe to develop couple of projects and I really liked it. I am still a fan of CakePHP but you should give a try if you don’t want to build a new CMS from scratch with CakePHP.

I want to show you how easy to add an image rotator to your SilverStripe site by using JW Image Rotator. You can see it in action at sumpa.com.tr.

First download the greatest flash image rotator, JW Image Rotator. Then copy/move it to your SilverStripe project folder. I prefer images folder.

In order to add or remove new images (or swf files) to your rotator easily, we are going to use a folder in our Uploads directory and list all the files in this folder. Let’s assume that our directory name is flash. To do that, go to your admin panel in SilverStripe, switch to Files & Images tab in your menu and create a new directory called flash.

In your Page class, define a new method called ImageRotator().

myproject/code/Page.php
1
2
3
4
5
6
7
8
9
class Page extends SiteTree
{
  static $db = array();
  static $has_one = array();

  function ImageRotator() {
    return DataObject::get('File', 'ClassName <> "Folder" AND ParentID = (SELECT ID FROM File WHERE ClassName = "Folder" AND Name = "flash" LIMIT 0,1)');
  }
}

Create a new file ImageRotator.ss in myproject/templates/

ImageRotator.ss
1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="utf-8"?>
<playlist version="1" xmlns="http://xspf.org/ns/0/">
  <trackList>
  <% if ImageRotator %>
    <% control ImageRotator %>
    <track>
      <title>$Title.XML</title>
      <location>$BaseHref$Filename.XML</location>
    </track>
    <% end_if %>
  <% end_control %>
  </trackList>
</playlist>

Create a new file Flash.ss in myproject/templates/Includes

Flash.ss
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<!--
Don't change the "file" variable. This is the URI of our XML which contains images
You can change other settings or add/remove.
-->

<script type="text/javascript">
var introFlashvars = {
  width: "950",
  height: "300",
  file: "$URLSegment/ImageRotatorXML",
  shownavigation: false,
  screencolor: "0xFFFFFF",
  rotatetime: 15
};

var params = {
  wmode: "transparent"
}
</script>

<!--
Following div with id "Intro" is used as a container for our image rotator.
Browsers which are not supporting Javascript, "dummyimage.jpg" image linked to the home page is displayed

Don't forget to add swfobject.js into your head tag!
You can download it from http://code.google.com/p/swfobject/
-->
<div id="Intro"><a href="/"><img src="dummyimage.jpg" /></a></div>
<script type="text/javascript">
  swfobject.embedSWF("$ThemeDir/images/imagerotator.swf", "Intro", introFlashvars.width, introFlashvars.height, "8", false, introFlashvars, params);
</script>

We complete the steps to create our rotator. Now, you can call it from any page that you want with the following include directive:

Flash.ss
1
<% include Flash %>

Upload your images and swf files into your flash directory and open the page by adding ?flush=1 to URI. It will refresh the cache.

I know, it should be a widget so that it can easily be added to the pages without hardcoding. I am not planning to do that kind of work but if you find it useful, convert it to a widget and share with community. You are free to do that! No license, no restrictions… :)

Contributions are welcome…

Using rakaz Combine With Your CakePHP Application

| Comments

Combine is a small PHP script and some clever URL rewriting designed to speed up the loading of pages that use many or large css and javascript files.

It is good but how can you integrate it with your CakePHP project? And solution comes with the question :).

Download a copy of combine.php and drop it into your app/webroot directory (or whatever your application folder name is XXX/webroot/).

Open the file in your text editor and change lines:

1
2
3
$cachedir = dirname(__FILE__) . '/cache';
$cssdir   = dirname(__FILE__) . '/css';
$jsdir    = dirname(__FILE__) . '/javascript';

with

1
2
3
$cachedir = dirname(__FILE__) . '/../tmp/cache';
$cssdir   = dirname(__FILE__) . '/css';
$jsdir    = dirname(__FILE__) . '/js';

Open your app/webroot/.htaccess file and add those two lines

1
2
RewriteRule ^css/(.*\.css) combine.php?type=css&files=$1
RewriteRule ^js/(.*\.js) combine.php?type=javascript&files=$1

That’s all! Check download times of your css and javascript files with a tool like Firebug. A fatty 527KB ext-all.js javascript framework file compressed to 140KB! Amazing ha?

CakePHP RequestHandler Extended

| Comments

A CakePHP component which extends built-in RequestHandler component by adding some useful geolocation information. It requires MaxMind WebService license key to work properly.

Save it with the name request_handler_ext.php into your application components directory under controllers.

You can call the following methods within your controller (e.g. $this->RequestHandlerExt->methodName()):

Method Returns
getClientCountryCode() ISO 3166 Two-letter Country Code
getClientRegionCode() Region Code
getClientCity() City
getClientPostalCode() Postal Code
getClientLatitude() Latitude
getClientLongitude() Longitude
getClientMetropolitanCode() Metropolitan Code
getClientAreaCode() Area Code
getClientIsp() ISP
getClientOrganization() Organization
request_handler_ext.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
<?php
/**
 * Extends RequestHandler component by adding some useful geolocation information.
 * 
 * Requires MaxMind WebService license key to work properly
 *
 * @copyright     2009 Erhan Abay
 * @package       app
 * @subpackage    app.controllers.components
 * @version       $Revision$
 * @lastmodified  $Date$
 */

App::import('Component', 'RequestHandler');

class RequestHandlerExtComponent extends RequestHandlerComponent
{
  /**
  * Required to query MaxMind WebService
  *
  * Provide your own key by replacing XXXXX.
  */
  const MM_LICENSE_KEY = 'XXXXXX';
  
  public function startup(&$controller)
  {
      parent::startup(&$controller);
      $this->controller =& $controller;
  }
  
  /**
  * Searches key value in array returned by function getGeoLocation()
  *
  * @param string $name Name of the method
  * @param unknown_type $arguments Not used, required not to give error
  * @return string if key found
  *         null else
  */
  public function __call($name, $raw = false)
  {
      $var = Inflector::underscore(preg_replace('/getClient/i', '', $name));
      $geo_location = (array)$this->getGeoLocation($raw);
      
      return array_key_exists($var, $geo_location) ? $geo_location[$var] : null;
  }

  /**
  * Queries to the MaxMind WebService and returns an array of information
  *
  * @param bool $raw
  * @return null if IP address is local
  *         bool false if webservice returns error code
  *         string if $raw is set true
  *         array else
  */
  public function getGeoLocation($raw = false)
  {
      if ($this->isLocalIP()) {
          return null;
      }
      
      if ($this->controller->Session->check('User.GeoLocation')) {
          return $raw ? $this->controller->Session->read('User.GeoLocation.raw') : $this->controller->Session->read('User.GeoLocation');
      }
      
    App::import('HttpSocket');

    $http = new HttpSocket();

    /*
     * Returns in order:
     * 
     * 0  ISO 3166 Two-letter Country Code,
     * 1  Region Code,
     * 2  City,
     * 3  Postal Code,
     * 4  Latitude,
     * 5  Longitude,
     * 6  Metropolitan Code,
     * 7  Area Code,
     * 8  ISP,
     * 9  Organization,
     * 10 Error code
     */
    $result = $http->get('http://geoip1.maxmind.com/f', array(
      'l' => self::MM_LICENSE_KEY,
      'i' => $this->getClientIP()
    ));

    $values = explode(',', $result);

    if (isset($values[10])) {
      return false;
    }

    if ($raw) {
      return $result;
    }

    $keys = array('country_code', 'region_code', 'city', 'postal_code', 'latitude', 'longitude', 'metropolitan_code', 'area_code', 'isp', 'organization');
    $data = array_combine($keys, $values);
    $data['coords'] = $values[4].','.$values[5];
    $data['raw'] = $result;
    $this->controller->Session->write('User.GeoLocation', $data);

    return $data;
  }

  /**
   * Detects whether IP address is local or not 
   *
   * @param string $ip IP address to check
   * @return bool
   */
  public function isLocalIP($ip = null) {
      $ip = is_null($ip) ? $this->getClientIP() : $ip;
      
      $regex = '/(192\.168\.[0-9]{1,3}\.[0-9]{1,3})';
      $regex .= '|(10\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})';
      $regex .= '|(172\.0?([1][6-9])¦([2][0-9])¦([3][0-1])\.[0-9]{1,3}\.[0-9]{1,3})';
      $regex .= '|(127\.0\.0\.1)/';
      
      return (bool)preg_match($regex, $ip);
  }
}

Make CakePHP Email Component Reusable

| Comments

I know the title of this post a little bit confusing but let me explain what I want to tell.

You are using CakePHP’s email component and you should set the same information in each time before you send your email like server address, username, password etc.

Just create a new file mailer.php with the following content and drop it into your CakePHP application components folder (I like convention over configuration! ;).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
App::import('Component', 'Email');

class MailerComponent extends EmailComponent
{
  var $from     = 'ME <[email protected]>';
  var $replyTo  = '[email protected]';
  var $sendAs   = 'both';
  var $delivery = 'smtp';
  var $xMailer  = 'Postman';
  var $smtpOptions = array(
    'port'     => 25,
    'host'     => 'serveradress',
    'timeout'  => 30,
    'username' => 'username',
    'password' => 'password'
  );
}

And right now you have a new component with the name Mailer and its server configuration is predefined. You can reuse it without being affected by any kind of mail server change.

You can define a new function inside your controller (_sendEmail() in our case) and make the email sending process more painless.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class AnyController extends AppController
{
  function contact()
  {
    if ($this->_sendEmail('Name', '[email protected]', 'Grate site!')) {
      $this->Session->setFlash(__("Thank you", true));
    } else {
      $this->Session->setFlash('Damn it!');
    }
  }

  function _sendEmail($name, $email, $message)
  {
    $this->Mailer->to = '[email protected]';
    $this->Mailer->subject = __("Site Contact", true);
    $this->Mailer->template = 'contact';

    $this->set('name', $name);
    $this->set('email', $email);
    $this->set('message', $message);
    $this->Mailer->send();

    $this->log( $this->Mailer->subject . ' -> Name:'. $name .' | E-posta: '. $email .' | Message: '. $message .' | smtp error: '. serialize($this->Mailer->smtpError) );
      
    return $this->Mailer->smtpError ? false : true;
  }
}

That’s all! Check Bakery for other cakes :)

Dynamic Grid Panel for Ext JS

| Comments

I am using Ext JS in my web application projects and I decided that it is time to give back to the community.

If you are developing a CRUD application and using grids to display data from your database you should define similar column models, fields, readers, stores etc. for your grids. It is not a big concern if it is not the first time you do the same things or you have your own components not to repeat yourself. But what if your database table needs a change in its structure? Let me guess… You change field and column model definitions in your grids and doing the same again and again even there are small changes.

Here is a small Ext JS component that I wrote:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
Ext.ux.DynamicGridPanel = Ext.extend(Ext.grid.GridPanel, {

  initComponent: function(){
    /**
     * Default configuration options.
     * 
     * You are free to change the values or add/remove options.
     * The important point is to define a data store with JsonReader
     * without configuration and columns with empty array. We are going
     * to setup our reader with the metaData information returned by the server.
     * See http://extjs.com/deploy/dev/docs/?class=Ext.data.JsonReader for more
     * information how to configure your JsonReader with metaData.
     * 
     * A data store with remoteSort = true displays strange behaviours such as
     * not to display arrows when you sort the data and inconsistent ASC, DESC option.
     * Any suggestions are welcome
     */
    var config = {
      viewConfig: {forceFit: true},
      enableColLock: false,
      loadMask: true,
      border: false,
      stripeRows: true,
      ds: new Ext.data.Store({
        url: this.storeUrl,
        reader: new Ext.data.JsonReader()
      }),
      columns: []
    };

    Ext.apply(this, config);
    Ext.apply(this.initialConfig, config);

    Ext.ux.DynamicGridPanel.superclass.initComponent.apply(this, arguments);
  },

  onRender: function(ct, position){
    this.colModel.defaultSortable = true;

    Ext.ux.DynamicGridPanel.superclass.onRender.call(this, ct, position);

    /**
     * Grid is not masked for the first data load.
     * We are masking it while store is loading data
     */
    this.el.mask('Loading...');
    this.store.on('load', function(){
      /**
       * Thats the magic! :)
       * 
       * JSON data returned from server has the column definitions
       */
      if(typeof(this.store.reader.jsonData.columns) === 'object') {
        var columns = [];
  
        /**
        * Adding RowNumberer or setting selection model as CheckboxSelectionModel
        * We need to add them before other columns to display first         
        */    
        if(this.rowNumberer) { columns.push(new Ext.grid.RowNumberer()); }
        if(this.checkboxSelModel) { columns.push(new Ext.grid.CheckboxSelectionModel()); }

        Ext.each(this.store.reader.jsonData.columns, function(column){
          columns.push(column);
        });
  
        /**
        * Setting column model configuration
        */
        this.getColumnModel().setConfig(columns);
      }
      /**
       * Unmasking grid
       */
      this.el.unmask();
    }, this);
    /**
     * And finally load the data from server!
     */
    this.store.load();
  }
});

How to use it?

1
2
3
4
5
6
7
new Ext.ux.DynamicGridPanel({
  id: 'my-grid',
  storeUrl: 'server/url/address/',
  rowNumberer: true,
  checkboxSelModel: true,
  sm: new Ext.grid.CheckboxSelectionModel(),
});

And here is the JSON which should be returned from server to dynamically create column and field definitions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
{
    "metaData": {
        "totalProperty": "total",
        "root": "records",
        "id": "id",
        "fields": [
            {
                "name": "id",
                "type": "int"
            },
            {
                "name": "name",
                "type": "string"
            }
        ]
    },
    "success": true,
    "total": 50,
    "records": [
        {
            "id": "1",
            "name": "AAA"
        },
        {
            "id": "2",
            "name": "BBB"
        }
    ],
    "columns": [
        {
            "header": "#",
            "dataIndex": "id"
        },
        {
            "header": "User",
            "dataIndex": "name"
        }
    ]
}

The code itself is somewhat self-explanatory but for newbie users learning center is a good start to understand how to extend Ext JS or simply understand how the framework works.

Suggestions and comments are welcome…

Mesajlaşma + E-posta + Sosyal Ağlar = Digsby

| Comments

Wakoopa‘da HWM BlackBox‘ın durumuna bakarken 2008’in en iyileri bölümünde digsby ile tanıştım. Hem MSN’e hem ICQ’ya bağlanan programlar bugünün keşfi değil fakat Facebook Chate de bağlanabilenle ilk defa karşılaştım. :)

digsby ile AIM, MSN, Yahoo, ICQ, Google Talk, Jabber ve Facebook Chat birarada. Üzerine Hotmail, Gmail, Yahoo Mail, AOL/AIM Mail, IMAP ve POP desteği ile e-postalara erişimi de katmışlar. Asıl yok artık dedirten de Facebook, Twitter, MySpace ve LinkedIn hesaplarınızla da etkileşimde olması. Yani biri sizi dürtünce digsby sizi uyarıyor. :)

Daha fazla uzatmama izin vermeden indirip denemenizi tavsiye ederim.

Kurulum sırasında digsby bazı ek yazılımları bilgisayarınıza kurmak isteyecek. DECLINE ile geçerseniz sorun yok…

IIS Kurulumu

| Comments

Microsoft’un Windows işletim sisteminde çalışan web sunucusu IIS’in (Internet Information Services - İnternet Bilgi Hizmetleri) kurulumu. Windows XP CD’si gerekmektedir. XP Home sürümünde bu bileşen bulunmaz.