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 29, 2010

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

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

Powered by WordPress