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