Un crud avec connexion, Coldfusion et Coldbox

Un crud avec connexion, Coldfusion et Coldbox

 

 

testcolbox

Dans ce petit exo, je reprend tout le code du précédent exercice Coldfusion pour réaliser un CRUD tout simple, et j’y ajoute un espace de connexion, deconnexion.

On installe Coldfusion, Coldbox en suivant les précédents tutos, et on crée un projet.Je l’appelle ici aussi ‘Contacts’.Je crée une base de donnée vide dans PhpMyAdmin, nommée contacts aussi pour l’exemple, et je crée une datasource ‘contacts’ dans l’administration de ColdFusion.On oublie pas de créer un mapping vers Coldbox,toujours dans l’admin de Coldfu,  afin que notre appli sache ou chercher toutes les dependances.

Le but ici, est d’avoir une appli, un première page de connexion qui nous permet d’accéder au CRUD, pour pouvoir ensuite éditer, supprimer ou ajouter des contacts.Une fois deconnecté, on revient à la page de connexion

Mon arbo est la suivante:

arbo

Voici un à un les fichiers à éditer:

1-Coldbox.cfc

component {

	// Configure ColdBox Application
	function configure(){

		// coldbox directives
		coldbox = {
			//Application Setup
			appName 				= "contacts",

			//Development Settings
			debugMode				= true,
			debugPassword			= "",
			reinitPassword			= "",
			handlersIndexAutoReload = true,

			//Implicit Events
			defaultEvent			= "contacts.index",

			//Error/Exception Handling
			customErrorTemplate		= "/coldbox/system/includes/BugReport.cfm",

			//Application Aspects
			handlerCaching 			= false,
			eventCaching			= false
		};

		// Custom Settings
		settings = {
		};

			orm = {
				// entity injection
				injection = {
					// enable it
					enabled = true,
					// the include list for injection
					include = "",
					// the exclude list for injection
					exclude = ""
				}
			};
		// environment settings, create a detectEnvironment() method to detect it yourself.
		// create a function with the name of the environment so it can be executed if that environment is detected
		// the value of the environment is a list of regex patterns to match the cgi.http_host.
		/*environments = {
			development = "^localhost,^cf,^railo"
		};*/

		//Layout Settings
		layoutSettings = {
			defaultLayout = "Layout.main.cfm"
		};

	//Interceptor Settings
	//Register interceptors as an array, we need order
		interceptors = [
			 //Autowire
			 {class="model.securityInterceptor"}
		];

			//LogBox DSL
	/*logBox = {
		// Define Appenders
		appenders = {
			coldboxTracer = { class="coldbox.system.logging.appenders.ColdboxTracerAppender" },
			coldboxFile = {
				class="coldbox.system.logging.appenders.AsyncRollingFileAppender",
				properties={filePath="logs",
				fileName=coldbox.appname,
				autoExpand=true,
				fileMaxSize=2000,
				fileMaxArchives=2}

			}
		},
		// Root Logger
		root = { levelmax="INFO", appenders="*" },
		// Implicit Level Categories
		info = [ "coldbox.system" ]
	};*/
			datasources = {
				myDSN = {name="newcontacts",dbtype="mysql"}
			};


		}

	function development(){
		coldbox.debugPassword = "";
		coldbox.debugMode = true;
		coldbox.reinitPassword = "";
		coldbox.handlerCaching = false;
		coldbox.handlersIndexAutoReload = true;
		coldbox.eventCaching = true;
	}

}

Remarquez la déclaration d’un interceptor, c’est lui qui va obliger l’utilisateur à se connecter, avant d’arriver à l’application.

2-Application.cfc: j’active l’ORM

component extends="coldbox.system.Coldbox" {
	this.name = hash(getCurrentTemplatePath());
	this.sessionManagement = true;
	this.sessionTimeout = createTimeSpan(0,0,30,0);
	this.setClientCookies = true;

	COLDBOX_APP_ROOT_PATH = getDirectoryFromPath(getCurrentTemplatePath());
	COLDBOX_APP_MAPPING   = "";
	COLDBOX_CONFIG_FILE   = "";
	COLDBOX_APP_KEY       = "";

	// ORM Settings
	this.ormEnabled 	  = true;
	this.ormSettings	  = {
		datasource		  = "newcontacts",
		cfclocation = "model",
		dialect="MySQL",
		autorebuild=true,
		dbcreate	= "update",
		logSQL 		= true,
		flushAtRequestEnd = false,
		autoManageSession = false,
		eventHandling 	  =  true
	/*eventHandler	  = "model.MyEventHandler"*/
	};

	/*
function onApplicationStart(){

	loadColdBox();
			return true;
	}

	function onRequestStart( required string targetPage ) {

		// ORM Reload Check
		if( structKeyExists( url, "ormreload" ) ) {
			ORMReload();
		}

		reloadChecks();

		// Process A ColdBox Request Only
		if( findNoCase( 'index.cfm', listLast( arguments.targetPage, '/' ) ) ) {
			processColdBoxRequest();
		}

		return true;
	}

	function onApplicationEnd(){

	}
	 function onSessionStart(){

	 super.onSessionStart();
	 }

	 function onSessionEnd(){
	 super.onSessionEnd(argumentCollection=arguments);
	 }


*/



}

 

3-Handlers : notre controller Contacts.cfc

/**
* I am a new handler
*/
component {



	function index(event,rc,prc){
		prc.contacts = EntityLoad("Contact", {}, "name");
		event.setView("contacts/index");
	}

	function editor(event,rc,prc){
		if(structKeyExists(rc, "contactID") && len(rc.contactID)){
			prc.contacts = entityLoad("Contact", rc.contactID, true);
		}else{
			prc.contacts = entityNew("Contact");
		}
		event.setView("contacts/editor");
	}

	function delete(event,rc,prc){
		  rc.oContact= entityLoad('Contact',rc.contactID,true);
		  entityDelete(rc.oContact);
		  ORMflush();

		getPlugin("MessageBox").info("Contact Removed!");
		setNextEvent("contacts.index");
	}

	function save(event,rc,prc){
		try{
			if(structKeyExists(rc, "contactID") && len(rc.contactID)){
				oContact = entityLoad("Contact", rc.contactID, true);
			}else{
				oContact = entityNew("Contact");
			}
			populateModel( oContact );
			entitySave(oContact);
		 	 ORMflush();
			getPlugin("MessageBox").info("Contact Created!");
			setNextEvent("contacts.index");
		}catch(any e){
			getPlugin("MessageBox").error(e.message);
			return editor(event,rc,prc);
		}
	}

/*function setMyservice(MyService) inject="SecurityService"{
	variables.SecurityService = arguments.SecurityService;
}*/
	// login form
	function login(event,rc,prc){
		event.setView("contacts/login");
	}

	// do login
	function doLogin(event,rc,prc){

			//Do Login Procedure.
			var objDataStore = "";

			var ValidationStruct = "";
			//Error checks, does the form variables username & password exist
			//in the request collection? if they do, are they blank?
			if( not Event.valueExists("name") or not Event.valueExists("password") ){
				//Set a message to display
				getPlugin("MessageBox").setMessage("error","No username or password defined.");
				getPlugin("Logger").logEntry("error","Login without variables set detected.");
				//Redirect to next event, you can also add extra parameters to the URL
				setNextEvent("Contacts.Login","name=#Event.getValue("name","")#");
				}
			else{
				//Init DataStorage
				objDataStore = CreateObject("component","#getSetting("AppMapping")#.model.datastore").init();
				ValidationStruct = objDataStore.validateUser(rc.name, rc.password);
				if ( ValidationStruct.validated ){
					//Login Correct.
					//set my session vars
					getPlugin("SessionStorage").setVar("loggedin",true);
					getPlugin("SessionStorage").setVar("name",ValidationStruct.qUser.name);
					//Log the entry
					getPlugin("Logger").logEntry("information","l'utilisateur' #validationStruct.qUser.name# es connecte.");
					//relocate to home page.
					setNextEvent("Contacts.Index");
				}
				else{
					//Set a message to display
					getPlugin("MessageBox").setMessage("error","Erreur.Recommencez");
					getPlugin("Logger").logEntry("warning","Invalid logon information detected. IP used: #cgi.remote_addr#");
					//Redirect to next event, you can also add extra parameters to the URL
					setNextEvent("Contacts.Login","name=#Event.getValue("name")#");
				}
			}
		}

	 function authenticate(required name, required password){
		// hash password
		arguments.password = hash( arguments.password, userService.getHashType() );
		var user = userService.findWhere({name=arguments.name,password=arguments.password,isActive=true});

		//check if found and return verification
		if( not isNull(user) ){
			// Set last login date
			updateUserLoginTimestamp( user );
			// set them in session
			setUserSession( user );
			return true;
		}
		return false;
	}
	// logout
	function doLogout(event,rc,prc){
		/*prc.contacts.deleteUserSession();*/
		getPlugin("SessionStorage").clearAll();
		setNextEvent("Contacts.Login");
	}

}

Par rapport au simple CRUD, ici je crée les fonctions de connexion et deconnection, en faisant appel notamment au plugin session storage.

 

4-Style.css

Un peu de style pour commencer:

body {
	font: 10pt verdana;
    font-family: verdana;
    padding: 0;
    margin: 10px;
}


 td {
     padding: 10px;
     margin: 0;
 }
 h2 {
     background-color: black;
 	 margin: 0px;
     color: #FFFFFF;
     padding: 20px;
 }

#container{max-width:968px;margin:0 auto;margin:auto;}
.main{display:table-row}
.content{display:table-cell;min-width: 600px;width:100%;background:#eee}
.aside{display:table-cell;  background: red;}
ul, li{list-style-type:none;display:inline-block;margin:0 auto;text-align:center}
ul li{min-width:120px;height:30px;}
ul li a{color:#fff;font-size:16px;line-height:30px;text-decoration:none}
ul li:hover{background:#333}
.menu_nav{background:#444; width:100%}
footer{line-height:30px;text-align:center;width:100%;background:#555}
.contact{max-width:300px;display:inline-block;padding:5px;text-align:left;margin:10px;border:1px solid #eee;background:#444}

5-Layout.Main.cfm

<cfset sessionstorage = getPlugin("SessionStorage")>
	<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
		<title>Welcome to SimpleCrud!!</title>
		<link rel="stylesheet" href="includes/styles/style.css" type="text/css">
	</head>
	<body>
	<!-- wrapper -->
	<div class="clr" id="top-head"> </div>
	<div id="container">
		<!--header -->
		<div id="header" >
			<div class="logo-bg" >
				<!--logo -->
				<div class="logo">
					LOGO
				</div>
				<!--head right -->
				<div class="right">
					<!--// Navigation //-->
						<div style="font-size:14px">
							<strong>
								<cfif sessionstorage.getVar("loggedin",false)>
									<cfoutput> Bienvenue #sessionstorage.getVar("name")#! </cfoutput>
									</cfif>
							</strong>
						</div>

					<div class="menu_nav">
						<div id="nav-wrap">
							<ul class="arrowunderline" id="nav">
							<cfif sessionstorage.getVar("loggedin",false)>
							<li class="index"><a href="index.cfm?event=contacts.index">Home</a></li>
							<li><a href="index.cfm?event=Contacts.doLogout" class="fisheyeItem"><span>Logout</span></a></li>
							<cfelse>
							<li class="edit"><a href="index.cfm?event=contacts.login">Connexion</a></li>
							</cfif>
							</ul>
						</div>
					</div>
					<!--// Navigation End //-->
				</div>
				<!--// -head right end //-->
			</div>
			<!--// logo bg end //-->
		</div>
		<div class="main">
				<!--header end -->
				<!--- Render The View. This is set wherever you want to render the view in your Layout. --->
				<cfoutput>#renderView()#</cfoutput>
		</div>
		<footer>
			Copyright User
		</footer>
	</body>
</html>

6- Views

Nos différentes vues:

Editor.cfm

<cfoutput>
<h1>Contact Editor</h1>

#getPlugin("MessageBox").renderit()#
#html.startForm(action="contacts.save")#
	#html.entityFields(entity=prc.contacts,fieldwrapper="div")#
	#html.submitButton()# or #html.href(href="contacts",text="Cancel")#
#html.endForm()#
</cfoutput>

index.cfm

<cfoutput>
<h1>Contacts</h1>
#getPlugin("MessageBox").renderit()#
#html.href(href='contacts.editor',text="Create Contact")#
<br><br>


<cfloop array="#prc.contacts#" index="contact">
<div class="contact">
	Nom: #contact.getName()#<br/>
	Password : #contact.getPassword()# <br/>
	(Mail :#contact.getEmail()#) <br/>
	#html.href(href='contacts.editor&contactID=#contact.getContactID()#',text="[ Edit ]")#
	#html.href(href='contacts.delete&contactID=#contact.getContactID()#',text="[ Delete ]",onclick="return confirm('Really Delete?')")#
	<hr>
</div>
</cfloop>

</cfoutput>

login.cfm

<cfoutput>
<div id="loginForm">
<h1>Espace de connexion</h1>
<p>Veuillez entrer votre nom et mot de passe</p>

<!--- display any messages in the event --->
#getPlugin("MessageBox").renderit()#

<form name="loginForm" method="POST" action="#event.buildLink('contacts.doLogin')#">
	<p>Username:<br/>
	<input type="text" name="name">
	<p>Password:<br/>

	<input type="password" name="password">
	<p>
	<input type="submit" value="Login" name="submit">
</form>

</div>
</cfoutput>

7-Model

Contact.cfc, notre objet

/**
* A cool Contact entity
*/
component persistent="true" table="newcontacts" {

	// Primary Key
	property name="contactID"  fieldtype="id" column="id" generator="native" setter="false";

	// Properties
	property name="name" ormtype="string";

	property name="email" ormtype="string";

	property name="password"  ormtype="string";
	// DEPENDENCIES via WireBOX



	// validation
	this.constraints = {
		name = {required=true},

		email = {required=true, type="email"},

		password = {required=true, type="password"}
	};

}

datastore.cfc qui contient les fonctions de validation de l’admin

<cfcomponent name="datastore" hint="A security datastore object">

<!------------------------------------------- CONSTRUCTOR ------------------------------------------->

	<cfset variables.instance = structnew()>
	<cfset variables.instance.datastoreFile = Expandpath("model/users.xml.cfm")>
	<cfif server.ColdFusion.ProductName eq "Coldfusion Server">
		<cfset variables.instance.qUsers = queryNew("id,name,password,name","varchar,varchar,varchar,varchar")>
	<cfelse>
		<cfset variables.instance.qUsers = queryNew("id,name,password,name")>
	</cfif>
	<cfset variables.instance.hashType = "SHA">

	<cffunction name="init" access="public" returntype="datastore" output="false">
		<cfset parseXML()>
		<cfreturn this>
	</cffunction>

<!------------------------------------------- PUBLIC ------------------------------------------->

	<cffunction name="validateUser" access="public" returntype="struct" output="false">
		<cfargument name="name" type="string" required="true">
		<cfargument name="password" type="string" required="true">
		<cfset var rtnStruct = structnew()>
		<cfset var thePassword = "">
		<cfset rtnStruct.validated = false>
		<cfset rtnStruct.qUser = "">

		<!--- BD --->
		<cfif server.ColdFusion.ProductName eq "Coldfusion Server">
			<cfset thePassword = hash(arguments.password,'SHA')>
		<cfelse>
			<cfset thePassword = hash(arguments.password,'SHA')>
		</cfif>
		<!--- Validate a user --->
		<cfquery name="rtnStruct.qUser" dbtype="query">
		select *
		from instance.qUsers
		where name= <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.name#"> and
			  password = <cfqueryparam cfsqltype="cf_sql_varchar" value="#thePassword#">
		</cfquery>
		<cfif rtnStruct.qUser.recordcount>
			<cfset rtnStruct.validated = true>
		</cfif>
		<cfreturn rtnStruct>
	</cffunction>

	<cffunction name="getUsers" access="public" returntype="query" output="false">
		<cfreturn instance.qUsers>
	</cffunction>

	<cffunction name="getInstance" access="public" returntype="struct" output="false">
		<cfreturn instance>
	</cffunction>

<!------------------------------------------- PRIVATE METHODS ------------------------------------------->

	<cffunction name="parseXML" access="private" returntype="any" output="false">
		<cfset var FileContents = "">
		<cfset var aUsers = ArrayNew(1)>
		<cfset var i = 0>
		<cffile action="read" file="#instance.datastoreFile#" variable="FileContents">
		<cfset aUsers = XMLParse(FileContents).xmlroot.XMLChildren>

		<cfloop from="1" to="#arrayLen(aUsers)#" index="i">
			<cfset QueryAddRow(instance.qUsers,1)>
			<cfset QuerySetCell(instance.qUsers,"id", trim(aUsers[i].XMLAttributes["id"]))>
			<cfset QuerySetCell(instance.qUsers,"name", trim(aUsers[i].XMLAttributes["name"]))>
			<cfset QuerySetCell(instance.qUsers,"password", trim(aUsers[i].XMLAttributes["password"]))>
			<cfset QuerySetCell(instance.qUsers,"name", trim(aUsers[i].XMLAttributes["name"]))>
		</cfloop>
	</cffunction>

</cfcomponent>

securityInterceptor.cfc qui nous permet de bloquer l’appli si non connecté

component {

	public void function preProcess (event,rc,prc) {

			var loggingIn = false;
			var oSession = getPlugin("SessionStorage");
			//si on est de le login
			if ( event.getCurrentEvent() eq "contacts.doLogin" )
				loggingIn = true;


			//S'il n'y a pas de session on redirige vers la page de connexion
			if ( (not oSession.exists("loggedin") or not oSession.getVar("loggedin") ) and not loggingIn ){
				//Override the incoming event.
				Event.overrideEvent("contacts.Login");
				getPlugin("MessageBox").setMessage("warning", "Interceptor: Veuillez vous connecter");
			}
		}

 }

 

users.xml.cfm dans lequel on définit les admin par défaut

<?xml version="1.0" encoding="UTF-8"?>
<users>
	<user id="469480AB-93E2-E53C-4D09BA5728275354" name="lmajano" password="F67B80E14E470066969BA913D4309CD91DE6051D"  />
	<user id="46A6965F-C9D7-1FCA-36689D8B808641B1" name="admin"   password="D033E22AE348AEB5660FC2140AEC35850C4DA997"  />
</users>

Voila, on a l’ensemble de nos fichiers pour une application Coldfusion qui marche

Ici j’ai volontairement mélangé du code pour montrer la variété des possibilités avec ce langage.(du code issu des tutos coldbox pour le crud, et du code issu des samples de coldbox comme simple blog, samplelogginapp etc)

Une partie ORM pour le CRUD, qui fonctionne avec des objets, et une partie normale, pour l’admin dont les informations sont contenues en dur dans un fichier xml, avec un fichier de fonctions ou requetes sql pour aller chercher l’info.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.