Moemoea

  1. Développement d'un site pour un équipe sportive

    Quand on est webdesigner, arrive toujours le moment fatidique où pour la bonne cause on se retrouve embarquée dans l'aventure associative.
    Premier satellite est à fond dans la régate, tout le monde cherche des sponsors, il faut rassembler les troupes, les documents et les photos. En avant pour un mini-site communautaire.
    Ayant déjà un peu patouillé avec CakePhp, c'est l'occasion d'y plonger encore plus.

    Les fonctionnalités du site :

    1. Un calendrier éditable
    2. Upload de photos
    3. Édition d'articles
    4. Création/ gestion des équipages
    5. Création / édition des profils utilisateurs
    6. édition des résultats par équipage
    7. Récupération de la météo et de la marée en fonction du lieu de navigation
    8. Gestion des droits d'utilisateur : admin, entraineur, équipier, visiteur

    Détails techniques

    Le site étant destiné à une communauté assez réduite, je choisi d'utiliser html5 et css3 pour réduire le temps de codage du layout et avoir un look and feel agréable.

    Les améliorations de fonctionalité reposent assez souvent sur jquery et ajax.

    Récupération des données météo et marées

    Windguru

    Le site prévoit l'utilisation de ses données à condition de s'inscrire et d'utiliser leur propre script.
    Ça fonctionne parfaitement mais le code retourné n'est pas valide html5.
    html purifier pèse 4Mo et je n'arrive pas à le faire marcher.
    Bonne occasion de me plonger sérieusement dans les regex et de mitonner une petite moulinette qui me servira aussi pour le contenu du shom.

    Le Shom

    Tous les sites qui publient des infos sur la marée récupère leur données via le site du Shom. Il faut demander une autorisation de publication pour reprendre leur données.

    Je choisi d'utiliser cUrl pour récupérer les hauteurs d'eau et coeff.

    la moulinette pour obtenir un code html valide :

    /**
    @function process_output
    
    @param string           s     the string to be processed
    */
    function process_output($s){
    	$stripAttr = array('border="0"', 'hspace="0"', 'vspace="0"');
    	
    	$s = filtre_html($s);
    	$data = explode("\n", $s);
    	foreach ($data as $line){
    		$line = str_replace('>', ">\t", $line);
    		$lines = explode ("\t", $line);
    		foreach ($lines as $tag){
    			if(preg_match('#<(table|tr|th|td)[ ]+(.+)>#Ui', $tag) > 0) {
    				tidy_table ($tag);
    			}
    			elseif(preg_match('##Ui', $tag) > 0){
    				$tag = str_replace ($stripAttr, '', $tag);
    					$tag = str_replace ('src="wg_images/', 'src="'.$imgDir.'/wg_images/',$tag)
    				echo $tag;
    			}			
    			else{
    				echo $tag;
    			}
    		}
    	}	
    }
    
    function filtre_html($s) {
    	$s = str_replace("\r\n","",$s);
    	$s = str_replace("\n","",$s);
    	$s = str_replace("","\n",$s);
    	$s = str_replace("","\n",$s);
    	return($s);
    }
    
    function tidy_table ($data){
    	$s = preg_replace_callback('#<([a-z]+)[ ]+(.+)>#i', 'process', $data);
    	// we fetch tag name (1°parenthese) and tag content (2° parenthèse)
    	echo $s."\n";	
    }
    
    function process($matches){
    	$deprecTags = array('bgcolor','cellspacing', 'cellpadding');
    	$replacTags = array('background-color', 'border-spacing', 'padding');
    	$attrOk = array('colspan', 'rowspan');
    	$noTags = array('cellpadding=0', 'cellspacing=0','cellpadding="0"', 'cellspacing="0"', 'align=center');
    	$new = str_replace($noTags, '', $matches[2]).' ';
    	$s = '';
    	$attrs = '';
    	$style = '';
    	if(preg_match_all('#([a-z]+)\s?={1}["]?(\#?[0-9A-F%px]+)["]?\s+#Ui', $new, $attr)){
    
    	// we fetch each couple (attribute) = (value)
    		$c = count($attr[0]);
    		for ($i=0; $i < $c; $i++){
    			if (!in_array($attr[1][$i],$attrOk)){
    				if (stripos($attr[1][$i], 'bgcolor') !== false && strlen($attr[2][$i]<7)){
    					// fill in with 0 to have complete 6digit color string
    					$attr[2][$i] .= '0000';
    					$attr[2][$i] = substr($attr[2][$i], 0, 7);
    				}
    				$s .= $attr[1][$i].': '.$attr[2][$i].'; ';
    			}
    			else{
    				$attrs .= ' '.$attr[1][$i].'="'.$attr[2][$i].'"';
    			}
    		}
    		$style = ($s != '') ? ' style="'.$s.'"' : $style;
    		$new = '<'.$matches[1].strtolower($attrs.$style).'>';
    		$new = str_replace($deprecTags, $replacTags, $new);
    	}
    	else{
    		$new = '<'.$matches[1].' '.$new.'>';
    	}
    
    	return $new;
    
    }
    	

    Gestion du calendrier

    Il existe un plugin cakePhp basé sur fullcalendar : Event.
    La doc est accessible ici : arshaw.com/fullcalendar/docs/, voir aussi la liste de discussion.

    Le calendrier n'étant éditable que par l'entraineur, j'intègre le script dans admin_index au lieu de index.ctp.
    Je veux aussi avoir les samedi et dimanches sur la même ligne et afficher la description :

    $(document).ready(function() {
    	$('#calendar').fullCalendar({
    	// ....
    		firstDay: 1,
    	// ....
    		eventDrop: function(event) {	
    		var dt = new Date(event.start);   
    		// change for formatDate function
    		var newdate = $.fullCalendar.formatDate( dt, "yyyy-MM-dd");	   
    		var edt = new Date(event.end);
    		var enddate =  $.fullCalendar.formatDate( edt, "yyyy-MM-dd");
    		//.........
    		}
    		eventRender: function(event, element){
    		element.append(''+event.description+"")
    		},		
    	

    Je veux aussi pouvoir éditer in-place les events en cliquant sur la case du jour :

    	dayClick: function(date, allDay, jsEvent, view) {
    		var ndt = $.fullCalendar.formatDate( date, "yyyy-MM-dd");
    		$('#overlayer').load("url(array('controller'=>'events','action'=>'add','admin'=>true)); ?>/startdate:"+ndt) ;	
    		}	
    	

    Voila, ça suffit pour cette fois !
    La suite une autre fois.