From 39a89382d5e693e2b9c9f4e10d73081d1311aeee Mon Sep 17 00:00:00 2001 From: Esther Brunner <esther@kaffeehaus.ch> Date: Sat, 15 Apr 2006 12:46:27 +0200 Subject: [PATCH] new metadata renderer; functions p_get_metadata() and p_set_metadata() darcs-hash:20060415104627-283c4-c7d35620fc9dbc21b8a47089692b76d35a9f9ca8.gz --- inc/common.php | 22 +++ inc/parser/metadata.php | 418 ++++++++++++++++++++++++++++++++++++++++ inc/parserutils.php | 92 +++++++++ 3 files changed, 532 insertions(+) create mode 100644 inc/parser/metadata.php diff --git a/inc/common.php b/inc/common.php index a073ebeaa..75c3e8574 100644 --- a/inc/common.php +++ b/inc/common.php @@ -1005,6 +1005,7 @@ function saveWikiText($id,$text,$summary,$minor=false){ }else{ // save file (datadir is created in io_saveFile) io_saveFile($file,$text); + saveMetadata($id, $file, $minor); $del = false; } @@ -1019,6 +1020,27 @@ function saveWikiText($id,$text,$summary,$minor=false){ } } +/** + * saves the metadata for a page + * + * @author Esther Brunner <wikidesign@gmail.com> + */ +function saveMetadata($id, $file, $minor){ + global $INFO; + + $user = $_SERVER['REMOTE_USER']; + + $meta = array(); + if (!$INFO['exists']){ // newly created + $meta['date']['created'] = @filectime($file); + if ($user) $meta['creator'] = $INFO['userinfo']['name']; + } elseif (!$minor) { // non-minor modification + $meta['date']['modified'] = @filemtime($file); + if ($user) $meta['contributor'][$user] = $INFO['userinfo']['name']; + } + p_set_metadata($id, $meta, true); +} + /** * moves the current version to the attic and returns its * revision date diff --git a/inc/parser/metadata.php b/inc/parser/metadata.php new file mode 100644 index 000000000..0ba59c65e --- /dev/null +++ b/inc/parser/metadata.php @@ -0,0 +1,418 @@ +<?php +/** + * Renderer for metadata + * + * @author Esther Brunner <wikidesign@gmail.com> + */ + +if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/'); + +if ( !defined('DOKU_LF') ) { + // Some whitespace to help View > Source + define ('DOKU_LF',"\n"); +} + +if ( !defined('DOKU_TAB') ) { + // Some whitespace to help View > Source + define ('DOKU_TAB',"\t"); +} + +require_once DOKU_INC . 'inc/parser/renderer.php'; + +/** + * The Renderer + */ +class Doku_Renderer_metadata extends Doku_Renderer { + + var $doc = ''; + var $meta = array(); + + var $headers = array(); + var $capture = true; + var $store = ''; + + function meta($data) { + if (is_array($data)){ + $this->meta = $data; + if (!$this->meta['title']) $this->meta['title'] = $data['first_heading']; + } + } + + function document_start(){ + //reset some variables + $this->meta['description']['abstract'] = ''; + $this->meta['description']['tableofcontents'] = array(); + $this->meta['relation']['haspart'] = array(); + $this->meta['relation']['references'] = array(); + $this->headers = array(); + } + + function document_end(){ + if (!$this->meta['description']['abstract']){ + // cut off too long abstracts + $this->doc = trim($this->doc); + if (strlen($this->doc) > 500) + $this->doc = substr($this->doc, 0, 500).'…'; + $this->meta['description']['abstract'] = $this->doc; + } + } + + function header($text, $level, $pos) { + global $conf; + + // create a unique header id + $hid = $this->_headerToLink($text,'true'); + + //handle TOC + if($level >= $conf['toptoclevel'] && $level <= $conf['maxtoclevel']){ + // the TOC is one of our standard ul list arrays ;-) + $this->meta['description']['tableofcontents'][] = array( + 'hid' => $hid, + 'title' => $text, + 'type' => 'ul', + 'level' => $level-$conf['toptoclevel']+1 + ); + } + + // add to summary + if ($this->capture && ($level > 1)) $this->doc .= DOKU_LF.$text.DOKU_LF; + } + + function section_open($level){} + function section_close(){} + + function cdata($text){ + if ($this->capture) $this->doc .= $text; + } + + function p_open(){ + if ($this->capture) $this->doc .= DOKU_LF; + } + + function p_close(){ + if ($this->capture){ + if (strlen($this->doc) > 250) $this->capture = false; + else $this->doc .= DOKU_LF; + } + } + + function linebreak(){ + if ($this->capture) $this->doc .= DOKU_LF; + } + + function hr(){ + if ($this->capture){ + if (strlen($this->doc) > 250) $this->capture = false; + else $this->doc .= DOKU_LF.'----------'.DOKU_LF; + } + } + + function strong_open(){} + function strong_close(){} + + function emphasis_open(){} + function emphasis_close(){} + + function underline_open(){} + function underline_close(){} + + function monospace_open(){} + function monospace_close(){} + + function subscript_open(){} + function subscript_close(){} + + function superscript_open(){} + function superscript_close(){} + + function deleted_open(){} + function deleted_close(){} + + /** + * Callback for footnote start syntax + * + * All following content will go to the footnote instead of + * the document. To achieve this the previous rendered content + * is moved to $store and $doc is cleared + * + * @author Andreas Gohr <andi@splitbrain.org> + */ + function footnote_open() { + if ($this->capture){ + // move current content to store and record footnote + $this->store = $this->doc; + $this->doc = ''; + } + } + + /** + * Callback for footnote end syntax + * + * All rendered content is moved to the $footnotes array and the old + * content is restored from $store again + * + * @author Andreas Gohr + */ + function footnote_close() { + if ($this->capture){ + // restore old content + $this->doc = $this->store; + $this->store = ''; + } + } + + function listu_open(){ + if ($this->capture) $this->doc .= DOKU_LF; + } + + function listu_close(){ + if ($this->capture && (strlen($this->doc) > 250)) $this->capture = false; + } + + function listo_open(){ + if ($this->capture) $this->doc .= DOKU_LF; + } + + function listo_close(){ + if ($this->capture && (strlen($this->doc) > 250)) $this->capture = false; + } + + function listitem_open($level){ + if ($this->capture) $this->doc .= str_repeat(DOKU_TAB, $level).'* '; + } + + function listitem_close(){ + if ($this->capture) $this->doc .= DOKU_LF; + } + + function listcontent_open(){} + function listcontent_close(){} + + function unformatted($text){ + if ($this->capture) $this->doc .= $text; + } + + function php($text){} + + function html($text){} + + function preformatted($text){ + if ($this->capture) $this->doc .= $text; + } + + function file($text){ + if ($this->capture){ + $this->doc .= DOKU_LF.$text; + if (strlen($this->doc) > 250) $this->capture = false; + else $this->doc .= DOKU_LF; + } + } + + function quote_open(){ + if ($this->capture) $this->doc .= DOKU_LF.DOKU_TAB.'“'; + } + + function quote_close(){ + if ($this->capture){ + $this->doc .= 'â€'; + if (strlen($this->doc) > 250) $this->capture = false; + else $this->doc .= DOKU_LF; + } + } + + function code($text, $language = NULL){ + if ($this->capture){ + $this->doc .= DOKU_LF.$text; + if (strlen($this->doc) > 250) $this->capture = false; + else $this->doc .= DOKU_LF; + } + } + + function acronym($acronym){ + if ($this->capture) $this->doc .= $acronym; + } + + function smiley($smiley){ + if ($this->capture) $this->doc .= $smiley; + } + + function entity($entity){ + if ($this->capture) $this->doc .= $entity; + } + + function multiplyentity($x, $y){ + if ($this->capture) $this->doc .= $x.'×'.$y; + } + + function singlequoteopening(){ + if ($this->capture) $this->doc .= '‘'; + } + + function singlequoteclosing(){ + if ($this->capture) $this->doc .= '’'; + } + + function doublequoteopening(){ + if ($this->capture) $this->doc .= '“'; + } + + function doublequoteclosing(){ + if ($this->capture) $this->doc .= 'â€'; + } + + function camelcaselink($link) { + $this->internallink($link, $link); + } + + function locallink($hash, $name = NULL){} + + /** + * keep track of internal links in $this->meta['relation']['references'] + */ + function internallink($id, $name = NULL){ + global $ID; + + $default = $this->_simpleTitle($id); + + // first resolve and clean up the $id + resolve_pageid(getNS($ID), $id, $exists); + list($id, $hash) = split('#', $id, 2); + + // set metadata + $this->meta['relation']['references'][$id] = $exists; + // $data = array('relation' => array('isreferencedby' => array($ID => true))); + // p_set_metadata($id, $data); + + // add link title to summary + if ($this->capture){ + $name = $this->_getLinkTitle($name, $default, $id); + $this->doc .= $name; + } + } + + function externallink($url, $name = NULL){ + if ($this->capture){ + if ($name) $this->doc .= $name; + else $this->doc .= '<'.$url.'>'; + } + } + + function interwikilink($match, $name = NULL, $wikiName, $wikiUri){ + if ($this->capture){ + list($wikiUri, $hash) = explode('#', $wikiUri, 2); + $name = $this->_getLinkTitle($name, $wikiName.'>'.$wikiUri); + $this->doc .= $name; + } + } + + function windowssharelink($url, $name = NULL){ + if ($this->capture){ + if ($name) $this->doc .= $name; + else $this->doc .= '<'.$url.'>'; + } + } + + function emaillink($address, $name = NULL){ + if ($this->capture){ + if ($name) $this->doc .= $name; + else $this->doc .= '<'.$address.'>'; + } + } + + function internalmedia($src, $title=NULL, $align=NULL, $width=NULL, + $height=NULL, $cache=NULL, $linking=NULL){ + if ($this->capture && $title) $this->doc .= '['.$title.']'; + } + + function externalmedia($src, $title=NULL, $align=NULL, $width=NULL, + $height=NULL, $cache=NULL, $linking=NULL){ + if ($this->capture && $title) $this->doc .= '['.$title.']'; + } + + function rss($url){} + + function table_open($maxcols = NULL, $numrows = NULL){} + function table_close(){} + + function tablerow_open(){} + function tablerow_close(){} + + function tableheader_open($colspan = 1, $align = NULL){} + function tableheader_close(){} + + function tablecell_open($colspan = 1, $align = NULL){} + function tablecell_close(){} + + //---------------------------------------------------------- + // Utils + + /** + * Removes any Namespace from the given name but keeps + * casing and special chars + * + * @author Andreas Gohr <andi@splitbrain.org> + */ + function _simpleTitle($name){ + global $conf; + + if($conf['useslash']){ + $nssep = '[:;/]'; + }else{ + $nssep = '[:;]'; + } + $name = preg_replace('!.*'.$nssep.'!','',$name); + //if there is a hash we use the ancor name only + $name = preg_replace('!.*#!','',$name); + return $name; + } + + /** + * Creates a linkid from a headline + * + * @param string $title The headline title + * @param boolean $create Create a new unique ID? + * @author Andreas Gohr <andi@splitbrain.org> + */ + function _headerToLink($title, $create=false) { + $title = str_replace(':','',cleanID($title,true)); //force ASCII + $title = ltrim($title,'0123456789._-'); + if(empty($title)) $title='section'; + + if($create){ + // make sure tiles are unique + $num = ''; + while(in_array($title.$num,$this->headers)){ + ($num) ? $num++ : $num = 1; + } + $title = $title.$num; + $this->headers[] = $title; + } + + return $title; + } + + /** + * Construct a title and handle images in titles + * + * @author Harry Fuecks <hfuecks@gmail.com> + */ + function _getLinkTitle($title, $default, $id=NULL) { + global $conf; + + $isImage = FALSE; + if (is_null($title)){ + if ($conf['useheading'] && $id){ + $heading = p_get_first_heading($id); + if ($heading) return $heading; + } + return $default; + } else if (is_string($title)){ + return $title; + } else if (is_array($title)){ + return '['.$title.']'; + } + } + +} + +//Setup VIM: ex: et ts=4 enc=utf-8 : \ No newline at end of file diff --git a/inc/parserutils.php b/inc/parserutils.php index b295ae152..981f33cc1 100644 --- a/inc/parserutils.php +++ b/inc/parserutils.php @@ -232,6 +232,98 @@ function p_get_instructions($text){ return $p; } +/** + * returns the metadata of a page + * + * @author Esther Brunner <esther@kaffeehaus.ch> + */ +function p_get_metadata($id, $key=false, $render=false){ + $file = metaFN($id, '.meta'); + + if (@file_exists($file)) $meta = unserialize(io_readFile($file, false)); + else $meta = array(); + + // metadata has never been rendered before - do it! + if ($render && !$meta['description']['abstract']){ + $meta = p_render_metadata($id, $meta); + io_saveFile($file, serialize($meta)); + } + + // filter by $key + if ($key){ + list($key, $subkey) = explode(' ', $key, 2); + if (trim($subkey)) return $meta[$key][$subkey]; + else return $meta[$key]; + } + + return $meta; +} + +/** + * sets metadata elements of a page + * + * @author Esther Brunner <esther@kaffeehaus.ch> + */ +function p_set_metadata($id, $data, $render=false){ + if (!is_array($data)) return false; + + $orig = p_get_metadata($id); + + // render metadata first? + if ($render) $meta = p_render_metadata($id, $orig); + else $meta = $orig; + + // now add the passed metadata + $protected = array('description', 'date', 'contributor'); + foreach ($data as $key => $value){ + + // be careful with sub-arrays of $meta['relation'] + if ($key == 'relation'){ + foreach ($value as $subkey => $subvalue){ + $meta[$key][$subkey] = array_merge($meta[$key][$subkey], $subvalue); + } + + // be careful with some senisitive arrays of $meta + } elseif (in_array($key, $protected)){ + if (is_array($value)){ + $meta[$key] = array_merge($meta[$key], $value); + } + + // no special treatment for the rest + } else { + $meta[$key] = $value; + } + } + + // save only if metadata changed + if ($meta == $orig) return true; + return io_saveFile(metaFN($id, '.meta'), serialize($meta)); +} + +/** + * renders the metadata of a page + * + * @author Esther Brunner <esther@kaffeehaus.ch> + */ +function p_render_metadata($id, $orig){ + require_once DOKU_INC."inc/parser/metadata.php"; + + // get instructions + $instructions = p_cached_instructions(wikiFN($id)); + + // set up the renderer + $renderer = & new Doku_Renderer_metadata(); + $renderer->meta = $orig; + + // loop through the instructions + foreach ($instructions as $instruction){ + // execute the callback against the renderer + call_user_func_array(array(&$renderer, $instruction[0]), $instruction[1]); + } + + return $renderer->meta; +} + /** * returns all available parser syntax modes in correct order * -- GitLab