<?php
if (!defined('WS_WEB')) die('not a valid entry point!');

require_once("WikiSelector.php");
require_once("WikiAccess.php");

class WikiQuery {

  var $order;
  var $defaultOrder;
  var $query;
  var $title;
  var $description;
  var $fields;

  var $filter;
  var $filterValue;
  
  var $sinceValue;
  var $sinceRequest;
  var $sinceChoices;
  var $feedSince;
  
  var $untilValue;
  var $untilRequest;
  var $untilChoices;
  var $feedUntil;
  
  var $feedLimit;
  var $feedCache;
  
  var $styles;
  var $parameters;
  var $options;
  
  var $allowRaw;
  var $allowWiki;
  var $feedFields;
  
  var $wiki;
  var $wikiSelector;
  var $useTranslations;
  
  var $chunks;
  
  var $messages;
  var $helpPageName;

  var $autoflush;
  var $dumpSQL;
  
  function WikiQuery ($title, $query) {
    
    $this->query= $query;
    $this->title= $title;
    $this->order= NULL;
    $this->defaultOrder= NULL;
    $this->fields= array();
    $this->description= NULL;
    $this->helpPageName= NULL;
    $this->useTranslations= false;

    $this->filter= NULL;
    $this->filterValue= NULL;
    
    $this->sinceValue= NULL;
    $this->sinceRequest= NULL;
    $this->feedSince= '-2h'; //since two hours ago
    
    $this->untilValue= NULL;
    $this->untilRequest= NULL;
    $this->feedUntil= NULL;
    
    $this->feedLimit= NULL;
    $this->feedCache= 60*60*1; //cache for 1 hour
    
    $this->styles= array();
    $this->parameters= array();
    $this->options= array();
    
    $this->allowRaw= true;
    $this->allowWiki= false;
    $this->feedFields= NULL;
    
    $this->wiki= NULL;
    $this->wikiSelector= new WikiSelector();
    
    $this->chunks= array( 100, 250, 500, 1000 );
    
    $this->messages= array();
    
    $this->autoflush= true;
    $this->dumpSQL= false;
  
    $this->sinceChoices= array(
            '' => 'ever',
            '-1h' => 'one hour ago',
            '-2h' => 'two hours ago',
            '-6h' => 'six hours ago',
            '-12h' => 'twelve hours ago',
            '-1d' => 'one day ago',
            '-3d' => 'three days ago',
            '-7d' => 'seven days ago',
    );
    
    $this->untilChoices= array(
            '' => 'now',
            '-1h' => 'one hour ago',
            '-2h' => 'two hours ago',
            '-6h' => 'six hours ago',
            '-12h' => 'twelve hours ago',
            '-1d' => 'one day ago',
            '-3d' => 'three days ago',
            '-7d' => 'seven days ago',
    );
  }
  
  function setWikiDomain( $domain ) {
    $this->wikiSelector->setDomain( $domain );
    $this->wiki=& WikiAccess::newInstance( $domain );
    if ($this->wiki && $this->useTranslations) $this->wiki->initTranslations();
  }
  
  function getQuery() {
    return $this->query;
  }
  
  function makeFeedItem( $row ) {
      $a= array(
        'date' => '',
        'author' => '',
        'comments' => '',
        'description' => '',
      );
      
      foreach ($this->feedFields as $ffield => $dbfield) {
          if (is_array($dbfield)) $value= $this->getFieldValue($row, $ffield, $dbfield, 'rss');
          else $value= $this->getFieldValue($row, $dbfield, NULL, 'rss');
          
          $a[$ffield]= $value;
      }
      
      #print_r($a);
      
      #if (!isset($a['title']) || !isset($a['description']) || !isset($a['url'])) return NULL;
      
      if ($a['description'] && preg_match('/<>&/',$a['description'])) $a['description']= htmlspecialchars($a['description']);
      if ($a['comments'] && preg_match('/<>&/',$a['comments'])) $a['comments']= htmlspecialchars($a['comments']);
      
      $item= new FeedItem( $a['title'], $a['description'], $a['url'], $a['date'], $a['author'], $a['comments'] );
      return $item;
  }
  
  function makeDate($dts, $format=NULL) {
      $t= wfTimestamp(TS_UNIX,$dts);
      if (!$format) $format='Y-m-d H:i';
      return date($format,$t);
  }
  
  function makeURL($title, $ns=0, $wiki= NULL) {
    $n= $title;
    
    if (!$wiki) $wiki=& $this->wiki;
    
    $n= $wiki->asDBKey($n);
    $n= urlencode($n);
    
    if ($ns) {
        if (is_int($ns) || is_numeric($ns)) $ns= $wiki->getNsText($ns);
        
        $n= urlencode($ns).':'.$n;
    }
    
    $u= $wiki->baseURL . '?title=' . $n;
    
    return $u;
  }
  
  function makeThumbnail($img, $maxwidth=NULL, $maxheight=NULL) {
      return $wiki->getThumbnailHTML($img, $maxwidth, $maxheight);
  }
  
  function makeLink($title, $ns=0, $showns= true, $wiki= NULL, $label = NULL) {
    if (!$wiki) $wiki=& $this->wiki;
    
    if (!$label) {
        $label= $wiki->asTitle($title);
        
        if ($ns && $showns) {
            if (is_int($ns) || is_numeric($ns)) $ns= $wiki->getNsText($ns);
            
            $ns= $wiki->asTitle($ns);
            $label= $ns.':'.$label;
        }
    }
    
    $u= $this->makeURL($title, $ns, $wiki);
    
    return "<a href='$u'>$label</a>";
  }
    
  function makeWikiLink($title, $ns=0, $showns= true, $prefix=NULL, $wiki= NULL, $label = NULL) {
    if ($prefix === true) $prefix= $this->wiki->language; #FIXME: use interwiki-table?!
    
    $name= $this->wiki->asTitle($title);
    $title= $this->wiki->asDBKey($title);
    
    if ($ns) {
        if (is_int($ns) || is_numeric($ns)) {
            $n= $ns;
            $ns= $this->wiki->getNsText($n);
        }
        else {
            $n= $this->wiki->getNsIndex($ns);
        }
        
        $ns= $this->wiki->asDBKey($ns);
        $title= $ns.':'.$title;
        
        if ($n==NS_IMAGE || $n==NS_CATEGORY) $title= ':'.$title;
    }
    
    if ($prefix) $title= ':'.$prefix.':'.$title;
    
    if (!$label) {
        if ($showns) {
            if ($ns) {
                $ns= $wiki->asTitle($ns);
                $label= $ns.':'.$label;
            }
            
            if ($prefix) $label= $prefix.':'.$label;
        }
        else $label= $name;
    }
    
    if ($label) $w=  "[[$title|$label]]";
    else $w= "[[$title]]";
    
    return $w;
  }
    
  function printData($ofs, $max = 100, $format = 'html', $order= NULL, $filter= NULL) {
      $sql= $this->getQuery();
      
      if (!$this->allowRaw && ($format=='csv' || $format=='tsv')) $format= 'html';
      if (!$this->allowWiki && ($format=='wiki')) $format= 'html';
      if (!$this->feedFields && ($format=='rss' || $format=='atom')) $format= 'html';
      
      if ($filter) {
          $tail= NULL;
          $m= array();
          if (preg_match('/^(.*[\s\)])((GROUP\s+BY)[\s\(].*)$/is',$sql,$m)) {
              $sql= $m[1];
              $tail= $m[2];
          }
      
          if ( preg_match('/\WWHERE\W/si',$sql) ) $sql.= ' AND ';
          else $sql.= ' WHERE ';
          
          $sql.= ' '.$filter.' ';
          
          if ($tail) $sql.= $tail;
      }
      
      if ($order) {
        $dir= ' ASC ';
        if (preg_match('/^-/',$order)) {
            $order= substr($order,1);
            $dir= ' DESC ';
        }
        
        $sql.= " ORDER BY ".$this->wiki->wikiDB->addQuotes($order)." ".$dir;
      }
      
      if ($max) {
        $ofs= (int)$ofs;
        $max= (int)$max;
        
        $sql.= " LIMIT $ofs, $max";
      }
      
      if (@$this->dumpSQL) {
          print "<p>*** ".htmlspecialchars($sql)." ***</p>"; 
          flush();
          if (@$this->dry) return false;
      }
      
      $res= $this->wiki->wikiDB->query( $sql );
      
      if (!$res) return false;
      
      if ( $format == 'csv' || $format == 'tsv' ) {
        require_once('CSV.php');
        
        if ($format == 'tsv') $csv= new CSV(false,"\t","text/plain; charset=utf-8");
        else $csv= new CSV();
        
        header( "Content-Type: " . $csv->mime );
        
        while ($row = $this->wiki->wikiDB->fetchRow($res)) {
            $this->printCSVRow( $csv, $row );
        }
      }
      else if ( $format == 'rss' || $format == 'atom' ) {
        require_once('includes/Feed.php');
        
        #UGLY HACK... this should already be initialized... somehow, wgOut vanishes o_O
        global $wgOut;
        if (!isset($wgOut) || !$wgOut) {
            $wgOut= new PseudoOut();
        }
        
        $u= $this->getURL('format',false);
        $u.= "&format=$format&max=$max";
        
        $t= $this->title." - ".$this->getScope();
        
        if ($format == 'rss') $feed= new RSSFeed($t, $this->description, $u);
        else $feed= new AtomFeed($t, $this->description, $u);
        
        global $wgRequest;
        $purge= $wgRequest->getVal('purge');
        $cacheURI= $this->getURL(NULL,false);
        if (!wsfCacheStart($cacheURI,$purge?0:$this->feedCache)) {
            $feed->outHeader();
    
            while ($row = $this->wiki->wikiDB->fetchRow($res)) {
                  $item = $this->makeFeedItem( $row );
                  if( $item ) $feed->outItem( $item );
            }
    
            $feed->outFooter();
            wsfCacheEnd($cacheURI);
         }
      }
      else if ($format=='wiki') {
        header( "Content-Type: text/plain; charset=utf-8" ); #NOTE: nicer for browser display. correct would be text/x-wiki or application/x-wiki

        $this->printWikiHeader();
        
        while ($row = $this->wiki->wikiDB->fetchRow($res)) {
            $this->printWikiRow( $row );
        }
      }
      else {
        $base= $_SERVER['PHP_SELF'];
        $base.= '?'.$this->getURLParameters();
      
        $this->printHTMLNavi( $base, $ofs, $max );
        $this->printHTMLHeader( );
        
        while ($row = $this->wiki->wikiDB->fetchRow($res)) {
            $this->printHTMLRow( $row );
        }
        
        $this->printHTMLFooter( );
        $this->printHTMLNavi( $base, $ofs, $max );
      }
      
      $this->wiki->wikiDB->freeResult($res);
      
      return true;
  }
  
  function makeOptionLink($options, $label) {
        $u= $this->getURL(array_keys($options));
        
        foreach ($options as $k => $v) {
            $u.= "&amp;$k=".urlencode($v);
        }
        
        return "<a href='$u'>".htmlspecialchars($label)."</a>";
  }
  
  function getURL($without = NULL, $escape = true) {
        $u= $_SERVER['PHP_SELF'];
        $u.= '?'.$this->getURLParameters($without, $escape);
        return $u;
  }
  
  function getURLParameters($without = NULL, $escape = true) {
      global $wgRequest;
      
      $amp= $escape ? '&amp;' : '&';
      
      if (!$without) $without= array();
      else if (!is_array($without)) $without= array($without);
      
      $s= 'wikifam='.urlencode($this->wikiSelector->selected);
      $s.= $amp.'wikilang='.urlencode($this->wikiSelector->lang);
      
      if (!in_array('order',$without) && $this->order) $s.= $amp.'order='.urlencode($this->order);
      if ($this->filter && $this->filterValue && !in_array($this->filter,$without)) $s.= $amp.$this->filter.'='.urlencode($this->filterValue);
      if ($this->sinceRequest && !in_array('since',$without)) $s.= $amp.'since='.urlencode($this->sinceRequest);
      if ($this->untilRequest && !in_array('until',$without)) $s.= $amp.'until='.urlencode($this->untilRequest);
      
      $max= $wgRequest->getVal('max');
      if (!in_array('max',$without) && $max) $s.= $amp.'max='.urlencode($max);
      
      foreach ($this->parameters as $p => $v) {
          if (!in_array($p,$without) && $v) $s.= $amp.$p.'='.urlencode($v);
      }
      
      foreach ($this->options as $k => $o) {
          $v= $wgRequest->getVal($k);
          
          if (!in_array($k,$without) && $v) $s.= $amp.$k.'='.urlencode($v);
      }
      
      return $s;
  }
  
  function printHiddenFields($without = NULL, $options=true) {
      global $wgRequest;
  
      if (!in_array('wikilang',$without) && $this->wikiSelector->lang) print '<input type="hidden" name="wikilang" value="'.urlencode($this->wikiSelector->lang).'"/>';
      if (!in_array('wikifam',$without) && $this->wikiSelector->selected) print '<input type="hidden" name="wikifam" value="'.urlencode($this->wikiSelector->selected).'"/>';
      
      if (!in_array('order',$without) && $this->order) print '<input type="hidden" name="order" value="'.urlencode($this->order).'"/>';
      
      foreach ($this->parameters as $p => $v) {
          if (!in_array($p,$without) && $v) print '<input type="hidden" name="'.$p.'" value="'.urlencode($v).'"/>';
      }
      
      if ($options) {
          foreach ($this->options as $k => $o) {
              $v= $wgRequest->getVal($k);
              
              if (!in_array($k,$without) && $v) print '<input type="hidden" name="'.$k.'" value="'.urlencode($v).'"/>';
          }
      }
  }
  
  function printHTMLNavi( $base, $ofs, $max = 100 ) {
    print "<p class='navi'>";
    if ($ofs) print "&lt;&lt; <a href='$base&ofs=".(max(0,$ofs-$max))."&max=$max'>prev $max</a> | ";
    print "show ";
    
    $first= true;
    foreach ($this->chunks as $c) {
        if ($first) $first= false;
        else print " / ";
        
        print "<a href='$base&ofs=$ofs&max=$c'>$c</a>";
    }
    
    print " | <a href='$base&ofs=".(max(0,$ofs+$max))."&max=$max'>next $max</a> &gt;&gt; ";
    print "</p>";
  }
  
  function makeSortLink($k, &$f) {
      $s= $f['label'];
      $s= htmlspecialchars($s);
      
      $u= $this->getURL('order');
      
      if ($this->order == $k) {
          $u.= '&amp;order=-'.urlencode($k);
      }
      else $u.= '&amp;order='.urlencode($k);
      
      $s= "<a href='$u'>$s</a>";
      
      if ($this->order == $k) $s= "<u>$s</u> &#8595;";
      else if ($this->order == "-$k") $s= "<u>$s</u> &#8593;";
      
      return $s;
  }
  
  function printHTMLHeader() {
      print "\n\t<table class='query-result'>\n";
      print "\t\t<tr>\n";
      
      foreach ( $this->fields as $k => $f ) {
        if (@$f['hidden']) continue;
        
        $u= NULL;
        if (@$f['sort']) $s= $this->makeSortLink($k, $f);
        else {
            $s= $f['label'];
            $s= htmlspecialchars($s);
        }
            
        print "\t\t\t<th align='left'>".$s."</th>\n";
      }
      
      print "\t\t</tr>\n";
  }
  
  function printHTMLFooter() {
      print "\t</table>\n";
  }
  
  function getFieldValue($row, $field, $f = NULL, $format='html') {
      if (!$f) {
          $f= @$this->fields[$field];
          if (!$f) return NULL;
      }
  
      if ($format=='wiki' && isset($f['wiki-function'])) $fun= $f['wiki-function'];
      else $fun= @$f['function'];
      
      if ($fun) $value= $fun($field,$row,$format);
      else {
        if ($format=='wiki' && isset($f['wiki-pattern'])) $e= $f['wiki-pattern'];
        else $e= @$f['pattern'];
        
        if ($e) $value= eval($e);
        else $value= $row[$field];
      }
      
      if ($format=='html' && !@$f['html']) $value= htmlspecialchars($value);

      return $value;  
  }
  
  function printHTMLRow($row) {
    print "\t\t<tr>\n";
    
    foreach ( $this->fields as $field => $f ) {
      if (@$f['hidden']) continue;
      
      $label= $f['label'];
      
      $value= $this->getFieldValue($row, $field, $f, 'html');
      $a= '';
      
      if (@$f['attributes']) {
          if (is_array($f['attributes'])) {
            foreach ($f['attributes'] as $n => $v) {
                $a.= ' ';
                $a.= $n;
                $a.= '=';
                $a.= '"';
                $a.= htmlspecialchars($v);
                $a.= '"';
            }
          }
          else $a= ' '.$f['attributes'];
      }
      
      print "\t\t\t<td valign='top' title='".htmlspecialchars($label)."'$a>$value</td>\n";
    }
    
    print "\t\t</tr>\n";
    if ($this->autoflush) flush();
  }
  
  function printWikiHeader() {
    //noop
  }
  
  function printWikiRow($row) {
    print "* ";
    
    $first= true;
    
    foreach ( $this->fields as $field => $f ) {
      if (@$f['hidden']) continue;
      
      #if (isset($f['wiki-label'])) $label= $f['wiki-label'];
      #else $label= $f['label'];
      
      $value= $this->getFieldValue($row, $field, $f, 'wiki');

      if ($first) $first= false;
      else if (isset($f['wiki-prefix'])) print $f['wiki-prefix'];
      else print '; ';
      
      #if ($label) print $label.': ';
      
      print $value;
      
    }
    
    print "\n";
  }
  
  function printCSVRow(&$csv, $row) {
    $line= array();
  
    foreach ( $this->fields as $field => $f ) {
      if (@$f['hidden']) continue;
      
      $label= $f['label'];
      $value= $this->getFieldValue($row, $field, $f, 'csv');
      
      if (@$f['html']) $value= stripHTML($value);

      $line[$field]= $value;      
    }
    
    print $csv->csvLine( $line );
    print "\r\n";
  }
  
  function printStyles() {
      ?>
      .error { font-weight:bold; color:red; }
      <?
      
      foreach ($this->styles as $s) {
          print $s;
      }
  }
  
  function getFeedURL($format='rss') {
      if (!$this->feedFields) return NULL;
      
      $u= $this->getURL(array('format','since','until'));
      
      $u.= "&amp;format=$format";
      
      if ($this->feedSince) $u.= "&amp;since=".$this->feedSince;
      if ($this->feedUntil) $u.= "&amp;until=".$this->feedUntil;
      
      if ($this->feedLimit) $u.= "&amp;max=".$this->feedLimit;
      else if ($this->feedSince) $u.= "&amp;max=1000";
      else $u.= "&amp;max=100";
      
      return $u;
  }
  
  function printPage( $standalone= true, $format = NULL ) {
    global $wgRequest;
    
    if (!$standalone) $format= 'html';
    if (!$format) $format= $wgRequest->getVal('format','html');
    
    if (!$this->wiki) {
      if ( $this->wikiSelector->domain ) $this->setWikiDomain( $this->wikiSelector->domain );
      $wikiSelector= true;
    }
    else {
      $wikiSelector= false;
    }
    
    if (!$this->order) {
      $this->order= $wgRequest->getVal('order');
      
      if ($this->order && !in_array(preg_replace('/^-/','',$this->order),array_keys($this->fields))) {
          $this->order= NULL;
      }
    }

    if (!$this->order) $this->order= $this->defaultOrder;
    
    if (!$this->order) {
        foreach ($this->fields as $k => $f) {
            if (@$f['sort']) {
                $this->order= $k;
                break;
            }
        }
    }
    
    if ($standalone && $format == 'html') {
    ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>
	<head>
		<title><?=htmlspecialchars($this->title)?></title>
		<meta http-equiv='Content-Type' content='text/html; charset=utf-8' />
                
                <?
                if ($this->wiki) print '<meta name="robots" content="noindex, nofollow"/>';
                else print '<meta name="robots" content="nofollow"/>';
                ?>
                
                <? 
                if ($this->wiki && $this->feedFields) {
                  $t= $this->title." - ".$this->getScope();
                  
                  ?>
                  <link rel="alternate" title="<?=$t?> (RSS)" href="<?= $this->getFeedURL('rss') ?>" type="application/rss+xml">
                  <link rel="alternate" title="<?=$t?> (Atom)" href="<?= $this->getFeedURL('atom') ?>" type="application/atom+xml">
                  <?
                }
                ?>
                
		<style type='text/css'>
                        body { font-family: sans-serif; font-size:14px; }
                        
                        <? $this->printStyles() ?>
		</style>
	</head>
        
	<body>
    <?  
        wsfHeader(); 
    }
    
    if ($standalone && $format == 'html') {
        $this->printTitle();
        $this->printForm( $wikiSelector );
    }
        
    if ($this->isComplete()) {
      $ofs= $wgRequest->getVal('ofs',0);
      $max= $wgRequest->getVal('max',$this->chunks[0]);
      if ($max>$this->chunks[sizeof($this->chunks)-1] && $format == 'html') $max= $this->chunks[sizeof($this->chunks)-1];
      
      $filter= $this->getFilter();
      
      if ($format == 'html') {
          $this->printScope();
      }
      
      $this->printData($ofs, $max, $format, $this->order, $filter);
    }
    
    if ($standalone && $format == 'html') {
      wsfFooter(false);
    ?>
	</body>
</html>        
    <?
    }    
  }
  
  function isComplete() {
      global $wgRequest;
  
      if (!$this->wiki) return false;
      
      foreach ($this->fields as $k => $f) {
          if (@$f['filter'] && @$f['filter-required']) {
              $v= $wgRequest->getVal($k);
              if (!$v) return false;
          }
      }
      
      return true;
  }
  
  function normalizeTimestampValue($v) {
      $v= trim($v);
      $m= array();

      if (preg_match('/^-(\d+)([smhd])/',$v,$m)) {
          $ofs= (int)$m[1];
          $u= $m[2];
          
          if ($u==='m') $ofs*= 60;
          else if ($u==='h') $ofs*= 60 * 60;
          else if ($u==='d') $ofs*= 60 * 60 * 24;
          
          $t= time() - $ofs;
          
          $v= wfTimestamp(TS_MW,$t);
      }
      else $v= wfTimestamp(TS_MW,$v);
      
      return $v;
  }
  
  function makePageFilter($title, $titlefield= 'page_title', $nsfield= 'page_namespace') {
      if (strpos($title,':')===false) $e= array('link'=>$title, 'namespace' => NS_MAIN);
      else $e= $this->wiki->makeLinkEntry($title);
      
      $f= '';
      $f.= $titlefield . ' = ' . $this->wiki->wikiDB->addQuotes($this->wiki->asDBKey($e['link']));
      $f= '('.$f.' AND '.$nsfield . ' = ' . ($e['namespace'] ? (int)$this->wiki->getNSIndex($e['namespace']) : 0 ).')';
      
      return $f;
  }
  
  function getFilter() {
        global $wgRequest;
        
        $fv= NULL;
        $fk= NULL;
        $ff= NULL;
        
        $timestamp= NULL;
  
        foreach ($this->fields as $k => $f) {
            if (!$fk && @$f['filter']) {
                $v= $wgRequest->getVal($k);
                if ($v) {
                    $fk= $k;
                    $fv= $v;
                    $ff= $f;
                }
            }
            
            if (!$timestamp && @$f['timestamp']) {
                $timestamp= $k;
            }
        }
        
        $filter= '';
        
        if ($fk && $fv) {
            $fv= trim($fv);
            
            if (@$ff['filter-normalize']) {
                $norm= $ff['filter-normalize'];
                
                if ($norm=='key') $fv= $this->wiki->asDBKey($fv);
                else if ($norm=='lc') $fv= $this->wiki->lc($fv);
                else if ($norm=='ucfirst') $fv= $this->wiki->ucfirst($fv);
                else if ($norm=='title') $fv= $this->wiki->asTitle($fv);
            }
        
            $this->filter= $fk;
            $this->filterValue= $fv;
            
            if (@$ff['filter-pattern']) {
                $value= $fv;
                $e= @$ff['filter-pattern'];
                $filter.= eval($e);
            }
            else {
                $filter.= $fk." = ".$this->wiki->wikiDB->addQuotes($fv);
            }
        }
        
        if ($timestamp) {
            $this->sinceRequest= $wgRequest->getVal('since');
            $this->untilRequest= $wgRequest->getVal('until');
            
            if ($this->sinceRequest) $this->sinceValue= $this->normalizeTimestampValue($this->sinceRequest);
            if ($this->untilRequest) $this->untilValue= $this->normalizeTimestampValue($this->untilRequest);

            if ($this->sinceValue) {
                if ($filter) $filter .= ' AND ';
                $filter.= $timestamp." > ".$this->wiki->wikiDB->addQuotes($this->sinceValue);
            }
            
            if ($this->untilValue) {
                if ($filter) $filter .= ' AND ';
                $filter.= $timestamp." < ".$this->wiki->wikiDB->addQuotes($this->untilValue);
            }
        }
        
        return $filter;
  }
  
  function printScope( ) {
    print "\n<h2>".htmlspecialchars($this->getScope())."</h2>\n";
  }
  
  function getScope( ) {
    $s= $this->wikiSelector->domain;
    
    if ($this->order) {
        $order= $this->order;
        
        if (preg_match('/^-/',$order)) {
            $order= substr($order,1);
            $dir= ' (reverse)';
        }
        else $dir= '';
        
        $f= @$this->fields[$order];
        
        if ($f) {
            $s.= ', by '.$f['label'].$dir;
        }
    }
    
    if ($this->filter && $this->filterValue) {
        $f= @$this->fields[$this->filter];
        
        if ($f) {
            $s.= ', with '.$f['label'].' = '.$this->filterValue;
        }
    }
    
    if ($this->sinceValue) {
        $s.= ', since '.htmlspecialchars(wfTimestamp(TS_DB,$this->sinceValue));
    }
    
    if ($this->untilValue) {
        $s.= ', until '.htmlspecialchars(wfTimestamp(TS_DB,$this->untilValue));
    }
    
    return $s;
  }
  
  function printTitle( ) {
    print "\n<h1>".htmlspecialchars($this->title)."</h1>\n";
    if ($this->helpPageName) wsfSubtitle($this->helpPageName);
    if ($this->description) print "\n<p>".htmlspecialchars($this->description)."</p>\n";
    
    $replag= wsfGetReplagHTML();
    if ($replag) {
      print "<div class='replag'>$replag</div>";
    }
  }
  
  function printOptions( ) {
      global $wgRequest;
      
      if ($this->options) {
          print "\n\t\t<p>";
          
          $first= true;
          foreach ($this->options as $k => $label) {
              $v= $wgRequest->getVal($k);
              
              if ($first) $first= false;
              else print ", ";
              
              print htmlspecialchars($label);
              printCheckBox($k,$v);
          }
          print "</p>\n";
      }
  }
  
  function printForm( $wikiSelector ) {
    global $wgRequest;
    
    $has= false;
    
    foreach ($this->messages as $m) {
        print "\n\t\t$m\n";
    }
    
    ?>
    <form action="<?=$_SERVER['PHP_SELF']?>">
      <?
      if ($wikiSelector && $this->wikiSelector) {
          $this->printWikiSelector();
          $has= true;
      }

      foreach ($this->fields as $k => $f) {
          if (@$f['filter']) {
            $v= $wgRequest->getVal($k);
            ?>
            <p>
            <?= @$f['filter-required'] ? '' : 'Filter by'?> <i><?= htmlspecialchars($f['label'])?></i>: <input type='text' name="<?=$k?>" value="<?=htmlspecialchars($v)?>">
            <?= @$f['filter-required'] ? '' : '<small>(optional)</small>'?>
            </p>
            <?
            
            $has= true;
          }
          
          if (@$f['timestamp']) {
            ?>
            <p>
            Since: <? printSelector('since', $this->sinceChoices, $wgRequest->getVal('since')) ?>
            Until: <? printSelector('until', $this->untilChoices, $wgRequest->getVal('until')) ?>
            </p>
            <?
          }
      }

      $this->printOptions();

      $this->printHiddenFields( ($wikiSelector && $this->wikiSelector) ? array('wikifam', 'wikilang') : array(), false );      
      ?>
      
      <? if ($has): ?>
        <p><input type="submit" value="Load"/>
        <? 
        if (!$this->options || $this->isComplete()) {
            $this->printOrderSelector( $this->order ); 
        }
        
        $this->printFormatSelector('html'); 
        ?>
        </p>
      <? endif; ?>
    </form>
    <?
    
  }
  
  function printFormatSelector( $current=NULL ) {
      if ($this->allowRaw || $this->allowWiki || $this->feedFields) {
          
          $formats= array( 
              'html' => 'normal (HTML)',
          );
          
          if ($this->allowWiki) $formats['wiki'] = 'Wiki Text (list)';
          if ($this->allowRaw)  $formats['csv'] = 'CSV (URL-encoded)';
          if ($this->allowRaw)  $formats['tsv'] = 'TSV (tabs)';
          if ($this->feedFields)  $formats['rss'] = 'RSS feed (XML)';
          if ($this->feedFields)  $formats['atom'] = 'Atom feed (XML)';
          
          print "<span class='formats'>";
          print "Format: ";
          printSelector('format',$formats,$current);
          print "</span>";
      }
  }
  
  function printOrderSelector( $current=NULL ) {
      global $wgRequest;
      
      if (!$current) $current= $this->order;
  
      $sort= array();
      
      foreach ($this->fields as $k => $f) {
          if (@$f['sort']) {
              $sort[$k]= $f['label'].' &#8595; (ascending)';
              $sort['-'.$k]= $f['label'].' &#8593; (descending)';
          }
      }
      
      if ($sort) {
          print "<span class='sort'>";
          print "Sort by: ";
          printSelector('order',$sort,$current);
          print "</span>";
      }
  }
  
  function printWikiSelector( ) {
    print "<p>Wiki: ";
    $this->wikiSelector->printSelector( false );
    print "</p>";
  }
  
}
