NOTE: This plug-in is no longer maintained.

I tried out the Blog plugin by Esther Brunner and it is a wonderful product. However, there is one shortcoming: it looks up the data from the change log.

I know it is needed for the performance reason. However, if the namespace is small, it is also reasonable to get the list of new files from the file system directly. And, sometimes, I love this way more! So I wrote this plugin.

This plugin get a list of recently changed wiki pages of a given namespace as the Blog plugin does. However, it have the following syntax:

[content>[namespace]?[number[&options[&options..]]]}]

[namespace] is a namespace in your wiki; defaults to the top namespace
[number] is the number of entries to show; default is 5
[options] is the option applied. Separated by the & character

All the three parameters are optional. The valid list of options are:

  • recursive - Include the pages recursively under the namespace
  • showpage - Show the page content, up to the first horizontal rule
  • noshowpage - Do not show the page content, show only the title
  • filesystem - Scan for the recently changed file from the file system
  • changelog - Scan for the recently changed file from the changelog

The title that used by the showpage or noshowpage options are defined as the first <h1> enclosed string. Similar to the Blog plugin, table of contents and category tags will be stripped and below the entry the username and the date of the last change is displayed if showpage option is given.

If the noshowpage option is given, however, the recently changed wiki pages will be shown as unordered lists. With the title, file name, and change date shown.

Code

This plugin is modified from version of 2005-09-27 of Esther Brunner’s Blog plugin. Hence the development version 2005-10-03 and newer may not work. Create a directory lib/plugins/content/ and put the following file as syntax.php to use.

<?php
/**
 * Content Plugin: displays a number of recent entries from either the
 * changelog or the filesystem
 * modified from the blog plugin by Esther Brunner and bin/wantedpages.php
 *
 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
 * @author     Adrian Sai-wah Tam <adrian [at] ieaa [dot] org>
 */

if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
require_once(DOKU_PLUGIN.'syntax.php');

define ('DW_DIR_CONTINUE',1);
define ('DW_DIR_NS',2);
define ('DW_DIR_PAGE',3);

// Reverse sort the pages by date (recents first)
function _content_cmp_pages($a,$b) {
    if ($a[date] == $b[date]) {
        return 0;
    }
    return ($a[date] < $b[date]) ? 1 : -1;
}

/**
 * All DokuWiki plugins to extend the parser/rendering mechanism
 * need to inherit from this class
 */
class syntax_plugin_content extends DokuWiki_Syntax_Plugin {
    /**
     * return some info
     */
    function getInfo(){
        return array(
            'author' => 'Adrian Sai-wha Tam',
            'email'  => 'adrian@ieaa.org',
            'date'   => '2005-09-27',
            'name'   => 'Content Plugin',
            'desc'   => 'Displays a number of recent entries from a given namespace, from filesystem or changelog',
            'url'    => 'http://aipl.ie.cuhk.edu.hk/~adrian/doku.php/project:dokuwiki:content',
        );
    }

    function getType(){ return 'substition'; }
    function getPType(){ return 'block'; }
    function getSort(){ return 307; }
    function connectTo($mode) { $this->Lexer->addSpecialPattern('\{\{content>.+?\}\}',$mode,'plugin_content'); }

    /**
     * Handle the match, syntax: content>[namespace][?num[&options[&options..]]]
     */
    function handle($match, $state, $pos, &$handler){
        global $conf;
        $match = substr($match,10,-2); // strip {{content> from start and }} from end
        list($ns,$options) = explode('?',$match,2);
        // Processing the options
        $options = explode('&',$options);
        $showpage = $conf['content']['showpage'];
        $scan = $conf['content']['scanmode'];
        $recursive = (in_array("recursive",$options))?true:false;
        $showpage = (in_array("showpage",$options))?true:$showpage;
        $showpage = (in_array("noshowpage",$options))?false:$showpage;
        $scan = (in_array("filesystem",$options))?true:$scan;
        $scan = (in_array("changelog",$options))?false:$scan;
        $num = $options[0];     // Suppose the first option is the number
        if (!is_integer($num)){
            if (is_integer($ns)){
                $num = $ns;
                $ns  = '';
            } else {
                $num = 5;
            }
        }
        return array($ns,$num,$recursive,$showpage,$scan);
    }

    /**
     * Create output
     */
    function render($mode, &$renderer, $data) {
        global $conf;
        global $ID;
        global $filechain;
        global $conf;

        if($mode == 'xhtml'){
            if (!isset($filechain)) $filechain[] = $ID;

            // list here pages which should not be included in the blog
            $exclude = array('start','sidebar');
            $exclude = array_merge($filechain,$exclude);

            $comments   = 'Comments'; // @todo: localize!

            // prevent caching to ensure the included page is always fresh
            // $renderer->info['cache'] = FALSE; // without caching it's to slow

            $num        = $data[1];
            $recents = ($data[4])?  // Scan filesystem or changelog?
                          $this->_get_pages($data[0],$data[2]) :
                          getRecents(0,$num,false,$data[0],$data[2]);
            if ($num > count($recents)) $num = count($recents);

            if (! $data[3]) {    // If showpage is not set, the pages are expressed as lists
                $renderer->doc .= '<ul>';
            };

            foreach($recents as $recent){
                $id = $recent['id'];
                if (in_array($id, $exclude)) continue;
                $filechain[] = $id;

                $date     = date($conf['dformat'],$recent['date']);
                if ($recent['user']){       // Available only if we obtained the list from changelog
                    $userInfo = auth_getUserData($recent['user']);
                    $nametag  = $userInfo['name'];
                } else {                    // If it is from filesystem scan, we should know the filename
                    $nametag  = $recent['file'];
                }
                $content  = p_cached_xhtml(wikiFN($id));
                if ($data[3]) {    // showpage ?
                    $patterns = array('!<div class="toc">.*?(</div>\n</div>)!s', // remove toc
                                      '!<h1>(.*?)</h1>!s',                       // link h1 (permalink)
                                      '#<!-- SECTION \[(\d*-\d*)\] -->#e',       // remove section edit buttons
                                      '!<div class="category">.*?</div>!s');     // remove category tags
                    $replace  = array('',
                                      '<h1>'.$renderer->internallink($id,"\\1",'',true).'</h1>',
                                      '',
                                      '');
                    $content  = preg_replace($patterns,$replace,$content);
                    $parts = preg_split('/<hr.*>/i', $content);
                    if (count($parts)>1) $content = $parts[0].'</div>';
                    $renderer->doc .= '<div class="include">';
                    $renderer->doc .= $content;
                    $renderer->doc .= '</div>';
                    $renderer->doc .= '<div class="meta">' . $nametag . ' &middot; ' . $date;
                    $dFN = wikiFN($this->_addDiscussionNS($id));
                    if ($dFN && @file_exists($dFN) && $data[3]){
                        $text = io_readFile($dFN);
                        $noc  = count(explode('----',$text)) - 1;
                        $renderer->doc .= ' &middot; '.
                            $renderer->internallink($id.'#discussion',$comments.' ('.$noc.')','',true);
                    }
                    $renderer->doc .= '</div><br /><br />';
                } else {    // no showpage
                    if (preg_match('!<h1>.*</h1>!',$content) == 0) {
                        $content = $renderer->internallink($id,$id,"",true);
                    } else {
                        $content  = preg_replace('!.*?<h1>(.*?)</h1>.*!s',
                                                 $renderer->internallink($id,"\\1",'',true),
                                                 $content);
                    };
                    $renderer->doc .= "<li>$content<br/>&nbsp;&nbsp;&nbsp;&nbsp;".$recent['file']." &middot $date</li>";
                };
            }
            if (! $data[3]) {    // If showpage is not set, the pages are expressed as lists
                $renderer->doc .= '</ul>';
            };
            return true;
        }
        return false;
    }

    /**
     * Convert a normal page ID to a discussion page ID.
     *
     * @author Dave Lawson <dlawson@masterytech.com>
     * @author Esther Brunner <esther@kaffeehaus.ch>
     */
    function _addDiscussionNS($id) {
        global $conf;
        $dNS = $conf['discussion']['namespace'];
        if (!$dNS) return false;
        if ($this->_isDiscussionID($ID)) return $id;
        $dID = getNS($id);
        return $dID.($dID ? ':' : '').$dNS.':'.noNS($id);;
    }

    /**
     * Is specified page ID in a discussion namespace?
     *
     * @author Dave Lawson <dlawson@masterytech.com>
     */
    function _isDiscussionID($id) {
        global $conf;
        $dNS = $conf['discussion']['namespace'];
        $pID = noNS($id);
        return strpos($id, $dNS.':'.$pID) !== false;
    }

    // Check for the type of directory entry
    // copied from bin/wantedpages.php
    function _dir_filter($entry, $basepath) {
        if ($entry == '.' || $entry == '..' ) {
            return DW_DIR_CONTINUE;
        }
        if ( is_dir($basepath . '/' . $entry) ) {
            if ( strpos($entry, '_') === 0 ) {
                return DW_DIR_CONTINUE;
            }
            return DW_DIR_NS;
        }
        if ( preg_match('/\.txt$/',$entry) ) {
            return DW_DIR_PAGE;
        }
        return DW_DIR_CONTINUE;
    }

    // Get a list of pages, without sorting
    // copied from bin/wantedpages.php with modification
    function _get_pages($dir,$recursive) {
        global $conf;
        static $trunclen = NULL;
        $fullpath = $conf['datadir'].'/'.$dir;
        if ( !$trunclen ) {
            global $conf;
            $trunclen = strlen($conf['datadir'].':');
        };
        if ( !is_dir($fullpath) ) {
            echo "Unable to read directory $dir\n";
            exit(1);
        };
        $pages = array();
        $dh = opendir($fullpath);
        while ( FALSE !== ( $entry = readdir($dh) ) ) {
            $status = $this->_dir_filter($entry, $fullpath);
            if ( $status == DW_DIR_CONTINUE ) {
                continue;
            } else if ( $status == DW_DIR_NS ) {
                if ($recursive) {
                  $pages = array_merge($pages, $this->_get_pages($dir . '/' . $entry,$recursive));
                };
            } else {
                $page = array(
                    'id'=>$this->pathID($dir . '/' . $entry),
                    'file'=>$dir . '/' . $entry,
                    'date'=>filemtime($fullpath . '/' . $entry),
                    );
                $pages[] = $page;
            }
        }
        closedir($dh);
        usort($pages,"_content_cmp_pages");
        return $pages;
    }

    // copied from inc/search.php
    function pathID($path,$keeptxt=false){
        $id = utf8_decodeFN($path);
        $id = str_replace('/',':',$id);
        if(!$keeptxt) $id = preg_replace('#\.txt$#','',$id);
        $id = preg_replace('#^:+#','',$id);
        $id = preg_replace('#:+$#','',$id);
        return $id;
    }
}

//Setup VIM: ex: et ts=4 enc=utf-8 :

Change History

  • 2005-09-27:
    • Initial release.

Options

After your creation of lib/plugins/content/syntax.php, make sure you include the following lines into conf/local.php as defaults options:

<%php
// Content plugin
$conf['content']['showpage']    = true;  // By default, show the page
$conf['content']['scanmode']    = true;  // By default, scan the filesystem for the list
%>