FoxMaSk'Z h0m3 - Dev-Gamer World


vendredi, avril 18 2008

CakePHP - BehaviorHelper

CakePHP : recherche globale

Pour les besoins de son portail il est fort utile d'avoir des fonctionnalités transversales à tout le portail.

C'est le cas de la fonction de recherche, qui peut aller à la pêche aux actualités/articles/billets/bogues etc...

La solution serait d'avoir une grosse fonction php qui parcourt toutes les tables,
mais cela n'est bien évidement pas satisfaisant, car inmaintenable, pas portable, trop lourd...

Donc l'idée va consister à créer une fonction de recherche par plugin (un plugin "cakephp" est une "partie" de l'application globale).

Puis lorsque le visiteur saisira une phrase sur le moteur de recherche, nous déclencherons la fonction de recherche de chaque plugin.

Comment procéder ?

Chaque plugin possède son propre répertoire, exemple
- app/plugins/news
- app/plugins/articles
- app/plugins/blog

dans chacun d'eux nous créons un script _prepend.php contenant ceci :

pour le plugins news :

<?php
$this->addBehavior('publicSearchHTML', array('newsBehavior','searchNews'));

class newsBehavior
{
	public function searchNews()
	{
	    #appel de son modèle
		App::import('Model','news.News');
		$news = & new News();
		
		$rs = $news->getNews(); 		
		
		/*
		* Ici le code sur l'iteration sur le recordset pour afficher le resultat
		* en invoquant le Helper HtmlHelper() pour le rendu bien sûr.
		*/
		
	}

?>

on comprend ici qu'il faut donc etre muni d'une fonction getNews() dans le model news.

Mais, pour le moment le _prepend.php est "inerte", par inerte j'entends, qu'il n'est pas utilisable en l'etat.

Effectivement, il manque une classe pour exploiter nos "comportements" (les classes xxxBehavior)

Voici donc le Helper (oui car il s'agit d'afficher des données, donc un helper;) qui gère tout cela :

(extrait issue de PunCake)

<?php
class PcbehaviorHelper extends Helper {

    public $puncake_behaviors = array();
    
    
    public function beforeRender() {
    
        /*
        *
        * ici le code permettant de parcourir le répertoire plugins 
        *
        */
        $this->path = dirname(__FILE__) . '/../../plugins/';
		foreach ($this->path as $root)
		{
			if (!is_dir($root) || !is_readable($root))
				continue;
			
			if (substr($root,-1) != '/') 
				$root .= '/';
			
			if (($d = @dir($root)) === false)
				continue;
			
			while (($entry = $d->read()) !== false)
			{
				$full_entry = $root.'/'.$entry;
				
				if ($entry != '.' && $entry != '..' && is_dir($full_entry)
				&& file_exists($full_entry.'/_prepend.php')
                    )
				{
					require $full_entry.'/_prepend.php';
				}
			}
			$d->close();
		}

    }
    
    /*
    * la fonction d'ajout du behavior 
    * au tableau des behaviors (utilisé par chaque _prepend.php)
    */
	public function addBehavior($behavior,$func)
	{
		if (is_callable($func)) {
			$this->puncake_behaviors[$behavior][] = $func;						
		}
	}

    
    /*
    * Fonction de rappel de la classe et fonction à éxécuter
    */

	public function callBehavior($behavior)
	{
		if (!empty($this->puncake_behaviors[$behavior]))
		{
			$args = func_get_args();
			array_shift($args);
			
			$res = '';
			
			foreach ($this->puncake_behaviors[$behavior] as $f) {
				$res .= call_user_func_array($f,$args);
			}
			
			return $res;
		}
	}
}
?>

Ceci fait, dans la vue de son choix, un simple appel $pcbehavior->callBehavior('publicSearchHTML');

déclenchera l'appel de la méthode searchNews de tous les plugins disposant d'un fichier _prepend.php .

Voilà !

Ce Helper s'applique donc quelque soit le behavior qu'on veut coder.

Par exemple, qd je me connecte sur mon portail, je veux etre avertis si j'ai des news/articles à modérer, des billets reçus etc.. etc..
Tout ceci ce gère de la même maniere que ci dessus.

1) ajout d'une fonction newToValidate de plus à ma class newsBehavior
2) ajout du behavior à la pile via $this->addBehavior('toValidate',array('newsBehavior','newsToValidate'))
3) appel du behavior dans la vue de mon choix par un $pcbehavior->callBehavior('toValidate');

Happy Cooking !

dimanche, mars 9 2008

Book CakePHP : Version Française !

CakePHP lance sa documentation pour la v 1.2.x.x dans la langue de votre choix ici http://book.cakephp.org/fr/

Vous avez la possibilité de venir y participer pour la traduire et la compléter !

Extrait :

Mode d'emploi :

1. Vous parcourez le site et décelez une erreur, une explication incomplète, une fonctionnalité qui n'est pas couverte, ou tout simplement un article qui n'est pas rédigé comme vous l'auriez rédigé.

2. Connectez-vous au Livre de Cuisine avec votre identifiant et votre mot de passe de la Bakery.

3. Rédigez des sections ou proposez-en de nouvelles, en utilisant un balisage HTML simple.

4. Revenez vérifier que votre contribution a été approuvée quelques jours plus tard.

Cette application est toute nouvelle, nous comptons donc sur votre indulgence et votre patience le temps que nous corrigions les dernières erreurs, avant la sortie définitive de CakePHP 1.2.

Cette doc est déjà commencée en bg · cz · de · en · es · fr · hu · it · ja · nl · pl · pt

......... Enfin ! :)

ps : bibi s'est atelé à la tâche pour la VF avec les volontaires de http://groups.google.com/group/cakephp-fr

mardi, février 19 2008

CakePHP : Componsant 'Security' et $form->input

Dans la "Vue", quand le componsant Security est 'activé (aka public $component = array('Security') dans notre controleur ), il n'y a pas moyen de contourner l'utilisation de $form->create $form->end et surtout $form->input. Si vous vous contentez de $form->create / end et codez vos input "à la main" (sans utiliser $form-> ) alors aucune chance pour que 'Security' vous laisse "passer" .

Ainsi donc ceci ne fonctionne pas pour la raison évoqué à l'instant :


<?php echo $form->create('Themes',array('action'=>'admin_save'));?>
        <p><?php echo __('Choose the theme to use on your portal.'); ?></p>

        <div class="two-cols">
        <?php
        $d = dir(PUNCAKE_THEMES_PATH);
        while (($entry = $d->read()) !== false)
        {
                if ($entry != '.' && $entry != '..' && $entry != '.svn'
                        && is_dir(PUNCAKE_THEMES_PATH.DS.$entry)
                        && file_exists(PUNCAKE_THEMES_PATH.DS.$entry.'/__infos.php'))
                {

                        $theme_infos = array(
                                'name' => '',
                                'desc' => array(),
                                'author' => '',
                                'version' => '',
                                'preview' => ''
                        );
                        require PUNCAKE_THEMES_PATH.DS.$entry.'/__infos.php';
?>

                        <div class="col">
                        <h3>
                        <input type="radio" name="data[Themes][pt_theme]" value="<?php echo
$entry;?>" <?php  echo ($pt_theme==$entry) ? "checked=\"checked\"" :
''; ?>/>
                        <label for="<?php echo $entry ?>"><?php echo $theme_infos['name']?
><?php echo (!empty($theme_infos['version']) ? ' (v'.

$theme_infos['version'].')' : '')?>
                        </label></h3>
                        <p class="field">
                        <?php echo (!empty($theme_infos['preview']) ? '<label for="'.
$entry.'"><img id="'.$entry.'" src="'.pt_portal_url.'themed/'.
$entry.'/'.$theme_infos['preview'].'" alt="" /></label>' : '') ?>
                        <span class="desc"><?php echo __('by',true).' '.
$theme_infos['author'] ?></span></p>
                        <?php echo (!empty($theme_infos['desc'][$user['language']]) ? '<p>'.
$theme_infos['desc'][$user['language']].'</p>' : (!
empty($theme_infos['desc']['English']) ? '<p>'.$theme_infos['desc']
['English'].'</p>' : ''))?>
                        </div>
<?php
                }
        }
        $d->close(); ?>
        </div>

        <?php echo $form->submit( __('Save',true) ,array('class'=>'submit'))?>
       <?php echo $form->end(); ?> 

Pour obtenir exactement le même code html mais qui satisfasse le composant 'Security' il faut la jouer comme ceci :


<?php echo $form->create('Themes',array('action'=>'admin_save'));?>
        <p><?php echo __('Choose the theme to use on your portal.'); ?></p>

        <div class="two-cols">
        <?php
        $d = dir(PUNCAKE_THEMES_PATH);
        while (($entry = $d->read()) !== false)
        {
                if ($entry != '.' && $entry != '..' && $entry != '.svn'
                        && is_dir(PUNCAKE_THEMES_PATH.DS.$entry)
                        && file_exists(PUNCAKE_THEMES_PATH.DS.$entry.'/__infos.php'))
                {

                        $theme_infos = array(
                                'name' => '',
                                'desc' => array(),
                                'author' => '',
                                'version' => '',
                                'preview' => ''
                        );
                        require PUNCAKE_THEMES_PATH.DS.$entry.'/__infos.php';

                        $version = (!empty($theme_infos['version']) ? ' (v'.
$theme_infos['version'].')' : '');

                        $before = '<div class="col">'."\n";
                        $before .= '<h3>'."\n";

            $after = '';
            $after .= '</h3>';
            $after .= '<p class="field">'."\n";

            $after .= (!empty($theme_infos['preview']) ? '<label
for="'.$entry.'"><img id="'.$entry.'" src="'.pt_portal_url.'themed/'.
$entry.'/'.$theme_infos['preview'].'" alt="" /></label>' :
'');
            $after .= '<span class="desc">'. __('by',true).' '.
$theme_infos['author'] .'</span></p>'."\n";
            $after .= (!empty($theme_infos['desc']
[$user['language']]) ? '<p>'.$theme_infos['desc']
[$user['language']].'</p>' : (!empty($theme_infos['desc']
['English']) ? '<p>'.$theme_infos['desc']['English'].'</p>' : ''));

            $after .= '</div>'."\n";

                        echo $form->input('pt_theme',
                                array(
                                'div'=>false,
                                    'label'=>true,
                                    'legend'=>false,
                                'type'=>'radio',
                                'before' => $before,
                                'after' => $after,
                                'value' => $pt_theme,
                                'options'=> array($entry=>$theme_infos['name'].
$version)

                        )
                );
                }
        }
        $d->close(); ?>
        </div>

        <?php echo $form->submit( __('Save',true) ,array('class'=>'submit'))?


<?php echo $form->end(); ?> 

lundi, février 4 2008

CakePHP - Security and Sanitize : r0x !

Avec CakePHP, renforcer son site web devient un jeu d'enfant grâce à 2 libs :

Le premier intervient lors de l'utilisation de formulaire.
Le second intervient lors du traitement des données des formulaires avant de stocker les infos dans la base de données.

voici le controller dans lequel on utilise Security :


class SettingsController extends AppController {
	public $name = 'Settings';
	public $helpers     = array('Html','Form', );
							
	public $components   = array( 'Security') ;	
	
	public function beforeFilter() {
	    #si des données sont renvoyées, activons la securité !

	    if (!empty($this->data))
                  #s'il s'agit d'un POST on autorise l'action "index" 
    	          $this->Security->requirePost('index');
    	    
	}

	public function admin_index () {
...
		if (!empty($this->data)) {
...
                }
       }

la fonction beforeFilter agit avant que admin_index ne soit appelé et c'est là que Security entre en jeu.
$this->Security->requirePost('index');

ceci permet de filtrer les appels directs à l'action "index" qui ne peuvent avoir lieu que via un POST ! Tout autre action sera envoyée dans un "blackHole" (Trou noir ;)

voyons donc maintenant la Vue :

  • on créé le fomulaire

<?php echo $form->create('Settings',array('action'=>'admin_index') ?>

  • on ajoute un champ de texte qqconque

<?php echo $form->text('p_title' ,array("size"=>50,"maxlength"=>255,"value"=>pt_portal_title)) ?>

  • on ajout un bouton

<?php echo $form->submit(__('Save',true)); ?>

  • on fini le formulaire

<?php echo $form->end(); ?>

c'est 4 lignes vont produire les lignes suivantes :


<form method="post" action="/admin/settings">
<fieldset style="display:none;"><input type="hidden" name="_method" value="POST" /><input type="hidden" name="data[__Token][key]" value="2dc4f7414071176b5eef24ee54f8db34adf83632" id="Token409653362" />
</fieldset>
<input name="data[Settings][p_title]" type="text" size="50" maxlength="255" value="" id="SettingsPTitle" />	
<div class="submit"><fieldset style="display:none;"><input type="hidden" name="data[__Token][fields]" value="9a18269dee671707eb5d1dbdaba4b16f9def551a" id="TokenFields1740898927" /></fieldset><input type="submit"  value="Enregistrer" /></div>		
</form>

décortiquons ce qui s'est passé :

le fait d'avoir ajouté la ligne public $components = array( 'Security') ; dans notre controller, indique à CakePHP, de générer dans la Vue, les champs __Token, lors de l'utilisation de $form->create() et $form->end()

Ainsi paré, votre application part à la plupart des tentatives d'accès frauduleuses ! :D

simple et efficace, on utilise Sanitize comme suit :

uses('sanitize');
// Next, create a new Sanitize object:
$mrClean = new Sanitize();

ensuite on filtre toutes les données avec un simple $mrClean->escape($my_data);

ceci s'utilise généralement pour filtrer les données provenant de formulaire, avant de les stocker dans notre base.

plus d'infos sur CakePHP Security et Satinize :)

Bonne lecture !

jeudi, janvier 3 2008

CakePHP (1.2.0.6311) sorti tout chaud du four

En cette période d'épiphany, CakePHP tire (non pas les rois) une version beta 1.2.0.6311 de son zoli :D framework MVC .

Au menu plein d'améliorations et corrections de bugs ( dont voici la liste complète ) dont une amélioration qui me fait plaisir concernant la gestions des plugins et des controllers ;-)

Pour ceux qui douterait de l'utilité/fiabilité de ce framework, sachez que celui ci est "juste" utilisé par le petit site Mozilla Addons ;)

mercredi, décembre 26 2007

CakePHP - PunBB - statistique du forum

Quelques conceptes de bases.

Pour pouvoir afficher des info réutilisables dans ses Views CakePHP fourni des "Helpers" et des Elements Le Helper étend les possibilités des Vues et les Elements sont des petits bouts de code php/html qui peuvent étre affichés plusieurs fois dans la même page.

Ainsi donc le "bloc de stats" pourrait très bien être affiché dans plusieurs pages pour chacun des plugins news/articles/lexique etc..

Donc nous allons faire un element "app/views/elements/stats.ctp" comme suit :

<?php
$bloc->startBloc(__('Forums statistics',true), 'stats');				
				
$pt_total_users = $this->requestAction('users/nbUsers');
$pt_last_user 	= $this->requestAction('users/lastUser');	
$pt_total_posts = $this->requestAction('forum/stats');
?>
<ul>
<li><?php echo __('Total numbers of members:',true).' '.$pt_total_users['0']['0']['total_user']?></li>
<li><?php echo __('Newest resgistered user:',true).' <a href="'.pt_forum_url.'profile.php?id='.$pt_last_user['0']['User']['id'].'">'.$pt_last_user['0']['User']['username']?></a></li>
<li><?php echo __('Total numbers of topics:',true).' '.$pt_total_posts['0']['0']['total_topics']?></li>
<li><?php echo __('Total numbers of posts:',true).' '.$pt_total_posts['0']['0']['total_posts']?></li>
</ul>
<?php
$bloc->endBloc();
?>

comment tout cela fonctionne :

dans le script app/app_controller.php de notre applications nous faisons ceci :

class AppController extends Controller {
public $helpers = array ('Bloc','Html');
...
}

et le helper bloc contient ceci (app/views/helpers/bloc.php):

<?php
class BlocHelper extends Helper {
 	 	
 	function startBloc($title='',$id='') {	
		static $i;	
		$id = $id != '' ? $id : $i;		
		$res = '';
		$res .= "\t".'<div class="block" id="'.$id.'">'."\n";		
		if ($title!='')
			$res .= "\t".'<h2><span>'.$title.'</span></h2>'."\n";		
		$res .= 
		"\t\t".'<div class="box" id="box_'.$id.'">'."\n";
		
		if (PT_DEFAULT_THEME == 'punbb') 
			$res .= "\t\t\t".'<div class="inbox">'."\n";
		
		echo $res;
		$i++;
	}
	function endBloc() {
 		if (PT_DEFAULT_THEME == 'punbb') {		
			echo 
			"\t\t\t".'</div>'."\n".
			"\t\t".'</div>'."\n".
			"\t".'</div>';
 		} 
 		else {
 			echo 
			"\t\t".'</div>'."\n".
			"\t".'</div>'; 			
 		}
	} 
 	
}
?>

Ainsi paré : le controlleur principal dispose d'un helper bloc
donc la vue peut utiliser $bloc->endBloc et ->startBloc

ceci est une premiere etape :
suit ensuite dans notre element les 3 lignes :


$pt_total_users = $this->requestAction('users/nbUsers');
$pt_last_user 	= $this->requestAction('users/lastUser');	
$pt_total_posts = $this->requestAction('forum/stats');

ces 3 lignes effectuent des "requestAction" lequelles permettent d'accèder aux methodes nbUser et lastUser de la class UsersController et à la methode stats de la class ForumControllers, en voici le détail :


<?php
class UsersController extends AppController {
	public $name 	= 'Users';
[...]
	public function lastUser () {		
		$strQuery =  'SELECT id, username FROM '.DB_PREFIX.'users as User' .
						' ORDER BY registered DESC LIMIT 1';
		$resultset = $this->User->query($strQuery);
		return $resultset; 									
	}	
	public function nbUsers () {
		
		$strQuery = 'SELECT COUNT(id)-1 AS total_user FROM '.DB_PREFIX.'users AS User ';		  
		$resultset = $this->User->query($strQuery);
		return $resultset; 	
     }
[...]
}

ceci nous permet donc d'avoir facilement les information de stats de notre forum de manière utlra simplissime dans un bloc qui s'affichera ou bon vous semble comme suit :

<?php  echo $this->renderElement('stats');?>

lundi, décembre 24 2007

CakePHP - PunBB - architecture generale

Voici l'architecture générale avec la liste des répertoires de son application

- app
--- config contient les fichiers de config de son application tel database.php, bootstrap.php etc..

--- controllers contient les controllers de votre application

--- locales contient les traductions

--- models contient les models

--- plugins contient des applications nommé plugins (détail plus bas)

--- vendors contient des librairies externes à CakePHP et nécéssaire à son application

--- views contient les views de votre application

--- webroot contient la partie "web" de votre application --- webroot/forum/ l'endroit où est installé punBB !

le répertoire plugins reprend l'integralité de l'arborscence MVC ce qui donne :

  1. plugins
  2. - news
  3. - news/news_app_controller.php class du controller du plugins news
  4. - news/news_app_model.php class du mdel du module du plugin news
  5. - news/controllers/news_controller.php le controlleur du plugin news
  6. - news/models/news.php le model du plugin news
  7. - news/views/themed/punbb/news/index.ctp la vue principale du module news

La configuration apache devra donc définir un VirtualHost sur le répertoire app/webroot/ comme DOCUMENT_ROOT.

samedi, décembre 22 2007

CakePHP - débuter son projet avec "Bake"

CakePHP permet de générer son application en quelques petites minutes grâce à "bake" !

Pour ce faire on ouvre une console (windows ou linux c'est kifkif) et on se fait :

cake bake foxmask

pour créer l'arboresence du projet "foxmask"

ensuite on créé le répertoire config dans le répertoire "foxmask" on défini les paramètre de connexion à sa/ses base(s) de données et on peut se lancer dans la création des Models Views Controller comme suit :

cake bake -app foxmask

Il vous suffira donc de suivre les choix des menus dans la console et en quelques instants vous aurez achevé votre projet ;)

Il restera alors à fignoler des détails mais vous pouvez dores et déjà accéder à votre application via http://localhost/foxmask/

Plus de détails sur le Tuto de creation de blog en vidéo

Poursuivre avec CakePHP intégrant PunBB

jeudi, décembre 6 2007

CakePHP et PunBB : introduction

Après l'édpisode des comparatifs de framework PHP, voici une astuce pour vous permettre de franchir le pas vers CakePHP (un bon framework PHP utilisant MVC) tout en utilisant PunBB comme outil de forum.

L'idée de base est la suivante : je veux créer un portail basé sur le framework CakePHP mais en utilisant une seul et unique session entre mon portail et mon forum.

Donc pour y parvenir, dans le script dispatcher.php de CakePHP je fais le classique include du script common.php de punbb comme suit :

j'edite le script cake/dispatcher.php et au dessus de

 class Dispatcher extends Object {

j'ajoute :

 define('PUN_ROOT',WWW_ROOT. 'forums/');
 require PUN_ROOT.'include/common.php';

de là, partout dans CakePHP nous avons la "main" sur les variables punBB et la gestion de la session.

Donc dans mon controlleur gérant l'utilisateur contiendra ni plus ni moins qu'un copier/coller du code de gestion de la fonction login (avec l'action = in et out selon le cas)

  class UsersController extends AppController {
      public function login ()
    {
    	global $pun, $db, $pun_config, $db_type, $lang_login;
        $this->set('error', false);
       #soumission du formulaire de login
        if (!empty($this->data))
        {
      #analyse du login/pass ; recherche dans la base et creation du cookie
       }
     }
   }

évidement on n'oubliera pas d'ajouter un global $pun_user par exemple, dans la fonction login, pour manipuler les données que cette variable contient.

Dans un prochain billet, j'aborderai les vues (le V de MVC ;) et comment produire un menu et recuperer dans un bloc de son portail les 'n' derniers sujets recents du forum.

dimanche, novembre 11 2007

Framework PHP5 - Comparatif

A la recherche d'un Framwork PHP5 utilisant l'architecture bien connu qu'est le "Modèle - Vue - Controleur" ou MVC pour coder un logiciel libre "dernière génération" :) , j'ai croisé sur mon chemin nombre de ceux cités sur wikipedia

Pour ceux ne connaissant pas le MVC, le principe est le suivant : le Modèle gère la logique métier et les accès aux bases de données, la Vue gère l'affichage de ce que voit l'utilisateur avec l'écran, le Controleur gère les evenements déclenchés par l'utilsateur. La force de cette architecture est sa modularité.

  • Jelix écrit en PHP5 - projet jeune et dynamique.
  1. Avantage : pour ceux codant déjà en POO ; tout y est ; DAO, CRUD et tout le toutim.
  2. Inconvénient : un peu "jeune" et pas si facile que cela à apréhender à prime abord mais en prennant bien son temps on fini par arriver à faire ce qu'on veut.
  3. Cible : Tout developpeur, webmaster, pas de restriction pour projet de logiciel libre
  • CakePHP écrit en PHP (tout court) et supportant PHP5 parfaitement - on peut donc coder en PHP5 en utilisant celui ci.
  1. Avantage : très complet ; communauté active ; projet dynamique.
  2. Inconvénient : pas préhemptif (à mon goût), manque cruellement de doc ... version beta ...
  3. Cible : Tout developpeur, webmaster, pas de restriction pour projet de logiciel libre
  1. Avantage : très facile de mise en oeuvre - tuto videos de 9et 20mn bleuffant !
  2. Inconvénient : ... pas encore trouvé ...
  3. Cible : les developpeurs de site web ne publiant pas de soft sous GPL ....
  • Symfony écrit en PHP5 - le mastodonte
  1. Avantage : complet et adapté pour un milieu pro ou pour un hébergement permettant de toucher à la config PHP. Dispose de scripts permettant de generer les class modèle via des scripts de CRUD (Create Request Update Delete).
  2. Inconvénient : on ne peut pas coder un logiciel libre "portable" d'hebergeur à hébergeur pour la raison évoquer ci dessus ... on doit pouvoir toucher au include_path dans le php.ini. Même si certains directive apache le permette ; tous les hébergeurs ne l'autorisent pas.
  3. Cible : les developpeurs en entreprise
  1. Avantage : la référence ? :D
  2. Incovénient : je réitèrerai l'inconvénient pour Symfony. Si vous pouvez toucher au include_path de php.ini alors le coeur balance entre ZF ou Symfony.
  3. Cible : les developpeurs en entreprise

Addendum  : après avoir approfondi l'histoire de licence pour CodeIgniter, il apparait que CI permette de releaser son soft sous GPL tant qu'on ne touche pas au core ou bien qu'on en averti son auteur.

Conclusion :

mon choix se porterait sur :

  1. CakePHP pour les avantages évoqués ;)
  2. CodeIgniter (puisque utilisable à souhait !)