'tikiwiki_save_data', 'load_function' => 'tikiwiki_parse_data', 'verify_function' => 'tikiwiki_verify_data', 'rename_function' => 'tikiwiki_rename', 'expunge_function' => 'tikiwiki_expunge', 'description' => 'TikiWiki Syntax Format Parser', 'edit_label' => 'Tiki Wiki Syntax', 'edit_field' => PLUGIN_GUID_TIKIWIKI, 'help_page' => 'TikiWikiSyntax', 'plugin_type' => FORMAT_PLUGIN ); $gLibertySystem->registerPlugin( PLUGIN_GUID_TIKIWIKI, $pluginParams ); /** * tikiwiki_save_data */ function tikiwiki_save_data( &$pParamHash ) { static $parser; if( empty( $parser ) ) { $parser = new TikiWikiParser(); } if( $pParamHash['edit'] ) { $parser->storeLinks( $pParamHash ); } LibertyContent::expungeCacheFile( $pParamHash['content_id'] ); } function tikiwiki_verify_data( &$pParamHash ) { $errorMsg = NULL; $pParamHash['content_store']['data'] = $pParamHash['edit']; return( $errorMsg ); } function tikiwiki_expunge( $pContentId ) { $parser = new TikiWikiParser(); $parser->expungeLinks( $pContentId ); LibertyContent::expungeCacheFile( $pContentId ); } function tikiwiki_rename( $pContentId, $pOldName, $pNewName, &$pCommonObject ) { $query = " 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 = $pCommonObject->mDb->query( $query, array( $pContentId ) ) ) { while( $row = $result->fetchRow() ) { // check if there are occasions of the old name with alternate display link name // --- ((WikiLink|Description)) // \({2} # check for (( // \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))"; // --- ((WikiLink)) or WikiLink // (\({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))"; } else { // 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`=?"; $pCommonObject->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`=?"; $pCommonObject->mDb->query( $query, array( $pNewName, $pContentId ) ); } function tikiwiki_parse_data( &$pParseHash, &$pCommonObject ) { global $gBitSystem; // cache data if we are using liberty cache if( $gBitSystem->isFeatureActive( 'liberty_cache' ) && !empty( $pParseHash['content_id'] ) && empty( $pParseHash['no_cache'] ) ) { if( $cacheFile = LibertyContent::getCacheFile( $pParseHash['content_id'], $pParseHash['cache_extension'] ) ) { // write / refresh cache if we are exceeding time limit of cache if( !is_file( $cacheFile ) || ( $gBitSystem->getConfig( 'liberty_cache' ) < ( time() - filemtime( $cacheFile ) ) ) ) { static $parser; if( empty( $parser ) ) { $parser = new TikiWikiParser(); } $ret = $parser->parse_data( $pParseHash, $pCommonObject ); // write parsed contents to cache file $h = fopen( $cacheFile, 'w' ); fwrite( $h, $ret ); fclose( $h ); } else { // get contents from cache file $h = fopen( $cacheFile, 'r' ); $ret = fread( $h, filesize( $cacheFile ) ); fclose( $h ); $pCommonObject->mInfo['is_cached'] = TRUE; } } } else { static $parser; if( empty( $parser ) ) { $parser = new TikiWikiParser(); } $ret = $parser->parse_data( $pParseHash, $pCommonObject ); } return $ret; } /** * TikiWikiParser * * @package kernel */ class TikiWikiParser extends BitBase { var $mWikiWordRegex; var $mUseWikiWords; var $mPageLookup; var $pre_handlers = array(); var $pos_handlers = array(); function TikiWikiParser () { BitBase::BitBase(); global $gBitSystem; $this->mUseWikiWords = $gBitSystem->isFeatureActive( 'wiki_words' ); // Setup the WikiWord regex $wiki_page_regex = $gBitSystem->getConfig( 'wiki_page_regex', 'strict' ); // Please DO NOT modify any of the brackets in the regex(s). // It may seem redundent but, really, they are ALL REQUIRED. if ($wiki_page_regex == 'strict') { $this->mWikiWordRegex = '([A-Za-z0-9_])([\.: A-Za-z0-9_\-])*([A-Za-z0-9_])'; } elseif ($wiki_page_regex == 'full') { $this->mWikiWordRegex = '([A-Za-z0-9_]|[\x80-\xFF])([\.: A-Za-z0-9_\-]|[\x80-\xFF])*([A-Za-z0-9_]|[\x80-\xFF])'; } else { // This is just evil. The middle section means "anything, as long // as it's not a | and isn't followed by ))". -rlpowell $this->mWikiWordRegex = '([^|\(\)])([^|](?!\)\)))*?([^|\(\)])'; } } function add_pre_handler($name) { if (!in_array($name, $this->pre_handlers)) { $this->pre_handlers[] = $name; } } function add_pos_handler($name) { if (!in_array($name, $this->pos_handlers)) { $this->pos_handlers[] = $name; } } function extractWikiWords( &$data ) { if( $this->mUseWikiWords ) { preg_match_all("/\(\(($this->mWikiWordRegex)\)\)/", $data, $words2); preg_match_all("/\(\(($this->mWikiWordRegex)\|(.+?)\)\)/", $data, $words3); preg_match_all( '/\b('.WIKI_WORDS_REGEX.')\b/', $data, $words ); $words = array_unique(array_merge($words[1], $words2[1], $words3[1])); } else { preg_match_all("/\(\(($this->mWikiWordRegex)\)\)/", $data, $words); preg_match_all("/\(\(($this->mWikiWordRegex)\|(.+?)\)\)/", $data, $words2); $words = array_unique(array_merge($words[1], $words2[1])); } return $words; } function storeLinks( &$pParamHash ) { global $gBitSystem; if( empty( $pParamHash['content_id'] ) ) { return; } $from_content_id = $pParamHash['content_id']; $from_title = $pParamHash['title']; // we need to remove the cache of any pages pointing to this one $clearCache = $this->mDb->getCol( "SELECT `from_content_id` FROM `".BIT_DB_PREFIX."liberty_content_links` WHERE (`to_content_id`=? or `to_content_id` is NULL ) and `to_title` = ?", array( 0, $from_title ) ); 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` = ?"; $this->mDb->query( $query, array( $from_content_id, 0, $from_title ) ); #get all the current links from this page $old_links_in_db = array(); $query = "SELECT * FROM `".BIT_DB_PREFIX."liberty_content_links` WHERE `from_content_id`=?"; if( $result = $this->mDb->query( $query, array( $from_content_id ) ) ) { while( $row = $result->fetchRow() ) { $old_links_in_db[$row['to_title']] = $row['to_content_id']; } } #get list of all wiki links on this page $wiki_links_in_content = $this->extractWikiWords( $pParamHash['edit'] ); if( !is_array( $wiki_links_in_content ) ) { $wiki_links_in_content = array(); } #create list of unique new wiki links on this page $unique_new_wiki_links = array(); foreach( $wiki_links_in_content as $to_title ) { if( empty( $to_title ) ) { continue; } if( isset( $old_links_in_db[$to_title] ) ) { # link already in DB - skip rest of processing continue; } $unique_new_wiki_links[$to_title] = $to_title; } #get list of all new links that point to existing content $new_link_pointing_to_existing_content = array(); $title_list_count = count($unique_new_wiki_links); if( $title_list_count > 0 ) { $title_list = '?' . str_repeat(',?',$title_list_count - 1); $query = "SELECT * FROM `".BIT_DB_PREFIX."liberty_content` WHERE `title` IN($title_list)"; if( $result = $this->mDb->query($query, array_keys($unique_new_wiki_links) ) ) { while( $row = $result->fetchRow() ) { $new_link_pointing_to_existing_content[$row['title']] = $row['content_id']; } } if( count($new_link_pointing_to_existing_content) > 0 ) { #insert all new links pointing to existing content $query_var = array_keys($new_link_pointing_to_existing_content); $query_var_list = '?' . str_repeat(',?', count($new_link_pointing_to_existing_content) - 1); $query = "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 ( $query_var_list )"; array_unshift($query_var,$from_content_id); $result = $this->mDb->query($query, $query_var); } } #insert all new links pointing to non-existing content foreach ($unique_new_wiki_links as $to_title) { if( isset($new_link_pointing_to_existing_content[$to_title]) ) { continue; } $query = "insert into `".BIT_DB_PREFIX."liberty_content_links` (`from_content_id`,`to_title`) values(?, ?)"; $result = $this->mDb->query($query, array( $from_content_id, $to_title ) ); } # now delete any links no longer on page foreach( $wiki_links_in_content as $to_title) { $wiki_links_in_content_table[$to_title] = 1; } foreach( array_keys($old_links_in_db) as $to_title ) { if( !isset($wiki_links_in_content_table[$to_title]) ) { $query = "DELETE FROM `".BIT_DB_PREFIX."liberty_content_links` WHERE `from_content_id`=? and `to_title` = ?"; $result = $this->mDb->query( $query, array( $from_content_id, $to_title ) ); } } } 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 ) ); } } /* old database intensive pageExists check // Use liberty_content_links to get all the existing links in a single query function pageExists( $pTitle, $pContentId, $pCommonObject ) { $pTitle = strtolower( $pTitle ); if( !empty( $pContentId ) ) { if( empty( $this->mPageLookup ) ) { $query = "SELECT LOWER( lc.`title` ) AS `hash_key`, `page_id`, lc.`content_id`, `description`, lc.`last_modified`, lc.`title` FROM `".BIT_DB_PREFIX."liberty_content_links` lcl 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` ) WHERE lcl.`from_content_id`=? ORDER BY lc.`title`"; if( $result = $this->mDb->query( $query, array( $pContentId ) ) ) { $lastTitle = ''; while( $row = $result->fetchRow() ) { if( $row['title'] == $lastTitle ) { // TODO - need to check ensure that liberty_content_links duplicate are properly inserted - spiderr } $this->mPageLookup[$row['hash_key']][] = $row; $lastTitle = $row['title']; } } } } if( !isset( $this->mPageLookup[$pTitle] ) ) { $this->mPageLookup[$pTitle] = $pCommonObject->pageExists( $pTitle ); if( !empty( $this->mPageLookup[$pTitle] ) && ( count( $this->mPageLookup[$pTitle] ) == 1 ) ) { // $this->mDb->query( "INSERT INTO `".BIT_DB_PREFIX."liberty_content_links` ( `from_content_id`, `to_content_id` ) VALUES ( ?, ? )" , array( $pContentId, $this->mPageLookup[$pTitle][0]['content_id'] ) ); } } return( !empty( $this->mPageLookup[$pTitle] ) ? $this->mPageLookup[$pTitle] : NULL ); } */ function getAllPages( $pContentId ) { global $gBitSystem; $ret = array(); if( $gBitSystem->isPackageActive( 'wiki' ) && @BitBase::verifyId( $pContentId ) ) { $query = "SELECT `page_id`, lc.`content_id`, `description`, lc.`last_modified`, lc.`title` FROM `".BIT_DB_PREFIX."liberty_content_links` lcl 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` ) WHERE lcl.`from_content_id`=? ORDER BY lc.`title`"; if( $result = $this->mDb->query( $query, array( $pContentId ) ) ) { $lastTitle = ''; while( $row = $result->fetchRow() ) { if( array_key_exists( strtolower( $row['title'] ), $ret ) ) { $row['description'] = tra( 'Multiple pages with this name' ); } $ret[strtolower( $row['title'] )] = $row; } } } return $ret; } function pageExists( $pTitle, $pPageList, $pCommonObject, $pContentId ) { $ret = FALSE; if( !empty( $pTitle ) && !empty( $pPageList ) ) { if( array_key_exists( strtolower( $pTitle ), $pPageList ) ) { $ret = $pPageList[strtolower( $pTitle )]; } } // final attempt to get page details if( empty( $ret ) ) { if( $ret = $pCommonObject->pageExists( $pTitle, FALSE, $pContentId ) ) { if( count( $ret ) > 1 ) { $ret[0]['description'] = tra( 'Multiple pages with this name' ); } $ret = $ret[0]; } } return $ret; } function parse_data_raw($data) { $data = $this->parseData($data); $data = str_replace( WIKI_PKG_URL."index", WIKI_PKG_URL."index_raw", $data ); return $data; } // This recursive function handles pre- and no-parse sections and plugins function parse_first(&$data, &$preparsed, &$noparsed, &$pCommonObject) { global $gLibertySystem; $this->parse_pp_np($data, $preparsed, $noparsed); // Handle pre- and no-parse sections parse_data_plugins( $data, $preparsed, $noparsed, $this, $pCommonObject ); } // AWC ADDITION // This function replaces pre- and no-parsed sections with unique keys // and saves the section contents for later reinsertion. function parse_pp_np(&$data, &$preparsed, &$noparsed) { // Find all sections delimited by ~pp~ ... ~/pp~ // and replace them in the data stream with a unique key preg_match_all("/\~pp\~((.|\n)*?)\~\/pp\~/", $data, $preparse); if( count( $preparse[0] ) ) { foreach (array_unique($preparse[1])as $pp) { $key = md5(BitSystem::genPass()); $aux["key"] = $key; $aux["data"] = $pp; $preparsed[] = $aux; $data = str_replace("~pp~$pp~/pp~", $key, $data); } // Temporary remove
tags too // TODO: Is this a problem if user insertbut after parsing // will get(lowercase)?? :) preg_match_all("/(<[Pp][Rr][Ee]>)((.|\n)*?)(<\/[Pp][Rr][Ee]>)/", $data, $preparse); $idx = 0; foreach (array_unique($preparse[2])as $pp) { $key = md5(BitSystem::genPass()); $aux["key"] = $key; $aux["data"] = $pp; $preparsed[] = $aux; $data = str_replace($preparse[1][$idx] . $pp . $preparse[4][$idx], $key, $data); $idx = $idx + 1; } } if( preg_match("!\~np\~(.*?)\~/np\~!s", $data, $preparse) ) { // Find all sections delimited by ~np~ ... ~/np~ $new_data = ''; $nopa = ''; $state = true; $skip = false; $dlength=strlen($data); for ($i = 0; $i < $dlength; $i++) { $tag5 = substr($data, $i, 5); $tag4 = substr($tag5, 0, 4); $tag1 = substr($tag4, 0, 1); // Beginning of a noparse section found if ($state && $tag4 == '~np~') { $i += 3; $state = false; $skip = true; } // Termination of a noparse section found if (!$state && ($tag5 == '~/np~')) { $state = true; $i += 4; $skip = true; $key = md5(BitSystem::genPass()); $new_data .= $key; $aux["key"] = $key; $aux["data"] = $nopa; $noparsed[] = $aux; $nopa = ''; } if (!$skip) { // This character is not part of a noparse tag if ($state) { // This character is not within a noparse section $new_data .= $tag1; } else { // This character is within a noparse section $nopa .= $tag1; } } else { // Tag is now skipped over $skip = false; } } $data = $new_data; } } // This function handles wiki codes for those special HTML characters // that textarea won't leave alone. function parse_htmlchar(&$data) { // cleaning some user input $data = preg_replace("/&(?!([a-z]{1,7};))/", "&", $data); // oft-used characters (case insensitive) $data = preg_replace("/~bull~/i", "•", $data); $data = preg_replace("/~bs~/i", "\", $data); $data = preg_replace("/~hs~/i", " ", $data); $data = preg_replace("/~amp~/i", "&", $data); $data = preg_replace("/~ldq~/i", "“", $data); $data = preg_replace("/~rdq~/i", "”", $data); $data = preg_replace("/~lsq~/i", "‘", $data); $data = preg_replace("/~rsq~/i", "’", $data); $data = preg_replace("/~c~/i", "©", $data); $data = preg_replace("/~--~/", "—", $data); $data = preg_replace("/ -- /", " — ", $data); $data = preg_replace("/~lt~/i", "<", $data); $data = preg_replace("/~gt~/i", ">", $data); // HTML numeric character entities $data = preg_replace("/~([0-9]+)~/", "$1;", $data); } function parse_smileys( $pData ) { global $gBitSystem, $gBitSmarty; if( defined( "SMILEYS_PKG_URL" ) && $gBitSystem->isPackageActive( 'smileys' ) ) { preg_match_all( "/\(:([^:]+):\)/", $pData, $smileys ); require_once $gBitSmarty->_get_plugin_filepath( 'function', 'biticon' ); $smileys[0] = array_unique( $smileys[0] ); $smileys[1] = array_unique( $smileys[1] ); if( !empty( $smileys[1] ) ) { foreach( $smileys[1] as $key => $smiley ) { $biticon = array( 'ipackage' => 'smileys', 'iname' => $smiley, 'iexplain' => $smiley, 'iforce' => 'icon', ); $pData = preg_replace( "/".preg_quote( $smileys[0][$key] )."/", smarty_function_biticon( $biticon, $gBitSmarty ), $pData ); } } } return $pData; } function parse_comment_data( $pData ) { // rel=\"nofollow\" is support for Google's Preventing comment spam // http://www.google.com/googleblog/2005/01/preventing-comment-spam.html $pData = preg_replace("/\[([^\|\]]+)\|([^\]]+)\]/", "$2", $pData); // Segundo intento reemplazar los [link] comunes $pData = preg_replace("/\[([^\]\|]+)\]/", "$1", $pData); // Llamar aqui a parse smileys $pData = $this->parse_smileys($pData); $pData = preg_replace("/---/", "
", $pData); // Reemplazar --- por
return $pData; } function get_language($user = false) { static $bitLanguage = false; global $gBitUser, $gBitSystem; if( empty( $bitLanguage ) ) { if( $gBitUser->isValid() ) { $bitLanguage = $gBitUser->getPreference('bitLanguage', 'en'); } else { $bitLanguage = $this->getPreference('bitLanguage', 'en'); } } return $bitLanguage; } function get_locale($user = false) { # TODO move to admin preferences screen static $locales = array( 'cs' => 'cs_CZ', 'de' => 'de_DE', 'dk' => 'da_DK', 'en' => 'en_US', 'fr' => 'fr_FR', 'he' => 'he_IL', # hebrew 'it' => 'it_IT', # italian 'pl' => 'pl_PL', # polish 'po' => 'po', 'ru' => 'ru_RU', 'es' => 'es_ES', 'sw' => 'sw_SW', # swahili 'tw' => 'tw_TW', ); if (!isset($locale) or !$locale) { $locale = ''; if (isset($locales[$this->get_language($user)])) $locale = $locales[$this->get_language($user)]; #print "get_locale(): locale=$locale\n"; } return $locale; } function get_links($data) { $links = array(); // Match things like [...], but ignore things like [[foo]. // -Robin if (preg_match_all("/(?isFeatureActive( 'liberty_cache_pages' ) ) { foreach ($links as $link) { if( !$pCommonObject->isCached( $link ) ) { $pCommonObject->cacheUrl($link); } } } } function how_many_at_start($str, $car) { $cant = 0; $i = 0; while (($i < strlen($str)) && (isset($str{$i})) && ($str{$i}== $car)) { $i++; $cant++; } return $cant; } function parse_mediawiki_tables( $data ) { //DEBUG: $data = "\n\n" . $data; /* Find all matches to {|...|} with no {| inside. */ while (preg_match('/\n\{\|(.*?)\n\|\}/sm', $data, $matches)) { //DEBUG: vd($matches); $table_data = str_replace("\r", "", $matches[1]); $table_data = str_replace('||', "\n|", $table_data); while (preg_match('/^![^!]+!!/m', $table_data)) { /* Replace !! with \n! but ONLY in !-defined header rows. */ $table_data = preg_replace('/^!([^!]+)!!/m', "!$1\n!", $table_data); } if (substr($table_data, 0, 1) != "\n") { /* We have table parameters. */ list($table_params, $table_data) = explode("\n", $table_data, 2); $table_params = trim($table_params); /* FIXME: This attempt to support foo:bar table params needs help! if (strlen($table_params)) { $table_params = preg_replace("/\b(\w+):/", '$1=', $table_params); } */ } else { $table_params = ''; } $content = "