00001 <?php
00002
00003 class LinkHolderArray {
00004 var $internals = array(), $interwikis = array();
00005 var $size = 0;
00006 var $parent;
00007
00008 function __construct( $parent ) {
00009 $this->parent = $parent;
00010 }
00011
00015 function __destruct() {
00016 foreach ( $this as $name => $value ) {
00017 unset( $this->$name );
00018 }
00019 }
00020
00024 function merge( $other ) {
00025 foreach ( $other->internals as $ns => $entries ) {
00026 $this->size += count( $entries );
00027 if ( !isset( $this->internals[$ns] ) ) {
00028 $this->internals[$ns] = $entries;
00029 } else {
00030 $this->internals[$ns] += $entries;
00031 }
00032 }
00033 $this->interwikis += $other->interwikis;
00034 }
00035
00039 function isBig() {
00040 global $wgLinkHolderBatchSize;
00041 return $this->size > $wgLinkHolderBatchSize;
00042 }
00043
00048 function clear() {
00049 $this->internals = array();
00050 $this->interwikis = array();
00051 $this->size = 0;
00052 }
00053
00061 function makeHolder( $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
00062 wfProfileIn( __METHOD__ );
00063 if ( ! is_object($nt) ) {
00064 # Fail gracefully
00065 $retVal = "<!-- ERROR -->{$prefix}{$text}{$trail}";
00066 } else {
00067 # Separate the link trail from the rest of the link
00068 list( $inside, $trail ) = Linker::splitTrail( $trail );
00069
00070 $entry = array(
00071 'title' => $nt,
00072 'text' => $prefix.$text.$inside,
00073 'pdbk' => $nt->getPrefixedDBkey(),
00074 );
00075 if ( $query !== '' ) {
00076 $entry['query'] = $query;
00077 }
00078
00079 if ( $nt->isExternal() ) {
00080
00081 $key = $this->parent->nextLinkID();
00082 $this->interwikis[$key] = $entry;
00083 $retVal = "<!--IWLINK $key-->{$trail}";
00084 } else {
00085 $key = $this->parent->nextLinkID();
00086 $ns = $nt->getNamespace();
00087 $this->internals[$ns][$key] = $entry;
00088 $retVal = "<!--LINK $ns:$key-->{$trail}";
00089 }
00090 $this->size++;
00091 }
00092 wfProfileOut( __METHOD__ );
00093 return $retVal;
00094 }
00095
00099 function getStubThreshold() {
00100 global $wgUser;
00101 if ( !isset( $this->stubThreshold ) ) {
00102 $this->stubThreshold = $wgUser->getOption('stubthreshold');
00103 }
00104 return $this->stubThreshold;
00105 }
00106
00113 function replace( &$text ) {
00114 wfProfileIn( __METHOD__ );
00115
00116 $colours = $this->replaceInternal( $text );
00117 $this->replaceInterwiki( $text );
00118
00119 wfProfileOut( __METHOD__ );
00120 return $colours;
00121 }
00122
00126 protected function replaceInternal( &$text ) {
00127 if ( !$this->internals ) {
00128 return;
00129 }
00130
00131 wfProfileIn( __METHOD__ );
00132 global $wgContLang;
00133
00134 $colours = array();
00135 $sk = $this->parent->getOptions()->getSkin();
00136 $linkCache = LinkCache::singleton();
00137 $output = $this->parent->getOutput();
00138
00139 wfProfileIn( __METHOD__.'-check' );
00140 $dbr = wfGetDB( DB_SLAVE );
00141 $page = $dbr->tableName( 'page' );
00142 $threshold = $this->getStubThreshold();
00143
00144 # Sort by namespace
00145 ksort( $this->internals );
00146
00147 # Generate query
00148 $query = false;
00149 $current = null;
00150 foreach ( $this->internals as $ns => $entries ) {
00151 foreach ( $entries as $index => $entry ) {
00152 $key = "$ns:$index";
00153 $title = $entry['title'];
00154 $pdbk = $entry['pdbk'];
00155
00156 # Skip invalid entries.
00157 # Result will be ugly, but prevents crash.
00158 if ( is_null( $title ) ) {
00159 continue;
00160 }
00161
00162 # Check if it's a static known link, e.g. interwiki
00163 if ( $title->isAlwaysKnown() ) {
00164 $colours[$pdbk] = '';
00165 } elseif ( ( $id = $linkCache->getGoodLinkID( $pdbk ) ) != 0 ) {
00166 $colours[$pdbk] = $sk->getLinkColour( $title, $threshold );
00167 $output->addLink( $title, $id );
00168 } elseif ( $linkCache->isBadLink( $pdbk ) ) {
00169 $colours[$pdbk] = 'new';
00170 } else {
00171 # Not in the link cache, add it to the query
00172 if ( !isset( $current ) ) {
00173 $current = $ns;
00174 $query = "SELECT page_id, page_namespace, page_title, page_is_redirect, page_len";
00175 $query .= " FROM $page WHERE (page_namespace=$ns AND page_title IN(";
00176 } elseif ( $current != $ns ) {
00177 $current = $ns;
00178 $query .= ")) OR (page_namespace=$ns AND page_title IN(";
00179 } else {
00180 $query .= ', ';
00181 }
00182
00183 $query .= $dbr->addQuotes( $title->getDBkey() );
00184 }
00185 }
00186 }
00187 if ( $query ) {
00188 $query .= '))';
00189
00190 $res = $dbr->query( $query, __METHOD__ );
00191
00192 # Fetch data and form into an associative array
00193 # non-existent = broken
00194 $linkcolour_ids = array();
00195 while ( $s = $dbr->fetchObject($res) ) {
00196 $title = Title::makeTitle( $s->page_namespace, $s->page_title );
00197 $pdbk = $title->getPrefixedDBkey();
00198 $linkCache->addGoodLinkObj( $s->page_id, $title, $s->page_len, $s->page_is_redirect );
00199 $output->addLink( $title, $s->page_id );
00200 # FIXME: convoluted data flow
00201 # The redirect status and length is passed to getLinkColour via the LinkCache
00202 # Use formal parameters instead
00203 $colours[$pdbk] = $sk->getLinkColour( $title, $threshold );
00204
00205 $linkcolour_ids[$s->page_id] = $pdbk;
00206 }
00207 unset( $res );
00208
00209 wfRunHooks( 'GetLinkColours', array( $linkcolour_ids, &$colours ) );
00210 }
00211 wfProfileOut( __METHOD__.'-check' );
00212
00213 # Do a second query for different language variants of links and categories
00214 if($wgContLang->hasVariants()) {
00215 $this->doVariants( $colours );
00216 }
00217
00218 # Construct search and replace arrays
00219 wfProfileIn( __METHOD__.'-construct' );
00220 $replacePairs = array();
00221 foreach ( $this->internals as $ns => $entries ) {
00222 foreach ( $entries as $index => $entry ) {
00223 $pdbk = $entry['pdbk'];
00224 $title = $entry['title'];
00225 $query = isset( $entry['query'] ) ? $entry['query'] : '';
00226 $key = "$ns:$index";
00227 $searchkey = "<!--LINK $key-->";
00228 if ( !isset( $colours[$pdbk] ) || $colours[$pdbk] == 'new' ) {
00229 $linkCache->addBadLinkObj( $title );
00230 $colours[$pdbk] = 'new';
00231 $output->addLink( $title, 0 );
00232
00233 $replacePairs[$searchkey] = $sk->makeBrokenLinkObj( $title,
00234 $entry['text'],
00235 $query );
00236 } else {
00237
00238 $replacePairs[$searchkey] = $sk->makeColouredLinkObj( $title, $colours[$pdbk],
00239 $entry['text'],
00240 $query );
00241 }
00242 }
00243 }
00244 $replacer = new HashtableReplacer( $replacePairs, 1 );
00245 wfProfileOut( __METHOD__.'-construct' );
00246
00247 # Do the thing
00248 wfProfileIn( __METHOD__.'-replace' );
00249 $text = preg_replace_callback(
00250 '/(<!--LINK .*?-->)/',
00251 $replacer->cb(),
00252 $text);
00253
00254 wfProfileOut( __METHOD__.'-replace' );
00255 wfProfileOut( __METHOD__ );
00256 }
00257
00261 protected function replaceInterwiki( &$text ) {
00262 if ( empty( $this->interwikis ) ) {
00263 return;
00264 }
00265
00266 wfProfileIn( __METHOD__ );
00267 # Make interwiki link HTML
00268 $sk = $this->parent->getOptions()->getSkin();
00269 $replacePairs = array();
00270 foreach( $this->interwikis as $key => $link ) {
00271 $replacePairs[$key] = $sk->link( $link['title'], $link['text'] );
00272 }
00273 $replacer = new HashtableReplacer( $replacePairs, 1 );
00274
00275 $text = preg_replace_callback(
00276 '/<!--IWLINK (.*?)-->/',
00277 $replacer->cb(),
00278 $text );
00279 wfProfileOut( __METHOD__ );
00280 }
00281
00285 protected function doVariants( &$colours ) {
00286 global $wgContLang;
00287 $linkBatch = new LinkBatch();
00288 $variantMap = array();
00289 $output = $this->parent->getOutput();
00290 $linkCache = LinkCache::singleton();
00291 $sk = $this->parent->getOptions()->getSkin();
00292 $threshold = $this->getStubThreshold();
00293
00294
00295 foreach ( $this->internals as $ns => $entries ) {
00296 foreach ( $entries as $index => $entry ) {
00297 $key = "$ns:$index";
00298 $pdbk = $entry['pdbk'];
00299 $title = $entry['title'];
00300 $titleText = $title->getText();
00301
00302
00303 $allTextVariants = $wgContLang->convertLinkToAllVariants($titleText);
00304
00305
00306 if ( !isset($colours[$pdbk]) ){
00307 foreach($allTextVariants as $textVariant){
00308 if($textVariant != $titleText){
00309 $variantTitle = Title::makeTitle( $ns, $textVariant );
00310 if(is_null($variantTitle)) continue;
00311 $linkBatch->addObj( $variantTitle );
00312 $variantMap[$variantTitle->getPrefixedDBkey()][] = $key;
00313 }
00314 }
00315 }
00316 }
00317 }
00318
00319
00320 $categoryMap = array();
00321 $varCategories = array();
00322 foreach( $output->getCategoryLinks() as $category ){
00323 $variants = $wgContLang->convertLinkToAllVariants($category);
00324 foreach($variants as $variant){
00325 if($variant != $category){
00326 $variantTitle = Title::newFromDBkey( Title::makeName(NS_CATEGORY,$variant) );
00327 if(is_null($variantTitle)) continue;
00328 $linkBatch->addObj( $variantTitle );
00329 $categoryMap[$variant] = $category;
00330 }
00331 }
00332 }
00333
00334
00335 if(!$linkBatch->isEmpty()){
00336
00337 $dbr = wfGetDB( DB_SLAVE );
00338 $page = $dbr->tableName( 'page' );
00339 $titleClause = $linkBatch->constructSet('page', $dbr);
00340 $variantQuery = "SELECT page_id, page_namespace, page_title, page_is_redirect, page_len";
00341 $variantQuery .= " FROM $page WHERE $titleClause";
00342 $varRes = $dbr->query( $variantQuery, __METHOD__ );
00343 $linkcolour_ids = array();
00344
00345
00346 while ( $s = $dbr->fetchObject($varRes) ) {
00347
00348 $variantTitle = Title::makeTitle( $s->page_namespace, $s->page_title );
00349 $varPdbk = $variantTitle->getPrefixedDBkey();
00350 $vardbk = $variantTitle->getDBkey();
00351
00352 $holderKeys = array();
00353 if(isset($variantMap[$varPdbk])){
00354 $holderKeys = $variantMap[$varPdbk];
00355 $linkCache->addGoodLinkObj( $s->page_id, $variantTitle, $s->page_len, $s->page_is_redirect );
00356 $output->addLink( $variantTitle, $s->page_id );
00357 }
00358
00359
00360 foreach($holderKeys as $key){
00361 list( $ns, $index ) = explode( ':', $key, 2 );
00362 $entry =& $this->internals[$ns][$index];
00363 $pdbk = $entry['pdbk'];
00364
00365 if(!isset($colours[$pdbk])){
00366
00367 $entry['title'] = $variantTitle;
00368 $entry['pdbk'] = $varPdbk;
00369
00370
00371 # FIXME: convoluted data flow
00372 # The redirect status and length is passed to getLinkColour via the LinkCache
00373 # Use formal parameters instead
00374 $colours[$varPdbk] = $sk->getLinkColour( $variantTitle, $threshold );
00375 $linkcolour_ids[$s->page_id] = $pdbk;
00376 }
00377 }
00378
00379
00380 if(isset($categoryMap[$vardbk])){
00381 $oldkey = $categoryMap[$vardbk];
00382 if($oldkey != $vardbk)
00383 $varCategories[$oldkey]=$vardbk;
00384 }
00385 }
00386 wfRunHooks( 'GetLinkColours', array( $linkcolour_ids, &$colours ) );
00387
00388
00389 if(count($varCategories)>0){
00390 $newCats = array();
00391 $originalCats = $output->getCategories();
00392 foreach($originalCats as $cat => $sortkey){
00393
00394 if( array_key_exists($cat,$varCategories) )
00395 $newCats[$varCategories[$cat]] = $sortkey;
00396 else $newCats[$cat] = $sortkey;
00397 }
00398 $output->setCategoryLinks($newCats);
00399 }
00400 }
00401 }
00402
00409 function replaceText( $text ) {
00410 wfProfileIn( __METHOD__ );
00411
00412 $text = preg_replace_callback(
00413 '/<!--(LINK|IWLINK) (.*?)-->/',
00414 array( &$this, 'replaceTextCallback' ),
00415 $text );
00416
00417 wfProfileOut( __METHOD__ );
00418 return $text;
00419 }
00420
00426 function replaceTextCallback( $matches ) {
00427 $type = $matches[1];
00428 $key = $matches[2];
00429 if( $type == 'LINK' ) {
00430 list( $ns, $index ) = explode( ':', $key, 2 );
00431 if( isset( $this->internals[$ns][$index]['text'] ) ) {
00432 return $this->internals[$ns][$index]['text'];
00433 }
00434 } elseif( $type == 'IWLINK' ) {
00435 if( isset( $this->interwikis[$key]['text'] ) ) {
00436 return $this->interwikis[$key]['text'];
00437 }
00438 }
00439 return $matches[0];
00440 }
00441 }