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.

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

November 1, 2008

N2F Yverdon v0.1

Filed under: General — Tags: , , , , , , — z|Andrew @ 6:03 pm

We’re putting the finishing touches on v0.1 of Yverdon, and I have to say I’m getting excited. As we open up this new framework to the world, we’re going to do everything within our power to create clear and effective training here in the form of “Blogtorials.” After the official release, we’ll post one or two blogtorials to help you learn how to use the database and template objects that come with the system. The range of information we’ll provide will only increase over time, and the format of this section of the site will change in the near future to accommodate a few more ideas we have for making learning our systems easier.

I know I speak for everyone here on the core staff when I say that we’re excited to be so close to releasing our hard work to the world. We’ve put a lot into this first release and are already planning for releases to come. With your feedback and contributions, we know that our system will make the lives of developers easier around the world.

- Andrew

Powered by WordPress