Source for file filter.bitlinks.php
Documentation is available at filter.bitlinks.php
* definitions ( guid character limit is 16 chars )
define( 'PLUGIN_GUID_FILTERWIKILINKS', 'filterbitlinks' );
'description' => 'If you use links of the format ((Wiki Page)) this filter will convert that to a link to a wiki page entitled <em>Wiki Page</em>',
'presplit_function' => 'bitlinks_prefilter',
'preparse_function' => 'bitlinks_prefilter',
'postsplit_function' => 'bitlinks_postfilter',
'postparse_function' => 'bitlinks_postfilter',
'poststore_function' => 'bitlinks_storefilter',
'expunge_function' => 'bitlinks_expungefilter',
define( 'WIKI_WORDS_REGEX', '[A-z0-9]{2}[\w\d_\-]+[A-Z_][\w\d_\-]+[A-z0-9]+' );
* @param array $pFilterHash
if( empty( $sBitLinks )) {
// extract ((Page|Description)) type links that they don't enter the parser.
// these can cause problems in various places such as tiki tables due to the |
preg_match_all( "/\({2}({$sBitLinks->mWikiWordRegex})\|(.+?)\){2}/ ", $pData, $protected );
if( !empty( $protected )) {
foreach( $protected[0] as $i => $prot ) {
$pFilterHash['bitlinks']['replacements'][$key] = $protected[0][$i];
* convert wiki links to html links e.g.: ((Wiki Page)) --> <a href="/wiki/Wiki+Page">Wiki Page</a>
* @param array $pFilterHash
* @return updated data string
if( empty( $sBitLinks )) {
// first we need to put the ((Page|Description)) type links back in that we can parse them below
if( !empty( $pFilterHash['bitlinks']['replacements'] )) {
foreach( $pFilterHash['bitlinks']['replacements'] as $key => $replace ) {
$pData = $sBitLinks->parseLinks( $pData, $pFilterHash, $pObject );
* store links to existing wiki pages in the database
* @param array $pFilterHash
if( empty( $sBitLinks )) {
$sBitLinks->storeLinks( $pData, $pFilterHash );
// if the title of this object was changed, we need to update links to it
$gBitSystem->isPackageActive( 'wiki' )
&& $pObject->mContentTypeGuid == BITPAGE_CONTENT_TYPE_GUID
&& !empty( $pFilterHash['title'] )
&& !empty( $pObject->mInfo['title'] )
&& $pFilterHash['title'] != $pObject->mInfo['title']
$sBitLinks->renameLinks( $pObject->mContentId, $pObject->mInfo['title'], $pFilterHash['title'] );
* expunge bitlinks in the database
* @param array $pFilterHash
* @return TRUE on success, FALSE on failure - mErrors will contain reason for failure
if( empty( $sBitLinks )) {
$sBitLinks->expungeLinks( $pObject->mContentId );
* @var array of links pointing to this page
* @return TRUE on success, FALSE on failure - mErrors will contain reason for failure
if( $gBitSystem->getConfig( 'wiki_page_regex', 'strict' ) == 'strict' ) {
$this->mWikiWordRegex = '([A-Za-z0-9_])([\'\.: A-Za-z0-9_\-])*([\.:A-Za-z0-9_])';
} elseif( $gBitSystem->getConfig( 'wiki_page_regex', 'strict' ) == 'full' ) {
$this->mWikiWordRegex = '([A-Za-z0-9_]|[\x80-\xFF])([\'\.: A-Za-z0-9_\-]|[\x80-\xFF])*([\.:A-Za-z0-9_]|[\x80-\xFF])';
// This is just evil. The middle section means "anything, as long
// as it's not a | and isn't followed by ))". -rlpowell
$this->mWikiWordRegex = '([^|\(\)])([^|](?!\)\)))*?([^|\(\)])';
// append anchor to regex
$this->mWikiWordRegex .= "(#\w+)?";
* Get all pages linking to a given content id
* @param array $pContentId
* @return TRUE on success, FALSE on failure - mErrors will contain reason for failure
if( $gBitSystem->isPackageActive( 'wiki' ) && @BitBase::verifyId( $pContentId )) {
$query = "SELECT `page_id`, lc.`content_id`, lc.`last_modified`, lc.`title`, lcds.`data` AS `summary`
INNER JOIN `". BIT_DB_PREFIX. "liberty_content` lc ON( lcl.`to_content_id`=lc.`content_id` )
INNER JOIN `". BIT_DB_PREFIX. "wiki_pages` wp ON( wp.`content_id`=lc.`content_id` )
LEFT OUTER JOIN `". BIT_DB_PREFIX. "liberty_content_data` lcds ON (lc.`content_id` = lcds.`content_id` AND lcds.`data_type`='summary')
WHERE lcl.`from_content_id`=? ORDER BY lc.`title`";
if( $result = $this->mDb->query( $query, array( $pContentId ))) {
while( $row = $result->fetchRow() ) {
$row['description'] = tra( 'Multiple pages with this name' );
* see if page has already been created and stored
* @param array $pTitle title of the page
* @param array $pObject current object
* @param array $pContentId content_id of the current page - sometimes we don't have the object but a content_id to work with
* @return TRUE on success, FALSE on failure - mErrors will contain reason for failure
function pageExists( $pTitle, $pObject, $pContentId ) {
// only update this hash once - this is initiated as NULL and will be set to at least array after first call
if( $this->mLinks === NULL ) {
if( !empty( $pTitle ) && !empty( $this->mLinks )) {
// final attempt to get page details
if( empty( $ret ) && !empty( $pObject ) && $gBitSystem->isPackageActive( 'wiki' ) ) {
require_once( WIKI_PKG_PATH. 'BitPage.php' );
if( count( $ret ) > 1 ) {
$ret[0]['description'] = tra( 'Multiple pages with this name' );
* @return array of wiki words in the data string
// we need to remove text that might contain unexpected wiki words
"!<a\b[^>]*>.*?</a>!si", // links
"!<[^>]*>!", // any html tags
$words1[1] = $words2[1] = $words3[1] = array();
preg_match_all( "/\({2}($this->mWikiWordRegex)\){2}/", $tmpData, $words2 );
preg_match_all( "/\({2}($this->mWikiWordRegex)\|(.+?)\){2}/", $tmpData, $words3 );
if( $gBitSystem->isFeatureActive( 'wiki_words' )) {
* convert wiki links to html links
* @param array $pParamHash
* @return TRUE on success, FALSE on failure - mErrors will contain reason for failure
function parseLinks( $pData, $pParamHash, $pObject ) {
// if wiki isn't active, there isn't much we can do here
if( !$gBitSystem->isPackageActive( 'wiki' )) {
// fetch BitPage in case it hasn't been loaded yet
require_once( WIKI_PKG_PATH. 'BitPage.php' );
// We need to remove ))WikiWords(( before links get made.
// users just need to be strict about not inserting spaces between
// this array is used to fill the text with temporary placeholders that get replaced back in further down
if( !empty( $protected )) {
foreach( $protected[0] as $i => $prot ) {
$replacements[$key] = $protected[1][$i];
// Process ((Wiki Page|Wiki Page Description)) type links first. Here
// we don't handle plurals and the like since the user should know what
// he's linking to when using these links
preg_match_all( "/\({2}({$this->mWikiWordRegex})\|(.+?)\){2}/ ", $pData, $pages );
for( $i = 0; $i < count( $pages[1] ); $i++ ) {
$page = str_replace( $pages[5][$i], "", $pages[1][$i] );
$exists = $this->pageExists( $page, $pObject, $pParamHash['content_id'] );
if( !empty( $pages[5][$i] )) {
$repl = preg_replace( '!href="([^"]*)"!', "href=\"$1{ $pages[5][$i]}\" ", BitPage::getPageLink( $page, $exists ));
$repl = BitPage::getPageLink( $page, $exists );
if( strlen( trim( $pages[6][$i] )) > 0 ) {
$repl = str_replace( $page."</a>", "{ $pages[6][$i]}</a> ", $repl );
$replacements[$key] = $repl;
$pData = str_replace( $pages[0][$i], $key, $pData );
// Process the simpler ((Wiki Page)) type links without the description
preg_match_all( "/\({2}({ $this->mWikiWordRegex})\){2}/ ", $pData, $pages );
foreach( array_unique( $pages[1] ) as $i => $page ) {
$page = str_replace( $pages[5][$i], "", $pages[1][$i] );
$exists = $this->pageExists( $page, $pObject, $pParamHash['content_id'] );
if( !empty( $pages[5][$i] )) {
$repl = preg_replace( '!href="([^"]*)"!', "href=\"$1{ $pages[5][$i]}\" ", BitPage::getPageLink( $page, $exists ));
$repl = BitPage::getPageLink( $page, $exists );
$replacements[$key] = $repl;
$pData = str_replace( "(({ $pages[1][$i]})) ", $key, $pData );
// Finally we deal with WikiWord links
if( $gBitSystem->isFeatureActive( 'wiki_words' )) {
foreach( $pages as $page) {
if( $exists = $this->pageExists( $page, $pObject, $pParamHash['content_id'] )) {
$repl = BitPage::getPageLink( $page, $exists );
} elseif( $gBitSystem->isFeatureActive( 'wiki_plurals' ) && $this->getLocale() == 'en_US' ) {
// Link plural topic names to singular topic names if the plural
// doesn't exist, and the language is english
// Plurals like policy / policies
$plural_tmp = preg_replace( "/ies$/", "y", $plural_tmp );
// Plurals like address / addresses
$plural_tmp = preg_replace( "/sses$/", "ss", $plural_tmp );
// Plurals like box / boxes
$plural_tmp = preg_replace( "/([Xx])es$/", "$1", $plural_tmp );
// Others, excluding ending ss like address(es)
$plural_tmp = preg_replace( "/([A-Za-rt-z])s$/", "$1", $plural_tmp );
// prevent redundant pageExists calls if plurals are on, and plural is same as original word
if( $page != $plural_tmp ) {
$exists = $this->pageExists( $plural_tmp, $pObject, $pParamHash['content_id'] );
$repl = BitPage::getPageLink( $plural_tmp, $exists );
$repl = BitPage::getPageLink( $page, $exists );
//$slashed = preg_replace( "/([\/\[\]\(\)])/", "\\\\$1", $page_parse );
//$data = preg_replace( "#([\s\,\;])\b$slashed\b([\s\,\;\.])#", "$1 ".$repl."$2", $data);
// i never understood why the simple stuff never worked but it
// seems to work now - xing - Sunday Jul 22, 2007 17:37:17 CEST
$pData = preg_replace( "/\b".preg_quote( $page, "/" )."\b/", $repl, $pData );
// replace protection keys with original words
foreach( $replacements as $key => $replace ) {
$pData = str_replace( $key, $replace, $pData );
'he' => 'he_IL', # hebrew
'it' => 'it_IT', # italian
'pl' => 'pl_PL', # polish
'sw' => 'sw_SW', # swahili
static $sBitLanguage = FALSE;
global $gBitUser, $gBitSystem;
if( empty( $sBitLanguage )) {
if( $gBitUser->isValid() ) {
$sBitLanguage = $gBitUser->getPreference( 'bitLanguage', 'en' );
$sBitLanguage = $this->getPreference( 'bitLanguage', 'en' );
* @param array $pFilterHash
* @return store wiki links in database
function storeLinks( $pData, $pFilterHash ) {
// if we don't have a content_id or wiki isn't active, get out of here.
if( empty( $pFilterHash['content_id'] ) || !$gBitSystem->isPackageActive( 'wiki' )) {
$from_content_id = $pFilterHash['content_id'];
$from_title = isset( $pFilterHash['title'] ) ? $pFilterHash['title'] : '';
// we need to remove the cache of any pages pointing to this one
$query = "SELECT `from_content_id` FROM `".BIT_DB_PREFIX."liberty_content_links` WHERE ( `to_content_id` = ? OR `to_content_id` IS NULL ) AND `to_title` = ?";
$clearCache = $gBitSystem->mDb->getCol( $query, array( $from_content_id, $from_title ));
if( is_array( $clearCache )) {
foreach( $clearCache as $content_id ) {
LibertyContent::expungeCacheFile( $content_id );
// if this is a new page, fix up any links that may already point to it
$query = "UPDATE `".BIT_DB_PREFIX."liberty_content_links` SET `to_content_id` = ? WHERE ( `to_content_id` = ? OR `to_content_id` IS NULL ) AND `to_title` = ?";
$gBitSystem->mDb->query( $query, array( $from_content_id, 0, $from_title ));
// get all the current links from this page
$query = "SELECT LOWER( `to_title` ), `to_content_id` FROM `". BIT_DB_PREFIX. "liberty_content_links` WHERE `from_content_id` = ?";
$oldLinks = $gBitSystem->mDb->getAssoc( $query, array( $from_content_id ));
// get list of all wiki links on this page
// wiki links with anchors are pulled out as well. we'll make copies of the wiki pagenames without the anchor to process these correctly as well
foreach( $extractedWikiWords as $link ) {
if( strstr( $link, '#' ) !== FALSE ) {
$extractedWikiWords[] = preg_replace( "!#.*!", "", $link );
// create list of unique new wiki links on this page
$uniqueNewWikiLinks = array();
foreach( $extractedWikiWords as $to_title ) {
if( !empty( $to_title ) && !isset( $oldLinks[strtolower($to_title)] )) {
$uniqueNewWikiLinks[] = $to_title;
array_unique( $uniqueNewWikiLinks );
// get list of all new links that point to existing content
if( !empty( $uniqueNewWikiLinks )) {
$inSql = '?'.str_repeat( ',?', count( $uniqueNewWikiLinks ) - 1 );
// All arguments to MySQL in() function have to be passed as strings or it doesn't work correctly
foreach( $uniqueNewWikiLinks as $var ) {
$bindVars[] = ( string )$var;
$bindVars[] = BITPAGE_CONTENT_TYPE_GUID;
$query = "SELECT LOWER( `title` ), `title` FROM `".BIT_DB_PREFIX."liberty_content` WHERE `title` IN( $inSql ) AND `content_type_guid` = ?";
$newLinksPointingToExistingContent = $gBitSystem->mDb->getAssoc( $query, $bindVars );
// insert all new links pointing to existing content
if( !empty( $newLinksPointingToExistingContent )) {
// All arguments to MySQL in() function have to be passed as strings or it doesn't work correctly
foreach( $newLinksPointingToExistingContent as $var ) {
$bindVars[] = ( string )$var;
$bindVars[] = BITPAGE_CONTENT_TYPE_GUID;
$inSql = '?'.str_repeat( ',?', count( $newLinksPointingToExistingContent ) - 1 );
INSERT INTO `".BIT_DB_PREFIX."liberty_content_links` ( `from_content_id`, `to_content_id`, `to_title` )
SELECT ?, `content_id`, `title` FROM `".BIT_DB_PREFIX."liberty_content`
WHERE `title` IN( $inSql ) AND `content_type_guid` = ?";
array_unshift( $bindVars, $from_content_id );
$result = $gBitSystem->mDb->query( $query, $bindVars );
// insert all new links pointing to non-existing content and that are not in the db yet
foreach( $uniqueNewWikiLinks as $to_title ) {
if( !isset( $newLinksPointingToExistingContent[strtolower( $to_title )] ) && !in_array( strtolower( $to_title ), array_keys( $oldLinks ))) {
$query = "INSERT INTO `".BIT_DB_PREFIX."liberty_content_links` ( `from_content_id`, `to_title` ) VALUES( ?, ? )";
$result = $gBitSystem->mDb->query( $query, array( $from_content_id, $to_title ));
// now delete any links no longer on page
foreach( $extractedWikiWords as $to_title ) {
$obsoleteLinks[strtolower( $to_title )] = 1;
foreach( array_keys( $oldLinks ) as $to_title ) {
if( !isset( $obsoleteLinks[$to_title] )) {
$query = "DELETE FROM `".BIT_DB_PREFIX."liberty_content_links` WHERE `from_content_id`=? AND LOWER( `to_title` ) = ?";
$result = $gBitSystem->mDb->query( $query, array( $from_content_id, $to_title ));
* @param array $pContentId
function renameLinks( $pContentId, $pOldName, $pNewName ) {
SELECT `from_content_id`, `data`
FROM `".BIT_DB_PREFIX."liberty_content_links` lcl
INNER JOIN `".BIT_DB_PREFIX."liberty_content` lc ON( lcl.`from_content_id`=lc.`content_id` )
WHERE `to_content_id` = ?";
if( $result = $this->mDb->query( $query, array( $pContentId ) ) ) {
while( $row = $result->fetchRow() ) {
// check if there are occasions of the old name with alternate display link name
// --- ((Wiki Page|Description))
// \b$pOldName\b # make sure the old name is on it's own
// \| # the seperating deliminator
// ([^\)]*) # get as many characters as possible up to the next ) - put this in $1
// \){2} # closing brackets ))
$pattern[] = "!\({2}\b$pOldName\b\|([^\)]*)\){2}!";
// - replace with new name leaving description in tact
$replace[] = "(($pNewName|$1))";
// --- ((Wiki Page)) or WikiPage
// (\({2})? # check for (( - optional - put this in $1
// \b$pOldName\b # make sure the old name is on it's own
// (\){2})? # closing brackets )) - optional - put this in $2
$pattern[] = "!(\({2})?\b$pOldName\b(\){2})?!";
// - the replacement depends on the new name
if( preg_match( "! !", $pNewName )) {
// since we have a space in the final name, we need to have ((
// and )) to make the link work
$replace[] = "(($pNewName))";
// no spaces in the new name either, so we only insert the ((
// and )) if the author used them to start off with
$replace[] = "$1$pNewName$2";
$data = preg_replace( $pattern, $replace, $row['data'] );
if( md5( $data ) != md5( $row['data'] ) ) {
$query = "UPDATE `".BIT_DB_PREFIX."liberty_content` SET `data`=? WHERE `content_id`=?";
$this->mDb->query( $query, array( $data, $row['from_content_id'] ) );
// remove any chached files pointing here
LibertyContent::expungeCacheFile( $row['from_content_id'] );
# Fix up titles in the link table
$query = "UPDATE `".BIT_DB_PREFIX."liberty_content_links` SET `to_title`=? WHERE `to_content_id`=?";
$this->mDb->query( $query, array( $pNewName, $pContentId ) );
* expunge bitlinks in the database
* @param numeric $pContentId
function expungeLinks( $pContentId ) {
if( !empty( $pContentId )) {
// remove any cached file pointing to this page
$links = $this->mDb->getCol( "SELECT `from_content_id` FROM `". BIT_DB_PREFIX. "liberty_content_links` WHERE to_content_id=?", array( $pContentId ));
foreach( $links as $content_id ) {
LibertyContent::expungeCacheFile( $content_id );
$this->mDb->query( "DELETE FROM `". BIT_DB_PREFIX. "liberty_content_links` WHERE from_content_id=? OR to_content_id=?", array( $pContentId, $pContentId ));
|