N2F Training Team Blogtorials

February 4, 2010

Yverdon N2F Yverdon Cookbook: Ajax login with data.php and jQuery

Filed under: Development, Yverdon — Tags: , , , , , , — z|MattH @ 11:37 am

Anyone who has used the N2 Framework is familiar with page.php.  By default, all requests for a given module go to that modules page.php, and it is in page.php that the template is selected, data is fetched and chewed, and the result is finally sent back to the browser.  This is great if you want to return a whole web page, but what about AJAX?

For this, we have data.php.  This is not to say that data.php exists only for AJAX.  The data.php file is intended to be used any time you need to return something other than a page to the browser.  AJAX is just a common example.  Let’s be really specific and implement an AJAX login.

To make this easy, we’ll start by using the Useful $user extension.  We’ll also save some pain by using jQuery, and the json2.js library.  We’ll implement a simple user panel consisting of three parts:  An n2f template file for the layout, a JavaScript file to manage the AJAX, and the data.php to respond to the requests.

For a template file, this one ends up being a little complicated.  It needs to respond to two potential states (logged in, and not logged in) and provide the correct interface.  So user_panel.tpl looks like:

</p>
<p><style><br />
#user_panel {border: 1px solid lightGrey; padding: 4px}<br />
#user_panel .greeting {font-weight: bold; color: grey; margin-right: 10px;}<br />
#user_panel form {margin: 0 0;}<br />
#user_panel label {display: inline;}<br />
#user_panel input {display: inline;}<br />
</style><br />
<div class="user_panel" id="user_panel"><br />
<% if(! ($user->user_id > 0 && $user->loggedin == 1)) : %><br />
<a name="login"></a><br />
<form action="./?nmod=main" method="post"><br />
<span class="greeting">You need to log in<%if($user->name != ''){echo ', '.$user->name;}%>!</span><br />
<label for="username">Username:</label><br />
<input type="text" name="username" id="username" /><br />
<label for="password">Password:</label><br />
<input type="password" name="password" id="password" /><br />
<input type="submit" name="login" id="login" value="login" onclick="user_panel.doLogin();return false;"/><br />
</form><br />
<p><br />
<% if(isset($message) &amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp; $message !== '') : %><br />
Click <strong><a href="javascript: //;" onclick="toggleDiv('user_messages');">here</a></strong> to hide messages.<br />
<div id='user_messages' class='main-content'><%$message%></div><br style='clear:both;' /><br />
<% else: %><br />
<div id='user_messages' class='main-content'></div><br style='clear:both;' /><br />
<% endif; %><br />
</p><br />
<% else: %><br />
<a name="user"></a><br />
<form action="./?nmod=main" method="post"><br />
<span class="greeting">Welcome, <%$user->name;%></span><br />
<span style="font-weight:bold;margin:5px;">Username:</span><%$user->username;%><br />
<span style="font-weight:bold;margin:5px;">Lasttime:</span><%$user->lasttime;%><br />
<input style="margin-left:10px" type="submit" name="logout" id="logout" value="logout" onclick="user_panel.doLogout();return false;"/><br />
</form><br />
<p><br />
<% if(isset($message) &amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp; $message !== '') : %><br />
Click <strong><a href="javascript: //;" onclick="toggleDiv('user_messages');">here</a></strong> to hide messages.<br />
<div id='user_messages' class='main-content'><%$message%></div><br style='clear:both;' /><br />
<% else: %><br />
<div id='user_messages' class='main-content'></div><br style='clear:both;' /><br />
<% endif; %><br />
</p><br />
<% endif; %><br />
</div></p>
<p>

I cheated and put some style code into a style block at the top. In real life that should be in a .css file. The whole panel fits inside a singe div, and appears as a single line login bar, with any messages appearing directly below. If the user is not logged in, the bar contains a form to collect their username and password. If the user is logged in, the bar shows a couple pieces of information and a logout button. The login and logout buttons call functions defined in in user_panel.js:

</p>
<p>/* create the user_panel js object */<br />
var user_panel = {};</p>
<p>/* add redirect configuration */<br />
user_panel.redirect = {};<br />
user_panel.redirect.login = './?nmod=main';<br />
user_panel.redirect.logout = './?nmod=main';</p>
<p>/* add ajax submission configuration */<br />
user_panel.submit = {};<br />
user_panel.submit.login = './?nmod=main&amp;amp;amp;amp;amp;nret=data';<br />
user_panel.submit.logout = './?nmod=main&amp;amp;amp;amp;amp;nret=data';</p>
<p>/* add ajax functions for login and logout*/<br />
user_panel.doLogin = function(state) {<br />
if (state == undefined) {<br />
/* We are trying to submit the form */<br />
var username = $("#username").val();<br />
var password = $("#password").val();<br />
if (username == '' || password == '') {<br />
/* complain */<br />
$("#user_messages").text("You must enter both username and password to log in");<br />
} else {<br />
/* attempt login */<br />
$.ajax({<br />
type: 'POST',<br />
url: user_panel.submit.login,<br />
data: {'action':'login', 'username':username, 'password':password},<br />
success: user_panel.doLogin<br />
});</p>
<p>}<br />
} else {<br />
/* We are getting a response from the server */<br />
try {<br />
state = JSON.parse(state);<br />
if(state.status == 'error') {<br />
$("#user_messages").html(state.message);<br />
} else {<br />
window.location = user_panel.redirect.login;<br />
}<br />
} catch(e) {<br />
$("#user_messages").text("json parse error");<br />
}</p>
<p>}<br />
}</p>
<p>user_panel.doLogout = function(state) {<br />
if (state == undefined) {<br />
/* We are trying to submit the request */<br />
$.ajax({<br />
type: 'POST',<br />
url: user_panel.submit.logout,<br />
data: {'action':'logout'},<br />
success: user_panel.doLogout<br />
});</p>
<p>} else {<br />
/* We are getting a response from the server */<br />
try {<br />
state = JSON.parse(state);<br />
if(state.status == 'error') {<br />
$("#user_messages").html(state.message);<br />
} else {<br />
window.location = user_panel.redirect.logout;<br />
}<br />
} catch(e) {<br />
$("#user_messages").text("json parse error");<br />
}</p>
<p>}<br />
}</p>
<p>

The doLogin function posts a request to the address in user_panel.submit.login and the doLogout function posts a request to the address in user_panel.submit.logout. In the example, these both point to ./?nmod=main&nret=data. If you wanted to have a special module just for handling authentication, you make that ./?nmod=authenticate&nret=data. Notice nret=data. This is how you tell n2f to use the data.php.

We start with:

</p>
<p><?php<br />
global $user;</p>
<p>if(isset($_REQUEST['action'])) {<br />
// Someone wanted us to do something</p>
<p>$message = '';  // message to be sent back<br />
$status = ''; // status to be sent back<br />

That just gets $user, makes sure we were asked to do something, and sets up a couple variables. We’ll send those variables back to the caller at the end. First the code to handle a log in request:

</p>
<p>// If user is trying to log in<br />
if(($_REQUEST['action'] == 'login') &amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp; isset($_REQUEST['username']) &amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp; isset($_REQUEST['password'])) {</p>
<p>// Assume success<br />
$status = "success";</p>
<p>// If already logged in, need to log out first<br />
if(($user->user_id > 0 &amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp; $user->loggedin == 1 )) {<br />
$status = "error";<br />
$message .= "You must log out before logging in as a different user.<br />";<br />
// Else try to log them in<br />
} else {<br />
$user->clearErrors()->login($_REQUEST['username'], $_REQUEST['password']);<br />
// If login was not successful<br />
if($user->user_id == 0 || $user->hasError()) {<br />
$status = "error";<br />
$message .= "Unable to log you in. <br />";<br />
if($user->hasError()) {<br />
$errors = $user->getErrors();<br />
foreach($errors as $err) {<br />
$message .= $err."<br />";<br />
}<br />
}<br />
}<br />
}<br />

Then the code to handle a logout request:

</p>
<p>// Else if user is trying to log out<br />
} elseif(($_REQUEST['action'] == 'logout')) {</p>
<p>// Assume success<br />
$status = "success";</p>
<p>// Ask for logout<br />
$user->clearErrors()->logout();<br />
// If logout was not successful<br />
if($user->loggedin !== 0 || $user->hasError()) {<br />
$status = "error";<br />
$message .= "Unable to log you out. <br />";<br />
if($user->hasError()) {<br />
$errors = $user->getErrors();<br />
foreach($errors as $err) {<br />
$message .= $err."<br />";<br />
}<br />
}<br />
}<br />
}<br />

And finally, packing the variables into some json to send back to the browser

</p>
<p>// All done with the proccessing - send the result back to the caller<br />
echo json_encode(array('status'=>$status, 'message'=>$message));<br />
}<br />
?></p>
<p>

Notice that there is no template object or template rendering required for the data.php to do it’s job. It just pulls some information from the request, does what it needs to, and then echos a result back to the browser. Easy.

You can download all files for this tutorial here.

January 31, 2010

N2F Yverdon Cookbook: A Useful $user

Filed under: Yverdon — Tags: — z|MattH @ 9:50 pm

Almost every site needs some security, or maybe user specific settings.  The n2f $user object exists for this purpose.  Since every site’s needs are different, the default $user is more of a template than anything else.  Luckily, expanding it is pretty easy.

The default user (see code here) only manages two properties, and only stores them in the session.  This minimalist approach is enough to track a user through the site for the length of their session, with code like:


global $user;
if($user->user_id === 0) {
// New user session
$user->user_id = now().rand();
} else {
// This is an existing session - do something about it
}

What if you want your users to be able to log in and you want to record it in a database?  Now we need to upgrade the user extension.   Let’s take a look at the steps.

First we add a simple table in the database:


CREATE TABLE IF NOT EXISTS `login` (
`user_id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(30) NOT NULL,
`password` varchar(50) NOT NULL,
`lasttime` int(11) DEFAULT NULL,
`name` varchar(100) NOT NULL,
`email` varchar(100) NOT NULL,
`admin` tinyint(1) NOT NULL DEFAULT '0' COMMENT '1 = admin 0 = regular',
`active` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1 = active 0 = inactive',
`loggedin` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`user_id`)
);

Now we add the attributes to the n2f_user:


class n2f_user {
public $user_id;
public $username;
public $lasttime;
public $name;
public $email;
public $admin; // 1 = admin 0 = regular
public $active; // 1 = active 0 = inactive
public $loggedin; // 1 = logged in

private $errors; // records any error messages

I also added a private variable to store any potential issues our object might experience. We need to initialize the new attributes in the constructor like so:


public function __construct($user_id = null) {

// Initialize the properties
$this->user_id = 0;
$this->username = '';
$this->lasttime = time();
$this->name = '';
$this->email = '';
$this->admin = 0; // 1 = admin 0 = regular
$this->active = 0; // 1 = active 0 = inactive
$this->loggedin = 0; // 0 = logged out

$this->errors = array(); // a empty array to hold error messages

if($user_id !== null) {
// Try to fetch the user from the database
$this->fetch($user_id);
}

// Return ourself for chaining
return ($this);
}

I made the constructor take an optional user id and try to do a fetch, to simplify steps in the code later. Here is the code for fetch():


public function fetch($user_id) {
global $db;

$sql = "SELECT * FROM `login` WHERE `user_id` = ?";
$query = $db->query($sql);
$query->addParam('user_id', $user_id, MYSQLIDB_TYPE_INTEGER);
$query->execQuery();

if($query->isError()) {
// Nevermind, we seem to have a db issue
$this->errors[] = "User database not available";
} elseif($query->numRows() !== 1) {
// Nevermind, we didn't find the user
$this->errors[] = "User not found in database";
} else {
// Load the user with the data
$row = $query->fetchRow();
$this->user_id = $row['user_id'];
$this->username = $row['username'];
$this->lasttime = $row['lasttime'];
$this->name = $row['name'];
$this->email = $row['email'];
$this->admin = $row['admin'];
$this->active = $row['active'];
$this->loggedin = $row['loggedin'];
}

// Return ourself for chaining
return ($this);
}

We can’t fetch anything that isn’t in the database so we need a function to register users like so:


public function register($username, $password, $name, $email, $admin, $active) {
global $db;
// Check that this username is not already in use
$sql = "SELECT * FROM `login` WHERE `username`=?";
$query = $db->query($sql);
$query->addParam('username', $username, MYSQLIDB_TYPE_STRING);
$query->execQuery();

if($query->isError()) {
// We failed because of a db problem
$this->errors[] = "User database not available";
} elseif($query->numRows() > 0) {
// The name is already taken
$this->errors[] = "Username is not available";
} else {
// Encrypt the password
$password = encStr($password);

// Add the new user
$sql = "INSERT INTO `login` SET `username`=?, `password`=?, `name`=?, ";
$sql .= "`email`=?, `admin`=?, `active`=?";
$query = $db->query($sql);
$query->addParam('username',	$username, 	MYSQLIDB_TYPE_STRING);
$query->addParam('password',	$password, 	MYSQLIDB_TYPE_STRING);
$query->addParam('name', 		$name, 		MYSQLIDB_TYPE_STRING);
$query->addParam('email',		$email, 	MYSQLIDB_TYPE_STRING);
$query->addParam('admin',		$admin, 	MYSQLIDB_TYPE_INTEGER);
$query->addParam('active',		$active, 	MYSQLIDB_TYPE_INTEGER);
$query->execQuery();

if($query->isError()) {
// We failed because of a db problem
$this->errors[] = "User database not available";
} else {
// Update user_id
$this->user_id = $query->fetchInc();
}
}
// Return ourself for chaining
return ($this);
}

And we’ll also need to be able to update those attributes in the database, so:


public function store($password = null) {
global $db;

$sql = "UPDATE `login` SET `username`=?, `name`=?, `email`=?, ";
$sql .= "`admin`=?, `active`=?, `lasttime`=?, `loggedin`=?";
if($passsword !== null) {
$password = encStr($password);
$sql .= ", `password`=?";
}
$sql .= " WHERE `user_id`=?";
$query = $db->query($sql);
$query->addParam('username',	$this->username, 	MYSQLIDB_TYPE_STRING);
$query->addParam('name', 		$this->name, 		MYSQLIDB_TYPE_STRING);
$query->addParam('email',		$this->email, 		MYSQLIDB_TYPE_STRING);
$query->addParam('admin',		$this->admin, 		MYSQLIDB_TYPE_INTEGER);
$query->addParam('active',		$this->active, 		MYSQLIDB_TYPE_INTEGER);
$query->addParam('lasttime',	$this->lasttime, 	MYSQLIDB_TYPE_INTEGER);
$query->addParam('loggedin',	$this->loggedin, 	MYSQLIDB_TYPE_INTEGER);
if($passsword !== null) {
$query->addParam('password',	$password, 		MYSQLIDB_TYPE_STRING);
}
$query->addParam('user_id',		$this->user_id, 	MYSQLIDB_TYPE_INTEGER);
$query->execQuery();

if($query->isError()) {
// We failed because of a db problem
$this->errors[] = "User data was not stored: ".$query->fetchError();
}

// Return ourself for chaining
return ($this);
}

Of course, part of the point is to be able to login and logout. Notice that they update the $sess object. We’ll see why in a moment.


public function login($username,$password) {
global $db, $sess;

if($username == null || $password == null) {
// We failed because of invalid parameters
$this->errors[] = "Username and password are required for login";
} else {

// Encrypt the password
$password = encStr($password);

$sql = "SELECT * FROM `login` WHERE `username`=? AND `password`=? AND `active`=?";
$query = $db->query($sql);
$query->addParam('username', $username, MYSQLIDB_TYPE_STRING);
$query->addParam('password', $password, MYSQLIDB_TYPE_STRING);
$query->addParam('active', 1, MYSQLIDB_TYPE_INTEGER);
$query->execQuery();

if($query->isError()) {
// We failed because of a db problem
$this->errors[] = "User database not available";
} elseif($query->numRows() === 0) {
// Username or Password does not match
$this->errors[] = "Incorrect username or password";
} elseif($query->numRows() > 1) {
// To prevent this, never allow two users to end up with the same username
$this->errors[] = "Duplicate users found for login";
} else {

// Load the data from the row
$row = $query->fetchRow();
$this->user_id = $row['user_id'];
$this->username = $row['username'];
$this->name = $row['name'];
$this->email = $row['email'];
$this->admin = $row['admin'];
$this->active = $row['active'];

// Update their lasttime and loggedin
$this->lasttime = time();
$this->loggedin = 1;
$this->store();

// Update the session
$sess->set('n2f_sess_user', $this->user_id);
}
}

// Return ourself for chaining
return ($this);
}

public function logout() {
global $db, $sess;

// Update their lasttime and loggedin
$this->lasttime = time();
$this->loggedin = 0;
$this->store();

// Update the session
$sess->set('n2f_sess_user', 0);

// Return ourself for chaining
return ($this);
}

The last things in our new n2f_user class are the mindlessly obvious error managing functions:


public function hasError() {
return (count($this->errors)>0);
}

public function getError() {
return $this->errors[count($this->errors)-1];
}

public function getErrors() {
return $this->errors;
}

public function clearErrors() {
$this->errors = array();

// Return ourself for chaining
return ($this);
}

public function addError($error) {
$this->errors[] = $error;

// Return ourself for chaining
return ($this);
}

}

And now for the magic that keeps a user recognized for the life of their session. This is also the reason that login and logout touched the $sess object.


// Hook the N2F_EVT_CORE_LOADED event
$n2f->hookEvent(N2F_EVT_CORE_LOADED, 'init_user');

function init_user(n2f_cls &amp;amp;amp;amp;amp;amp;amp;$n2f, $results) {
// Check if there was a massive failure or if the session extension isn't loaded
if ($results === false || $n2f->hasExtension('session') === false) {
// And if either is the case, just stop here
return(null);
}

// Pull in global variable(s)
global $user, $sess;

// Initialize the timeout stamp
$timeout = (time() - 300);

// Check if there is a session user
if ($sess->exists('n2f_sess_user')) {
// There is, so pull them out
$user_id = $sess->get('n2f_sess_user');

// Fetch them from the db
$user = new n2f_user($user_id);

// First check if they've timed out
if ($user->lasttime < $timeout) {
// And if so, reset their properties
$user->user_id = 0;
$user->active = 0;
$user->admin = 0;
$user->addError("User found, but session timed out!");

// And log them out
$user->logout();

// If we're supposed to track warnings..
if ($n2f->debug->showLevel(N2F_DEBUG_WARN)) {
// Throw a warning to the main debug object
$n2f->debug->throwWarning(N2F_WARN_USER_TIMEOUT, S('N2F_WARN_USER_TIMEOUT'), 'system/extensions/user.ext.php');
}
// Else if they are logged in
} elseif($user->loggedin == 1) {
// Update their lasttime
$user->lasttime = time();
$user->store();
}
} else {
// Otherwise, initialize a new user and set the session
$user = new n2f_user();
$sess->set('n2f_sess_user', $user_id);
}

// If we're supposed to track notices..
if ($n2f->debug->showLevel(N2F_DEBUG_NOTICE)) {
// Throw a notice to the main debug object
$n2f->debug->throwNotice(N2F_NOTICE_USER_INIT, S('N2F_NOTICE_USER_INIT'), 'system/extensions/user.ext.php');
}

// And stop processing, we've got nothing left to do!
return(null);
}

Since that function is registered to be called on every page load, it handles keeping users logged in across page loads, and also logging them out after the timeout. With all this in place, you can now go to a page.php in a module and write code like:


// If user is logged in
if($user->loggedin == 1 &amp;amp;amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp;amp;amp; $user->user_id > 0) {
// If the user as an admin
if($user->admin == 1) {
// Set up and send them to the admin page
} else {
// Set up and send them to the regular users' page
}
} else {
// Send visitor to the login page
}

Isn’t that nice?  If you’d like a full copy of the code, I’ve uploaded the SQL and PHP here.

January 29, 2010

N2F Yverdon Cookbook: Get Records from a Database

Filed under: Yverdon — Tags: — z|MattH @ 3:19 pm

A common task is retrieving records from the database.  Despite the existence of N2F Yverdon Basics: Part 4 - The Database Engine, this common task is often confusing to people used to using the mysql_* functions.  The n2f database system takes care of most of the annoying code for you, and setup is easy.  Getting a batch of records from your database is as simple as the following two steps:

1.  Configure the database in system/config.inc


// name of the db extension you want to use
$cfg['db']['type']    = 'mysqli';
// name of the server that hosts the database
$cfg['db']['host']    = 'localhost';
// name of the database on the server
$cfg['db']['name']    = 'my_database';
// name of the user for this database
$cfg['db']['user']    = 'my_db_user';
// password for the user
$cfg['db']['pass']    = 'my_db_pass';
// file to be used if using the file db extension
$cfg['db']['file']    = '';

2.  Where you want the records, use some code like:


global $db;
$sql = "SELECT * FROM `table` WHERE `field`=?";
$query = $db->query($sql);
$query->addParam('field','value', <DBEXT>_TYPE_CONSTANT);
$query->execQuery();

if($query->isError()) {
// deal with a db error here
} elseif($query->numRows() === 0) {
// deal with no results here
} else {
// do whatever it was you wanted with the data
$records = $query->fetchRows();
}

Remember to replace <DBEXT>_TYPE_CONSTANT with the appropriate constant for the database extension and data type you are using.  For a full list of the methods available on the database and query objects, see the phpDoc for n2f_database and n2f_database_query.

N2F Yverdon: The mod.ext.php File

Filed under: Development, Yverdon — Tags: , , , , — z|Andrew @ 10:27 am

One of the new features introduced with v0.2 is the mod.ext.php feature.  This feature was a request from a few users and can be a useful way to add functionality to your system.

The premise behind mod.ext.php is similar to that of the sys.ext.php file.  However, instead of being included globally, the mod.ext.php file is only included when a specific module is brought into scope.  This is particularly helpful if you have large/complicated modules and need some classes or global information that is only necessary when that module is in use.

As an example, let’s pretend that we have a set of classes that are particularly large.  We only need these classes inside of our documentation module, so we use a mod.ext.php file to make that possible.  Our mod.ext.php is saved in the module directory at ~/modules/documentation/mod.ext.php, just like a sys.ext.php file.  The code in the file might look like this:

<?php

require('cls/docmgr.cls.php');
require('dat/docs.dat.php');

?>

Just as with the sys.ext.php files we don’t have to do anything special to use mod.ext.php.  Once you create one and the module is called, it will be automatically called into scope before the module’s code is executed.

- Andrew

January 25, 2010

Yverdon v0.2 Changes/Additions

Filed under: Uncategorized — z|Andrew @ 2:12 pm

Now that we’ve finally released v0.2 officially, it seems appropriate to outline the various changes and additions that were done between v0.1 and v0.2.  Lots of testing and planning went into v0.2’s release, so I’ll do my best to summarize in a clear way what we have for the end result.

Bug Fixes

  • Fixed bug with n2f_paginate::listPages() incorrectly listing page numbers
  • Fixed bug with n2f_database::close() trying to close non-objects
  • Fixed bug with n2f_events::hitEvent() throwing error from null arguments
  • Corrected cache method used for dynamic template extension
  • Fixed numerous bugs with MySQLi db extension (complete rewrite)
  • Removed N2F stamp from showing on ?nret=data requests
  • Changed dynamic template parsing method to not use short open tags
  • Fixed relative path issue with n2f-enabled subdirectories
  • Fixed notice thrown by recursive calls to debugEcho()
  • Fixed bug with dynamic template breaking XML declarations
  • Fixed logging bug with setting non-string fields for dynamic templates
  • Changed failure procedure for database extension loading

New Features

  • Added MySQL database extension
  • Added PostgreSQL database extension
  • Added SQLite database extension
  • Added simple meta information system for all extensions
  • Added IsSuccess() function for easily checking n2f_return objects for success/failure status
  • Added notification events to template system
  • Added ability to return cached object from n2f_cache::startCaching()
  • Added N2F version constant (N2F_VERSION)
  • Added port/sock options to database configuration
  • Added n2f_database_query::addParams()
  • Added OS_WINDOWS constant
  • Added ‘mod.ext.php’ modular extension functionality
  • Added optional arguments to n2f_database_query constructor
  • Added ‘global’ skin aliases (for all themes)
  • Created new dynamic argument system for template and database objects
  • Added str_replace_once()
  • Added FirePHP extension
  • Added Memcache capabilities to n2f_cache
  • Added garbage collection to n2f_cache

We’ll do what we can to cover some of the new features here in the coming weeks, as well as to start putting up some better tutorials on the Wiki for how to use the framework in general.

 

- Andrew

July 24, 2009

N2F Yverdon: Using Securimage PHP CAPTCHA

Most people building a website today are familiar with the need to have a CAPTCHA on their site.  For years I’ve had a set of small functions I built for the purpose of easily fulfilling this request for clients and internal sites.  The solution worked but it was poorly packaged and never improved upon.

I recently started doing a site for a friend and as usual found the need for a CAPTCHA on the contact form.  Given how many projects I have going on, I decided it was time to give up and see if the PHP community had any better solution than my couple of functions.  Sure enough, someone has built a pretty useful CAPTCHA system.  Securimage, built by drew010, is a simple CAPTCHA class that allows you to even do audio files of the CAPTCHA’s text.  Within 10 minutes I had it working as an extension in N2F Yverdon v0.2 RC, so I thought I’d share the fruits of my minimal labor to get this great utility working.

Download Here

The above download is a ZIP with the extension already setup with the proper file paths (you should be able to just drag the system folder in the zip into the root directory of your install).  The only caveat about this setup is that you will need the securimage extensions directory to be accessible, but you can use virtual directories to do this and change the constants I’ve defined in securimage.ext.php.  As a quick example of usage, here’s what it looks like in a page.php:

<?php

global $n2f;

if (!$n2f->hasExtension('securimage')) {
    $n2f->loadExtension('securimage');
}

$si = new Securimage();

?>
<img id="captcha" src="<?php echo SI_IMAGE_PATH;?>" alt="CAPTCHA Image" />
<a href="<?php echo SI_AUDIO_PATH;?>" style="font-size: 13px">(Audio)</a>

Obviously this won’t do much, but it does show you how it works (as well as how to include the audio feature).  When we release Yverdon v0.2 and the new site, I’ll put up some proper documentation for this extension.

- Andrew

June 26, 2009

N2F Yverdon: v0.2 Improvements

Filed under: Development, Yverdon — Tags: , , , , , , , , , — z|Andrew @ 5:51 pm

With today’s development release of Yverdon v0.2, I decided I would take some time to highlight a few of the smaller changes we’ve implemented in the next iteration of our PHP 5 framework.

MySQLi Database Extension
We were lucky to be working with Waterfall Data Solutions from the start of N2F Yverdon, so we’ve had some great testing done for us on some specific extensions and sets of core functionality.  One of the biggest contributions that we received was massive amounts of test data for the MySQLi database extension.  With all of the data we ended up doing a complete rewrite of the extension, and we still have more improvements that we weren’t able to get into the v0.2 release.  The rewrite concentrated on resolving issues resulting from the buggy implementation of the MySQLi extension in PHP, as well as some irregularities we found when dealing with parametrized queries.  This all leads to more secure interactions with MySQL through the extension, which is never a bad thing.

PHP 5.3 Compatibility
It makes us very proud to say that we sat down with the intention of making Yverdon v0.2 fully compatible with the PHP 5.3 RC’s, and we ended up changing about 5 lines of code in total.  It’s very possible that we’ll be making more changes in the future to account for bugs we haven’t yet found, but at the least we’re already working hard to make sure we don’t fall behind the times in the PHP world.

Memcache Caching
Chris (ctd1500) put in a bit of work to add memcache functionality to our sponsored cache extension.  This means that anything which uses the cache extension can be easily configured to take advantage of a memcache installation, including the template engine.

New Database Extensions
Another set of additions that have been taking a lot of time are the new database extensions we’ve added to our sponsored list.  Now you have access to MySQL, SQLite and PostgreSQL through our new sponsored extensions.  One of the last things we’re doing to the code is running these three new engines through a test routine that will be used on every release moving forward.

There’s a lot more that we’re working on releasing with the first stable release of v0.2, but we’ll save some of them for that time just to keep it that much more of a surprise.  Hopefully you have a better idea of what we’ve been working on with Yverdon over the past 7 or 8 months.

- Andrew

May 22, 2009

N2F Yverdon: What Is A sys.ext.php File?

In Yverdon v0.1, we added the ability to use sys.ext.php files inside of each module.  Their purpose is very simple and yet sometimes beautifully useful.

The Problem
Sometimes we find ourselves working on very large projects.  Inside of those large projects, the tendency is to have each developer concentrate on specific modules or extensions.  The module/extension layout works great in most situations, but it occurred to us that it wouldn’t always be appropriate to put certain system-wide functionality into an extension.  As an example, it would seem odd to build an entire extension just to create some template aliases for one level of the site.

The Solution
Our solution, the sys.ext.php file, allows a developer to include code system-wide on a particular directory level without having to create an extension.  The sys.ext.php files are included after all extensions have been included but before any modules are included.  This gives you the ability to use any of the constructs provided by your extensions and have your code execute before a page.php or data.php is called.  Going back to our example, you will see that the default installation’s main module has a sys.ext.php file inside of it, with the following contents:

<?php

/***********************************************\
* N2F Yverdon v0                              *
* Copyright (c) 2008 Zibings Incorporated     *
*                                             *
* You should have received a copy of the      *
* Microsoft Reciprocal License along with     *
* this program.  If not, see:                 *
* <http://opensource.org/licenses/ms-rl.html> *
\***********************************************/

// Create our template aliases
n2f_template_dynamic::addAlias('header', 'header', 'main', N2F_REL_PATH.'modules', 'default');
n2f_template_dynamic::addAlias('footer', 'footer', 'main', N2F_REL_PATH.'modules', 'default');

?>

This file only needs to be in the main module for all of the other modules in the root level to have access to it’s values.  The result of this code is that now all templates executed in the root level will now be able to use aliases for <%header%> and <%footer%>.

Conclusion
The sys.ext.php files are easy to use and very handy in certain situations.  Hopefully this blogtorial has been helpful in showing you the basic idea behind sys.ext.php files.  Yverdon v0.2 will also be introducing a new file, the mod.ext.php file, but we’ll talk about that when the time comes.  ;)

- Andrew

May 4, 2009

N2F Yverdon Basics: Part 4 - The Database Engine

Filed under: Development, Yverdon — Tags: , , , , , — z|Andrew @ 9:47 pm

The final part in our series is going to touch quickly on our database engine.  With Yverdon v0.1, the engine is limited to use with the MySQLi library in PHP.  With v0.2, we will be adding many more libraries to the engine’s capabilities.  As with the template engine, each new library is added through a database extension.

Connecting The Engine
So long as you configure the following lines of your config.inc.php file (located in /home/someuser/public_html/system), the system will take care of connection your database object.


// Database configuration
$cfg['db']['type']           = 'mysqli';
$cfg['db']['host']           = '';
$cfg['db']['name']         = '';
$cfg['db']['user']           = '';
$cfg['db']['pass']           = '';

Make sure you use the proper information for your MySQL database, otherwise the MySQLi extension will throw an error and not work properly.

Starting A Query
The database engine works in a way that separates queries into their own objects.  This allows you to keep query information encapsulated in one object while executing another query on the connection, all without the fear of overwriting the query that had run before.  Initializing a query is very simple:


// Grab the global database engine object
global $db;

// Create a new query
$query = $db->query("SELECT * FROM `members` WHERE `active` = ?");

The $query variable is now an instantiated database query object, which you can use to do a number of different operations.

Using Parameters
Now that you have the $query object available for use, let’s go ahead and take care of the parameter we’re setting up in the query (”.. `active` = ?”).  Since we’re using the MySQLi extension (to both PHP and Yverdon), parameters are only able to be used as question marks (?).  The above query therefore gives us one parameter we must fill in before executing the query.  For our demonstration we’d like to get all active members, which means we need to get all members from the database who’s `active` field has a value of ‘1′.  This is accomplished via the following code:


$query->addParam("", 1, MYSQLIDB_TYPE_INTEGER);

The first argument to the addParam() method is a parameter name, which we leave blank because MySQL does not accept named parameters.  Instead, MySQL relies on the order with which you provide the parameters to determine which parameter is being populated.  The second argument is the value being passed for the parameter.  Finally, the last argument is the type of the parameter.  The type can be one of the following constants:

MYSQLIDB_TYPE_BLOB
MYSQLIDB_TYPE_DOUBLE
MYSQLIDB_TYPE_INTEGER
MYSQLIDB_TYPE_STRING

The argument types are important since they tell the database engine how to sanitize the parameter, thus making your queries safer against vulnerabilities such as SQL injections.

Executing, Catching Errors and Returned Data
Once you have supplied all parameters for your query, it’s time to execute and try receiving data from the query.  The execQuery() method attempts to run the query against the database.  When executed, the $query object will contain more information available through various methods to help you determine what happened with your query:


$query->execQuery();

if ($query->isError()) {
    die($query->fetchError());
} else {
    if ($query->numRows() > 0) {
        $active_users = $query->fetchRows();
    } else {
        die("No active users available");
    }
}

After execQuery() is called, we check whether or not an error occurred using the isError() method, and should it return true we stop processing our script while outputting the last error message thrown inside of the $query object using fetchError().

If isError() returns false, no errors were thrown by the engine and we attempt to get data from the query’s resultset.  The numRows() method returns an integer representing the number of rows present in the query’s resultset, and should there be any rows we use the fetchRows() method to get all rows returned by the query.  There are multiple ways of working with the n2f_database_query object, but for the sake of time I’ll just mention that they can be found in our phpDocs.

Bringing It All Together
Just so you can see the full code, here’s everything we’ve just done put together:


// Grab the global database engine object
global $db;

// Create a new query
$query = $db->query("SELECT * FROM `members` WHERE `active` = ?");

// Assign the `active` parameter
$query->addParam("", 1, MYSQLIDB_TYPE_INTEGER);

// Execute the query
$query->execQuery();

// If there was an error
if ($query->isError()) {
    // Die and print the last error
    die($query->fetchError());
} else {
    // Otherwise, if we have rows in the resultset
    if ($query->numRows() > 0) {
        // Pull all rows out in a multi-dimensional array for use elsewhere
        $active_users = $query->fetchRows();
    } else {
        // No rows, no continue
        die("No active users available");
    }
}

Summary
This is the conclusion of the 4 part crash course on N2F Yverdon v0.1.  We’re getting ready for v0.2 and are excited to announce that there are quite a few fixes and additions coming to the database engine, the template engine and the system in general.  If you have any questions, feel free to join us on IRC or ask on our forums.  Thanks for joining us, we hope you enjoy!

- Andrew

November 10, 2008

N2F Yverdon Basics: Part 3 - The Template Engine

Part 2 of the series introduces us to the layout of N2F Yverdon module. In this part of the series, I’ll be showing you the basics of the dynamic template extension built for our template system. Our template system, while not required for use of Yverdon, is a fast and simple solution to the problem of separating the presentation and business layers of an application.  In plain English, we just mean to say we’re offering a way to separate the code you write from the way it’s displayed.

Template Extensions

N2F Yverdon utilizes a system of extensions for expanding the capabilities of the framework.  When building the template engine, we realized that there was a lot of value in being able to change how the template system worked in certain situations.  As a result we decided to leverage the extension system for our template engine.  First v0.1, we have created the first of these template extensions, called the Dynamic extension.  That is the flavor of template we will be using in this entry.

Directory Structure
One of the first things to know about how dynamic templates work is the directory structure you must use for storing your templates.  We opted to organize templates by skins inside of each module.  The name for the skin Yverdon comes configured to use is simply, ‘default’.  If you are inside of an ‘aboutme’ module, your template directories will look as such:

/home/someuser/public_html/modules/aboutme/tpl
/home/someuser/public_html/modules/aboutme/tpl/default

You can create however many skins you like, but we’re not going to get into using different skins in this post.

The Template File
The system expects templates to have a .tpl extension on them, but beyond that naming the extension is up to your wildest dreams and desires.  As a habit, the N2F Yverdon staff almost always uses index.tpl as the base template for a module.  We’re going to create three files for this post:

/home/someuser/public_html/modules/aboutme/tpl/default/index.tpl
/home/someuser/public_html/modules/aboutme/tpl/default/header.tpl
/home/someuser/public_html/modules/aboutme/tpl/default/footer.tpl

We’ll put the following code into the header.tpl file:

<html>
<head>
<title>My Page</title>
</head>
<body>

The following code into the index.tpl file:

This is the body of my page.

And the following code into the footer.tpl file:

</body>
</html>

Once all the files are saved, we’ll move onto creating one of the nice features of the dynamic template extension.

Aliases Are Short For Something
With a dynamic template, we found the need to include blocks of HTML here and there across multiple pages and modules.  To solve this common problem, we created the alias system.  There are lots of different terms and ways of explaining this part of the extension, but the basic idea remains the same.  All this does is allow you to break pieces of your markup (HTML/XHTML/CSS) into separate .tpl files and use it across the rest of your Yverdon installation.  To define aliases, you will create a page.php file similar to this:

<?php

// Pull the global n2f object
global $n2f;

// Define our header and footer aliases
n2f_template_dynamic::addAlias('header', 'header', 'aboutme', './modules', 'default');
n2f_template_dynamic::addAlias('footer', 'footer', 'aboutme', './modules', 'default');

?>

All aliases are currently defined using the n2f_template_dynamic::addAlias() method.  You can view the method signature here, but the quick and dirty is that the first argument is the text to match in templates for this alias, the second argument is the name of the .tpl file to pull from, the third is the module the .tpl file comes from, the fourth is the base path (you won’t need to change that normally) and the final argument is the skin where the alias resides.

To use a defined alias, you must put the text in between the <% and %> tags.  We’ll put these two to use inside of our index.tpl file, making it now look like this:

<%header%>
This is the body of my page.
<%footer%>

Bringing It All Together
Finally, we get to use our template.  Because of the aliases we made, we only need to display one template.  Anytime you wish to display a template, you must create a new n2f_template object.  I’ll add the code to our page.php file first, and explain after:

<?php

// Pull the global n2f object
global $n2f;

// Define our header and footer aliases
n2f_template_dynamic::addAlias('header', 'header', 'aboutme', './modules', 'default');
n2f_template_dynamic::addAlias('footer', 'footer', 'aboutme', './modules', 'default');

// Create the template object
$tpl = new n2f_template($n2f, 'dynamic');

// Set the module and file to use
$tpl->setModule('aboutme');
$tpl->setFile('index');

// Render and display the template
$tpl->render();
echo($tpl->fetch());

?>

An n2f_template object can be initialized several different ways, but the way used here offers the global $n2f object as the first argument (this helps with default configuration) and the string ‘dynamic’ as the second argument to tell the n2f_template object that it is to use the dynamic template extension.  The n2f_template::setModule() and n2f_template::setFile() methods are fairly self-explanatory.  The n2f_template::render() method prepares the template for being displayed.  This is where the work is done to pull the <%header%> and <%footer%> aliases into the template.  The n2f_template::fetch() method does little more than return the string produced by the template, which we immediately echo to the browser.

When you load up your browser to the aboutme module (http://www.mysite.com/?nmod=aboutme), the template system will produce the following markup from your work:

<html>
<head>
<title>My Page</title>
</head>
<body>
This is the body of my page.
</body>
</html>

Summary
That’s it!  There are many more things you can do with the template engine, and we’re still working on building the proper documentation for you to get into the nitty-gritty of the system.  On top of that, we have at least one other template extension planned for release with Yverdon v0.2.  If you have any problems, feel free to drop by IRC or on our forums so we can give you a hand.  Part 3 of the series will be about using our MySQLi database extension, so be sure to stay tuned!

- Andrew

Older Posts »

Powered by WordPress