J'avais abordé, il y a déjà quelques temps, cette histoire de template personnalisé par catégorie. Mais à l'époque, je m'étais contenté de fournir un moyen d'utiliser un balisage XHTML différent pour la partie liste.
En travaillant sur le prochain habillage de Bricoland Drive
, je me suis rendu compte que j'avais besoin d'aller au-delà : j'aimerais bien pouvoir jouer sur la structure des billets rattachés à une catégorie donnée. Pourquoi s'en priver puisque le principe est le même, et que ça se fait en 2 coups de cuillères à pot.
Encore une fois, il n'y a absolument rien de sorcier là dedans. Mais comme j'ai reçu aujourd'hui même une demande par mail portant sur cet aspect là, autant laisser une trace publique.
Reprenons donc le chemin de ce cher fichier _public.php, présent dans notre thème depuis l'épisode précédent.
Tout ce que nous avons à faire, c'est de lui ajouter la prise en charge du mode post.
On commence donc par y placer l'interception des URLs correspondant à ce mode, en faisant pointer la réponse vers la méthode personnalisée que nous fournirons tout de suite après :
$core->url->register('post','post','^post/(.+)$', array('myURLHandlers','post'));
Nous ajoutons maintenant la nouvelle méthode post() dans notre classe myURLHandlers, une fois de plus en ayant recours à un vilain copier/coller depuis le fichier dotclear/inc/public/lib.urlhandlers.php. Et nous n'en modifierons que l'essentiel, c'est-à-dire la partie finale destinée à appeler le rendu du template.
Le code d'origine :
# The entry
self::serveDocument('post.html');
exit;
n'aura qu'à faire place à celui-ci :
# The entry
$tpl = 'post.html';
if ($_ctx->posts->cat_id) {
$alt_tpl = 'post-cat'.strtolower($_ctx->posts->cat_id).'.html';
if ($core->tpl->getFilePath($alt_tpl)) {
$tpl = $alt_tpl;
}
}
self::serveDocument($tpl);
exit;
Voilà. Nous y sommes. Ou presque... :-)
Presque
parce que maintenant que Dotclear 2 nous permet une jolie prévisualisation in situ d'un billet en cours de rédaction, il pourrait être sympa que cette dernière prenne en compte notre modification. Une fois encore, l'ajout de ce petit raffinement sera réglé par copier/coller (* sic *) .
Au final, notre nouveau _public.php hérite de cette tête :
<?php
if (!defined('DC_RC_PATH')) die;
$core->url->register('category','category','^category/(.+)$',array('myURLHandlers','category'));
$core->url->register('post','post','^post/(.+)$',array('myURLHandlers','post'));
$core->url->register('preview','preview','^preview/(.+)$',array('myURLHandlers','preview'));
class myURLHandlers extends dcUrlHandlers
{
public static function category($args)
{
$_ctx =& $GLOBALS['_ctx'];
$core =& $GLOBALS['core'];
$n = self::getPageNumber($args);
if ($args == '' && !$n) {
self::p404();
}
$params['cat_url'] = $args;
$params['post_type'] = 'post';
$_ctx->categories = $core->blog->getCategories($params);
if ($_ctx->categories->isEmpty()) {
self::p404();
} else {
if ($n) {
$GLOBALS['_page_number'] = $n;
}
$tpl = 'category-'.$_ctx->categories->cat_id.'.html';
if (!$core->tpl->getFilePath($tpl)) {
$tpl = 'category.html';
}
self::serveDocument($tpl);
exit;
}
}
public static function post($args)
{
if ($args == '') {
self::p404();
}
$_ctx =& $GLOBALS['_ctx'];
$core =& $GLOBALS['core'];
$core->blog->withoutPassword(false);
$params = new ArrayObject();
$params['post_url'] = $args;
$_ctx->posts = $core->blog->getPosts($params);
$_ctx->comment_preview = new ArrayObject();
$_ctx->comment_preview['content'] = '';
$_ctx->comment_preview['rawcontent'] = '';
$_ctx->comment_preview['name'] = '';
$_ctx->comment_preview['mail'] = '';
$_ctx->comment_preview['site'] = '';
$_ctx->comment_preview['preview'] = false;
$_ctx->comment_preview['remember'] = false;
$core->blog->withoutPassword(true);
if ($_ctx->posts->isEmpty())
{
# No entry
self::p404();
}
$post_id = $_ctx->posts->post_id;
$post_password = $_ctx->posts->post_password;
# Password protected entry
if ($post_password != '')
{
# Get passwords cookie
if (isset($_COOKIE['dc_passwd'])) {
$pwd_cookie = unserialize($_COOKIE['dc_passwd']);
} else {
$pwd_cookie = array();
}
# Check for match
if ((!empty($_POST['password']) && $_POST['password'] == $post_password)
|| (isset($pwd_cookie[$post_id]) && $pwd_cookie[$post_id] == $post_password))
{
$pwd_cookie[$post_id] = $post_password;
setcookie('dc_passwd',serialize($pwd_cookie),0,'/');
}
else
{
self::serveDocument('password-form.html','text/html',false);
exit;
}
}
$post_comment =
isset($_POST['c_name']) && isset($_POST['c_mail']) &&
isset($_POST['c_site']) && isset($_POST['c_content']) &&
$_ctx->posts->commentsActive();
# Posting a comment
if ($post_comment)
{
# Spam trap
if (!empty($_POST['f_mail'])) {
http::head(412,'Precondition Failed');
header('Content-Type: text/plain');
echo "So Long, and Thanks For All the Fish";
exit;
}
$name = $_POST['c_name'];
$mail = $_POST['c_mail'];
$site = $_POST['c_site'];
$content = $_POST['c_content'];
$preview = !empty($_POST['preview']);
if ($content != '')
{
if ($core->blog->settings->wiki_comments) {
$core->initWikiComment();
} else {
$core->initWikiSimpleComment();
}
$content = $core->wikiTransform($content);
$content = $core->HTMLfilter($content);
}
$_ctx->comment_preview['content'] = $content;
$_ctx->comment_preview['rawcontent'] = $_POST['c_content'];
$_ctx->comment_preview['name'] = $name;
$_ctx->comment_preview['mail'] = $mail;
$_ctx->comment_preview['site'] = $site;
if ($preview)
{
$_ctx->comment_preview['preview'] = true;
}
else
{
# Post the comment
$cur = $core->con->openCursor($core->prefix.'comment');
$cur->comment_author = $name;
$cur->comment_site = html::clean($site);
$cur->comment_email = html::clean($mail);
$cur->comment_content = $content;
$cur->post_id = $_ctx->posts->post_id;
$cur->comment_status = $core->blog->settings->comments_pub ? 1 : -1;
$cur->comment_ip = http::realIP();
$redir = $_ctx->posts->getURL();
$redir .= strpos($redir,'?') !== false ? '&' : '?';
try
{
if (!text::isEmail($cur->comment_email)) {
throw new Exception(__('You must provide a valid email address.'));
}
# --BEHAVIOR-- publicBeforeCommentCreate
$core->callBehavior('publicBeforeCommentCreate',$cur);
if ($cur->post_id) {
$comment_id = $core->blog->addComment($cur);
# --BEHAVIOR-- publicAfterCommentCreate
$core->callBehavior('publicAfterCommentCreate',$cur,$comment_id);
}
if ($cur->comment_status == 1) {
$redir_arg = 'pub=1';
} else {
$redir_arg = 'pub=0';
}
header('Location: '.$redir.$redir_arg);
exit;
}
catch (Exception $e)
{
$_ctx->form_error = $e->getMessage();
$_ctx->form_error;
}
}
}
# The entry
$tpl = 'post.html';
if ($_ctx->posts->cat_id) {
$alt_tpl = 'post-cat'.strtolower($_ctx->posts->cat_id).'.html';
if ($core->tpl->getFilePath($alt_tpl)) {
$tpl = $alt_tpl;
}
}
self::serveDocument($tpl);
exit;
}
public static function preview($args)
{
$core = $GLOBALS['core'];
if (!preg_match('#^(.+?)/([0-9a-z]{40})/(.+?)$#',$args,$m)) {
self::p404();
}
$user_id = $m[1];
$user_key = $m[2];
$post_url = $m[3];
if (!$core->auth->checkUser($user_id,null,$user_key)) {
self::p404();
}
self::post($post_url);
exit;
}
}
?>
Ainsi, pour effectuer le rendu d'un billet :
- Nous regardons d'abord si le billet appartient à une catégorie
- Si c'est le cas, nous demandons à Dotclear 2 de nous dire si un fichier template nommé
post-cat##.htmlexiste (où##représente l'identifiant numérique de la catégorie). - Si Dotclear 2 nous répond positivement, c'est ce template là qui sera rendu.
- Dans tous les autres cas, nous continuerons à servir un
post.html.
Une fois de plus, c'est un jeu d'enfant.
Néanmoins, les plus pointilleux d'entre vous auront remarqué une chose flagrante : cette solution n'est pas élégante, puisqu'elle impose de recourir à de vilains copier/coller. Nous sommes donc bien loin du principe DRY[1], et sommes peut-être bien en face d'une faiblesse de conception de ce côté là de Dotclear 2.
Mais là, c'est une autre histoire et le Bricoland Drive
n'est sans doute pas le lieu le plus approprié. ;-)
Notes
[1] Pour les moins pointilleux - mais néanmoins curieux - d'entre vous, reportez-vous à : http://en.wikipedia.org/wiki/DRY_code.
Commentaires
#1
Olivier
samedi 18 octobre 2008, 13:55
Tu sais qu'avec les nightly, il suffit de faire :
<tpl:EntryIf category="toto">
{{tpl:include src="montemplate.html"}}
</tpl:EntryIf>
Je dis ça, je dis rien ;)
#2
Pep
samedi 18 octobre 2008, 14:31
Yep. Je me doute.
Mais il faudra attendre la release 2.1 pour généraliser la chose, hein. :-p
#3
Osku
lundi 20 octobre 2008, 18:22
Le code d'Olivier ne marche pas avec la 2.0.2 ?
Parce que le coup de l'extension de post, je m'étais fait tapé sur les doigts pour mon pseudo plugin Javatar :/
PS: pre2ol vaincra !
#4
Pep
lundi 20 octobre 2008, 19:59
L'option permettant le test d'appartenance d'un billet à une catégorie donnée n'a été introduite que depuis le changeset 2357. Donc après la release de Dotclear 2.0.2. :-)
Et je vais militer contre la prolifération des trolls en PS ! :-p
#5
Franck
vendredi 5 juin 2009, 11:25
Une question : pourquoi tu redéfinis la fonction
home()dans ta classemyURLHandlers? Pourcategory(),post()etpreview()je comprends mais pourhome()ça me laisse perplexe !D'autre part est-ce que la remarque d'Olivier sur le test
EntryIfchange quelque chose à ta solution ?#6
Pep
vendredi 5 juin 2009, 11:51
Franck > Finement observé : non, la méthode
home()n'est absolument pas nécessaire.D'ailleurs elle n'est pas liée à l'aiguillage. Ce n'est qu'un effet de bord d'un copier/coller. ;-) Je vais de ce pas l'enlever du billet.
Pour la remarque d'Olivier, c'est une question de choix :
En gros, imaginons le cas d'un Dotclear 2 utilisé pour faire un site classique, avec une demi-dizaine de sections, clairement définies et considérées comme immuables, l'approche d'Olivier est à privilégier.
Dans le cas d'un gros blog plus , avec des catégories susceptibles d'apparaitre ou de disparaitre, j'aurais une préférence pour la méthode que j'ai décrite ici.
#7
Franck
vendredi 5 juin 2009, 12:28
Merci pour la réponse rapide.
Pour la seconde question, je cherche le moyen le plus simple d'intégrer un thème photoblog utilisé uniquement sur les billets d'une catégorie (url category et post, ça tombe bien, le preview en plus c'est la cerise sur le gâteau).
Je pense que je vais opter pour la solution la moins intrusive vu que j'ai déjà un switcher de thèmes et que ta solution m'évitera de modifier tous les fichiers template correspondant.