/gforge/plugins/wiki/www/lib/WikiDB.php
PHP | 2638 lines | 1285 code | 175 blank | 1178 comment | 253 complexity | 65dd65c437575b0f1b41bac0d39117be MD5 | raw file
Possible License(s): GPL-2.0, MPL-2.0-no-copyleft-exception
Large files files are truncated, but you can click here to view the full file
- <?php //-*-php-*-
- rcs_id('$Id: WikiDB.php,v 1.135 2005/09/11 14:19:44 rurban Exp $');
- require_once('lib/PageType.php');
- /**
- * The classes in the file define the interface to the
- * page database.
- *
- * @package WikiDB
- * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
- * Reini Urban
- */
- /**
- * Force the creation of a new revision.
- * @see WikiDB_Page::createRevision()
- */
- if (!defined('WIKIDB_FORCE_CREATE'))
- define('WIKIDB_FORCE_CREATE', -1);
- /**
- * Abstract base class for the database used by PhpWiki.
- *
- * A <tt>WikiDB</tt> is a container for <tt>WikiDB_Page</tt>s which in
- * turn contain <tt>WikiDB_PageRevision</tt>s.
- *
- * Conceptually a <tt>WikiDB</tt> contains all possible
- * <tt>WikiDB_Page</tt>s, whether they have been initialized or not.
- * Since all possible pages are already contained in a WikiDB, a call
- * to WikiDB::getPage() will never fail (barring bugs and
- * e.g. filesystem or SQL database problems.)
- *
- * Also each <tt>WikiDB_Page</tt> always contains at least one
- * <tt>WikiDB_PageRevision</tt>: the default content (e.g. "Describe
- * [PageName] here."). This default content has a version number of
- * zero.
- *
- * <tt>WikiDB_PageRevision</tt>s have read-only semantics. One can
- * only create new revisions or delete old ones --- one can not modify
- * an existing revision.
- */
- class WikiDB {
- /**
- * Open a WikiDB database.
- *
- * This is a static member function. This function inspects its
- * arguments to determine the proper subclass of WikiDB to
- * instantiate, and then it instantiates it.
- *
- * @access public
- *
- * @param hash $dbparams Database configuration parameters.
- * Some pertinent paramters are:
- * <dl>
- * <dt> dbtype
- * <dd> The back-end type. Current supported types are:
- * <dl>
- * <dt> SQL
- * <dd> Generic SQL backend based on the PEAR/DB database abstraction
- * library. (More stable and conservative)
- * <dt> ADODB
- * <dd> Another generic SQL backend. (More current features are tested here. Much faster)
- * <dt> dba
- * <dd> Dba based backend. The default and by far the fastest.
- * <dt> cvs
- * <dd>
- * <dt> file
- * <dd> flat files
- * </dl>
- *
- * <dt> dsn
- * <dd> (Used by the SQL and ADODB backends.)
- * The DSN specifying which database to connect to.
- *
- * <dt> prefix
- * <dd> Prefix to be prepended to database tables (and file names).
- *
- * <dt> directory
- * <dd> (Used by the dba backend.)
- * Which directory db files reside in.
- *
- * <dt> timeout
- * <dd> Used only by the dba backend so far.
- * And: When optimizing mysql it closes timed out mysql processes.
- * otherwise only used for dba: Timeout in seconds for opening (and
- * obtaining lock) on the dbm file.
- *
- * <dt> dba_handler
- * <dd> (Used by the dba backend.)
- *
- * Which dba handler to use. Good choices are probably either
- * 'gdbm' or 'db2'.
- * </dl>
- *
- * @return WikiDB A WikiDB object.
- **/
- function open ($dbparams) {
- $dbtype = $dbparams{'dbtype'};
- include_once("lib/WikiDB/$dbtype.php");
-
- $class = 'WikiDB_' . $dbtype;
- return new $class ($dbparams);
- }
- /**
- * Constructor.
- *
- * @access private
- * @see open()
- */
- function WikiDB (&$backend, $dbparams) {
- $this->_backend = &$backend;
- // don't do the following with the auth_dsn!
- if (isset($dbparams['auth_dsn'])) return;
-
- $this->_cache = new WikiDB_cache($backend);
- if (!empty($GLOBALS['request'])) $GLOBALS['request']->_dbi = $this;
- // If the database doesn't yet have a timestamp, initialize it now.
- if ($this->get('_timestamp') === false)
- $this->touch();
-
- //FIXME: devel checking.
- //$this->_backend->check();
- }
-
- /**
- * Close database connection.
- *
- * The database may no longer be used after it is closed.
- *
- * Closing a WikiDB invalidates all <tt>WikiDB_Page</tt>s,
- * <tt>WikiDB_PageRevision</tt>s and <tt>WikiDB_PageIterator</tt>s
- * which have been obtained from it.
- *
- * @access public
- */
- function close () {
- $this->_backend->close();
- $this->_cache->close();
- }
-
- /**
- * Get a WikiDB_Page from a WikiDB.
- *
- * A {@link WikiDB} consists of the (infinite) set of all possible pages,
- * therefore this method never fails.
- *
- * @access public
- * @param string $pagename Which page to get.
- * @return WikiDB_Page The requested WikiDB_Page.
- */
- function getPage($pagename) {
- static $error_displayed = false;
- $pagename = (string) $pagename;
- if (DEBUG) {
- if ($pagename === '') {
- if ($error_displayed) return false;
- $error_displayed = true;
- if (function_exists("xdebug_get_function_stack"))
- var_dump(xdebug_get_function_stack());
- trigger_error("empty pagename", E_USER_WARNING);
- return false;
- }
- } else {
- assert($pagename != '');
- }
- return new WikiDB_Page($this, $pagename);
- }
- /**
- * Determine whether page exists (in non-default form).
- *
- * <pre>
- * $is_page = $dbi->isWikiPage($pagename);
- * </pre>
- * is equivalent to
- * <pre>
- * $page = $dbi->getPage($pagename);
- * $current = $page->getCurrentRevision();
- * $is_page = ! $current->hasDefaultContents();
- * </pre>
- * however isWikiPage may be implemented in a more efficient
- * manner in certain back-ends.
- *
- * @access public
- *
- * @param string $pagename string Which page to check.
- *
- * @return boolean True if the page actually exists with
- * non-default contents in the WikiDataBase.
- */
- function isWikiPage ($pagename) {
- $page = $this->getPage($pagename);
- return $page->exists();
- }
- /**
- * Delete page from the WikiDB.
- *
- * Deletes the page from the WikiDB with the possibility to revert and diff.
- * //Also resets all page meta-data to the default values.
- *
- * Note: purgePage() effectively destroys all revisions of the page from the WikiDB.
- *
- * @access public
- *
- * @param string $pagename Name of page to delete.
- */
- function deletePage($pagename) {
- // don't create empty revisions of already purged pages.
- if ($this->_backend->get_latest_version($pagename))
- $result = $this->_cache->delete_page($pagename);
- else
- $result = -1;
- /* Generate notification emails? */
- if (! $this->isWikiPage($pagename) and !isa($GLOBALS['request'],'MockRequest')) {
- $notify = $this->get('notify');
- if (!empty($notify) and is_array($notify)) {
- global $request;
- //TODO: deferr it (quite a massive load if you remove some pages).
- //TODO: notification class which catches all changes,
- // and decides at the end of the request what to mail.
- // (type, page, who, what, users, emails)
- // could be used for PageModeration and RSS2 Cloud xml-rpc also.
- $page = new WikiDB_Page($this, $pagename);
- list($emails, $userids) = $page->getPageChangeEmails($notify);
- if (!empty($emails)) {
- $from = $request->_user->getId();
- $editedby = sprintf(_("Removed by: %s"), $from);
- $emails = join(',', $emails);
- $headers = "From: $from <nobody>\r\n" .
- "Bcc: $emails\r\n" .
- "MIME-Version: 1.0\r\n" .
- "Content-Type: text/plain; charset=".CHARSET."; format=flowed\r\n" .
- "Content-Transfer-Encoding: 8bit";
- $subject = sprintf(_("Page removed %s"), urlencode($pagename));
- if (mail("<undisclosed-recipients>","[".WIKI_NAME."] ".$subject,
- $subject."\n".
- $editedby."\n\n".
- "Deleted $pagename",
- $headers))
- trigger_error(sprintf(_("PageChange Notification of %s sent to %s"),
- $pagename, join(',',$userids)), E_USER_NOTICE);
- else
- trigger_error(sprintf(_("PageChange Notification Error: Couldn't send %s to %s"),
- $pagename, join(',',$userids)), E_USER_WARNING);
- }
- }
- }
- //How to create a RecentChanges entry with explaining summary? Dynamically
- /*
- $page = $this->getPage($pagename);
- $current = $page->getCurrentRevision();
- $meta = $current->_data;
- $version = $current->getVersion();
- $meta['summary'] = _("removed");
- $page->save($current->getPackedContent(), $version + 1, $meta);
- */
- return $result;
- }
- /**
- * Completely remove the page from the WikiDB, without undo possibility.
- */
- function purgePage($pagename) {
- $result = $this->_cache->purge_page($pagename);
- $this->deletePage($pagename); // just for the notification
- return $result;
- }
-
- /**
- * Retrieve all pages.
- *
- * Gets the set of all pages with non-default contents.
- *
- * @access public
- *
- * @param boolean $include_defaulted Normally pages whose most
- * recent revision has empty content are considered to be
- * non-existant. Unless $include_defaulted is set to true, those
- * pages will not be returned.
- *
- * @return WikiDB_PageIterator A WikiDB_PageIterator which contains all pages
- * in the WikiDB which have non-default contents.
- */
- function getAllPages($include_empty=false, $sortby=false, $limit=false,
- $exclude=false)
- {
- // HACK: memory_limit=8M will fail on too large pagesets. old php on unix only!
- if (USECACHE) {
- $mem = ini_get("memory_limit");
- if ($mem and !$limit and !isWindows() and !check_php_version(4,3)) {
- $limit = 450;
- $GLOBALS['request']->setArg('limit', $limit);
- $GLOBALS['request']->setArg('paging', 'auto');
- }
- }
- $result = $this->_backend->get_all_pages($include_empty, $sortby, $limit,
- $exclude);
- return new WikiDB_PageIterator($this, $result,
- array('include_empty' => $include_empty,
- 'exclude' => $exclude,
- 'limit' => $limit));
- }
- /**
- * $include_empty = true: include also empty pages
- * exclude: comma-seperated list pagenames: TBD: array of pagenames
- */
- function numPages($include_empty=false, $exclude='') {
- if (method_exists($this->_backend, 'numPages'))
- // FIXME: currently are all args ignored.
- $count = $this->_backend->numPages($include_empty, $exclude);
- else {
- // FIXME: exclude ignored.
- $iter = $this->getAllPages($include_empty, false, false, $exclude);
- $count = $iter->count();
- $iter->free();
- }
- return (int)$count;
- }
-
- /**
- * Title search.
- *
- * Search for pages containing (or not containing) certain words
- * in their names.
- *
- * Pages are returned in alphabetical order whenever it is
- * practical to do so.
- *
- * FIXME: clarify $search syntax. provide glob=>TextSearchQuery converters
- *
- * @access public
- * @param TextSearchQuery $search A TextSearchQuery object
- * @return WikiDB_PageIterator A WikiDB_PageIterator containing the matching pages.
- * @see TextSearchQuery
- */
- function titleSearch($search, $sortby='pagename', $limit=false, $exclude=false) {
- $result = $this->_backend->text_search($search, false, $sortby, $limit, $exclude);
- return new WikiDB_PageIterator($this, $result,
- array('exclude' => $exclude,
- 'limit' => $limit));
- }
- /**
- * Full text search.
- *
- * Search for pages containing (or not containing) certain words
- * in their entire text (this includes the page content and the
- * page name).
- *
- * Pages are returned in alphabetical order whenever it is
- * practical to do so.
- *
- * @access public
- *
- * @param TextSearchQuery $search A TextSearchQuery object.
- * @return WikiDB_PageIterator A WikiDB_PageIterator containing the matching pages.
- * @see TextSearchQuery
- */
- function fullSearch($search, $sortby='pagename', $limit=false, $exclude=false) {
- $result = $this->_backend->text_search($search, true, $sortby, $limit, $exclude);
- return new WikiDB_PageIterator($this, $result,
- array('exclude' => $exclude,
- 'limit' => $limit,
- 'stoplisted' => $result->stoplisted
- ));
- }
- /**
- * Find the pages with the greatest hit counts.
- *
- * Pages are returned in reverse order by hit count.
- *
- * @access public
- *
- * @param integer $limit The maximum number of pages to return.
- * Set $limit to zero to return all pages. If $limit < 0, pages will
- * be sorted in decreasing order of popularity.
- *
- * @return WikiDB_PageIterator A WikiDB_PageIterator containing the matching
- * pages.
- */
- function mostPopular($limit = 20, $sortby = '-hits') {
- $result = $this->_backend->most_popular($limit, $sortby);
- return new WikiDB_PageIterator($this, $result);
- }
- /**
- * Find recent page revisions.
- *
- * Revisions are returned in reverse order by creation time.
- *
- * @access public
- *
- * @param hash $params This hash is used to specify various optional
- * parameters:
- * <dl>
- * <dt> limit
- * <dd> (integer) At most this many revisions will be returned.
- * <dt> since
- * <dd> (integer) Only revisions since this time (unix-timestamp) will be returned.
- * <dt> include_minor_revisions
- * <dd> (boolean) Also include minor revisions. (Default is not to.)
- * <dt> exclude_major_revisions
- * <dd> (boolean) Don't include non-minor revisions.
- * (Exclude_major_revisions implies include_minor_revisions.)
- * <dt> include_all_revisions
- * <dd> (boolean) Return all matching revisions for each page.
- * Normally only the most recent matching revision is returned
- * for each page.
- * </dl>
- *
- * @return WikiDB_PageRevisionIterator A WikiDB_PageRevisionIterator containing the
- * matching revisions.
- */
- function mostRecent($params = false) {
- $result = $this->_backend->most_recent($params);
- return new WikiDB_PageRevisionIterator($this, $result);
- }
- /**
- * @access public
- *
- * @return Iterator A generic iterator containing rows of (duplicate) pagename, wantedfrom.
- */
- function wantedPages($exclude_from='', $exclude='', $sortby=false, $limit=false) {
- return $this->_backend->wanted_pages($exclude_from, $exclude, $sortby, $limit);
- //return new WikiDB_PageIterator($this, $result);
- }
- /**
- * Call the appropriate backend method.
- *
- * @access public
- * @param string $from Page to rename
- * @param string $to New name
- * @param boolean $updateWikiLinks If the text in all pages should be replaced.
- * @return boolean true or false
- */
- function renamePage($from, $to, $updateWikiLinks = false) {
- assert(is_string($from) && $from != '');
- assert(is_string($to) && $to != '');
- $result = false;
- if (method_exists($this->_backend, 'rename_page')) {
- $oldpage = $this->getPage($from);
- $newpage = $this->getPage($to);
- //update all WikiLinks in existing pages
- //non-atomic! i.e. if rename fails the links are not undone
- if ($updateWikiLinks) {
- require_once('lib/plugin/WikiAdminSearchReplace.php');
- $links = $oldpage->getBackLinks();
- while ($linked_page = $links->next()) {
- WikiPlugin_WikiAdminSearchReplace::replaceHelper($this,
- $linked_page->getName(),
- $from, $to);
- }
- // ape: Disabled to avoid recursive modification when renaming
- // a page like 'PageApe' to 'PageApeTwo'.
- //
- // $links = $newpage->getBackLinks();
- // while ($linked_page = $links->next()) {
- // WikiPlugin_WikiAdminSearchReplace::replaceHelper($this,
- // $linked_page->getName(),
- // $from, $to);
- // }
- }
- if ($oldpage->exists() and ! $newpage->exists()) {
- if ($result = $this->_backend->rename_page($from, $to)) {
- //create a RecentChanges entry with explaining summary
- $page = $this->getPage($to);
- $current = $page->getCurrentRevision();
- $meta = $current->_data;
- $version = $current->getVersion();
- $meta['summary'] = sprintf(_("renamed from %s"), $from);
- $page->save($current->getPackedContent(), $version + 1, $meta);
- }
- } elseif (!$oldpage->getCurrentRevision(false) and !$newpage->exists()) {
- // if a version 0 exists try it also.
- $result = $this->_backend->rename_page($from, $to);
- }
- } else {
- trigger_error(_("WikiDB::renamePage() not yet implemented for this backend"),
- E_USER_WARNING);
- }
- /* Generate notification emails? */
- if ($result and !isa($GLOBALS['request'], 'MockRequest')) {
- $notify = $this->get('notify');
- if (!empty($notify) and is_array($notify)) {
- list($emails, $userids) = $oldpage->getPageChangeEmails($notify);
- if (!empty($emails)) {
- $oldpage->sendPageRenameNotification($to, $meta, $emails, $userids);
- }
- }
- }
- return $result;
- }
- /** Get timestamp when database was last modified.
- *
- * @return string A string consisting of two integers,
- * separated by a space. The first is the time in
- * unix timestamp format, the second is a modification
- * count for the database.
- *
- * The idea is that you can cast the return value to an
- * int to get a timestamp, or you can use the string value
- * as a good hash for the entire database.
- */
- function getTimestamp() {
- $ts = $this->get('_timestamp');
- return sprintf("%d %d", $ts[0], $ts[1]);
- }
-
- /**
- * Update the database timestamp.
- *
- */
- function touch() {
- $ts = $this->get('_timestamp');
- $this->set('_timestamp', array(time(), $ts[1] + 1));
- }
-
- /**
- * Access WikiDB global meta-data.
- *
- * NOTE: this is currently implemented in a hackish and
- * not very efficient manner.
- *
- * @access public
- *
- * @param string $key Which meta data to get.
- * Some reserved meta-data keys are:
- * <dl>
- * <dt>'_timestamp' <dd> Data used by getTimestamp().
- * </dl>
- *
- * @return scalar The requested value, or false if the requested data
- * is not set.
- */
- function get($key) {
- if (!$key || $key[0] == '%')
- return false;
- /*
- * Hack Alert: We can use any page (existing or not) to store
- * this data (as long as we always use the same one.)
- */
- $gd = $this->getPage('global_data');
- $data = $gd->get('__global');
- if ($data && isset($data[$key]))
- return $data[$key];
- else
- return false;
- }
- /**
- * Set global meta-data.
- *
- * NOTE: this is currently implemented in a hackish and
- * not very efficient manner.
- *
- * @see get
- * @access public
- *
- * @param string $key Meta-data key to set.
- * @param string $newval New value.
- */
- function set($key, $newval) {
- if (!$key || $key[0] == '%')
- return;
-
- $gd = $this->getPage('global_data');
- $data = $gd->get('__global');
- if ($data === false)
- $data = array();
- if (empty($newval))
- unset($data[$key]);
- else
- $data[$key] = $newval;
- $gd->set('__global', $data);
- }
- /* TODO: these are really backend methods */
- // SQL result: for simple select or create/update queries
- // returns the database specific resource type
- function genericSqlQuery($sql, $args=false) {
- if (function_exists('debug_backtrace')) { // >= 4.3.0
- echo "<pre>", printSimpleTrace(debug_backtrace()), "</pre>\n";
- }
- trigger_error("no SQL database", E_USER_ERROR);
- return false;
- }
- // SQL iter: for simple select or create/update queries
- // returns the generic iterator object (count,next)
- function genericSqlIter($sql, $field_list = NULL) {
- if (function_exists('debug_backtrace')) { // >= 4.3.0
- echo "<pre>", printSimpleTrace(debug_backtrace()), "</pre>\n";
- }
- trigger_error("no SQL database", E_USER_ERROR);
- return false;
- }
-
- // see backend upstream methods
- // ADODB adds surrounding quotes, SQL not yet!
- function quote ($s) {
- return $s;
- }
- function isOpen () {
- global $request;
- if (!$request->_dbi) return false;
- else return false; /* so far only needed for sql so false it.
- later we have to check dba also */
- }
- function getParam($param) {
- global $DBParams;
- if (isset($DBParams[$param])) return $DBParams[$param];
- elseif ($param == 'prefix') return '';
- else return false;
- }
- function getAuthParam($param) {
- global $DBAuthParams;
- if (isset($DBAuthParams[$param])) return $DBAuthParams[$param];
- elseif ($param == 'USER_AUTH_ORDER') return $GLOBALS['USER_AUTH_ORDER'];
- elseif ($param == 'USER_AUTH_POLICY') return $GLOBALS['USER_AUTH_POLICY'];
- else return false;
- }
- };
- /**
- * An abstract base class which representing a wiki-page within a
- * WikiDB.
- *
- * A WikiDB_Page contains a number (at least one) of
- * WikiDB_PageRevisions.
- */
- class WikiDB_Page
- {
- function WikiDB_Page(&$wikidb, $pagename) {
- $this->_wikidb = &$wikidb;
- $this->_pagename = $pagename;
- if (DEBUG) {
- if (!(is_string($pagename) and $pagename != '')) {
- if (function_exists("xdebug_get_function_stack")) {
- echo "xdebug_get_function_stack(): "; var_dump(xdebug_get_function_stack());
- } elseif (function_exists("debug_backtrace")) { // >= 4.3.0
- printSimpleTrace(debug_backtrace());
- }
- trigger_error("empty pagename", E_USER_WARNING);
- return false;
- }
- } else {
- assert(is_string($pagename) and $pagename != '');
- }
- }
- /**
- * Get the name of the wiki page.
- *
- * @access public
- *
- * @return string The page name.
- */
- function getName() {
- return $this->_pagename;
- }
-
- // To reduce the memory footprint for larger sets of pagelists,
- // we don't cache the content (only true or false) and
- // we purge the pagedata (_cached_html) also
- function exists() {
- if (isset($this->_wikidb->_cache->_id_cache[$this->_pagename])) return true;
- $current = $this->getCurrentRevision(false);
- if (!$current) return false;
- return ! $current->hasDefaultContents();
- }
- /**
- * Delete an old revision of a WikiDB_Page.
- *
- * Deletes the specified revision of the page.
- * It is a fatal error to attempt to delete the current revision.
- *
- * @access public
- *
- * @param integer $version Which revision to delete. (You can also
- * use a WikiDB_PageRevision object here.)
- */
- function deleteRevision($version) {
- $backend = &$this->_wikidb->_backend;
- $cache = &$this->_wikidb->_cache;
- $pagename = &$this->_pagename;
- $version = $this->_coerce_to_version($version);
- if ($version == 0)
- return;
- $backend->lock(array('page','version'));
- $latestversion = $cache->get_latest_version($pagename);
- if ($latestversion && ($version == $latestversion)) {
- $backend->unlock(array('page','version'));
- trigger_error(sprintf("Attempt to delete most recent revision of '%s'",
- $pagename), E_USER_ERROR);
- return;
- }
- $cache->delete_versiondata($pagename, $version);
- $backend->unlock(array('page','version'));
- }
- /*
- * Delete a revision, or possibly merge it with a previous
- * revision.
- *
- * The idea is this:
- * Suppose an author make a (major) edit to a page. Shortly
- * after that the same author makes a minor edit (e.g. to fix
- * spelling mistakes he just made.)
- *
- * Now some time later, where cleaning out old saved revisions,
- * and would like to delete his minor revision (since there's
- * really no point in keeping minor revisions around for a long
- * time.)
- *
- * Note that the text after the minor revision probably represents
- * what the author intended to write better than the text after
- * the preceding major edit.
- *
- * So what we really want to do is merge the minor edit with the
- * preceding edit.
- *
- * We will only do this when:
- * <ul>
- * <li>The revision being deleted is a minor one, and
- * <li>It has the same author as the immediately preceding revision.
- * </ul>
- */
- function mergeRevision($version) {
- $backend = &$this->_wikidb->_backend;
- $cache = &$this->_wikidb->_cache;
- $pagename = &$this->_pagename;
- $version = $this->_coerce_to_version($version);
- if ($version == 0)
- return;
- $backend->lock(array('version'));
- $latestversion = $cache->get_latest_version($pagename);
- if ($latestversion && $version == $latestversion) {
- $backend->unlock(array('version'));
- trigger_error(sprintf("Attempt to merge most recent revision of '%s'",
- $pagename), E_USER_ERROR);
- return;
- }
- $versiondata = $cache->get_versiondata($pagename, $version, true);
- if (!$versiondata) {
- // Not there? ... we're done!
- $backend->unlock(array('version'));
- return;
- }
- if ($versiondata['is_minor_edit']) {
- $previous = $backend->get_previous_version($pagename, $version);
- if ($previous) {
- $prevdata = $cache->get_versiondata($pagename, $previous);
- if ($prevdata['author_id'] == $versiondata['author_id']) {
- // This is a minor revision, previous version is
- // by the same author. We will merge the
- // revisions.
- $cache->update_versiondata($pagename, $previous,
- array('%content' => $versiondata['%content'],
- '_supplanted' => $versiondata['_supplanted']));
- }
- }
- }
- $cache->delete_versiondata($pagename, $version);
- $backend->unlock(array('version'));
- }
-
- /**
- * Create a new revision of a {@link WikiDB_Page}.
- *
- * @access public
- *
- * @param int $version Version number for new revision.
- * To ensure proper serialization of edits, $version must be
- * exactly one higher than the current latest version.
- * (You can defeat this check by setting $version to
- * {@link WIKIDB_FORCE_CREATE} --- not usually recommended.)
- *
- * @param string $content Contents of new revision.
- *
- * @param hash $metadata Metadata for new revision.
- * All values in the hash should be scalars (strings or integers).
- *
- * @param array $links List of pagenames which this page links to.
- *
- * @return WikiDB_PageRevision Returns the new WikiDB_PageRevision object. If
- * $version was incorrect, returns false
- */
- function createRevision($version, &$content, $metadata, $links) {
- $backend = &$this->_wikidb->_backend;
- $cache = &$this->_wikidb->_cache;
- $pagename = &$this->_pagename;
- $cache->invalidate_cache($pagename);
-
- $backend->lock(array('version','page','recent','link','nonempty'));
- $latestversion = $backend->get_latest_version($pagename);
- $newversion = ($latestversion ? $latestversion : 0) + 1;
- assert($newversion >= 1);
- if ($version != WIKIDB_FORCE_CREATE and $version != $newversion) {
- $backend->unlock(array('version','page','recent','link','nonempty'));
- return false;
- }
- $data = $metadata;
-
- foreach ($data as $key => $val) {
- if (empty($val) || $key[0] == '_' || $key[0] == '%')
- unset($data[$key]);
- }
-
- assert(!empty($data['author']));
- if (empty($data['author_id']))
- @$data['author_id'] = $data['author'];
-
- if (empty($data['mtime']))
- $data['mtime'] = time();
- if ($latestversion and $version != WIKIDB_FORCE_CREATE) {
- // Ensure mtimes are monotonic.
- $pdata = $cache->get_versiondata($pagename, $latestversion);
- if ($data['mtime'] < $pdata['mtime']) {
- trigger_error(sprintf(_("%s: Date of new revision is %s"),
- $pagename,"'non-monotonic'"),
- E_USER_NOTICE);
- $data['orig_mtime'] = $data['mtime'];
- $data['mtime'] = $pdata['mtime'];
- }
-
- // FIXME: use (possibly user specified) 'mtime' time or
- // time()?
- $cache->update_versiondata($pagename, $latestversion,
- array('_supplanted' => $data['mtime']));
- }
- $data['%content'] = &$content;
- $cache->set_versiondata($pagename, $newversion, $data);
- //$cache->update_pagedata($pagename, array(':latestversion' => $newversion,
- //':deleted' => empty($content)));
-
- $backend->set_links($pagename, $links);
- $backend->unlock(array('version','page','recent','link','nonempty'));
- return new WikiDB_PageRevision($this->_wikidb, $pagename, $newversion,
- $data);
- }
- /** A higher-level interface to createRevision.
- *
- * This takes care of computing the links, and storing
- * a cached version of the transformed wiki-text.
- *
- * @param string $wikitext The page content.
- *
- * @param int $version Version number for new revision.
- * To ensure proper serialization of edits, $version must be
- * exactly one higher than the current latest version.
- * (You can defeat this check by setting $version to
- * {@link WIKIDB_FORCE_CREATE} --- not usually recommended.)
- *
- * @param hash $meta Meta-data for new revision.
- */
- function save($wikitext, $version, $meta) {
- $formatted = new TransformedText($this, $wikitext, $meta);
- $type = $formatted->getType();
- $meta['pagetype'] = $type->getName();
- $links = $formatted->getWikiPageLinks();
- $backend = &$this->_wikidb->_backend;
- $newrevision = $this->createRevision($version, $wikitext, $meta, $links);
- if ($newrevision and !WIKIDB_NOCACHE_MARKUP)
- $this->set('_cached_html', $formatted->pack());
- // FIXME: probably should have some global state information
- // in the backend to control when to optimize.
- //
- // We're doing this here rather than in createRevision because
- // postgresql can't optimize while locked.
- if ((DEBUG & _DEBUG_SQL)
- or (DATABASE_OPTIMISE_FREQUENCY > 0 and
- (time() % DATABASE_OPTIMISE_FREQUENCY == 0))) {
- if ($backend->optimize()) {
- if (DEBUG)
- trigger_error(_("Optimizing database"), E_USER_NOTICE);
- }
- }
- /* Generate notification emails? */
- if (isa($newrevision, 'WikiDB_PageRevision')) {
- // Save didn't fail because of concurrent updates.
- $notify = $this->_wikidb->get('notify');
- if (!empty($notify) and is_array($notify) and !isa($GLOBALS['request'],'MockRequest')) {
- list($emails, $userids) = $this->getPageChangeEmails($notify);
- if (!empty($emails)) {
- $this->sendPageChangeNotification($wikitext, $version, $meta, $emails, $userids);
- }
- }
- $newrevision->_transformedContent = $formatted;
- }
- return $newrevision;
- }
- function getPageChangeEmails($notify) {
- $emails = array(); $userids = array();
- foreach ($notify as $page => $users) {
- if (glob_match($page, $this->_pagename)) {
- foreach ($users as $userid => $user) {
- if (!$user) { // handle the case for ModeratePage: no prefs, just userid's.
- global $request;
- $u = $request->getUser();
- if ($u->UserName() == $userid) {
- $prefs = $u->getPreferences();
- } else {
- // not current user
- if (ENABLE_USER_NEW) {
- $u = WikiUser($userid);
- $u->getPreferences();
- $prefs = &$u->_prefs;
- } else {
- $u = new WikiUser($GLOBALS['request'], $userid);
- $prefs = $u->getPreferences();
- }
- }
- $emails[] = $prefs->get('email');
- $userids[] = $userid;
- } else {
- if (!empty($user['verified']) and !empty($user['email'])) {
- $emails[] = $user['email'];
- $userids[] = $userid;
- } elseif (!empty($user['email'])) {
- global $request;
- // do a dynamic emailVerified check update
- $u = $request->getUser();
- if ($u->UserName() == $userid) {
- if ($request->_prefs->get('emailVerified')) {
- $emails[] = $user['email'];
- $userids[] = $userid;
- $notify[$page][$userid]['verified'] = 1;
- $request->_dbi->set('notify', $notify);
- }
- } else {
- // not current user
- if (ENABLE_USER_NEW) {
- $u = WikiUser($userid);
- $u->getPreferences();
- $prefs = &$u->_prefs;
- } else {
- $u = new WikiUser($GLOBALS['request'], $userid);
- $prefs = $u->getPreferences();
- }
- if ($prefs->get('emailVerified')) {
- $emails[] = $user['email'];
- $userids[] = $userid;
- $notify[$page][$userid]['verified'] = 1;
- $request->_dbi->set('notify', $notify);
- }
- }
- // ignore verification
- /*
- if (DEBUG) {
- if (!in_array($user['email'],$emails))
- $emails[] = $user['email'];
- }
- */
- }
- }
- }
- }
- }
- $emails = array_unique($emails);
- $userids = array_unique($userids);
- return array($emails, $userids);
- }
- /**
- * Send udiff for a changed page to multiple users.
- * See rename and remove methods also
- */
- function sendPageChangeNotification(&$wikitext, $version, $meta, $emails, $userids) {
- global $request;
- if (@is_array($request->_deferredPageChangeNotification)) {
- // collapse multiple changes (loaddir) into one email
- $request->_deferredPageChangeNotification[]
- = array($this->_pagename, $emails, $userids);
- return;
- }
- $backend = &$this->_wikidb->_backend;
- //$backend = &$request->_dbi->_backend;
- $subject = _("Page change").' '.urlencode($this->_pagename);
- $previous = $backend->get_previous_version($this->_pagename, $version);
- if (!isset($meta['mtime'])) $meta['mtime'] = time();
- if ($previous) {
- $difflink = WikiURL($this->_pagename, array('action'=>'diff'), true);
- $cache = &$this->_wikidb->_cache;
- //$cache = &$request->_dbi->_cache;
- $this_content = explode("\n", $wikitext);
- $prevdata = $cache->get_versiondata($this->_pagename, $previous, true);
- if (empty($prevdata['%content']))
- $prevdata = $backend->get_versiondata($this->_pagename, $previous, true);
- $other_content = explode("\n", $prevdata['%content']);
-
- include_once("lib/difflib.php");
- $diff2 = new Diff($other_content, $this_content);
- //$context_lines = max(4, count($other_content) + 1,
- // count($this_content) + 1);
- $fmt = new UnifiedDiffFormatter(/*$context_lines*/);
- $content = $this->_pagename . " " . $previous . " " .
- Iso8601DateTime($prevdata['mtime']) . "\n";
- $content .= $this->_pagename . " " . $version . " " .
- Iso8601DateTime($meta['mtime']) . "\n";
- $content .= $fmt->format($diff2);
-
- } else {
- $difflink = WikiURL($this->_pagename,array(),true);
- $content = $this->_pagename . " " . $version . " " .
- Iso8601DateTime($meta['mtime']) . "\n";
- $content .= _("New page");
- }
- $from = $request->_user->getId();
- $editedby = sprintf(_("Edited by: %s"), $from);
- $emails = join(',',$emails);
- $headers = "From: $from <nobody>\r\n" .
- "Bcc: $emails\r\n" .
- "MIME-Version: 1.0\r\n" .
- "Content-Type: text/plain; charset=".CHARSET."; format=flowed\r\n" .
- "Content-Transfer-Encoding: 8bit";
- if (mail("<undisclosed-recipients>",
- "[".WIKI_NAME."] ".$subject,
- $subject."\n". $editedby."\n". $difflink."\n\n". $content,
- $headers))
- trigger_error(sprintf(_("PageChange Notification of %s sent to %s"),
- $this->_pagename, join(',',$userids)), E_USER_NOTICE);
- else
- trigger_error(sprintf(_("PageChange Notification Error: Couldn't send %s to %s"),
- $this->_pagename, join(',',$userids)), E_USER_WARNING);
- }
- /** support mass rename / remove (not yet tested)
- */
- function sendPageRenameNotification($to, &$meta, $emails, $userids) {
- global $request;
- if (@is_array($request->_deferredPageRenameNotification)) {
- $request->_deferredPageRenameNotification[] = array($this->_pagename,
- $to, $meta, $emails, $userids);
- } else {
- $oldname = $this->_pagename;
- $from = $request->_user->getId();
- $editedby = sprintf(_("Edited by: %s"), $from);
- $emails = join(',',$emails);
- $subject = sprintf(_("Page rename %s to %s"), urlencode($oldname), urlencode($to));
- $link = WikiURL($to, true);
- $headers = "From: $from <nobody>\r\n" .
- "Bcc: $emails\r\n" .
- "MIME-Version: 1.0\r\n" .
- "Content-Type: text/plain; charset=".CHARSET."; format=flowed\r\n" .
- "Content-Transfer-Encoding: 8bit";
- if (mail("<undisclosed-recipients>",
- "[".WIKI_NAME."] ".$subject,
- $subject."\n".$editedby."\n".$link."\n\n"."Renamed $from to $to",
- $headers))
- trigger_error(sprintf(_("PageChange Notification of %s sent to %s"),
- $oldname, join(',',$userids)), E_USER_NOTICE);
- else
- trigger_error(sprintf(_("PageChange Notification Error: Couldn't send %s to %s"),
- $oldname, join(',',$userids)), E_USER_WARNING);
- }
- }
- /**
- * Get the most recent revision of a page.
- *
- * @access public
- *
- * @return WikiDB_PageRevision The current WikiDB_PageRevision object.
- */
- function getCurrentRevision ($need_content = true) {
- $backend = &$this->_wikidb->_backend;
- $cache = &$this->_wikidb->_cache;
- $pagename = &$this->_pagename;
-
- // Prevent deadlock in case of memory exhausted errors
- // Pure selection doesn't really need locking here.
- // sf.net bug#927395
- // I know it would be better to lock, but with lots of pages this deadlock is more
- // severe than occasionally get not the latest revision.
- // In spirit to wikiwiki: read fast, edit slower.
- //$backend->lock();
- $version = $cache->get_latest_version($pagename);
- // getRevision gets the content also!
- $revision = $this->getRevision($version, $need_content);
- //$backend->unlock();
- assert($revision);
- return $revision;
- }
- /**
- * Get a specific revision of a WikiDB_Page.
- *
- * @access public
- *
- * @param integer $version Which revision to get.
- *
- * @return WikiDB_PageRevision The requested WikiDB_PageRevision object, or
- * false if the requested revision does not exist in the {@link WikiDB}.
- * Note that version zero of any page always exists.
- */
- function getRevision ($version, $need_content=true) {
- $cache = &$this->_wikidb->_cache;
- $pagename = &$this->_pagename;
-
- if (! $version or $version == -1) // 0 or false
- return new WikiDB_PageRevision($this->_wikidb, $pagename, 0);
- assert($version > 0);
- $vdata = $cache->get_versiondata($pagename, $version, $need_content);
- if (!$vdata) {
- return new WikiDB_PageRevision($this->_wikidb, $pagename, 0);
- }
- return new WikiDB_PageRevision($this->_wikidb, $pagename, $version,
- $vdata);
- }
- /**
- * Get previous page revision.
- *
- * This method find the most recent revision before a specified
- * version.
- *
- * @access public
- *
- * @param integer $version Find most recent revision before this version.
- * You can also use a WikiDB_PageRevision object to specify the $version.
- *
- * @return WikiDB_PageRevision The requested WikiDB_PageRevision object, or false if the
- * requested revision does not exist in the {@link WikiDB}. Note that
- * unless $version is greater than zero, a revision (perhaps version zero,
- * the default revision) will always be found.
- */
- function getRevisionBefore ($version=false, $need_content=true) {
- $backend = &$this->_wikidb->_backend;
- $pagename = &$this->_pagename;
- if ($version === false)
- $version = $this->_wikidb->_cache->get_latest_version($pagename);
- else
- $version = $this->_coerce_to_version($version);
- if ($version == 0)
- return false;
- //$backend->lock();
- $previous = $backend->get_previous_version($pagename, $version);
- $revision = $this->getRevision($previous, $need_content);
- //$backend->unlock();
- assert($revision);
- return $revision;
- }
- /**
- * Get all revisions of the WikiDB_Page.
- *
- * This does not include the version zero (default) revision in the
- * returned revision set.
- *
- * @return WikiDB_PageRevisionIterator A
- * WikiDB_PageRevisionIterator containing all revisions of this
- * WikiDB_Page in reverse order by version number.
- */
- function getAllRevisions() {
- $backend = &$this->_wikidb->_backend;
- $revs = $backend->get_all_revisions($this->_pagename);
- return new WikiDB_PageRevisionIterator($this->_wikidb, $revs);
- }
-
- /**
- * Find pages which link to or are linked from a page.
- *
- * @access public
- *
- * @param boolean $reversed Which links to find: true for backlinks (default).
- *
- * @return WikiDB_PageIterator A WikiDB_PageIterator containing
- * all matching pages.
- */
- function getLinks ($reversed = true, $include_empty=false, $sortby=false,
- $limit=false, $exclude=false) {
- $backend = &$this->_wikidb->_backend;
- $result = $backend->get_links($this->_pagename, $reversed,
- $include_empty, $sortby, $limit, $exclude);
- return new WikiDB_PageIterator($this->_wikidb, $result,
- array('include_empty' => $include_empty,
- 'sortby' => $sortby,
- 'limit' => $limit,
- 'exclude' => $exclude));
- }
- /**
- * All Links from other pages to this page.
- */
- function getBackLinks($include_empty=false, $sortby=false, $limit=false, $exclude=false) {
- return $this->getLinks(true, $include_empty, $sortby, $limit, $exclude);
- }
- /**
- * Forward Links: All Links from this page to other pages.
- */
- function getPageLinks($include_empty=false, $sortby=false, $limit=false, $exclude=false) {
- return $this->getLinks(false, $include_empty, $sortby, $limit, $exclude);
- }
-
- /**
- * possibly faster link existance check. not yet accelerated.
- */
- function existLink($link, $reversed=false) {
- $backend = &$this->_wikidb->_backend;
- if (method_exists($backend,'exists_link'))
- return $backend->exists_link($this->_pagename, $link, $reversed);
- //$cache = &$this->_wikidb->_cache;
- // TODO: check cache if it is possible
- $iter = $this->getLinks($reversed, false);
- while ($page = $iter->next()) {
- if ($page->getName() == $link)
- return $page;
- }
- $iter->free();
- return false;
- }
-
- /**
- * Access WikiDB_Page non version-specific meta-data.
- *
- * @access public
- *
- * @param string $key Which meta data to get.
- * Some reserved meta-data keys are:
- * <dl>
- * <dt>'date' <dd> Created as unixtime
- * <dt>'locked'<dd> Is page locked? 'yes' or 'no'
- * <dt>'hits' <dd> Page hit counter.
- * <dt>'_cached_html' <dd> Transformed CachedMarkup object, serialized + optionally gzipped.
- * In SQL stored now in an extra column.
- * Optional data:
- * <dt>'pref' <dd> Users preferences, stored only in homepages.
- * <dt>'owner' <dd> Default: first author_id. We might add a group with a dot here:
- * E.g. "owner.users"
- * <dt>'perm' <dd> Permission flag to authorize read/write/execution of
- * page-headers and content.
- + <dt>'moderation'<dd> ModeratedPage data
- * <dt>'score' <dd> Page score (not yet implement, do we need?)
- * </dl>
- *
- * @return scalar The requested value, or false if the requested data
- * is not set.
- */
- function get($key) {
- $cache = &$this->_wikidb->_cache;
- $backend = &$this->_wikidb->_backend;
- if (!$key || $key[0] == '%')
- return false;
- // several new SQL backends optimize this.
- if (!WIKIDB_NOCACHE_MARKUP
- and $key == '_cached_html'
- and method_exists($backend, 'get_cached_html'))
- {
- return $backend->get_cached_html($this->_pagename);
- }
- $data = $cache->get_pagedata($this->_pagename);
- …
Large files files are truncated, but you can click here to view the full file