N2F Training Team Blogtorials

July 15, 2011

N2F Yverdon v0.5 Changelist

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

The following changelist covers everything we’ve done with v0.5:

- Fixed code-documentation throughout system
- Changed order of parameters for n2f_cache::setObject()
- Added global static methods to template system (no more calling n2f_template_dynamic directly)
- Added derivative support to template system (fallback from skin to default skin if missing)
- Created *::setFile() method for template engines to support file extensions
- Fixed attribute parser to handle spaces
- Removed automatic support for nested static blocks (buggy, can still be done with manual calls)
- Fixed minor bug with n2f_database initialization
- Fixed bug with *DB_CAN_CACHE constant definitions
- Moved call to $n2f->dumpDebug() into _cleanResources()
- Added a new template to the trunk (thanks to CButcher)
- Added n2f_return::isSuccess() for clarity
- Implemented fix for bug with n2f_database::query() provided by CButcher
- Fixed bug with setField() on both template engines
- Suppressed filemtime() in n2f_cache
- Added new L() function and modified core extensions to use it
- Removed firephp.ext.php and created a Codeplex project for it
- Added internal counter to events array, performance fix
- Moved charset header to _initCore() so it can be overridden
- Added ’show_ad’ configuration value for turning off HTML comment advertisement
- Moved session/cache configuration blocks into respective extensions

- Andrew

January 8, 2011

N2F Yverdon: Extension Roundup

Filed under: Uncategorized — z|Andrew @ 6:16 am

As we’re hard at work on v0.4 beta, I wanted to take a few minutes and highlight some of the extensions that we’ve started building.  Most of these are still incomplete as they await a more official release of v0.4, but they are also mostly usable despite that fact.

Database Helper
The database helper extension (being developed by myself and ctd1500) is a tiny addition to the database system in N2F that simplifies certain types of queries.  It also allows for storage of queries based on engine to help with building applications that support multiple database engines.

Site: http://n2fydbhelper.codeplex.com/
Releasing In: < 1 Month

Form Helper
The form helper extension adds some support for cleaner form construction.  It takes cues from several popular systems (including ASP.NET MVC) and binds form elements to a model.  Validation and sanitization callbacks are supported and the extension does not take over your ability to construct forms however you like.

Site: http://n2fyformhelper.codeplex.com/
Releasing In: < 1 Month

InfoBoxes
The infoboxes extension, inspired by a post on Reddit, ties into the n2f_debug class and provides an easy (template-able) method for showing messages to your users.  Comes with some styles and uses the template system to produce error, warning, notice and success messages on your pages with minimal effort.

Site: http://n2fyinfoboxes.codeplex.com/
Release: Released v1 in November 2010, v2 in < 1 Month

Sanitizers
The sanitizers extension provides a very simple framework for building sanitization objects/types based on a common abstract class.  The default release comes with a few basic sanitizers that can be used out of the box.

Site: http://n2fysanitizers.codeplex.com/
Release: Released Dec 28th, 2010

Scryle Manager
The scryle (short for script + style) manager extension provides a way of controlling javascript and stylesheet files for templates inside of your PHP code.  Provides basic features such as combination of files, compression and javascript minification.

Site: http://n2fyscrylemgr.codeplex.com/
Release: Released Nov 17th, 2010

Solar Flare Reflector

For those times when you need to be sure, the solar flare reflector extension provides “base-range protection for your N2F Yverdon installation against solar flare interference.”

Site: http://n2fysolarflares.codeplex.com/

Release: Released Nov 17th, 2010

 

Do you know of any other extensions being built for Yverdon that we should know about?  Drop us a line on the forums and we’ll be sure to get a list going!

- Andrew

December 5, 2010

Yverdon v0.3 Changes/Additions

Filed under: Uncategorized — z|Andrew @ 10:34 am

Even though we’ve gone a bit faster through the final stages of Yverdon v0.3 than we had planned, we still had enough time to get a good number of features into the release. We also broke a few things in the process, but we’ll be doing a lot more discussion on that once v0.4 is nearing beta (it should all make sense then, I promise).

Bug Fixes

  • Some minor tweaks for PHP 5.3+ compatibility (or so we think)
  • Fix bug with config.inc.php and encStr()
  • Rebuilt dynamic template engine to fix event issues

New Features

  • Moved database and template systems out of core, now full extensions
  • Added new template engine, ’static’
  • Added ‘bindings’ and global ‘bindings’ to template system
  • Added n2f_cls::requireExtensions() method
  • Added constant support to template engines
  • Added sub-session support to session extension
  • Added singleton system for globals ($n2f, $db and $sess)
  • Made n2f_cfg_dbg argument optional for n2f_debug

We’re all looking forward to moving onto v0.4 and getting the documentation completed, so look for more clarification on these as we hit beta with the new version.

- Andrew

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

Older Posts »

Powered by WordPress