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##.html existe (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.