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()):
getClientCountryCode() : ISO 3166 Two-letter Country Code
getClientRegionCode() : Region Code
getClientCity() : City
getClientPostalCode() : Postal Code
getClientLatitude() : Latitude
getClientLongitude() : Longitude
getClientMetropolitanCode() : Metropolitan Code
getClientMetropolitanCode() : Area Code
getClientIsp() : ISP
getClientOrganization() : Organization
<?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(&amp;amp;amp;$controller)
{
parent::startup(&amp;amp;amp;$controller);
$this->controller =&amp;amp;amp; $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);
}
}
?>
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!
).
<?php
App::import('Component', 'Email');
class MailerComponent extends EmailComponent
{
var $from = 'ME <me@localhost>';
var $replyTo = 'noreply@localhost';
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.
class AnyController extends AppController
{
function contact()
{
if ($this->_sendEmail('Name', 'blabla@fakeemail', 'Grate site!')) {
$this->Session->setFlash(__("Thank you", true));
} else {
$this->Session->setFlash('Damn it!');
}
}
function _sendEmail($name, $email, $message)
{
$this->Mailer->to = 'info@localhost';
$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
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:
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?
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:
{
"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…