summaryrefslogtreecommitdiff
path: root/includes/parser/LinkHolderArray.php
diff options
context:
space:
mode:
authorPierre Schmitz <pierre@archlinux.de>2009-02-22 13:37:51 +0100
committerPierre Schmitz <pierre@archlinux.de>2009-02-22 13:37:51 +0100
commitb9b85843572bf283f48285001e276ba7e61b63f6 (patch)
tree4c6f4571552ada9ccfb4030481dcf77308f8b254 /includes/parser/LinkHolderArray.php
parentd9a20acc4e789cca747ad360d87ee3f3e7aa58c1 (diff)
updated to MediaWiki 1.14.0
Diffstat (limited to 'includes/parser/LinkHolderArray.php')
-rw-r--r--includes/parser/LinkHolderArray.php438
1 files changed, 438 insertions, 0 deletions
diff --git a/includes/parser/LinkHolderArray.php b/includes/parser/LinkHolderArray.php
new file mode 100644
index 00000000..35b672b9
--- /dev/null
+++ b/includes/parser/LinkHolderArray.php
@@ -0,0 +1,438 @@
+<?php
+
+class LinkHolderArray {
+ var $internals = array(), $interwikis = array();
+ var $size = 0;
+ var $parent;
+
+ function __construct( $parent ) {
+ $this->parent = $parent;
+ }
+
+ /**
+ * Reduce memory usage to reduce the impact of circular references
+ */
+ function __destruct() {
+ foreach ( $this as $name => $value ) {
+ unset( $this->$name );
+ }
+ }
+
+ /**
+ * Merge another LinkHolderArray into this one
+ */
+ function merge( $other ) {
+ foreach ( $other->internals as $ns => $entries ) {
+ $this->size += count( $entries );
+ if ( !isset( $this->internals[$ns] ) ) {
+ $this->internals[$ns] = $entries;
+ } else {
+ $this->internals[$ns] += $entries;
+ }
+ }
+ $this->interwikis += $other->interwikis;
+ }
+
+ /**
+ * Returns true if the memory requirements of this object are getting large
+ */
+ function isBig() {
+ global $wgLinkHolderBatchSize;
+ return $this->size > $wgLinkHolderBatchSize;
+ }
+
+ /**
+ * Clear all stored link holders.
+ * Make sure you don't have any text left using these link holders, before you call this
+ */
+ function clear() {
+ $this->internals = array();
+ $this->interwikis = array();
+ $this->size = 0;
+ }
+
+ /**
+ * Make a link placeholder. The text returned can be later resolved to a real link with
+ * replaceLinkHolders(). This is done for two reasons: firstly to avoid further
+ * parsing of interwiki links, and secondly to allow all existence checks and
+ * article length checks (for stub links) to be bundled into a single query.
+ *
+ */
+ function makeHolder( $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
+ wfProfileIn( __METHOD__ );
+ if ( ! is_object($nt) ) {
+ # Fail gracefully
+ $retVal = "<!-- ERROR -->{$prefix}{$text}{$trail}";
+ } else {
+ # Separate the link trail from the rest of the link
+ list( $inside, $trail ) = Linker::splitTrail( $trail );
+
+ $entry = array(
+ 'title' => $nt,
+ 'text' => $prefix.$text.$inside,
+ 'pdbk' => $nt->getPrefixedDBkey(),
+ );
+ if ( $query !== '' ) {
+ $entry['query'] = $query;
+ }
+
+ if ( $nt->isExternal() ) {
+ // Use a globally unique ID to keep the objects mergable
+ $key = $this->parent->nextLinkID();
+ $this->interwikis[$key] = $entry;
+ $retVal = "<!--IWLINK $key-->{$trail}";
+ } else {
+ $key = $this->parent->nextLinkID();
+ $ns = $nt->getNamespace();
+ $this->internals[$ns][$key] = $entry;
+ $retVal = "<!--LINK $ns:$key-->{$trail}";
+ }
+ $this->size++;
+ }
+ wfProfileOut( __METHOD__ );
+ return $retVal;
+ }
+
+ /**
+ * Get the stub threshold
+ */
+ function getStubThreshold() {
+ global $wgUser;
+ if ( !isset( $this->stubThreshold ) ) {
+ $this->stubThreshold = $wgUser->getOption('stubthreshold');
+ }
+ return $this->stubThreshold;
+ }
+
+ /**
+ * Replace <!--LINK--> link placeholders with actual links, in the buffer
+ * Placeholders created in Skin::makeLinkObj()
+ * Returns an array of link CSS classes, indexed by PDBK.
+ */
+ function replace( &$text ) {
+ wfProfileIn( __METHOD__ );
+
+ $colours = $this->replaceInternal( $text );
+ $this->replaceInterwiki( $text );
+
+ wfProfileOut( __METHOD__ );
+ return $colours;
+ }
+
+ /**
+ * Replace internal links
+ */
+ protected function replaceInternal( &$text ) {
+ if ( !$this->internals ) {
+ return;
+ }
+
+ wfProfileIn( __METHOD__ );
+ global $wgContLang;
+
+ $colours = array();
+ $sk = $this->parent->getOptions()->getSkin();
+ $linkCache = LinkCache::singleton();
+ $output = $this->parent->getOutput();
+
+ wfProfileIn( __METHOD__.'-check' );
+ $dbr = wfGetDB( DB_SLAVE );
+ $page = $dbr->tableName( 'page' );
+ $threshold = $this->getStubThreshold();
+
+ # Sort by namespace
+ ksort( $this->internals );
+
+ # Generate query
+ $query = false;
+ $current = null;
+ foreach ( $this->internals as $ns => $entries ) {
+ foreach ( $entries as $index => $entry ) {
+ $key = "$ns:$index";
+ $title = $entry['title'];
+ $pdbk = $entry['pdbk'];
+
+ # Skip invalid entries.
+ # Result will be ugly, but prevents crash.
+ if ( is_null( $title ) ) {
+ continue;
+ }
+
+ # Check if it's a static known link, e.g. interwiki
+ if ( $title->isAlwaysKnown() ) {
+ $colours[$pdbk] = '';
+ } elseif ( ( $id = $linkCache->getGoodLinkID( $pdbk ) ) != 0 ) {
+ $colours[$pdbk] = $sk->getLinkColour( $title, $threshold );
+ $output->addLink( $title, $id );
+ } elseif ( $linkCache->isBadLink( $pdbk ) ) {
+ $colours[$pdbk] = 'new';
+ } else {
+ # Not in the link cache, add it to the query
+ if ( !isset( $current ) ) {
+ $current = $ns;
+ $query = "SELECT page_id, page_namespace, page_title, page_is_redirect, page_len";
+ $query .= " FROM $page WHERE (page_namespace=$ns AND page_title IN(";
+ } elseif ( $current != $ns ) {
+ $current = $ns;
+ $query .= ")) OR (page_namespace=$ns AND page_title IN(";
+ } else {
+ $query .= ', ';
+ }
+
+ $query .= $dbr->addQuotes( $title->getDBkey() );
+ }
+ }
+ }
+ if ( $query ) {
+ $query .= '))';
+
+ $res = $dbr->query( $query, __METHOD__ );
+
+ # Fetch data and form into an associative array
+ # non-existent = broken
+ $linkcolour_ids = array();
+ while ( $s = $dbr->fetchObject($res) ) {
+ $title = Title::makeTitle( $s->page_namespace, $s->page_title );
+ $pdbk = $title->getPrefixedDBkey();
+ $linkCache->addGoodLinkObj( $s->page_id, $title, $s->page_len, $s->page_is_redirect );
+ $output->addLink( $title, $s->page_id );
+ # FIXME: convoluted data flow
+ # The redirect status and length is passed to getLinkColour via the LinkCache
+ # Use formal parameters instead
+ $colours[$pdbk] = $sk->getLinkColour( $title, $threshold );
+ //add id to the extension todolist
+ $linkcolour_ids[$s->page_id] = $pdbk;
+ }
+ unset( $res );
+ //pass an array of page_ids to an extension
+ wfRunHooks( 'GetLinkColours', array( $linkcolour_ids, &$colours ) );
+ }
+ wfProfileOut( __METHOD__.'-check' );
+
+ # Do a second query for different language variants of links and categories
+ if($wgContLang->hasVariants()) {
+ $this->doVariants( $colours );
+ }
+
+ # Construct search and replace arrays
+ wfProfileIn( __METHOD__.'-construct' );
+ $replacePairs = array();
+ foreach ( $this->internals as $ns => $entries ) {
+ foreach ( $entries as $index => $entry ) {
+ $pdbk = $entry['pdbk'];
+ $title = $entry['title'];
+ $query = isset( $entry['query'] ) ? $entry['query'] : '';
+ $key = "$ns:$index";
+ $searchkey = "<!--LINK $key-->";
+ if ( !isset( $colours[$pdbk] ) || $colours[$pdbk] == 'new' ) {
+ $linkCache->addBadLinkObj( $title );
+ $colours[$pdbk] = 'new';
+ $output->addLink( $title, 0 );
+ $replacePairs[$searchkey] = $sk->makeBrokenLinkObj( $title,
+ $entry['text'],
+ $query );
+ } else {
+ $replacePairs[$searchkey] = $sk->makeColouredLinkObj( $title, $colours[$pdbk],
+ $entry['text'],
+ $query );
+ }
+ }
+ }
+ $replacer = new HashtableReplacer( $replacePairs, 1 );
+ wfProfileOut( __METHOD__.'-construct' );
+
+ # Do the thing
+ wfProfileIn( __METHOD__.'-replace' );
+ $text = preg_replace_callback(
+ '/(<!--LINK .*?-->)/',
+ $replacer->cb(),
+ $text);
+
+ wfProfileOut( __METHOD__.'-replace' );
+ wfProfileOut( __METHOD__ );
+ }
+
+ /**
+ * Replace interwiki links
+ */
+ protected function replaceInterwiki( &$text ) {
+ if ( empty( $this->interwikis ) ) {
+ return;
+ }
+
+ wfProfileIn( __METHOD__ );
+ # Make interwiki link HTML
+ $sk = $this->parent->getOptions()->getSkin();
+ $replacePairs = array();
+ foreach( $this->interwikis as $key => $link ) {
+ $replacePairs[$key] = $sk->link( $link['title'], $link['text'] );
+ }
+ $replacer = new HashtableReplacer( $replacePairs, 1 );
+
+ $text = preg_replace_callback(
+ '/<!--IWLINK (.*?)-->/',
+ $replacer->cb(),
+ $text );
+ wfProfileOut( __METHOD__ );
+ }
+
+ /**
+ * Modify $this->internals and $colours according to language variant linking rules
+ */
+ protected function doVariants( &$colours ) {
+ global $wgContLang;
+ $linkBatch = new LinkBatch();
+ $variantMap = array(); // maps $pdbkey_Variant => $keys (of link holders)
+ $output = $this->parent->getOutput();
+ $linkCache = LinkCache::singleton();
+ $sk = $this->parent->getOptions()->getSkin();
+ $threshold = $this->getStubThreshold();
+
+ // Add variants of links to link batch
+ foreach ( $this->internals as $ns => $entries ) {
+ foreach ( $entries as $index => $entry ) {
+ $key = "$ns:$index";
+ $pdbk = $entry['pdbk'];
+ $title = $entry['title'];
+ $titleText = $title->getText();
+
+ // generate all variants of the link title text
+ $allTextVariants = $wgContLang->convertLinkToAllVariants($titleText);
+
+ // if link was not found (in first query), add all variants to query
+ if ( !isset($colours[$pdbk]) ){
+ foreach($allTextVariants as $textVariant){
+ if($textVariant != $titleText){
+ $variantTitle = Title::makeTitle( $ns, $textVariant );
+ if(is_null($variantTitle)) continue;
+ $linkBatch->addObj( $variantTitle );
+ $variantMap[$variantTitle->getPrefixedDBkey()][] = $key;
+ }
+ }
+ }
+ }
+ }
+
+ // process categories, check if a category exists in some variant
+ $categoryMap = array(); // maps $category_variant => $category (dbkeys)
+ $varCategories = array(); // category replacements oldDBkey => newDBkey
+ foreach( $output->getCategoryLinks() as $category ){
+ $variants = $wgContLang->convertLinkToAllVariants($category);
+ foreach($variants as $variant){
+ if($variant != $category){
+ $variantTitle = Title::newFromDBkey( Title::makeName(NS_CATEGORY,$variant) );
+ if(is_null($variantTitle)) continue;
+ $linkBatch->addObj( $variantTitle );
+ $categoryMap[$variant] = $category;
+ }
+ }
+ }
+
+
+ if(!$linkBatch->isEmpty()){
+ // construct query
+ $dbr = wfGetDB( DB_SLAVE );
+ $page = $dbr->tableName( 'page' );
+ $titleClause = $linkBatch->constructSet('page', $dbr);
+ $variantQuery = "SELECT page_id, page_namespace, page_title, page_is_redirect, page_len";
+ $variantQuery .= " FROM $page WHERE $titleClause";
+ $varRes = $dbr->query( $variantQuery, __METHOD__ );
+ $linkcolour_ids = array();
+
+ // for each found variants, figure out link holders and replace
+ while ( $s = $dbr->fetchObject($varRes) ) {
+
+ $variantTitle = Title::makeTitle( $s->page_namespace, $s->page_title );
+ $varPdbk = $variantTitle->getPrefixedDBkey();
+ $vardbk = $variantTitle->getDBkey();
+
+ $holderKeys = array();
+ if(isset($variantMap[$varPdbk])){
+ $holderKeys = $variantMap[$varPdbk];
+ $linkCache->addGoodLinkObj( $s->page_id, $variantTitle, $s->page_len, $s->page_is_redirect );
+ $output->addLink( $variantTitle, $s->page_id );
+ }
+
+ // loop over link holders
+ foreach($holderKeys as $key){
+ list( $ns, $index ) = explode( ':', $key, 2 );
+ $entry =& $this->internals[$ns][$index];
+ $pdbk = $entry['pdbk'];
+
+ if(!isset($colours[$pdbk])){
+ // found link in some of the variants, replace the link holder data
+ $entry['title'] = $variantTitle;
+ $entry['pdbk'] = $varPdbk;
+
+ // set pdbk and colour
+ # FIXME: convoluted data flow
+ # The redirect status and length is passed to getLinkColour via the LinkCache
+ # Use formal parameters instead
+ $colours[$varPdbk] = $sk->getLinkColour( $variantTitle, $threshold );
+ $linkcolour_ids[$s->page_id] = $pdbk;
+ }
+ }
+
+ // check if the object is a variant of a category
+ if(isset($categoryMap[$vardbk])){
+ $oldkey = $categoryMap[$vardbk];
+ if($oldkey != $vardbk)
+ $varCategories[$oldkey]=$vardbk;
+ }
+ }
+ wfRunHooks( 'GetLinkColours', array( $linkcolour_ids, &$colours ) );
+
+ // rebuild the categories in original order (if there are replacements)
+ if(count($varCategories)>0){
+ $newCats = array();
+ $originalCats = $output->getCategories();
+ foreach($originalCats as $cat => $sortkey){
+ // make the replacement
+ if( array_key_exists($cat,$varCategories) )
+ $newCats[$varCategories[$cat]] = $sortkey;
+ else $newCats[$cat] = $sortkey;
+ }
+ $output->setCategoryLinks($newCats);
+ }
+ }
+ }
+
+ /**
+ * Replace <!--LINK--> link placeholders with plain text of links
+ * (not HTML-formatted).
+ * @param string $text
+ * @return string
+ */
+ function replaceText( $text ) {
+ wfProfileIn( __METHOD__ );
+
+ $text = preg_replace_callback(
+ '/<!--(LINK|IWLINK) (.*?)-->/',
+ array( &$this, 'replaceTextCallback' ),
+ $text );
+
+ wfProfileOut( __METHOD__ );
+ return $text;
+ }
+
+ /**
+ * @param array $matches
+ * @return string
+ * @private
+ */
+ function replaceTextCallback( $matches ) {
+ $type = $matches[1];
+ $key = $matches[2];
+ if( $type == 'LINK' ) {
+ list( $ns, $index ) = explode( ':', $key, 2 );
+ if( isset( $this->internals[$ns][$index]['text'] ) ) {
+ return $this->internals[$ns][$index]['text'];
+ }
+ } elseif( $type == 'IWLINK' ) {
+ if( isset( $this->interwikis[$key]['text'] ) ) {
+ return $this->interwikis[$key]['text'];
+ }
+ }
+ return $matches[0];
+ }
+}