Cette page avait pour objectif de décrire les avantages que
WikiNi gagnerait à ne pas charger l'entête en premier (comportement de la plupart des handlers) et à proposer une solution technique (ou du moins un exemple de mise en oeuvre).
Depuis le 02/10/2006 les changements proposés ont été intégrés au CVS HEAD, avec la solution
b. comme point de départ de la mise en oeuvre. Cela a été intégré en raison notemment en raison des besoins de la
RoadMapNouveauStyleWikiNi.
Fonctionnement actuel de WikiNi
Description du fonctionnement actuel de
WikiNi concernant le chargement et l'affichage des différents éléments d'une page par la plupart des handlers (sauf le
HandlerEdit? dans la version cvs suite à une de mes modifications):
- chargement de l'entête (ActionHeader?)
- affichage de l'entête
- chargement et traitement de la page
- affichage de la page traitée
- chargement du pied de page (ActionFooter?)
- affichage du pied de page
avantages
- suit la logique d'affichage d'une page
- ne nécessite pas de mise en cache (affichage direct) bien qu'en fait il y en a tout de même une par l'intermédiaire des fonctions ob
inconvénients
- il est impossible d'interagir avec l'entête puisqu'elle est générée dès le départ
- l'entête est parfois chargée inutilement (principalement lors de redirections ou un message d'erreur provoquant un die())
Correction des inconvénients
Pour corriger les inconvénients, il faut changer l'ordre de traitement des éléments qui composent la page comme ceci:
- chargement et traitement de la page
- chargement de l'entête (ActionHeader?)
- affichage de l'entête
- affichage de la page traitée
- chargement du pied de page (ActionFooter?)
- affichage du pied de page
Ceci résoud les inconvéniants
- pour interagir avec l'entête il suffit de sauvegarder les informations nécessaires à l'interaction pendant l'étape 1 et de les utiliser à l'étape 2
- si quelque chose rend inutile le chargement de l'entête (une redirection par exemple), elle se produit à l'étape 1 et empêchera les autres étapes de s'exécuter.
Mise en oeuvre de la solution
Pour implémenter cette technique, il faut modifier chaque handler. Il existe deux solutions pour ce faire:
- Utiliser une mise en cache dans une variable: c'est ce qui se fait dans le HandlerEdit? (WikiNi >= 0.5.0):
- on définit une variable $buffer initialisée à la chaine vide ''
- on remplace chaque appel de echo par $buffer .=
- on remplace l'affichage de html brut (c'est à dire le html en dehors des balises php) par des chaines de caractères équivalentes que l'on concate de la même façon à $buffer
- à noter qu'il est certainement plus simple d'utiliser des guillemets simples pour ce faire vu que le html contient déjà des guillemets doubles
- Utiliser la mise en cache fournie par php, c'est à dire les fonctions ob (déjà utilisées massivement dans WikiNi, notemment dans la méthode IncludeBuffered)
- on remplace l'appel du header par ob_start();
- juste avant l'apel du footer, on ajoute:
- $buffer = ob_get_clean();
- echo $this->Header();
- echo $buffer;
La deuxième technique est beaucoup plus simple puisqu'elle ne nécessite que très peu de modification des handlers. Par exemple si on l'applique au
HandlerShow, ça donne ceci:
<?php
/*
$Id: show.php,v 1.19 2005/03/07 22:40:09 nepote Exp $
Copyright (c) 2002, Hendrik Mans <hendrik@mans.de>
Copyright 2002, 2003 David DELON
Copyright 2002, 2003 Charles NEPOTE
Copyright 2003 Eric DELORD
Copyright 2003 Eric FELDSTEIN
Copyright 2004 Jean Christophe ANDRÉ
Copyright 2005-2006 Didier Loiseau
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. The name of the author may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// Vérification de sécurité
if (!defined("WIKINI_VERSION"))
{
die ("accès direct interdit");
}
ob_start();
?>
<div class="page">
<?php
if ($HasAccessRead=$this->HasAccess("read"))
{
if (!$this->page)
{
echo "Cette page n'existe pas encore, voulez vous la <a href=\"".$this->href("edit")."\">créer</a> ?" ;
}
else
{
// comment header?
if ($this->page["comment_on"])
{
echo "<div class=\"commentinfo\">Ceci est un commentaire sur ",$this->ComposeLinkToPage($this->page["comment_on"], "", "", 0),", posté par ",$this->Format($this->page["user"])," à ",$this->page["time"],"</div>";
}
if ($this->page["latest"] == "N")
{
echo "<div class=\"revisioninfo\">Ceci est une version archivée de <a href=\"",$this->href(),"\">",$this->GetPageTag(),"</a> à ",$this->page["time"],".</div>";
}
// display page
$this->RegisterInclusion($this->GetPageTag());
echo $this->Format($this->page["body"], "wakka");
$this->UnregisterLastInclusion();
// if this is an old revision, display some buttons
if (($this->page["latest"] == "N") && $this->HasAccess("write"))
{
$latest = $this->LoadPage($this->tag);
?>
<br />
<?php echo $this->FormOpen("edit") ?>
<input type="hidden" name="previous" value="<?php echo $latest["id"] ?>" />
<input type="hidden" name="body" value="<?php echo htmlentities($this->page["body"]) ?>" />
<input type="submit" value="Rééditer cette version archivée" />
<?php echo $this->FormClose(); ?>
<?php
}
}
}
else
{
echo "<i>Vous n'êtes pas autorisé à lire cette page</i>" ;
}
?>
<hr class="hr_clear" />
</div>
<?php
if ($HasAccessRead && (!$this->page || !$this->page["comment_on"]))
{
// load comments for this page
$comments = $this->LoadComments($this->tag);
// store comments display in session
$tag = $this->GetPageTag();
if (!isset($_SESSION["show_comments"][$tag]))
$_SESSION["show_comments"][$tag] = ($this->UserWantsComments() ? "1" : "0");
if (isset($_REQUEST["show_comments"])){
switch($_REQUEST["show_comments"])
{
case "0":
$_SESSION["show_comments"][$tag] = 0;
break;
case "1":
$_SESSION["show_comments"][$tag] = 1;
break;
}
}
// display comments!
if ($this->page && $_SESSION["show_comments"][$tag])
{
// display comments header
?>
<div class="commentsheader">
Commentaires [<a href="<?php echo $this->href("", "", "show_comments=0") ?>">Cacher commentaires/formulaire</a>]
</div>
<?php
// display comments themselves
if ($comments)
{
foreach ($comments as $comment)
{
echo "<a name=\"",$comment["tag"],"\"></a>\n" ;
echo "<div class=\"comment\">\n" ;
if ($this->HasAccess('write', $comment['tag'])
|| $this->UserIsOwner($comment['tag'])
|| $this->UserIsAdmin($comment['tag']))
{
echo '<div class="commenteditlink">';
if ($this->HasAccess('write', $comment['tag']))
{
echo '<a href="',$this->href('edit',$comment['tag']),'">Éditer ce commentaire</a>';
}
if ($this->UserIsOwner($comment['tag'])
|| $this->UserIsAdmin($comment['tag']))
{
echo '<br />','<a href="',$this->href('deletepage',$comment['tag']),'">Supprimer ce commentaire</a>';
}
echo "</div>\n";
}
echo $this->Format($comment["body"]),"\n" ;
echo "<div class=\"commentinfo\">\n-- ",$this->Format($comment["user"])," (".$comment["time"],")\n</div>\n" ;
echo "</div>\n" ;
}
}
// display comment form
echo "<div class=\"commentform\">\n" ;
if ($this->HasAccess("comment"))
{
?>
Ajouter un commentaire à cette page:<br />
<?php echo $this->FormOpen("addcomment"); ?>
<textarea name="body" rows="6" cols="65" style="width: 100%"></textarea><br />
<input type="submit" value="Ajouter Commentaire" accesskey="s" />
<?php echo $this->FormClose(); ?>
<?php
}
echo "</div>\n" ;
}
else
{
?>
<div class="commentsheader">
<?php
switch (count($comments))
{
case 0:
echo "Il n'y a pas de commentaire sur cette page." ;
break;
case 1:
echo "Il y a un commentaire sur cette page." ;
break;
default:
echo "Il y a ",count($comments)," commentaires sur cette page." ;
}
?>
[<a href="<?php echo $this->href("", "", "show_comments=1") ?>">Afficher commentaires/formulaire</a>]
</div>
<?php
}
}
$content = ob_get_clean();
echo $this->Header();
echo $content;
echo $this->Footer();
?>
Interaction avec l'entête (ActionHeader?)
Comme dit plus haut, cette technique permet d'interagir avec l'entête. La plupart de ces interactions résident probablement au niveau des actions utilisées dans la page, bien que cela laisse également au handler la possibilité d'interagir aussi. Une première interraction possible est celle qui permettrait de modifier le titre et les meta description et keywords de l'entête, pour une meilleure indexation dans les moteurs de recherche, et pour donner un titre plus clair à chaque page. Pour ce faire je vais baser mes modifications sur la possibilité de
GererLesActionsSousFormeDObjets, ce qui simplifie grandement l'implémentation (mémoire propre de l'action, facilité de vérifier les arguments). Cette implémentation est extrêmement simple puisqu'il suffit d'adapter l'
ActionHeader? elle-même pour l'appliquer. Je me base donc sur la version de l'
ActionHeader? que j'avais fournie dans
GererLesActionsSousFormeDObjets, voici ce que ça donne:
<?php
/* header.php
Copyright (c) 2002, Hendrik Mans <hendrik@mans.de>
Copyright 2002, 2003 David DELON
Copyright 2002, 2003, 2004 Charles NEPOTE
Copyright 2002 Patrick PAUL
Copyright 2003 Eric DELORD
Copyright 2006 Didier LOISEAU
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. The name of the author may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
class ActionHeader extends WikiniAction {
var $title;
var $desc;
var $keyw;
function ActionHeader(&$wiki)
{
parent::WikiniAction($wiki);
}
function PerformAction($argz)
{
$err = true;
$res = '';
if (isset($argz['title']))
{
$err = false;
$this->title = $argz['title'];
$res .= $this->HtmlWarning('Définition du titre: ' . htmlspecialchars($this->title));
}
if (isset($argz['pagedesc']))
{
$err = false;
$this->desc = $argz['pagedesc'];
$res .= $this->HtmlWarning('Définition du meta description: ' . htmlspecialchars($this->desc));
}
if (isset($argz['pagekeyw']))
{
$err = false;
$this->keyw = $argz['pagekeyw'];
$res .= $this->HtmlWarning('Définition du meta keywords: ' . htmlspecialchars($this->keyw));
}
if($err)
{
return 'L\'action header nécessite au moins un argument parmis "title", "pagedesc" ou "pagekeyw"<br />';
}
if ($this->wiki->GetMethod() != 'show')
{
return $res;
}
return '';
}
function HtmlWarning($text)
{
return '<span style="color: red; font-weight: bold;">' . $text . '</span><br />';
}
function GenerateHeader()
{
$wiki = &$this->wiki;
$message = $wiki->GetMessage();
$user = $wiki->GetUser();
ob_start();
?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title><?php echo empty($this->title) ? $wiki->GetWakkaName().":".$wiki->GetPageTag() : htmlspecialchars($this->title); ?></title>
<?php if (($wiki->GetMethod() != 'show') or (isset($_REQUEST['phrase'])) or ($_REQUEST['show_comments'] == '0') or (isset($_REQUEST['time']))) echo "<meta name=\"robots\" content=\"noindex, nofollow\"/>\n";?>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"/>
<meta name="keywords" content="<?php echo empty($this->keyw) ? $wiki->GetConfigValue("meta_keywords") : htmlspecialchars($this->keyw); ?>" />
<meta name="description" content="<?php echo empty($this->desc) ? $wiki->GetConfigValue("meta_description") : htmlspecialchars($this->desc); ?>" />
<link rel="stylesheet" type="text/css" media="screen" href="wakka.basic.css" />
<style type="text/css" media="all"> @import "<?php echo (!isset($_COOKIE["sitestyle"]))?'wakka':$_COOKIE["sitestyle"] ?>.css";</style>
<script type="text/javascript">
function fKeyDown() {
if (event.keyCode == 9) {
event.returnValue= false;
document.selection.createRange().text = String.fromCharCode(9) } }
</script>
</head>
<body <?php echo (!$user || ($user["doubleclickedit"] == 'Y')) && ($wiki->GetMethod() == "show") ? "ondblclick=\"document.location='" . addslashes($wiki->href("edit")) . "';\" " : "" ?>
<?php echo $message ? "onLoad=\"alert('".$message."');\" " : "" ?> >
<div style="display: none;"><a href="<?php echo $wiki->href() ?>/resetstyle" accesskey="7"></a></div>
<h1 class="wiki_name"><?php echo $wiki->config["wakka_name"] ?></h1>
<h1 class="page_name">
<a href="<?php echo $wiki->config["base_url"] ?>RechercheTexte&phrase=<?php echo urlencode($wiki->GetPageTag()); ?>">
<?php echo empty($this->title) ? $wiki->GetPageTag() : htmlspecialchars($this->title); ?>
</a>
</h1>
<div class="header">
<?php echo $wiki->ComposeLinkToPage($wiki->config["root_page"]); ?> ::
<?php echo $wiki->config["navigation_links"] ? $wiki->Format($wiki->config["navigation_links"])." :: \n" : "" ?>
Vous êtes <?php echo $wiki->Format($wiki->GetUserName()); if ($user = $wiki->GetUser()) echo " (<a href=\"".$wiki->config["base_url"] ."ParametresUtilisateur&action=logout\">Déconnexion</a>)\n"; ?>
</div>
<?
return ob_get_clean();
} // GenerateHeader
} // class ActionHeader
?>
Vous noterez que l'action mémorise donc à présent les paramètres titre, description et keywords lorsqu'elle est appelée dans la page, et les réutilise une fois qu'elle est affichée pour de bon. Lors de l'appel de l'action une variable
$res stocke les changements qui ont été effectués, afin de les afficher si on est dans un autre mode que show (ça permet de voir le changement lors d'un diff par exemple). Ceci est assez facultatif mais ça me parait tout de même utile.
D'autres formes d'interactions sont également tout à fait envisageables, quelques idées me viennent à l'esprit:
- génération de la TOC après affichage de la page (bien que ce serait plutôt une interaction avec le handler en fait)
- choix d'une feuille de style spéciale pour la page (voir RoadMapNouveauStyleWikiNi)
- ajout de liens de navigation spécifiques à la page (quelque chose dans le genre de l'ActionTrail mais mieux intégré)
- ...
Discussions
Qu'en pensez-vous ? --
LordFarquaad
Pour moi c'est une évolution absolument nécessaire car certaines actions ont ou pourraient avoir vocation à créer un code XHtml qui ne doit pas atterrir dans la zone de texte. En effet si on crée des groupes de pages, il vaut mieux sortir de la zone de texte le menu de ces pages pour le mettre plus en valeur. On pourrait aussi imaginer une action qui servirait à délimiter des sections dans le wiki. Le nom de la section serait alors extrait de la page pour être affiché de manière plus ostensible. Enfin la possibilité d'insérer ponctuellement une feuille de style (voire un javascript) permettrait d'avoir quelques pages plus raffinées sur le plan graphique ou interaction avec l'utilisateur, notamment la page d'accueil. --
JmPhilippe
- Oui, en fait je pense qu'il faudrait prévoir des modes d'interaction plus génériques avec les objets (l'entête, le pied de page mais aussi les actions, les handlers et même pourquoi pas les formatters ou encore d'autres) manipulés dans une page. A mon avis ces deux améliorations (cette page et le page GererLesActionsSousFormeDObjets) peuvent servir de base à des fonctionnalités (techniques) bien plus "puissantes". Suivant les besoins il suffira alors de faire évoluer l'API pour permettre une modularité encore plus grande et plus simple à utiliser. -- LordFarquaad
D'autres idées me viennent à l'esprit. Le petit texte de la page d'accueil "Les développeurs de
WikiNi vous invitent..." pourrait être géré par une action. Ceci permettrait d'afficher de petits messages bien visibles dans certaines pages. De même, on peut imaginer une action PageEnConstruction qui changerait par exemple les couleurs de la mise en page... J'ai donc l'impression que cela ouvrirait plein de possibilités. Pour les aspects techniques, je te fais confiance ! --
JmPhilippe
Je pense qu'il faut voir encore plus générique. Il faut toujours charger tout le contenu d'une page avant de commencer le tout premier affichage. C'est ce que font tous les CMS. Cette fonctionnalité permetterai de pouvoir séparer le traitement de l'affichage et donc créer des templates. Et des templates, c'est exactement ce dont on aura besoin pour l'i18n. --
JulienLanglois?
LordFarquaadASuivre