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 !

mardi, mars 25 2008

La maturité de PHP en entreprise

Suite à quelques rencontres (pro/perso) diverses et variées, j'ai pu me rendre compte à quel point PHP perçait dans le monde professionnel.

Un (trop long) temps, PHP était perçu comme un langage de bidouilleurs pour faire des sites écrit à la vas-vite.
Mais ces derniers temps, avec la naissance et l'arrivée de framweworks MVC en PHP, tels CakePHP, Jelix, Zend Framwork, PHP fini sa "puberté" pour entrer dans la court des grands.

Zend Framework est d'ailleurs paru en version 1.5 avec derrière Zend, de grosses compagnies qui ont contribué telles Google et IBM ou encore Microsoft, FoxMedia interactive.

Tout cela laisse augurer un bel avenir pour le langage PHP et toute la communauté qui gravite autour de lui ;-)

dimanche, mars 9 2008

FoxMaSk'Z h0m3 4ans avec DotClear

4 ans avec DotClear en ce mois de mars 2008...

J'en profite pour changer de peau :)

D'ailleurs à propos de thèmes, après Lakme utilisé tout l'hiver, voici le thème Natural, frais (en ces journées de giboulées ;)

Et c'est donc reparti pour une nouvelle année : sous le signe de quelques projets persos (Open Source) tels PunCake CMS, et DafunSpirit.

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 !

PunBB 1.3 beta - la magie des 'hameçons' (aka Hook)

Après de très long mois d'attente (près de 18?), PunBB 1.3 sort en version béta avec quelques nouveautés dont :

  • le support de l'utf-8
  • des 'hooks'

cela parait peu dit comme cela mais attention les yeux ....

les hooks permettent dès maintenant, de créer des "extensions" sans toucher au coeur du code de punbb.
Ainsi l'avantage est de pouvoir produire des extensions de son choix qui soit installable / désinstable avec aisance et une maintenance accrue.

un exemple :
dans le script header.php on trouve la ligne suivante :

($hook = get_hook('hd_template_loaded')) ? eval($hook) : null;

celle ci permet d'ajouter ce que l'on veut après que le template se soit chargé.

Pour exploiter ce hook, prennons l'exemple "Random Quote" permettant de changer la description du forum aléatoirement.

le noeud hook id identifie quel hook de punbb on compte exploiter, ici donc hook id vaut hd_template_loaded

une fois installé dans le répertoire extensions, rendez vous dans l'administration du forum puis installez l'extension.

vous verrez immédiatement le hook produire son effet en changeant la description ;)

Pour info, les hooks commençants par :

  1. hd_ sont dans le header
  2. ft_ sont dans le footer
  3. in_ sont dans l'index
  4. li_ sont dans le login
  5. dl_ sont dans delete
  6. ex_ sont dans externe
  7. he_ sont dans l'aide
  8. mi_ sont dans misc
  9. mr_ sont dans moderate
  10. po_ sont dans post
  11. pf_ sont dans profile
  12. rg_ sont dans register
  13. re_ sont dans rewrite
  14. se_ sont dans search
  15. ul_ sont dans userlist
  16. vf_ sont dans viewforum
  17. vt_ sont dans viewtopic

vous voici parés prêt à créer/migrer vos MOD existantes ;-)

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.

- page 1 de 18