*
locked
If the page is locked. *
hits
The page hit count. *
created
Unix time of page creation. (FIXME: Deprecated: I * don't think we need this...) * */ function get_pagedata($pagename) { trigger_error("virtual", E_USER_ERROR); } /** * Update the page meta-data. * * Set page meta-data. * * Only meta-data whose keys are preset in $newdata is affected. * * For example: *
     *   $backend->update_pagedata($pagename, array('locked' => 1));
     * 
* will set the value of 'locked' to 1 for the specified page, but it * will not affect the value of 'hits' (or whatever other meta-data * may have been stored for the page.) * * To delete a particular piece of meta-data, set its value to false. *
     *   $backend->update_pagedata($pagename, array('locked' => false));
     * 
* * @param $pagename string Page name. * @param $newdata hash New meta-data. */ function update_pagedata($pagename, $newdata) { trigger_error("virtual", E_USER_ERROR); } /** * Get the current version number for a page. * * @param $pagename string Page name. * @return int The latest version number for the page. Returns zero if * no versions of a page exist. */ function get_latest_version($pagename) { trigger_error("virtual", E_USER_ERROR); } /** * Get preceding version number. * * @param $pagename string Page name. * @param $version int Find version before this one. * @return int The version number of the version in the database which * immediately preceeds $version. */ function get_previous_version($pagename, $version) { trigger_error("virtual", E_USER_ERROR); } /** * Get revision meta-data and content. * * @param $pagename string Page name. * @param $version integer Which version to get. * @param $want_content boolean * Indicates the caller really wants the page content. If this * flag is not set, the backend is free to skip fetching of the * page content (as that may be expensive). If the backend omits * the content, the backend might still want to set the value of * '%content' to the empty string if it knows there's no content. * * @return hash The version data, or false if specified version does not * exist. * * Some keys which might be present in the $versiondata hash are: *
*
%content *
This is a pseudo-meta-data element (since it's actually * the page data, get it?) containing the page content. * If the content was not fetched, this key may not be present. *
* For description of other version meta-data see WikiDB_PageRevision::get(). * @see WikiDB_PageRevision::get */ function get_versiondata($pagename, $version, $want_content = false) { trigger_error("virtual", E_USER_ERROR); } /** * Delete page from the database with backup possibility. * This should remove all links (from the named page) from * the link database. * * @param $pagename string Page name. * i.e save_page('') and DELETE nonempty id * Can be undone and is seen in RecentChanges. */ function delete_page($pagename) { $mtime = time(); $user =& $GLOBALS['request']->_user; $vdata = array('author' => $user->getId(), 'author_id' => $user->getAuthenticatedId(), 'mtime' => $mtime); $this->lock(); // critical section: $version = $this->get_latest_version($pagename); $this->set_versiondata($pagename, $version+1, $vdata); $this->set_links($pagename, false); // links are purged. // SQL needs to invalidate the non_empty id if (! WIKIDB_NOCACHE_MARKUP) { // need the hits, perms and LOCKED, otherwise you can reset the perm // by action=remove and re-create it with default perms $pagedata = $this->get_pagedata($pagename); unset($pagedata['_cached_html']); $this->update_pagedata($pagename, $pagedata); } $this->unlock(); } /** * Delete page (and all its revisions) from the database. * */ function purge_page($pagename) { trigger_error("virtual", E_USER_ERROR); } /** * Delete an old revision of a page. * * Note that one is never allowed to delete the most recent version, * but that this requirement is enforced by WikiDB not by the backend. * * In fact, to be safe, backends should probably allow the deletion of * the most recent version. * * @param $pagename string Page name. * @param $version integer Version to delete. */ function delete_versiondata($pagename, $version) { trigger_error("virtual", E_USER_ERROR); } /** * Create a new page revision. * * If the given ($pagename,$version) is already in the database, * this method completely overwrites any stored data for that version. * * @param $pagename string Page name. * @param $version int New revisions content. * @param $data hash New revision metadata. * * @see get_versiondata */ function set_versiondata($pagename, $version, $data) { trigger_error("virtual", E_USER_ERROR); } /** * Update page version meta-data. * * If the given ($pagename,$version) is already in the database, * this method only changes those meta-data values whose keys are * explicity listed in $newdata. * * @param $pagename string Page name. * @param $version int New revisions content. * @param $newdata hash New revision metadata. * @see set_versiondata, get_versiondata */ function update_versiondata($pagename, $version, $newdata) { $data = $this->get_versiondata($pagename, $version, true); if (!$data) { assert($data); return; } foreach ($newdata as $key => $val) { if (empty($val)) unset($data[$key]); else $data[$key] = $val; } $this->set_versiondata($pagename, $version, $data); } /** * Set links for page. * * @param $pagename string Page name. * * @param $links array List of page(names) which page links to. */ function set_links($pagename, $links) { trigger_error("virtual", E_USER_ERROR); } /** * Find pages which link to or are linked from a page. * * @param $pagename string Page name. * @param $reversed boolean True to get backlinks. * * FIXME: array or iterator? * @return object A WikiDB_backend_iterator. */ function get_links($pagename, $reversed, $include_empty=false, $sortby='', $limit='', $exclude='') { //FIXME: implement simple (but slow) link finder. die("FIXME get_links"); } /** * Get all revisions of a page. * * @param $pagename string The page name. * @return object A WikiDB_backend_iterator. */ function get_all_revisions($pagename) { include_once('lib/WikiDB/backend/dumb/AllRevisionsIter.php'); return new WikiDB_backend_dumb_AllRevisionsIter($this, $pagename); } /** * Get all pages in the database. * * Pages should be returned in alphabetical order if that is * feasable. * * @access protected * * @param $include_defaulted boolean * If set, even pages with no content will be returned * --- but still only if they have at least one revision (not * counting the default revision 0) entered in the database. * * Normally pages whose current revision has empty content * are not returned as these pages are considered to be * non-existing. * * @return object A WikiDB_backend_iterator. */ function get_all_pages($include_defaulted, $orderby=false, $limit='', $exclude='') { trigger_error("virtual", E_USER_ERROR); } /** * Title or full text search. * * Pages should be returned in alphabetical order if that is * feasable. * * @access protected * * @param $search object A TextSearchQuery object describing the parsed query string, * with efficient methods for SQL and PCRE match. * * @param $fullsearch boolean If true, a full text search is performed, * otherwise a title search is performed. * * @return object A WikiDB_backend_iterator. * * @see WikiDB::titleSearch */ function text_search($search, $fulltext=false, $sortby='', $limit='', $exclude='') { // This method implements a simple linear search // through all the pages in the database. // // It is expected that most backends will overload // this method with something more efficient. include_once('lib/WikiDB/backend/dumb/TextSearchIter.php'); // ignore $limit $pages = $this->get_all_pages(false, $sortby, false, $exclude); return new WikiDB_backend_dumb_TextSearchIter($this, $pages, $search, $fulltext, array('limit' => $limit, 'exclude' => $exclude)); } /** * * @access protected * @param $pages object A TextSearchQuery object. * @param $linkvalue object A TextSearchQuery object for the linkvalues * (linkto, relation or backlinks or attribute values). * @param $linktype string One of the 4 linktypes. * @param $relation object A TextSearchQuery object or false. * @param $options array Currently ignored. hash of sortby, limit, exclude. * @return object A WikiDB_backend_iterator. * @see WikiDB::linkSearch */ function link_search( $pages, $linkvalue, $linktype, $relation=false, $options=array() ) { include_once('lib/WikiDB/backend/dumb/LinkSearchIter.php'); $pageiter = $this->text_search($pages); return new WikiDB_backend_dumb_LinkSearchIter($this, $pageiter, $linkvalue, $linktype, $relation, $options); } /** * Find pages with highest hit counts. * * Find the pages with the highest hit counts. The pages should * be returned in reverse order by hit count. * * @access protected * @param integer $limit No more than this many pages * @return object A WikiDB_backend_iterator. */ function most_popular($limit, $sortby='-hits') { // This is method fetches all pages, then // sorts them by hit count. // (Not very efficient.) // // It is expected that most backends will overload // method with something more efficient. include_once('lib/WikiDB/backend/dumb/MostPopularIter.php'); $pages = $this->get_all_pages(false, $sortby, false); return new WikiDB_backend_dumb_MostPopularIter($this, $pages, $limit); } /** * Find recent changes. * * @access protected * @param $params hash See WikiDB::mostRecent for a description * of parameters which can be included in this hash. * @return object A WikiDB_backend_iterator. * @see WikiDB::mostRecent */ function most_recent($params) { // This method is very inefficient and searches through // all pages for the most recent changes. // // It is expected that most backends will overload // method with something more efficient. include_once('lib/WikiDB/backend/dumb/MostRecentIter.php'); $pages = $this->get_all_pages(true, '-mtime'); return new WikiDB_backend_dumb_MostRecentIter($this, $pages, $params); } function wanted_pages($exclude_from='', $exclude='', $sortby='', $limit='') { include_once('lib/WikiDB/backend/dumb/WantedPagesIter.php'); $allpages = $this->get_all_pages(true,false,false,$exclude_from); return new WikiDB_backend_dumb_WantedPagesIter($this, $allpages, $exclude, $sortby, $limit); } /** * Lock backend database. * * Calls may be nested. * * @param $write_lock boolean Unless this is set to false, a write lock * is acquired, otherwise a read lock. If the backend doesn't support * read locking, then it should make a write lock no matter which type * of lock was requested. * * All backends should support write locking. */ function lock($write_lock = true) { } /** * Unlock backend database. * * @param $force boolean Normally, the database is not unlocked until * unlock() is called as many times as lock() has been. If $force is * set to true, the the database is unconditionally unlocked. */ function unlock($force = false) { } /** * Close database. */ function close () { } /** * Synchronize with filesystem. * * This should flush all unwritten data to the filesystem. */ function sync() { } /** * Optimize the database. */ function optimize() { } /** * Check database integrity. * * This should check the validity of the internal structure of the database. * Errors should be reported via: *
     *   trigger_error("Message goes here.", E_USER_WARNING);
     * 
* * @return boolean True iff database is in a consistent state. */ function check($args=false) { } /** * Put the database into a consistent state * by reparsing and restoring all pages. * * This should put the database into a consistent state. * (I.e. rebuild indexes, etc...) * * @return boolean True iff successful. */ function rebuild($args=false) { global $request; $dbh = $request->getDbh(); $iter = $dbh->getAllPages(false); while ($page = $iter->next()) { $current = $page->getCurrentRevision(true); $pagename = $page->getName(); $meta = $current->_data; $version = $current->getVersion(); $content =& $meta['%content']; $formatted = new TransformedText($page, $content, $current->getMetaData()); $type = $formatted->getType(); $meta['pagetype'] = $type->getName(); $links = $formatted->getWikiPageLinks(); // linkto => relation $this->lock(array('version','page','recent','link','nonempty')); $this->set_versiondata($pagename, $version, $meta); $this->set_links($pagename, $links); $this->unlock(array('version','page','recent','link','nonempty')); } } function _parse_searchwords($search) { $search = strtolower(trim($search)); if (!$search) return array(array(),array()); $words = preg_split('/\s+/', $search); $exclude = array(); foreach ($words as $key => $word) { if ($word[0] == '-' && $word != '-') { $word = substr($word, 1); $exclude[] = preg_quote($word); unset($words[$key]); } } return array($words, $exclude); } /** * Split the given limit parameter into offset,limit. (offset is optional. default: 0) * Duplicate the PageList function here to avoid loading the whole PageList.php * Usage: * list($offset,$count) = $this->limit($args['limit']); */ function limit($limit) { if (strstr($limit, ',')) { list($from, $limit) = explode(',', $limit); if ((!empty($from) && !is_numeric($from)) or (!empty($limit) && !is_numeric($limit))) { return $this->error(_("Illegal 'limit' argument: must be numeric")); } return array($from, $limit); } else { if (!empty($limit) && !is_numeric($limit)) { return $this->error(_("Illegal 'limit' argument: must be numeric")); } return array(0, $limit); } } /** * Handle sortby requests for the DB iterator and table header links. * Prefix the column with + or - like "+pagename","-mtime", ... * supported actions: 'flip_order' "mtime" => "+mtime" => "-mtime" ... * 'db' "-pagename" => "pagename DESC" * In PageList all columns are sortable. (patch by DanFr) * Here with the backend only some, the rest is delayed to PageList. * (some kind of DumbIter) * Duplicate the PageList function here to avoid loading the whole * PageList.php, and it forces the backend specific sortable_columns() */ function sortby ($column, $action, $sortable_columns=false) { if (empty($column)) return ''; //support multiple comma-delimited sortby args: "+hits,+pagename" if (strstr($column, ',')) { $result = array(); foreach (explode(',', $column) as $col) { if (empty($this)) $result[] = WikiDB_backend::sortby($col, $action); else $result[] = $this->sortby($col, $action); } return join(",",$result); } if (substr($column,0,1) == '+') { $order = '+'; $column = substr($column,1); } elseif (substr($column,0,1) == '-') { $order = '-'; $column = substr($column,1); } // default order: +pagename, -mtime, -hits if (empty($order)) if (in_array($column,array('mtime','hits'))) $order = '-'; else $order = '+'; if ($action == 'flip_order') { return ($order == '+' ? '-' : '+') . $column; } elseif ($action == 'init') { $this->_sortby[$column] = $order; return $order . $column; } elseif ($action == 'check') { return (!empty($this->_sortby[$column]) or ($GLOBALS['request']->getArg('sortby') and strstr($GLOBALS['request']->getArg('sortby'),$column))); } elseif ($action == 'db') { // native sort possible? if (!empty($this) and !$sortable_columns) $sortable_columns = $this->sortable_columns(); if (in_array($column, $sortable_columns)) // asc or desc: +pagename, -pagename return $column . ($order == '+' ? ' ASC' : ' DESC'); else return ''; } return ''; } function sortable_columns() { return array('pagename'/*,'mtime','author_id','author'*/); } // adds surrounding quotes function quote ($s) { return "'".$s."'"; } // no surrounding quotes because we know it's a string function qstr ($s) { return $s; } function isSQL () { return in_array(DATABASE_TYPE, array('SQL','ADODB','PDO')); } function backendType() { return DATABASE_TYPE; } function write_accesslog(&$entry) { global $request; if (!$this->isSQL()) return; $dbh = &$this->_dbh; $log_tbl = $entry->_accesslog->logtable; // duration problem: sprintf "%f" might use comma e.g. "100,201" in european locales $dbh->query("INSERT INTO $log_tbl" . " (time_stamp,remote_host,remote_user,request_method,request_line,request_args," . "request_uri,request_time,status,bytes_sent,referer,agent,request_duration)" . " VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)", array( // Problem: date formats are backend specific. Either use unixtime as %d (long), // or the native timestamp format. $entry->time, $entry->host, $entry->user, $entry->request_method, $entry->request, $entry->request_args, $entry->request_uri, $entry->_ncsa_time($entry->time), $entry->status, (int)$entry->size, $entry->referer, $entry->user_agent, $entry->duration)); } }; /** * Iterator returned by backend methods which (possibly) return * multiple records. * * FIXME: This might be two seperate classes: page_iter and version_iter. * For the versions we have WikiDB_backend_dumb_AllRevisionsIter. */ class WikiDB_backend_iterator { /** * Get the next record in the iterator set. * * This returns a hash. The hash may contain the following keys: *
*
pagename
(string) the page name or linked page name on link iterators *
version
(int) the version number *
pagedata
(hash) page meta-data (as returned from backend::get_pagedata().) *
versiondata
(hash) page meta-data (as returned from backend::get_versiondata().) *
linkrelation
(string) the page naming the relation (e.g. isa:=page <=> isa) * * If this is a page iterator, it must contain the 'pagename' entry --- the others * are optional. * * If this is a version iterator, the 'pagename', 'version', and 'versiondata' * entries are mandatory. ('pagedata' is optional.) * * If this is a link iterator, the 'pagename' is mandatory, 'linkrelation' is optional. */ function next() { trigger_error("virtual", E_USER_ERROR); } function count() { if (!empty($this->_pages)) return count($this->_pages); else return 0; } function asArray() { if (!empty($this->_pages)) { reset($this->_pages); return $this->_pages; } else { $result = array(); while ($page = $this->next()) $result[] = $page; return $result; } } /** * limit - if empty the pagelist iterator will do nothing. * Some backends limit the result set itself (dba, file, flatfile), * Some SQL based leave it to WikiDB/PageList - deferred filtering in the iterator. */ function limit() { return empty($this->_options['limit']) ? 0 : $this->_options['limit']; } /** * Release resources held by this iterator. */ function free() { } }; /** * search baseclass, pcre-specific */ class WikiDB_backend_search { function WikiDB_backend_search($search, &$dbh) { $this->_dbh = $dbh; $this->_case_exact = $search->_case_exact; $this->_stoplist =& $search->_stoplist; $this->stoplisted = array(); } function _quote($word) { return preg_quote($word, "/"); } //TODO: use word anchors function EXACT($word) { return "^".$this->_quote($word)."$"; } function STARTS_WITH($word) { return "^".$this->_quote($word); } function ENDS_WITH($word) { return $this->_quote($word)."$"; } function WORD($word) { return $this->_quote($word); } function REGEX($word) { return $word; } //TESTME function _pagename_match_clause($node) { $method = $node->op; $word = $this->$method($node->word); return "preg_match(\"/\".$word.\"/\"".($this->_case_exact ? "i":"").")"; } /* Eliminate stoplist words. * Keep a list of Stoplisted words to inform the poor user. */ function isStoplisted ($node) { // check only on WORD or EXACT fulltext search if ($node->op != 'WORD' and $node->op != 'EXACT') return false; if (preg_match("/^".$this->_stoplist."$/i", $node->word)) { array_push($this->stoplisted, $node->word); return true; } return false; } function getStoplisted($word) { return $this->stoplisted; } } /** * search baseclass, sql-specific */ class WikiDB_backend_search_sql extends WikiDB_backend_search { function _pagename_match_clause($node) { // word already quoted by TextSearchQuery_node_word::_sql_quote() $word = $node->sql(); if ($word == '%') // ALL shortcut return "1=1"; else return ($this->_case_exact ? "pagename LIKE '$word'" : "LOWER(pagename) LIKE '$word'"); } function _fulltext_match_clause($node) { // force word-style %word% for fulltext search $word = '%' . $node->_sql_quote($node->word) . '%'; // eliminate stoplist words if ($this->isStoplisted($node)) return "1=1"; // and (pagename or 1) => and 1 else return $this->_pagename_match_clause($node) // probably convert this MATCH AGAINST or SUBSTR/POSITION without wildcards . ($this->_case_exact ? " OR content LIKE '$word'" : " OR LOWER(content) LIKE '$word'"); } } // Local Variables: // mode: php // tab-width: 8 // c-basic-offset: 4 // c-hanging-comment-ender-p: nil // indent-tabs-mode: nil // End: