diff --git a/config/database.json b/config/database.json index e8b8c69..fc2c6ed 100755 --- a/config/database.json +++ b/config/database.json @@ -3,4 +3,4 @@ "dbname" : "stefproject", "user" : "php", "password" : "QbzjZACndQM6NmuD" -} \ No newline at end of file +} diff --git a/index.php b/index.php index 0f0ba8b..3882610 100755 --- a/index.php +++ b/index.php @@ -1,10 +1,13 @@ get('f(?:/([\w-]+))*/?', function(){ new ResourceDispatcher($_GET['url'], true); }); // Api - $R->post('api/?', function(){ - $request = ModuleRequest::fromPost($_POST); + $R->post('api(?:/(.*))?', function($url){ + $request = ModuleRequest::fromPost($url, $_POST); $answer = $request->dispatch(); - - echo $answer->serialize(); + + // Si c'est une réponse (et non un download) + if( $answer instanceof ModuleResponse ) + echo $answer->serialize(); }); + // N'importe -> page d'accueil $R->get('.+', function(){ header('Location: /dashboard/'); }); + $R->post('.+', function(){ header('Location: /dashboard/'); }); - // $R->post('.*', function(){ - // var_dump( 'Acces POST : '.$_GET['url'] ); - // var_dump( $_POST ); - // }); - - - /* [2] On lance le routeur ===================================================*/ $R->run(); -?> \ No newline at end of file +?> diff --git a/js/action-script-min.js b/js/action-script-min.js new file mode 100644 index 0000000..cae1fe9 --- /dev/null +++ b/js/action-script-min.js @@ -0,0 +1,6 @@ +DOM={WRAPPER:$("WRAPPER"),HEADER:$("HEADER"),MENUSIDE:$("MENU-SIDE"),CONTAINER:$("CONTAINER")};var pageManager=new pageManagerClass;pageManager.setPage(null,"/view",DOM.CONTAINER,"profile dashboard machines users groups analytics settings".split(" "));var api=new APIClass("/api/"); +function navSubMenu(a){var c=document.querySelector('#CONTAINER > .sub-menu-side > span[data-sublink="'+pageManager.vars[0]+'"]'),b=null,b=a instanceof Element?a:null,b="string"==typeof a?document.querySelector('#CONTAINER > .sub-menu-side > span[data-sublink="'+a+'"]'):b,b=null==b?document.querySelector("#CONTAINER > .sub-menu-side > span[data-sublink]"):b;if(null==b)return!1;null!=c&&c.remClass("active");a=document.querySelectorAll("#CONTAINER > section[data-sublink].active");for(c=0;c section[data-sublink="'+b.getData("sublink")+'"]'),null!=a&&a.addClass("active"));if(!b.getData("sublink"))return!1;a=pageManager.vars[0]!=b.getData("sublink");pageManager.vars[0]=b.getData("sublink");a&&pageManager.updateURL()} +function navMenu(a){var c=document.querySelector('#WRAPPER > #MENU-SIDE > span[data-link="'+pageManager.page+'"]'),b=null,b=a instanceof Element?a:null,b="string"==typeof a?document.querySelector('#WRAPPER > #MENU-SIDE > span[data-link="'+a+'"]'):b,b=null==b?document.querySelector('#WRAPPER > #MENU-SIDE > span[data-link="'+pageManager.pagelist[0]+'"]'):b;if(null==b)return!1;null!=c&&c.remClass("active");null!=b&&b.addClass("active");b.getData("link")&&(DOM.HEADER.addClass("loading"),pageManager.setPage(b.getData("link")), +pageManager.activeXHR.addEventListener("loadend",function(){DOM.HEADER.remClass("loading");1<=pageManager.vars.length&&null!=document.querySelector('#CONTAINER > .sub-menu-side > [data-sublink="'+pageManager.vars[0]+'"]')?navSubMenu(pageManager.vars[0]):navSubMenu(null);document.querySelector("#CONTAINER > .sub-menu-side").addEventListener("click",function(a){for(a=a.target;a!=document.body&&!a.getData("sublink");)a=a.parentNode;a.getData("sublink")&&navSubMenu(a)},!1)},!1))}navMenu(pageManager.page); +DOM.MENUSIDE.addEventListener("click",function(a){for(a=a.target;a!=document.body&&!a.getData("link");)a=a.parentNode;a.getData("link")&&navMenu(a.getData("link"))},!1); diff --git a/js/action-script.js b/js/action-script.js index 148c506..71eab38 100755 --- a/js/action-script.js +++ b/js/action-script.js @@ -26,7 +26,7 @@ function navSubMenu(subsection){ var target = null; // si @subsection est un element, on le prends - target = (subsection instanceof Element) ? subsection : null; + target = (subsection instanceof Element) ? subsection : null; // Si string, on trouve l'element correspondant target = (typeof subsection == 'string') ? document.querySelector('#CONTAINER > .sub-menu-side > span[data-sublink="'+subsection+'"]') : target; @@ -42,7 +42,7 @@ function navSubMenu(subsection){ // On desactive l'element courant if( current != null ) current.remClass('active'); - + // On cache les sections visibles var visibleSections = document.querySelectorAll('#CONTAINER > section[data-sublink].active'); for( var i = 0 ; i < visibleSections.length ; i++ ) @@ -86,7 +86,7 @@ function navSubMenu(subsection){ /* [2] Toggle du side-menu <-> navigation ===========================================*/ function navMenu(section){ - + /* [1] Format du param ------------------------------------------------*/ // Contient l'element courant @@ -96,7 +96,7 @@ function navMenu(section){ var target = null; // si @section est un element, on le prends - target = (section instanceof Element) ? section : null; + target = (section instanceof Element) ? section : null; // Si string, on trouve l'element correspondant target = (typeof section == 'string') ? document.querySelector('#WRAPPER > #MENU-SIDE > span[data-link="'+section+'"]') : target; @@ -188,6 +188,3 @@ DOM.MENUSIDE.addEventListener('click', function(e){ if( target.getData('link') ) navMenu(target.getData('link')); }, false); - - - diff --git a/js/lib/api-min.js b/js/lib/api-min.js new file mode 100644 index 0000000..86eeb6d --- /dev/null +++ b/js/lib/api-min.js @@ -0,0 +1,4 @@ +function APIClass(c){this.target=c} +APIClass.prototype={xhr:[],buffer:null,optionalParams:[],send:function(c,f,g){c.hasOwnProperty("path")||f({ModuleError:4});for(var a=0;a l'objet passé en JSON à http://host/api/ + * @param pRequest l'objet passé en POST (attribut->postfield) à http://host/api/ * @param pHandler fonction qui s'éxécutera lors de la réponse (1 argument -> réponse) + * @param pToken Optionnel, token d'auth pour l'api + * @param pParams Optionnels, liste d'arguments à passer au scope de @pHandler * - * @return answer l'objet retourné par http://host/api/ via pHandler (1er argument) + * @return answer l'objet retourné par http://host/api/ via pHandler (1er argument) * *************************************************************************************************** * * @usecase * 1. var answerObject = sendRequest( - * 2. { var1: "exemple", var2: 198294 }, - * 3. function(rep){ alert(rep); } + * 2. { var1: "exemple", var2: 198294 }, + * 3. function(rep){ alert(rep); } * 4. ); * @explain - * 1. on appelle la fonction <=> on créé la requête - * 2. on passe l'objet qui sera envoyé + * 1. on appelle la fonction <=> on créé la requête + * 2. on passe l'objet qui sera envoyé * 3. on passe une fonction qui utilise un argument (sera la réponse de http://host/api/) (sous forme d'objet) * */ - send: function(pRequest, pHandler){ + send: function(pRequest, pHandler, pToken){ // Si le chemin de delegation n'est pas renseigne, on renvoie une erreur if( !pRequest.hasOwnProperty('path') ) pHandler({ModuleError:4}); // on efface les requêtes qui sont terminées (toutes celles de this.xhr) - for( var i = 0 ; i < this.xhr.length ; i++ ){ + for( var i = 0 ; i < this.xhr.length ; i++ ) if( this.xhr[i].readyState == 4 ) // si terminée - this.xhr = this.xhr.slice(0,i-1).concat(this.xhr.slice(i,this.xhr.length-1)); // suppression entrée - } + this.xhr.splice(i, 1); + // on créé une nouvelle entrée this.xhr.push(null); i = this.xhr.length-1; + + // Gestion des paramètres optionnels à passer au scope de @pHandler + this.optionalParams[i] = []; + if( arguments.length > 3 ) + for( var arg = 3 ; arg < arguments.length ; arg++ ) + this.optionalParams[i].push( arguments[arg] ); + + // création de l'objet AJAX if(window.XMLHttpRequest) // IE7+, Firefox, Chrome, Opera, Safari this.xhr[i] = new XMLHttpRequest(); - else // IE5, IE6 + else // IE5, IE6 this.xhr[i] = new ActiveXObject('Microsoft.XMLHttpRequest'); - console.log(pRequest); - var ptrAPI = this; this.xhr[i].onreadystatechange = function(){ if( ptrAPI.xhr[i].readyState == 4 ){ // si la requête est terminée + ptrAPI.buffer = ptrAPI.xhr[i].responseText; /* DEBUG : affiche la réponse BRUTE de http://host/api/ */ // console.log('http://host/api/ => '+ptrAPI.xhr[i].responseText); // console.log( JSON.parse(ptrAPI.xhr[i].responseText) ); + console.log('api request', pRequest); + /* si success de requête */ if( [0,200].indexOf(ptrAPI.xhr[i].status) > -1 ){ // si fichier existe et reçu - try{ pHandler( JSON.parse(ptrAPI.xhr[i].responseText) ); } // si on peut parser, on envoie - catch(e){ pHandler({ModuleError:1}); } // sinon on envoie obj.request = 'corrupted' + try{ pHandler( JSON.parse(ptrAPI.xhr[i].responseText), ptrAPI.optionalParams[i]); } // si on peut parser, on envoie + catch(e){ pHandler({ModuleError:-1, ErrorDescription:'Erreur au niveau de api.js'}, ptrAPI.optionalParams[i]); console.warn(e); } // sinon on envoie obj.request = 'corrupted' } /* sinon retourne obj.request = 'unreachable' */ else pHandler({ModuleError:3}); } - } + }; // on créé un formulaire POST (virtuel) var form = new FormData(); - form.append('path', pRequest.path ); // on créé la variable $_POST['json']=>request - if( pRequest.hasOwnProperty('data') ) - form.append('data', JSON.stringify(pRequest.data) ); + // On ajoute tous les attributs en POST + for( var key in pRequest ) + // On envoie le 'path' tel quel + if( key == 'path' ) form.append(key, pRequest[key]); + // On envoie un fichier tel quel + else if( pRequest[key] instanceof File ) form.append(key, pRequest[key]); + // On envoie le reste en JSON + else form.append(key, JSON.stringify(pRequest[key])); + this.xhr[i].open('POST', this.target, true); + + + // Gestion du token optionnel + if( pToken != null ) this.xhr[i].setRequestHeader('Authorization', 'Digest '+pToken); + + + // Header pour dire que c'est AJAX + this.xhr[i].setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + + + this.xhr[i].send( form ); } diff --git a/js/lib/form-deflater-min.js b/js/lib/form-deflater-min.js new file mode 100644 index 0000000..1b698b8 --- /dev/null +++ b/js/lib/form-deflater-min.js @@ -0,0 +1,5 @@ +function FormDeflater(b,c,a){for(var d=0;d Formulaire ou autre élément contenant les champs +* @tags Tableau contenant les éléments à prendre en compte +* @attr Tableau contenant les attributs à prendre pour le nom (par ordre de priorité) +* +*/ +function FormDeflater(container, tags, attr){ + /* [0] Vérification des INPUT + =========================================================*/ + var correctParams = container instanceof Element; + correctParams = correctParams && tags instanceof Array; + correctParams = correctParams && attr instanceof Array; + + + /* [1] On formatte les données + =========================================================*/ + // On met les tags en minuscule + for( var i = 0 ; i < tags.length ; i++ ) + tags[i] = tags[i].toLowerCase(); + + // On met les attributs en minuscule + for( var i = 0 ; i < attr.length ; i++ ) + attr[i] = attr[i].toLowerCase(); + + /* [2] On enregistre les attributs + =========================================================*/ + this.container = container; + this.tags = tags; + this.attr = attr; +} + + +FormDeflater.prototype = { + container: this.container, // Contiendra le 'formulaire' (
ou autre) + tags: this.tags, // Contiendra les balises HTML à ne pas prendre en compte + attr: this.attr // Contiendra la liste des attributs à prendre pour nom (par ordre de priorité) +}; + + + +/* RETOURNE UN OBJET CONTENANT LES DONNÉES DU FORMULAIRE +* +* @return form Objet correspondant aux données du formulaire +* +*/ +FormDeflater.prototype.deflate = function(){ + /* [1] On récupère tous les enfants + =========================================================*/ + var children = this.getChildren( this.container ); + + /* [2] On filtre les éléments qui ont pas le bon tag + =========================================================*/ + children = this.filterElements( children ); + + + /* [3] On essaie de trouver les attributs primants (non vides et en premier dans la liste @this.attr) + =========================================================*/ + /* (0) On initialise l'objet de retour */ + var object = {}; + + /* (1) Pour chacun des éléments */ + for( var c = 0 ; c < children.length ; c++ ){ + + /* (2) Pour chacun des attributs par ordre de priorité */ + for( var a = 0 ; a < this.attr.length ; a++ ){ + // On récupère l'attribut + var attr = children[c].getAttribute(this.attr[a]); + + + + /* (3) Si l'attribut est défini (pas null ni vide) */ + if( attr !== null && attr.length > 0 ){ + + /* (4) Si on a pas déja un champ de même nom */ + if( object.hasOwnProperty(attr) ){ + var existing = object[attr]; + + // {1} Si l'existant est un tableau, on ajoute notre valeur // + if( existing instanceof Array ) + object[attr].push( { target: children[c], attr: this.attr[a], value: children[c].value, checked: children[c].checked } ); + + // {2} Sinon, si c'est une valeur seule, on crée un tableau // + else + object[attr] = [ object[attr], { target: children[c], attr: this.attr[a], value: children[c].value, checked: children[c].checked } ]; + + /* (5) Si c'est le premier champ avec ce nom, on le crée */ + }else + object[attr] = { target: children[c], attr: this.attr[a], value: children[c].value, checked: children[c].checked }; + + // On en a fini pour cet élément + break; + } + + + + } + } + + + + /* [4] On met en forme les données + =========================================================*/ + object = this.cleanOutput(object); + + + return object; +}; + + + + +/* RETOURNE SI UN ELEMENT EST UN BOUTON DE TYPE RADIO/CHECKBOX OU NON +* +* @element Element en question +* +* @return result Renvoie si TRUE or FALSE il en est un +* +*/ +FormDeflater.prototype.checkable = function(element){ + if( element.tagName != 'INPUT' ) + return false; + + if( ['radio', 'checkbox'].indexOf( element.getAttribute('type').toLowerCase() ) == -1 ) + return false; + + return true; +}; + + + + +/* NETTOIE LES DONNÉES EN SORTIE POUR QU'ELLES SOIENT UTILISABLES ET OPTIMISÉES +* +* @input Données "brutes" +* +* @return output Données sans les valeurs inutiles et explicitées +* +*/ +FormDeflater.prototype.cleanOutput = function(input){ + var output = {}; + + /* [1] On parcourt toutes les valeurs récupérées + =========================================================*/ + for( var key in input ){ + + /* [2] Si c'est un tableau + =========================================================*/ + if( input[key] instanceof Array ){ + + // VRAI si tous les éléments sont radio/checkbox + var areCheckable = true; + // Contiendra les indices des valeurs ou 'checked=TRUE' + var checkedIndexes = []; + + /* (1) On vérifie si tous les champs sont checkables */ + for( var i in input[key]) + // si pas checkable, on arrête de vérifier + if( !this.checkable(input[key][i].target) ){ + areCheckable = false; + break; + // Sinon si checkable et checked=TRUE, on incrémente @nbChecked + }else if( input[key][i].checked === true ) + checkedIndexes.push(i); + + /* (2) Si c'est que des radio ou des checkbox avec une seule valeur à TRUE */ + if( areCheckable ) + if( checkedIndexes.length == 1 ) + output[key] = input[key][checkedIndexes[0]].value; + + /* (3) Si c'est que des radio ou des checkbox avec plusieurs valeurs à TRUE */ + else{ + output[key] = []; + for( var i in checkedIndexes ) + output[key].push( input[key][checkedIndexes[i]].value ); + } + + /* (4) Si c'est pas que des radio ou des checkbox, on met les valeurs */ + else{ + output[key] = []; + for( var i in input[key] ) + output[key].push( input[key][i].value ); + } + + /* [3] S'il n'y a qu'une donnée (pas un tableau) + =========================================================*/ + }else{ + + /* (1) Si de type 'radio' ou 'checkbox', on met la valeur de 'checked' */ + if( this.checkable(input[key].target) ) + output[key] = input[key].checked ? input[key].value : null; + + /* (2) Sinon, on met la valeur de 'value' */ + else + output[key] = input[key].value; + + } + + } + + + return output; +}; + + + + + + + +/* RETOURNE LA LISTE DE TOUS LES ÉLÉMENTS QUEL QUE SOIT LE NIVEAU HIÉRARCHIQUE +* +* @parent Parent duquel on veut les enfants +* +* @return children Tableau contenant tous les enfants +* +*/ +FormDeflater.prototype.getChildren = function(parent){ + // Si le parent n'est pas un élément, on retourne aucun enfant + if( !(parent instanceof Element) ) return []; + + /* [1] Initialisation des variables + =========================================================*/ + // Contient la liste des enfants directs + var children = [].slice.call(parent.children); + + // Contiendra la liste des enfants directs et indirects + var allChildren = children; + + /* [2] On parcourt tous les enfants + =========================================================*/ + for( var i = 0 ; i < children.length ; i++ ){ + // On relance la fonction récursivement sur tous les enfants + allChildren = allChildren.concat( [].slice.call(this.getChildren(children[i])) ); + } + + /* [3] On retourne le résultat + =========================================================*/ + return allChildren; +}; + + + + +/* FILTRE LES éléments en fonction de @this.tags et @this.attr +* +* @elements Le tableau contenant les éléments à trier +* +* @return filtered Retourne le tableau des éléments filtrés +* +*/ +FormDeflater.prototype.filterElements = function(elements){ + // Contiendra les éléments correspondants aux critères + var filtered = []; + + /* [1] On parcourt tous les éléments + =========================================================*/ + for( var i = 0 ; i < elements.length ; i++ ) + // Si l'élément a le bon tag, on le garde + if( this.tags.indexOf( elements[i].tagName.toLowerCase() ) > -1 ) + filtered.push(elements[i]); + + /* [2] On retourne les éléments filtržés + =========================================================*/ + return filtered; +}; + + + + +/************/ +/* USE CASE */ +/************/ +/* (1) Instanciation */ +/*HIDDEN*/// var instance = new FormDeflater( +/*HIDDEN*/// document.getElementById('myform'), +/*HIDDEN*/// ['input', 'select'], // éléments à prendre en compte (tagName) +/*HIDDEN*/// ['id', 'name', 'data-elementname'] // Attributs par ordre de priorité +/*HIDDEN*/// ); +/*HIDDEN*/// +/* (2) On récupère l'objet */ +/*HIDDEN*/// +/*HIDDEN*/// var object = instance.deflate(); +/*HIDDEN*/// diff --git a/js/lib/input-checker-min.js b/js/lib/input-checker-min.js new file mode 100644 index 0000000..a655d2f --- /dev/null +++ b/js/lib/input-checker-min.js @@ -0,0 +1,7 @@ +function formatChecker(a,c,b){this.value=a;this.pattern=c;this.rules=2a)return!1;this.checker[a].value=this.input[a].value;return this.checker[a].check()},checkAll:function(){for(var a=!0,c=0;c(index=this.input.indexOf(a)))return null;c=1=this.input[index].value.length));e++){var f=new RegExp("^"+b[e]+"$");0==d.length||null==d[e]?d=d.slice(0,e).concat(this.defval[index][e]).concat(d.slice(e)):null==d[e].match(f)&&(d=null!=d[e].match(new RegExp("^"+ +b[e+1]+"$"))?d.slice(0,e).concat(this.defval[index][e]).concat(d.slice(e)):d.slice(0,e).concat(this.defval[index][e]).concat(d.slice(e+1)))}d=d.slice(0,b.length);this.input[index].value=d}}}; diff --git a/js/lib/input-checker.js b/js/lib/input-checker.js index f025c3e..baff462 100755 --- a/js/lib/input-checker.js +++ b/js/lib/input-checker.js @@ -1,8 +1,8 @@ // __ _ _ _ -// / _| ___ _ __ _ __ ___ __ _| |_ ___| |__ ___ ___| | _____ _ __ +// / _| ___ _ __ _ __ ___ __ _| |_ ___| |__ ___ ___| | _____ _ __ // | |_ / _ \| '__| '_ ` _ \ / _` | __|____ / __| '_ \ / _ \/ __| |/ / _ \ '__| -// | _| (_) | | | | | | | | (_| | ||_____| (__| | | | __/ (__| < __/ | -// |_| \___/|_| |_| |_| |_|\__,_|\__| \___|_| |_|\___|\___|_|\_\___|_| +// | _| (_) | | | | | | | | (_| | ||_____| (__| | | | __/ (__| < __/ | +// |_| \___/|_| |_| |_| |_|\__,_|\__| \___|_| |_|\___|\___|_|\_\___|_| // function formatChecker(pValue, pPattern, pRules){ this.value = pValue; @@ -64,7 +64,7 @@ formatChecker.prototype = { /* [1] On génère la RegExp si ce n'est pas déjà fait ====================================================*/ if( this.regexp == null ) this.compile(); - + /* [2] Gestion de la plaque à trous ====================================================*/ @@ -96,7 +96,7 @@ formatChecker.prototype = { } } - + // on retourne TRUE si c'est bon, FALSE sinon return this.value.match( this.regexp ) != null; @@ -131,11 +131,11 @@ formatChecker.prototype = { -// _ _ _ _ -// (_)_ __ _ __ _ _| |_ ___| |__ ___ ___| | _____ _ __ +// _ _ _ _ +// (_)_ __ _ __ _ _| |_ ___| |__ ___ ___| | _____ _ __ // | | '_ \| '_ \| | | | __|____ / __| '_ \ / _ \/ __| |/ / _ \ '__| -// | | | | | |_) | |_| | ||_____| (__| | | | __/ (__| < __/ | -// |_|_| |_| .__/ \__,_|\__| \___|_| |_|\___|\___|_|\_\___|_| +// | | | | | |_) | |_| | ||_____| (__| | | | __/ (__| < __/ | +// |_|_| |_| .__/ \__,_|\__| \___|_| |_|\___|\___|_|\_\___|_| // function inputChecker(){}; @@ -220,7 +220,7 @@ inputChecker.prototype = { * @pInputElement l'élément concerné * @pToEnd si on doit corriger jusqu'à la fin ou uniquement jusqu'à l'avancéé actuelle * - * + * * @return correctValue retourne la valeur corrigée * retourne NULL si erreur */ @@ -265,12 +265,12 @@ inputChecker.prototype = { -----------------------------------------------------------*/ if( tmpValue.length == 0 || tmpValue[i] == null ) tmpValue = tmpValue.slice(0, i).concat( this.defval[index][i] ).concat( tmpValue.slice(i) ); - + /* (2) Valeur ne correspond pas au schéma du caractère -----------------------------------------------------------*/ else if( tmpValue[i].match(tmpRegExp) == null ){ // si le caractère suivant match, on décale d'une position - if( tmpValue[i].match(new RegExp( '^'+RegExpByChar[i+1]+'$' )) != null ) + if( tmpValue[i].match(new RegExp( '^'+RegExpByChar[i+1]+'$' )) != null ) tmpValue = tmpValue.slice(0, i).concat( this.defval[index][i] ).concat( tmpValue.slice(i) ); // sinon on remplace else @@ -283,11 +283,11 @@ inputChecker.prototype = { tmpValue = tmpValue.slice(0, RegExpByChar.length); // on met à jour la valeur de l'élément input - this.input[index].value = tmpValue; + this.input[index].value = tmpValue; } } -}; \ No newline at end of file +}; diff --git a/js/lib/page-manager-min.js b/js/lib/page-manager-min.js new file mode 100644 index 0000000..d6f0f79 --- /dev/null +++ b/js/lib/page-manager-min.js @@ -0,0 +1,9 @@ +function pageManagerClass(){}var ptrPageManagerClass; +pageManagerClass.prototype={depJS:null,depCSS:null,xhr:[],activeXHR:null,page:null,vars:[],path:"",jsPath:"js",cssPath:"css",pagelist:null,container:null,ajax:function(b,c,f,a){for(var d=0;d= 1) ? url_data : document.URL; // si pageList est correct et que l'URL correspond à un schéma de page => continue [sinon] return null - if( this.pagelist != null && /^(?:(?:http:\/\/)?[^\/]+)\/([a-z0-9_]+)\/?(?:\/((?:.+\/)+)\/?)?$/i.test(url_data) ){ + if( this.pagelist != null && /^(?:(?:https?:\/\/)?[^\/]+)\/([a-z0-9_]+)\/?(?:\/((?:.+\/)+)\/?)?$/i.test(url_data) ){ // si la page récupérée dans l'url est dans la liste => renvoi de l'objet [sinon] null var vars = RegExp.$2.split('/'); while( vars[vars.length-1] == '' ) // on supprime les dernières entrées vides @@ -156,7 +156,7 @@ pageManagerClass.prototype = { }, /* ======================================================================= - Met à jour l'URL de la page en fonction de la page chargée et des + Met à jour l'URL de la page en fonction de la page chargée et des variables associées (ne recharge aucune ressource) ======================================================================= */ updateURL: function(){ @@ -177,13 +177,13 @@ pageManagerClass.prototype = { - pContainer l'élément du DOM qui contiendra la page chargée (**) - pPageList> tableau contenant la liste des pages sous forme de chaînes de caractères (**) (***) * Le chemin du dossier sans le '/' final si c'est le dossier actuel le chemin est une chaîne vide - Si le dossier est 'page' et que l'on cherche la page 'accUe1l', la requête sera vers 'page/accUe1l.php' - le nom de la page est sensible à la casse + Si le dossier est 'page' et que l'on cherche la page 'accUe1l', la requête sera vers 'page/accUe1l.php' + le nom de la page est sensible à la casse ** 1. pPageList et pContainer doivent être mis en paramètres uniquement à la première utilisation - et la première utilisation doit se faire au chargement de la page car elle permetra - de mettre l'URL à jour et/ou charger la page de l'URL + et la première utilisation doit se faire au chargement de la page car elle permetra + de mettre l'URL à jour et/ou charger la page de l'URL *** la première page du tableau est la page par défaut (qui est chargée si l'URL ne contient - pas la page ou si la page de l'URL ne correspond à aucune page de la liste) + pas la page ou si la page de l'URL ne correspond à aucune page de la liste) ========================================================================== */ setPage: function(pName, pPath, pContainer, pPageList){ @@ -205,7 +205,7 @@ pageManagerClass.prototype = { /* on attribue le paramètre pContainer à l'attribut si il est spécifié */ this.container = ( typeof pContainer == 'object' && pContainer instanceof Element ) ? pContainer : this.container; - // si this.pagelist && this.container ne sont pas null && + // si this.pagelist && this.container ne sont pas null && if( this.pagelist != null && this.container != null ){ // si le pName est renseigné et qu'il est dans pagelist if( typeof pName == 'string' && this.pagelist.indexOf(pName) > -1 ){ @@ -219,15 +219,15 @@ pageManagerClass.prototype = { var fd = new FormData(); for( var i = 0 ; i < this.vars.length ; i++ ) fd.append(this.vars[i], null); - + this.ajax(this.path+'/'+this.page+'.php', function(e){ ptrPageManagerClass.container.innerHTML = e; - ptrPageManagerClass.loadDependencies(); + ptrPageManagerClass.loadDependencies(); }, 'POST', fd); // change l'URL en conséquences(stateObj, titre, url) this.updateURL(); - + }else{ // si la page n'est pas spécifiée ou qu'elle n'est pas dans la liste des pages var urlGet = this.explodeURL(); @@ -248,12 +248,12 @@ pageManagerClass.prototype = { this.ajax(this.path+'/'+this.page+'.php', function(e){ ptrThis.container.innerHTML = e; - ptrThis.loadDependencies(); + ptrThis.loadDependencies(); }, 'POST', fd); // change l'URL en conséquences(stateObj, titre, url) this.updateURL(); - + }else // si l'url ne contient rien, on charge la page par défaut this.setPage(this.pagelist[0]); } @@ -276,4 +276,4 @@ pageManagerClass.prototype = { return this; } -} \ No newline at end of file +} diff --git a/js/lib/reset-min.js b/js/lib/reset-min.js new file mode 100644 index 0000000..80b3bd9 --- /dev/null +++ b/js/lib/reset-min.js @@ -0,0 +1,2 @@ +function $(a){var b=document.querySelectorAll("#"+a);a=document.querySelectorAll("."+a);return 0 'f/json/database-local/conf', @@ -29,8 +29,8 @@ $this->dbname = $dbname; $this->username = $username; $this->password = $password; - - try{ + + try{ self::$pdo = new \PDO('mysql:host='.$this->host.';dbname='.$this->dbname, $this->username, $this->password); // On signale que tout s'est bien passe @@ -48,7 +48,7 @@ if( self::$instance == null || self::$error != ManagerError::Success ){ // Si aucune instance existante OU erreur de connection // chargement de la configuration du server SQL - if( !isset($_SERVER['HTTP_HOST']) || isset($_SERVER['HTTP_HOST']) && $_SERVER['HTTP_HOST'] == 'stefproject' ) + if( !checkdnsrr($_SERVER['SERVER_NAME'], 'NS') ) $conf = json_decode( ResourceDispatcher::getResource(self::$config_path['local']), true ); else $conf = json_decode( ResourceDispatcher::getResource(self::$config_path['remote']), true ); @@ -57,7 +57,7 @@ self::$instance = new DataBase($conf['host'], $conf['dbname'], $conf['user'], $conf['password']); } - + return self::$instance; } @@ -104,7 +104,7 @@ * @fetchData le résultat d'une $requeteSQL->fetchAll() * @oneDimension FAUX <=> fetchAll ; VRAI <=> fetch * - * @return newFetchData retourne le tableau donné en paramètre mais sans les valeurs à clés numériques + * @return newFetchData retourne le tableau donné en paramètre mais sans les valeurs à clés numériques * */ public static function delNumeric($fetchData, $oneDimension=false){ @@ -121,19 +121,19 @@ // on supprime les doublons des entrées (indice numérique) for( $i = 0 ; $i < count($fetchData) ; $i++ ) // pour tout les utilisateurs foreach($fetchData[$i] as $col => $val){ // pour toutes les entrées - - if( !mb_detect_encoding($val, 'UTF-8') ) + + if( !\mb_detect_encoding($val, 'UTF-8') ) $fetchData[$i][$col] = utf8_encode($val); - + if( is_int($col) ){ // Si indice numerique if( $nextEquivalent ) // Si suit un indice textuel unset( $fetchData[$i][$col] ); // on supprime l'indice - + $nextEquivalent = false; // Dans tous les cas, on dit que le prochain ne pourra pas etre supprime si numerique }else // Si l'indice n'est pas un entier $nextEquivalent = true; // On signale qu'il y aura peut etre un indice numerique suivant - + } /* [2] 1 dimensions @@ -142,14 +142,14 @@ // on supprime les doublons des entrées (indice numérique) foreach($fetchData as $i=>$val){ // pour toutes les entrées - - if( !mb_detect_encoding($val, 'UTF-8') ) + + if( !\mb_detect_encoding($val, 'UTF-8') ) $fetchData[$i] = utf8_encode($val); if( is_int($i) ){ // Si indice numerique if( $nextEquivalent ) // Si suit un indice textuel unset( $fetchData[$i] ); // on supprime l'indice - + $nextEquivalent = false; // Dans tous les cas, on dit que le prochain ne pourra pas etre supprime si numerique }else // Si l'indice n'est pas un entier @@ -165,12 +165,12 @@ //////////////////////////////////////////////////////////////// - // _ __ _ _ _ - // __ _____ _ __(_)/ _(_) ___ __ _| |_(_) ___ _ __ ___ - // \ \ / / _ \ '__| | |_| |/ __/ _` | __| |/ _ \| '_ \/ __| - // \ V / __/ | | | _| | (_| (_| | |_| | (_) | | | \__ \ - // \_/ \___|_| |_|_| |_|\___\__,_|\__|_|\___/|_| |_|___/ - // + // _ __ _ _ _ + // __ _____ _ __(_)/ _(_) ___ __ _| |_(_) ___ _ __ ___ + // \ \ / / _ \ '__| | |_| |/ __/ _` | __| |/ _ \| '_ \/ __| + // \ V / __/ | | | _| | (_| (_| | |_| | (_) | | | \__ \ + // \_/ \___|_| |_|_| |_|\___\__,_|\__|_|\___/|_| |_|___/ + // //////////////////////////////////////////////////////////////// @@ -183,43 +183,104 @@ * */ public static function check($type, $value){ - $checker = !is_null($value); - + $checker = true; + + /* [0] On verifie que $value n'est pas nul + =========================================================*/ + if( is_null($value) ) return false; + + + + /* [1] Si de type VARCHAR(min, max) + =========================================================*/ + if( preg_match('/^varchar\((\d+), ?(\d+)\)$/', $type, $match) ){ + // On recupere la taille min + $min = (int) $match[1]; + // On recupere la taille max + $max = (int) $match[2]; + + // On effectue la verification + return $checker && is_string($value) && strlen($value) <= $max && strlen($value) >= $min; + } + + + /* [2] Si de type ARRAY(type_elements) + =========================================================*/ + if( preg_match('/^array<(.+)>$/', $type, $match) ){ + + // Si c'est pas un tableau on retourne une erreur + if( !is_array($value) ) + return false; + + + $elements_type = $match[1]; + + // On verifie le type pour chaque element + foreach($value as $element) + // Si erreur dans au moins 1 element, on retourne que c'est incorrect + if( !self::check($elements_type, $element) ) + return false; + + // Si aucune erreur, on retourne que tout est bon + return true; + } + + + /* [n] Sinon, tous les autres types definis + =========================================================*/ switch($type){ - /* (1) Global */ - case 'auto_increment_id': - return $checker && is_numeric($value) && $value <= 2147483647 && $value >= -2147483647; + // Quoi que ce soit + case 'mixed': + return $checker && !is_null($value); break; - /* (2) Utilisateur */ - case 'user.code': - case 'machine.code': - return $checker && is_string($value) && preg_match('/^[\dA-F]{2}(\-[\dA-F]{2}){3,5}$/i', $value); + // Entier positif (id dans BDD) + case 'id': + return $checker && is_numeric($value) && $value <= 2147483647 && $value >= 0; break; - - case 'user.username': - case 'machine.name': - case 'group.name': - return $checker && is_string($value) && preg_match('/^[\w-]{1,30}$/i', $value); - break; - - case 'user.firstname': - case 'user.lastname': - return $checker && is_string($value) && preg_match('/^[a-z -]{3,30}$/i', $value); - break; - - case 'user.mail': + + // String quelconque (peut etre vide) + case 'text': + return $checker && is_string($value); + + // Adresse mail (255 caracteres max) + case 'mail': return $checker && is_string($value) && strlen($value) <= 50 && preg_match('/^[\w\.-]+@[\w\.-]+\.[a-z]{2,4}$/i', $value); break; - - case 'user.password': + + // Hash sha1 + case 'sha1': return $checker && is_string($value) && preg_match('/^[\da-f]{40}$/i', $value); break; - case 'user.status': - return $checker && is_numeric($value) && floor($value) == $value && $value >= 0 && $value <= 100; + // Numéro de téléphone + case 'number': + return $checker && is_string($value) && preg_match('/^(?:0|\+33 ?|0?0?33 ?|)([1-9] ?(?:[0-9] ?){8})$/i', $value); break; + // Tableau non vide + case 'array': + return $checker && is_array($value) && count($value) > 0; + break; + + // Boolean + case 'boolean': + return $checker && is_bool($value); + break; + + // Objet non vide + case 'object': + return $checker && is_object($value) && count((array) $value) > 0; + break; + + // Chaine JSON (on vérifie via le parser) + case 'json': + return $checker && is_string($value) && json_decode($value, true) !== NULL; + break; + + default: + return false; + break; } return $checker; @@ -227,5 +288,80 @@ } + + + + /* FONCTION QUI FORMATTE UN NUMÉRO DE TÉLÉPHONE + * + * @number Numéro de téléphone en +336/336/06/0336/00336 + * + * @return formatted Numéro formatté (06), on FALSE si erreur + * + */ + public static function formatNumber($number){ + // On met en quel que soit le type + $number = (string) $number; + + // On supprime tous les espaces + $number = str_replace(' ', '', $number); + + // On formatte le numéro + if( preg_match("/^(?:\+33|0?0?33|0)(.+)/", $number, $m) ) + $number = '0'.$m[1]; + + // On retourne le numéro formatté + return $number; + } + + + public static function readableNumber($number){ + /* (1) On formatte le numéro si c'est pas fait */ + $formatted = self::formatNumber($number); + + for( $i = 1 ; $i < strlen($formatted) ; $i++ ) + if( ($i-2) % 3 == 0 ) + $formatted = substr($formatted, 0, $i).' '.substr($formatted, $i); + + return $formatted; + } + + + //////////////////////////////////// + // _ _ + // __| | __ _| |_ ___ ___ + // / _` |/ _` | __/ _ \/ __| + // | (_| | (_| | || __/\__ \ + // \__,_|\__,_|\__\___||___/ + // + //////////////////////////////////// + // 1) Convertis une date en en francais explicite + public static function frDate($date){ + /* [1] On definit les traductions + =========================================================*/ + // Jours de la semaine + $days = array("Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche"); + // Mois de l'annee + $months = array("Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"); + + /* [2] On recupere le timestamp et les indices + =========================================================*/ + $time = strtotime($date); // timestamp + $daynum = intval( date('N', $time)-1 ); // jour dans la semaine + $monthnum = intval( date('n', $time)-1 ); // numero du mois dans l'annee + + + /* [3] On recupere les infos independemment + =========================================================*/ + $result = array( + $days[$daynum], // nom de jour + date('j', $time), // jour du mois + $months[$monthnum], // nom du mois + date('Y', $time), // annee + ); + + + return implode(" ", $result); + } + } ?> diff --git a/manager/ManagerError.php b/manager/ManagerError.php index c85d962..d6a5994 100755 --- a/manager/ManagerError.php +++ b/manager/ManagerError.php @@ -1,6 +1,6 @@ géré en js + /* EXPLICITE UN CODE D'ERREUR * @@ -70,33 +85,43 @@ */ public static function explicit($error){ switch($error){ - case self::Success: return "Tout s'est bien deroule"; break; + case self::Success: return "Tout s'est bien deroulé."; break; - case self::ParsingFailed: return "La lecture du fichier JSON a echoue"; break; - - case self::InvalidFlags: return "Les specifications (drapeaux) sont incorrects"; break; - case self::UnreachableResource: return "La ressource n'existe pas (404)"; break; - case self::MissingPath: return "Le chemin de delegation n'a pas ete renseigne"; break; - case self::WrongPathModule: return "Le chemin de delegation est incorrect ('nomModule/nomMethode')"; break; - case self::WrongPathRepo: return "Le chemin de delegation est incorrect ('nomRepo/nomMethode')"; break; - case self::UnknownModule: return "Le module n'existe pas"; break; - case self::UnknownRepo: return "Le repo n'existe pas"; break; - case self::UnknownMethod: return "Le methode n'existe pas"; break; - case self::UncallableMethod: return "Le methode n'est pas amorcable"; break; + case self::ParsingFailed: return "La lecture du fichier JSON ou XML a echouée."; break; - case self::ParamError: return "Un ou plusieurs parametres sont manquants ou incorrects"; break; - case self::ModuleError: return "Erreur lors du traitement du module"; break; - case self::RepoError: return "Erreur lors du traitement du repo"; break; + case self::InvalidFlags: return "Les spécifications (drapeaux) sont incorrects."; break; + case self::UnreachableResource: return "La ressource n'existe pas (404)."; break; + case self::MissingPath: return "Le chemin de délégation n'a pas été renseigné."; break; + case self::WrongPathModule: return "Le chemin de délégation est incorrect ('nomModule/nomMethode')."; break; + case self::WrongPathRepo: return "Le chemin de délégation est incorrect ('nomRepo/nomMethode')."; break; + case self::UnknownModule: return "Le module n'existe pas."; break; + case self::UnknownRepo: return "Le repo n'existe pas."; break; + case self::UnknownMethod: return "Le methode n'existe pas."; break; + case self::UncallableMethod: return "Le methode n'est pas amorçable."; break; - case self::PDOConnection: return "La connexion avec la base de donnees a echoue"; break; + case self::ParamError: return "Un ou plusieurs paramètres sont manquants ou incorrects."; break; + case self::ModuleError: return "Erreur lors du traitement du module."; break; + case self::RepoError: return "Erreur lors du traitement du repo."; break; - // default: return "Erreur inconnue..."; break; + case self::PDOConnection: return "La connexion avec la base de données a echouée."; break; + + case self::TokenError: return "Le token de connection est absent, érroné ou expiré."; break; + case self::PermissionError: return "Vous n'avez pas la permission d'effectuer cette action."; break; + case self::UploadError: return "Une erreur d'upload est survenue."; break; + case self::FormatError: return "Le fichier n'est pas au bon format."; break; + + default: return "Description d'erreur inconnue..."; break; } // Erreur inconnue return null; } + + public static function setHttpCode($error){ + http_response_code( $error == self::Success ? 200 : 417 ); + } + } -?> \ No newline at end of file +?> diff --git a/manager/ModuleRequest.php b/manager/ModuleRequest.php index cc7dab3..06fd7c1 100755 --- a/manager/ModuleRequest.php +++ b/manager/ModuleRequest.php @@ -1,28 +1,22 @@ - false + ); // Attributs prives utiles (initialisation) private $path; - private $data; + private $params; private $modules; + private $options; // Contiendra la reponse a la requete public $answer; @@ -37,12 +31,13 @@ /* CONSTRUCTEUR D'UNE REQUETE DE MODULE * * @path Chemin de delegation ("module/methode") - * @data Tableau contenant les parametres utiles au traitement + * @param Tableau associatif contenant les parametres utiles au traitement + * @token Token d'acces a l'api (OPTIONNEL) * * @return status Retourne si oui ou non tout s'est bien passe * */ - public function __construct($path=null, $data=null){ + public function __construct($path=null, $params=null, $token=null){ // Si pas parametre manquant, on quitte if( $path == null ){ $this->error = ManagerError::MissingPath; @@ -53,7 +48,7 @@ =========================================================*/ // Modules specifies $this->modules = json_decode( ResourceDispatcher::getResource(self::$config_path), true ); - + // Gestion de l'erreur de parsage if( $this->modules == null ){ $this->error = ManagerError::ParsingFailed; @@ -71,19 +66,37 @@ } // Type de @data (optionnel) - $data = (is_array($data)) ? $data : array(); + $params = (is_array($params)) ? $params : array(); /* [2] Verification du chemin (existence module+methode) =========================================================*/ if( !$this->checkPath($path) ) // Verification de la coherence du chemin + attribution return false; - // Gestion d'erreur interne - /* [3] Construction de l'objet + + /* [3] Verification des droits =========================================================*/ - $this->data = $data; + if( !$this->checkPermission($token) ) // Si on a pas les droits + return false; + + + /* [4] Verification des parametres (si @type est defini) + =========================================================*/ + if( !$this->checkParams($params) ){ // Verification de tous les types + $this->error = ManagerError::ParamError; + return false; + } + + /* [5] Récupèration des options + =========================================================*/ + $this->buildOptions(); + + + /* [6] Construction de l'objet + =========================================================*/ + $this->params = $params; $this->error = ManagerError::Success; return true; // On retourne que tout s'est bien passe @@ -94,35 +107,40 @@ /* EXECUTE LE TRAITEMENT ASSOCIE ET REMPLIE LA REPONSE * - * @return answer Retourne une reponse de type si tout s'est bien passe + * @return answer Retourne une reponse de type si tout s'est bien passe * */ public function dispatch(){ + /* [0] Si c'est un download, on lance la methode `download()` + =========================================================*/ + if( $this->options['download'] === true ) + return $this->download(); + /* [1] On verifie qu'aucune erreur n'a ete signalee =========================================================*/ if( $this->error != ManagerError::Success ) // si il y a une erreur - return new ModuleAnswer($this->error); // on la passe a la reponse + return new ModuleResponse($this->error); // on la passe a la reponse /* [2] On verifie que la methode est amorcable =========================================================*/ if( !is_callable($this->getFunctionCaller()) ){ $this->error = ManagerError::UncallableMethod; - return new ModuleAnswer($this->error); + return new ModuleResponse($this->error); } /* [3] On amorce la methode =========================================================*/ - $returned = call_user_func_array( $this->getFunctionCaller(), $this->data ); + $returned = call_user_func( $this->getFunctionCaller(), $this->params ); /* [4] Gestion de la reponse =========================================================*/ - $answer = new ModuleAnswer($this->error); - $answer->appendAll($returned); + $response = new ModuleResponse($this->error); + $response->appendAll($returned); - return $answer; + return $response; } @@ -130,71 +148,168 @@ - /* DESERIALISATION ET CREATION D'UN OBJET - * - * @jsonString Json au format string contenant les donnees - * - * @return instance Retourne un objet de type + + + + /* EXECUTE LE TRAITEMENT ASSOCIE ET RENVOIE UN FICHIER AVEC LE HEADER ET LE BODY SPECIFIE * */ - public static function fromString($jsonString){ - $json = json_decode( $jsonString, true ); + public function download(){ + /* [1] On verifie qu'aucune erreur n'a ete signalee + =========================================================*/ + if( $this->error != ManagerError::Success ) // si il y a une erreur + return new ModuleResponse($this->error); // on la passe a la reponse - // Verification du parsage - if( $json == null ) - return new ModuleRequest(); - // Verification des parametres - if( !isset($json['path']) ) - return new ModuleRequest(); + /* [2] On verifie que la methode est amorcable + =========================================================*/ + if( !is_callable($this->getFunctionCaller()) ){ + $this->error = ManagerError::UncallableMethod; + return new ModuleResponse($this->error); + } - // On definit $data au cas ou il soit vide - $data = (isset($json['data'])) ? $json['data'] : array(); - return new ModuleRequest($json['path'], $data); + /* [3] On amorce la methode + =========================================================*/ + $returned = call_user_func( $this->getFunctionCaller(), $this->params ); + + + /* [4] Vérification des erreurs et paramètres + =========================================================*/ + /* (1) Vérification de l'erreur retournée, si pas Success, on retourne l'erreur */ + if( isset($returned['ModuleError']) && $returned['ModuleError'] != ManagerError::Success ){ + $this->error = $returned['ModuleError']; + return new ModuleResponse($this->error); + } + + /* (2) Vérification du contenu, si pas défini */ + if( !isset($returned['body']) ){ + $this->error = ManagerError::ParamError; + return new ModuleResponse($this->error); + } + + /* (3) Si @headers n'est pas défini on met par défaut */ + if( !isset($returned['headers']) || !is_array($returned['headers']) ) + $returned['headers'] = array(); + + + $fromAjax = isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest'; + + /* [5] Si la requête vient d'ajax on crée un fichier temporaire et on renvoie son URL + =========================================================*/ + if( $fromAjax ){ + + + $tmpfname = '/tmp/download_'.uniqid().'.php'; + $bodyfname = __ROOT__.'/tmp/content_'.uniqid().'.php'; + + /* (1) On crée le fichier temporaire */ + $tmpfnameroot = __ROOT__.$tmpfname; + $tmpfile = fopen($tmpfnameroot, 'w'); + + fwrite($tmpfile, '$value) + fwrite($tmpfile, "header(\"$header: $value\");".PHP_EOL); + + /* (3) Script qui écrira le contenu */ + // 1) On écrit le contenu dans un fichier temporaire (et oui encore) + $bodyfile = fopen($bodyfname, 'w'); + fwrite($bodyfile, $returned['body']); + fclose($bodyfile); + chmod($bodyfname, 0775); + + fwrite($tmpfile, "readfile('$bodyfname');".PHP_EOL); + + /* (4) Script qui supprimera les fichiers temporaires */ + fwrite($tmpfile, "unlink('$bodyfname');".PHP_EOL); + fwrite($tmpfile, "unlink(__FILE__);".PHP_EOL); + + fwrite($tmpfile, '?>'.PHP_EOL); + + /* (5) On ferme le fichier */ + fclose($tmpfile); + chmod($tmpfnameroot, 0775); + + $response = new ModuleResponse(ManagerError::Success); + $response->append('link', $tmpfname); + + return $response; + + /* [6] Gestion du download direct si pas AJAX + =========================================================*/ + }else{ + /* (1) On définit les headers */ + foreach($returned['headers'] as $header=>$value) + header($header.': '.$value); + + /* (2) On affiche le contenu */ + echo $returned['body']; + + return true; + } } - - - - /* DESERIALISATION A PARTIR DES DONNEES POST * + * @url Contenu de l'url après api/ (si existe) * @post Tableau des donnes $_POST => @path + @data (opt) * * @return instance Retourne un objet de type * */ - public static function fromPost($post){ + public static function fromPost($url, $post){ + + /* [0] Verification de l'authentification + =========================================================*/ + // On definit le token + $token = isset($_SERVER['PHP_AUTH_DIGEST']) ? $_SERVER['PHP_AUTH_DIGEST'] : null; + /* [1] On verifie que le @path est renseigne =========================================================*/ + /* (1) Si le path est dans @url */ + $pathInUrl = is_string($url[0]) && strlen($url[0]) > 0 && preg_match('#^([\w_-]+)/([\w_-]+)/?$#', $url[0], $urlMatches); + + // On l'utilise pour le chemin + if( $pathInUrl ) + $post['path'] = $urlMatches[1].'/'.$urlMatches[2]; + + /* (2) On vérifie dans tous les cas si le path existe */ if( !isset($post['path']) ) return new ModuleRequest(); + + /* [2] On verifie que @data est renseigne =========================================================*/ // Si variable n'existe pas, on cree un tableau vide - $data = (isset($post['data'])) ? $post['data'] : array(); + $params = $post; - // Si c'est toujours pas un tableau, on essaie de voir si c'est un json - $data = (!is_array($data)) ? json_decode($data, true) : $data; - - // Si toujours pas de tableau, on cree un tableau vide - $data = (!is_array($data)) ? array() : $data; + // On retire le @path de @params + unset($params['path']); - /* [3] On retourne une instance de + + /* [3] On met les paramètres JSON en JSON (si ils décodent sans erreur) =========================================================*/ - return new ModuleRequest($post['path'], $data); - + foreach($params as $name=>$value){ + $json = json_decode( $value, true ); + // Si aucune erreur, on affecte la valeur + if( !is_null($json) ) + $params[$name] = $json; + } + /* [4] On retourne une instance de + =========================================================*/ + // On cree notre requete avec le token + return new ModuleRequest($post['path'], $params, $token); } - /* VERIFICATION DU FORMAT ET DE LA COHERENCE DU CHEMIN SPECIFIE * * @path String correspondant au chemin de delegation ("module/methode") @@ -214,6 +329,7 @@ $module = $matches[1]; $method = $matches[2]; + /* [2] Verification de l'existence du module (conf) =========================================================*/ if( !array_key_exists($module, $this->modules) ){ // Si le module n'est pas specifie dans la conf @@ -223,11 +339,11 @@ /* [3] Verification de l'existence de la methode (conf) =========================================================*/ - if( array_search($method, $this->modules[$module]) === false ){ // Si la methode n'est pas specifie dans la conf + if( array_key_exists($method, $this->modules[$module]) === false ){ // Si la methode n'est pas specifie dans la conf $this->error = ManagerError::UnknownMethod; return false; // On retourne FALSE, si erreur } - + /* [4] Enregistrement du chemin et renvoi de SUCCESS @@ -244,6 +360,180 @@ + + /* RETOURNE SI ON A LA PERMISSION D'EXECUTER CETTE METHODE + * + * @token Token d'acces a l'API (OPTIONNEL) + * + * @return permission Retourne si on a les droits ou pas pour executer cette methode + * + */ + private function checkPermission($token=null){ + /* [1] On recupere les informations utiles + =========================================================*/ + // On recupere le nom de la methode + $method = $this->modules[$this->path['module']][$this->path['method']]; + + // Si aucune permission n'est definie + if( !isset($method['permissions']) ) return true; + + /* [2] Gestion si un @token est defini + =========================================================*/ + if( Database::check('sha1', $token) ){ + + + /* (1) On verifie que le token est valide */ + $checkToken = new Repo('token/check', array($token) ); + $token_permissions = $checkToken->answer(); + + // Si le token est invalide, on retourne une erreur -> FAUX + if( $token_permissions === false ){ + $this->error = ManagerError::TokenError; + return false; + } + + $local_permissions = $token_permissions; + + + /* [3] Gestion si aucun token, avec utilisateur connecté + =========================================================*/ + }else if( isset($_SESSION['permission']) ) + $local_permissions = $_SESSION['permission']; + // Si ni token, ni SESSION, erreur + else{ + $this->error = ManagerError::PermissionError; + return false; + } + + + /* [4] Verification des droits parmi les permissions donnees + =========================================================*/ + /* (1) On recupere la liste des permissions possibles */ + $permissions = $method['permissions']; + + /* (2) Si aucune permission n'est definie, on laisse l'acces */ + if( count($permissions) == 0 ) return true; + + /* (3) On verifie qu'il y a au moins une permission ok */ + foreach($permissions as $permission) + if( in_array($permission, $local_permissions) ) return true; + + + /* [5] On retourne FAUX si aucun droit n'a ete trouve + =========================================================*/ + $this->error = ManagerError::PermissionError; + return false; + } + + + + + /* VERIFICATION DU TYPE DES PARAMETRES ENVOYES + * + * @params Tableau associatif contenant les parametres + * @params peut se voir rajouter les paramètres optionnels s'ils ne sont pas renseignés (initialisés à NULL) + * + * @return correct Retourne si oui ou non les parametres ont le bon type + * + */ + private function checkParams(&$params){ + /* [1] On verifie qu'il ne manque aucun parametre + =========================================================*/ + // Si @params n'est pas un tableau + if( !is_array($params) ) return false; + + $method = $this->modules[$this->path['module']][$this->path['method']]; + + + /* [2] Si le type est defini, pour chaque param, on teste + =========================================================*/ + foreach($method['parameters'] as $name=>$paramsdata){ + /* (1) On récupère si le paramètre est optionnel ou pas */ + $optional = isset($paramsdata['optional']) && $paramsdata['optional'] === true; + + /* (2) Récupère si le paramètre est un fichier et définit comme de type 'FILE' */ + $isFile = isset($paramsdata['type']) && $paramsdata['type'] == 'FILE' && isset($_FILES[$name]); + + /* (3) Si le paramètre est obligatoire et qu'il n'est pas donné -> erreur */ + if( !isset($params[$name]) && !$optional && !$isFile ) + return false; + + /* (4) Si le type n'est pas defini, on a pas besoin de le vérifier */ + if( !isset($paramsdata['type']) ) + continue; + + /* (5) Si le paramètre est optionnel et n'est pas donné */ + if( $isFile || $optional && (!isset($params[$name]) || is_null($params[$name])) ){ + // On le crée le param optionnel avec la valeur NULL + $params[$name] = null; + + // On donne une référence vers le fichier, si c'en est un + if( $isFile ) + $params[$name] = &$_FILES[$name]; + + continue; // On passe au paramètre suivant + + + /* (6) Si le paramètre est renseigné */ + }else + // Si la verification est fausse, on retourne faux + if( !Database::check($paramsdata['type'], $params[$name]) ) + return false; + + } + + /* [3] Gestion du retour, si tout s'est bien passe + =========================================================*/ + return true; + } + + + + + + /* AJOUT DES OPTIONS A PARTIR DE LA CONFIGURATION + * + */ + private function buildOptions(){ + /* [0] On récupère les options de la méthode en cours + =========================================================*/ + $method = $this->modules[$this->path['module']][$this->path['method']]; + + /* (1) Si 'option' n'est pas défini (ou incorrect), on met les valeurs par défaut */ + if( !isset($method['options']) || !is_array($method['options']) ) + return true; + + /* (2) Par défaut on définit les options par défaut */ + $this->options = self::$default_options; + + + /* (3) On récupère les options données */ + $options = $method['options']; + + + /* [1] Gestion des différentes options + =========================================================*/ + foreach($options as $option=>$value){ + /* (1) On ne prend en compte l'option que si elle est dans les options par défaut */ + if( !isset(self::$default_options[$option]) ) + continue; + + /* (2) Le type de la valeur doit être le même que celui de la valeur par défaut */ + if( gettype($value) != gettype(self::$default_options[$option]) ) + continue; + + /* (3) Si tout est bon, on définit la valeur */ + $this->options[$option] = $value; + } + + return true; + + } + + + + + /* RENVOI LE CHEMIN D'AMORCAGE DE LA METHODE * * @return path Retourne le chemin d'amorcage de la requete @@ -256,4 +546,4 @@ } -?> \ No newline at end of file +?> diff --git a/manager/ModuleAnswer.php b/manager/ModuleResponse.php similarity index 86% rename from manager/ModuleAnswer.php rename to manager/ModuleResponse.php index 808a4b3..e519ec9 100755 --- a/manager/ModuleAnswer.php +++ b/manager/ModuleResponse.php @@ -1,20 +1,20 @@ -error); + + // Type de contenu + // header('Content-Type: application/json'); + // On rajoute l'erreur au message - $returnData = array_merge( array('ModuleError' => $this->error), $this->data ); + $returnData = array_merge( + array( + 'ModuleError' => $this->error, + 'ErrorDescription' => ManagerError::explicit($this->error) + ), + $this->data + ); return json_encode($returnData); @@ -147,4 +159,4 @@ } -?> \ No newline at end of file +?> diff --git a/manager/Repo.php b/manager/Repo.php index 3d54c0b..a87edd8 100755 --- a/manager/Repo.php +++ b/manager/Repo.php @@ -1,18 +1,18 @@ -repositories = json_decode( ResourceDispatcher::getResource(self::$config_path), true ); - + // Gestion de l'erreur de parsage if( $this->repositories == null ){ $this->error = ManagerError::ParsingFailed; @@ -72,7 +72,7 @@ } // Type de @data (optionnel) - $data = (is_array($data)) ? $data : array(); + $data = (is_array($data)) ? $data : array(); /* [2] Verification du chemin (existence repo+methode) @@ -165,7 +165,7 @@ $this->error = ManagerError::UnknownMethod; return false; // On retourne FALSE, si erreur } - + /* [4] Enregistrement du chemin et renvoi de SUCCESS @@ -194,4 +194,4 @@ } -?> \ No newline at end of file +?> diff --git a/manager/autoloader.php b/manager/autoloader.php index e651e07..79bd249 100755 --- a/manager/autoloader.php +++ b/manager/autoloader.php @@ -1,11 +1,33 @@ Tableau contenant le nom des classes - * - */ - function autoload($classes){ - foreach($classes as $class){ - $name_only = substr(strrchr($class, '\\'), 1); - var_dump('use '.$class.' as '.$name_only.';'); - eval('use '.$class.' as '.$name_only.';'); - } - } - - - - - - - - - /* AUTOLOADER * * @className Nom de la classe appelee @@ -63,7 +63,7 @@ require_once $path; // on inclue le fichier } - + // On definit l'autoloader comme autoloader (obvious) spl_autoload_register('autoloader', false, true); @@ -73,4 +73,15 @@ /* On demarre la session securisee PHP =========================================================*/ \manager\sessionManager::session_start(); -?> \ No newline at end of file + + + + /* [3] Gestion des droits des utilisateurs + =========================================================*/ + /* (1) Retourne si l'utilisateur est connecte ou non */ + function connected(){ return isset($_SESSION['permission']) && count($_SESSION['permission']) > 0; } + + /* (2) Retourne si l'utilisateur a le status en question */ + function permission($type){ return connected() && in_array($type, $_SESSION['permission']); } + +?> diff --git a/manager/repo/parentRepo.php b/manager/repo/parentRepo.php new file mode 100644 index 0000000..b3e1558 --- /dev/null +++ b/manager/repo/parentRepo.php @@ -0,0 +1,116 @@ + Nom du getter du type 'getAll' ou 'getX' avec 'X' une colonne de la table en question + * @args Liste des arguments, $args[0] est la valeur du getter (sauf pour 'getAll') + * + * @return lines Retourne le résultat du fetchAll() + * + */ + public static function __callStatic($method, $args){ + // Si static::table_name() NULL + if( is_null(static::table_name()) ) return false; + + /* [1] On vérifie que la méthode est 'getX', avec X une chaine + =========================================================*/ + // Si c'est pas le bon format, on retourne une erreur + if( !preg_match('/^get(?:By(\w+)|(All))$/', $method, $matches) ) return false; + + /* [2] On charge la liste des colonnes de la table + =========================================================*/ + $getColumns = Database::getPDO()->query('SHOW COLUMNS FROM '.static::table_name()); + $cols = Database::delNumeric( $getColumns->fetchAll() ); + + $table_columns = array( + '_PRIMARY_' => array() // Contiendra les champs de la clé primaire + ); + + // On ajoute la liste des colonnes + foreach($cols as $column){ + // On enregistre la clé primaire (si elle l'est) + if( $column['Key'] == 'PRI' ) array_push($table_columns['_PRIMARY_'], $column['Field']); + + array_push($table_columns, $column['Field']); + } + + + /* [3] On vérifie que la valeur après 'get' est dans $table_columns + =========================================================*/ + $columnName = strtolower($matches[1]); + + $getAll = count($matches) > 2; // Si 'getAll' + $getById = $columnName == 'id'; + $getSomething = count($args) > 0 && in_array($columnName, $table_columns); // Si 'getX', et 'X' dans la liste des colonnes + + // Si ni 'getAll' ni 'getSomething' -> erreur + if( !$getById && !$getAll && !$getSomething ) return false; + + + /* [4] On rédige la requête + =========================================================*/ + $getRequestString = 'SELECT * FROM '.static::table_name(); + + // Si c'est 'getById', on ajoute une condition (clé primaire) + if( $getById ){ + + // S'il manque un paramètre, on retourne une erreur + if( count($args) < count($table_columns['_PRIMARY_']) ) + return false; + + // Pour chaque clé primaire (si elle est composée) + foreach($table_columns['_PRIMARY_'] as $i=>$primary_column) + // Première ligne + if( $i == 0 ) $getRequestString .= ' WHERE '.$table_columns['_PRIMARY_'][$i].' = :primary'.$i; + // Lignes suivantes + else $getRequestString .= ' AND '.$table_columns['_PRIMARY_'][$i].' = :primary'.$i; + + + // Si c'est 'getSomething', on ajoute une condition + }else if( $getSomething ) + $getRequestString .= ' WHERE '.$columnName.' = :value'; + + $getRequestString .= ' ORDER BY 1 ASC'; + + // On prépare la requête + $getRequest = Database::getPDO()->prepare($getRequestString); + + + /* [5] On exécute la requête + =========================================================*/ + // Si 'getById', on compose la clé primaire + if( $getById ){ + + $pdo_vars = array(); + + foreach($table_columns['_PRIMARY_'] as $i=>$primary_column) + $pdo_vars[':primary'.$i] = $args[$i]; + + $getRequest->execute( $pdo_vars ); + + // Si 'getSomething', on ajoute le champ + }else + $getRequest->execute(array( + ':value' => ($getSomething||$getById) ? $args[0] : null + )); + + /* [6] On récupère le résultat + =========================================================*/ + return Database::delNumeric( $getRequest->fetchAll() ); + } + + } + + +?> diff --git a/manager/sessionManager.php b/manager/sessionManager.php index 3963e00..9c36978 100755 --- a/manager/sessionManager.php +++ b/manager/sessionManager.php @@ -26,7 +26,7 @@ // On definit l'id session si donne en argument if( $session_id != null ) session_id( $session_id ); - + // Precaution: on met a jour le cookie setcookie('PHPSESSID', session_id(), time()+60*30 ); @@ -45,7 +45,7 @@ /*******************/ private static function update_token(){ $token = self::$prefix.self::secure_sha1(uniqid()); - + // On definit le token en session $_SESSION['session_token'] = $token; @@ -53,11 +53,17 @@ $_COOKIE['session_token'] = $_SESSION['session_token']; setcookie('session_token', $_COOKIE['session_token'], time()+60*30 ); } - + /************/ /* AMORCEUR */ /************/ public static function session_start(){ + + // \session_start(); + // return; + + + /* [1] Génération et Gestion des donnees a utiliser ==============================================================*/ // On genere le hash a partir des donnees personnelles @@ -79,16 +85,16 @@ \session_start(); // On verifie l'id session (5 premiers chars du hash des donnees perso) - $valid_sessid = strpos( session_id(), substr(self::$prefix,0,5) ) === 0; + $valid_sessid = strpos( session_id(), substr(self::$prefix,0,5) ) === 0; - // Si id session incorrect ou pas de token + // Si id session incorrect ou pas de token if( !$valid_sessid ) self::reset_session( $sessid ); // On initialise la session (bon id session) - + // si id session invalide - - + + /* [3] Verification du token ==============================================================*/ // On verifie que le token est valide diff --git a/test/ajaxTest.php b/test/ajaxTest.php new file mode 100644 index 0000000..f8765af --- /dev/null +++ b/test/ajaxTest.php @@ -0,0 +1,12 @@ + diff --git a/automate.php b/test/automate.php similarity index 99% rename from automate.php rename to test/automate.php index 1d65ac1..b2f330f 100755 --- a/automate.php +++ b/test/automate.php @@ -1,4 +1,4 @@ - Description du retour - + */ function testModuleDispatcher(){ @@ -189,7 +189,7 @@ /* TEST DU DISPATCHER DES REPO * * @return nomRetour Description du retour - + */ function testRepoDispatcher(){ @@ -305,4 +305,4 @@ // new ResourceDispatcher('f/svg/search/st/sub-menu-side/ff0000', true); -?> \ No newline at end of file +?> diff --git a/test/sessionTest.php b/test/sessionTest.php new file mode 100644 index 0000000..036c6ad --- /dev/null +++ b/test/sessionTest.php @@ -0,0 +1,53 @@ + + +
NO VALUE

+
NO VALUE
+ +



+ + + + diff --git a/todo.md b/todo.md index 7809e2d..aabafb0 100755 --- a/todo.md +++ b/todo.md @@ -135,7 +135,7 @@ - [x] [view/][view] Ajout de "use CLASS;" - [x] Ajout de "use CLASS;" dans les fichiers pour simplifier la lisibilite - [x] [phpunit/sessionManager] test unitaires du manager de session php -- [x] [ModuleAnswer] Gestion des erreurs au niveau interne des Modules +- [x] [ModuleResponse] Gestion des erreurs au niveau interne des Modules - [x] [autoloader][phpunit/bootstrap.php] Correction des bugs de $_SERVER avec PHPUnit -> autoloader + bootstrap personnalise - [x] [sessionManager] Import de sessionManager - [x] [phpunit/tests/Database_*] Tests unitaire de delNumeric() @@ -156,8 +156,8 @@ - [x] [view/*.php] Modification des views - [x] Mise a jour / Modification / Correction des images du menu-side - [x] [ModuleRequest->dispatch] Passage de l'erreur a la reponse -- [x] [ModuleAnswer->serialize] Integration de l'erreur dans la serialisation -- [x] [ModuleAnswer->get+getAll] Accesseurs aux donnees de la reponse +- [x] [ModuleResponse->serialize] Integration de l'erreur dans la serialisation +- [x] [ModuleResponse->get+getAll] Accesseurs aux donnees de la reponse - [x] Gestion des erreurs - [x] [ModuleError::explicit] Explicitation - [x] Conception du systeme de delegation des managers @@ -165,8 +165,8 @@ - [x] [ModuleRequest->__construct] Inline (en php) - [x] [ModuleRequest::fromString] Serialise (en json ) - [x] [ModuleRequest::FromURL] Par url (POST) - - [x] [ModuleAnswer] Module Answer - - [x] [ModuleAnswer->serialize] Serialisation pour renvoi + - [x] [ModuleResponse] Module Answer + - [x] [ModuleResponse->serialize] Serialisation pour renvoi - [x] [index.php] Gestion de url/api avec donnees POST - [x] [ModuleRequest->getFunctionCaller] Correction semantique du chemin d'amorcage, utilisation de tableau - [x] Gestion des erreurs diff --git a/view/machines.php b/view/machines.php index 026dd71..b32da0a 100755 --- a/view/machines.php +++ b/view/machines.php @@ -54,7 +54,7 @@ echo "
"; // echo 'Liste des utilisateurs:
'; - + // si erreur, on affiche l'explicitation if( $answer->error != ManagerError::Success ){ // var_dump( ManagerError::explicit($answer->error) ); @@ -72,14 +72,14 @@ foreach( $answer->get('machines') as $machine){ $clusters = new Repo('machine/getClusters', array($machine['id_machine'])); $clusters = $clusters->answer(); - + echo "
"; // Nom de la machine echo "#".$machine['name'].""; - // Icone vers la suppression + // Icone vers la suppression echo ""; echo ResourceDispatcher::getResource('f/svg/remove/st/sub-menu-side'); echo ""; @@ -91,12 +91,12 @@ - + // Code RFID echo ""; echo ResourceDispatcher::getResource('f/svg/card/st/container'); - + echo ""; echo $machine['code']; echo ""; @@ -105,7 +105,7 @@ // Groupes de la machine echo ""; echo ResourceDispatcher::getResource('f/svg/group/st/container'); - + if( $clusters != false ) foreach($clusters as $cluster) echo "".$cluster['name'].""; @@ -163,7 +163,7 @@ // Indice du resultat echo "machine 0 sur 0

"; echo "
"; - + echo "




"; echo "
"; @@ -194,9 +194,9 @@ // Indice du resultat echo "machine 0 sur 0

"; echo "
"; - + echo "




"; - + echo "
"; echo "
"; echo "";