MediaWiki  REL1_20
LinkHolderArray.php
Go to the documentation of this file.
00001 <?php
00027 class LinkHolderArray {
00028         var $internals = array(), $interwikis = array();
00029         var $size = 0;
00030         var $parent;
00031         protected $tempIdOffset;
00032 
00033         function __construct( $parent ) {
00034                 $this->parent = $parent;
00035         }
00036 
00040         function __destruct() {
00041                 foreach ( $this as $name => $value ) {
00042                         unset( $this->$name );
00043                 }
00044         }
00045 
00054         function __sleep() {
00055                 foreach ( $this->internals as &$nsLinks ) {
00056                         foreach ( $nsLinks as &$entry ) {
00057                                 unset( $entry['title'] );
00058                         }
00059                 }
00060                 unset( $nsLinks );
00061                 unset( $entry );
00062 
00063                 foreach ( $this->interwikis as &$entry ) {
00064                         unset( $entry['title'] );
00065                 }
00066                 unset( $entry );
00067 
00068                 return array( 'internals', 'interwikis', 'size' );
00069         }
00070 
00074         function __wakeup() {
00075                 foreach ( $this->internals as &$nsLinks ) {
00076                         foreach ( $nsLinks as &$entry ) {
00077                                 $entry['title'] = Title::newFromText( $entry['pdbk'] );
00078                         }
00079                 }
00080                 unset( $nsLinks );
00081                 unset( $entry );
00082 
00083                 foreach ( $this->interwikis as &$entry ) {
00084                         $entry['title'] = Title::newFromText( $entry['pdbk'] );
00085                 }
00086                 unset( $entry );
00087         }
00088 
00093         function merge( $other ) {
00094                 foreach ( $other->internals as $ns => $entries ) {
00095                         $this->size += count( $entries );
00096                         if ( !isset( $this->internals[$ns] ) ) {
00097                                 $this->internals[$ns] = $entries;
00098                         } else {
00099                                 $this->internals[$ns] += $entries;
00100                         }
00101                 }
00102                 $this->interwikis += $other->interwikis;
00103         }
00104 
00117         function mergeForeign( $other, $texts ) {
00118                 $this->tempIdOffset = $idOffset = $this->parent->nextLinkID();
00119                 $maxId = 0;
00120 
00121                 # Renumber internal links
00122                 foreach ( $other->internals as $ns => $nsLinks ) {
00123                         foreach ( $nsLinks as $key => $entry ) {
00124                                 $newKey = $idOffset + $key;
00125                                 $this->internals[$ns][$newKey] = $entry;
00126                                 $maxId = $newKey > $maxId ? $newKey : $maxId;
00127                         }
00128                 }
00129                 $texts = preg_replace_callback( '/(<!--LINK \d+:)(\d+)(-->)/', 
00130                         array( $this, 'mergeForeignCallback' ), $texts );
00131 
00132                 # Renumber interwiki links
00133                 foreach ( $other->interwikis as $key => $entry ) {
00134                         $newKey = $idOffset + $key;
00135                         $this->interwikis[$newKey] = $entry;
00136                         $maxId = $newKey > $maxId ? $newKey : $maxId;
00137                 }
00138                 $texts = preg_replace_callback( '/(<!--IWLINK )(\d+)(-->)/', 
00139                         array( $this, 'mergeForeignCallback' ), $texts );
00140 
00141                 # Set the parent link ID to be beyond the highest used ID
00142                 $this->parent->setLinkID( $maxId + 1 );
00143                 $this->tempIdOffset = null;
00144                 return $texts;
00145         }
00146 
00147         protected function mergeForeignCallback( $m ) {
00148                 return $m[1] . ( $m[2] + $this->tempIdOffset ) . $m[3];
00149         }
00150 
00156         function getSubArray( $text ) {
00157                 $sub = new LinkHolderArray( $this->parent );
00158 
00159                 # Internal links
00160                 $pos = 0;
00161                 while ( $pos < strlen( $text ) ) {
00162                         if ( !preg_match( '/<!--LINK (\d+):(\d+)-->/', 
00163                                 $text, $m, PREG_OFFSET_CAPTURE, $pos ) ) 
00164                         {
00165                                 break;
00166                         }
00167                         $ns = $m[1][0];
00168                         $key = $m[2][0];
00169                         $sub->internals[$ns][$key] = $this->internals[$ns][$key];
00170                         $pos = $m[0][1] + strlen( $m[0][0] );
00171                 }
00172 
00173                 # Interwiki links
00174                 $pos = 0;
00175                 while ( $pos < strlen( $text ) ) {
00176                         if ( !preg_match( '/<!--IWLINK (\d+)-->/', $text, $m, PREG_OFFSET_CAPTURE, $pos ) ) {
00177                                 break;
00178                         }
00179                         $key = $m[1][0];
00180                         $sub->interwikis[$key] = $this->interwikis[$key];
00181                         $pos = $m[0][1] + strlen( $m[0][0] );
00182                 }
00183                 return $sub;
00184         }
00185 
00190         function isBig() {
00191                 global $wgLinkHolderBatchSize;
00192                 return $this->size > $wgLinkHolderBatchSize;
00193         }
00194 
00199         function clear() {
00200                 $this->internals = array();
00201                 $this->interwikis = array();
00202                 $this->size = 0;
00203         }
00204 
00218         function makeHolder( $nt, $text = '', $query = array(), $trail = '', $prefix = ''  ) {
00219                 wfProfileIn( __METHOD__ );
00220                 if ( ! is_object($nt) ) {
00221                         # Fail gracefully
00222                         $retVal = "<!-- ERROR -->{$prefix}{$text}{$trail}";
00223                 } else {
00224                         # Separate the link trail from the rest of the link
00225                         list( $inside, $trail ) = Linker::splitTrail( $trail );
00226 
00227                         $entry = array(
00228                                 'title' => $nt,
00229                                 'text' => $prefix.$text.$inside,
00230                                 'pdbk' => $nt->getPrefixedDBkey(),
00231                         );
00232                         if ( $query !== array() ) {
00233                                 $entry['query'] = $query;
00234                         }
00235 
00236                         if ( $nt->isExternal() ) {
00237                                 // Use a globally unique ID to keep the objects mergable
00238                                 $key = $this->parent->nextLinkID();
00239                                 $this->interwikis[$key] = $entry;
00240                                 $retVal = "<!--IWLINK $key-->{$trail}";
00241                         } else {
00242                                 $key = $this->parent->nextLinkID();
00243                                 $ns = $nt->getNamespace();
00244                                 $this->internals[$ns][$key] = $entry;
00245                                 $retVal = "<!--LINK $ns:$key-->{$trail}";
00246                         }
00247                         $this->size++;
00248                 }
00249                 wfProfileOut( __METHOD__ );
00250                 return $retVal;
00251         }
00252 
00259         function replace( &$text ) {
00260                 wfProfileIn( __METHOD__ );
00261 
00262                 $colours = $this->replaceInternal( $text );
00263                 $this->replaceInterwiki( $text );
00264 
00265                 wfProfileOut( __METHOD__ );
00266                 return $colours;
00267         }
00268 
00272         protected function replaceInternal( &$text ) {
00273                 if ( !$this->internals ) {
00274                         return;
00275                 }
00276 
00277                 wfProfileIn( __METHOD__ );
00278                 global $wgContLang;
00279 
00280                 $colours = array();
00281                 $linkCache = LinkCache::singleton();
00282                 $output = $this->parent->getOutput();
00283 
00284                 wfProfileIn( __METHOD__.'-check' );
00285                 $dbr = wfGetDB( DB_SLAVE );
00286                 $threshold = $this->parent->getOptions()->getStubThreshold();
00287 
00288                 # Sort by namespace
00289                 ksort( $this->internals );
00290 
00291                 $linkcolour_ids = array();
00292 
00293                 # Generate query
00294                 $queries = array();
00295                 foreach ( $this->internals as $ns => $entries ) {
00296                         foreach ( $entries as $entry ) {
00297                                 $title = $entry['title'];
00298                                 $pdbk = $entry['pdbk'];
00299 
00300                                 # Skip invalid entries.
00301                                 # Result will be ugly, but prevents crash.
00302                                 if ( is_null( $title ) ) {
00303                                         continue;
00304                                 }
00305 
00306                                 # Check if it's a static known link, e.g. interwiki
00307                                 if ( $title->isAlwaysKnown() ) {
00308                                         $colours[$pdbk] = '';
00309                                 } elseif ( $ns == NS_SPECIAL ) {
00310                                         $colours[$pdbk] = 'new';
00311                                 } elseif ( ( $id = $linkCache->getGoodLinkID( $pdbk ) ) != 0 ) {
00312                                         $colours[$pdbk] = Linker::getLinkColour( $title, $threshold );
00313                                         $output->addLink( $title, $id );
00314                                         $linkcolour_ids[$id] = $pdbk;
00315                                 } elseif ( $linkCache->isBadLink( $pdbk ) ) {
00316                                         $colours[$pdbk] = 'new';
00317                                 } else {
00318                                         # Not in the link cache, add it to the query
00319                                         $queries[$ns][] = $title->getDBkey();
00320                                 }
00321                         }
00322                 }
00323                 if ( $queries ) {
00324                         $where = array();
00325                         foreach( $queries as $ns => $pages ){
00326                                 $where[] = $dbr->makeList(
00327                                         array(
00328                                                 'page_namespace' => $ns,
00329                                                 'page_title' => $pages,
00330                                         ),
00331                                         LIST_AND
00332                                 );
00333                         }
00334 
00335                         $res = $dbr->select(
00336                                 'page',
00337                                 array( 'page_id', 'page_namespace', 'page_title', 'page_is_redirect', 'page_len', 'page_latest' ),
00338                                 $dbr->makeList( $where, LIST_OR ),
00339                                 __METHOD__
00340                         );
00341 
00342                         # Fetch data and form into an associative array
00343                         # non-existent = broken
00344                         foreach ( $res as $s ) {
00345                                 $title = Title::makeTitle( $s->page_namespace, $s->page_title );
00346                                 $pdbk = $title->getPrefixedDBkey();
00347                                 $linkCache->addGoodLinkObjFromRow( $title, $s );
00348                                 $output->addLink( $title, $s->page_id );
00349                                 # @todo FIXME: Convoluted data flow
00350                                 # The redirect status and length is passed to getLinkColour via the LinkCache
00351                                 # Use formal parameters instead
00352                                 $colours[$pdbk] = Linker::getLinkColour( $title, $threshold );
00353                                 //add id to the extension todolist
00354                                 $linkcolour_ids[$s->page_id] = $pdbk;
00355                         }
00356                         unset( $res );
00357                 }
00358                 if ( count($linkcolour_ids) ) {
00359                         //pass an array of page_ids to an extension
00360                         wfRunHooks( 'GetLinkColours', array( $linkcolour_ids, &$colours ) );
00361                 }
00362                 wfProfileOut( __METHOD__.'-check' );
00363 
00364                 # Do a second query for different language variants of links and categories
00365                 if($wgContLang->hasVariants()) {
00366                         $this->doVariants( $colours );
00367                 }
00368 
00369                 # Construct search and replace arrays
00370                 wfProfileIn( __METHOD__.'-construct' );
00371                 $replacePairs = array();
00372                 foreach ( $this->internals as $ns => $entries ) {
00373                         foreach ( $entries as $index => $entry ) {
00374                                 $pdbk = $entry['pdbk'];
00375                                 $title = $entry['title'];
00376                                 $query = isset( $entry['query'] ) ? $entry['query'] : array();
00377                                 $key = "$ns:$index";
00378                                 $searchkey = "<!--LINK $key-->";
00379                                 $displayText = $entry['text'];
00380                                 if ( $displayText === '' ) {
00381                                         $displayText = null;
00382                                 }
00383                                 if ( !isset( $colours[$pdbk] ) ) {
00384                                         $colours[$pdbk] = 'new';
00385                                 }
00386                                 $attribs = array();
00387                                 if ( $colours[$pdbk] == 'new' ) {
00388                                         $linkCache->addBadLinkObj( $title );
00389                                         $output->addLink( $title, 0 );
00390                                         $type = array( 'broken' );
00391                                 } else {
00392                                         if ( $colours[$pdbk] != '' ) {
00393                                                 $attribs['class'] = $colours[$pdbk];
00394                                         }
00395                                         $type = array( 'known', 'noclasses' );
00396                                 }
00397                                 $replacePairs[$searchkey] = Linker::link( $title, $displayText,
00398                                                 $attribs, $query, $type );
00399                         }
00400                 }
00401                 $replacer = new HashtableReplacer( $replacePairs, 1 );
00402                 wfProfileOut( __METHOD__.'-construct' );
00403 
00404                 # Do the thing
00405                 wfProfileIn( __METHOD__.'-replace' );
00406                 $text = preg_replace_callback(
00407                         '/(<!--LINK .*?-->)/',
00408                         $replacer->cb(),
00409                         $text);
00410 
00411                 wfProfileOut( __METHOD__.'-replace' );
00412                 wfProfileOut( __METHOD__ );
00413         }
00414 
00418         protected function replaceInterwiki( &$text ) {
00419                 if ( empty( $this->interwikis ) ) {
00420                         return;
00421                 }
00422 
00423                 wfProfileIn( __METHOD__ );
00424                 # Make interwiki link HTML
00425                 $output = $this->parent->getOutput();
00426                 $replacePairs = array();
00427                 foreach( $this->interwikis as $key => $link ) {
00428                         $replacePairs[$key] = Linker::link( $link['title'], $link['text'] );
00429                         $output->addInterwikiLink( $link['title'] );
00430                 }
00431                 $replacer = new HashtableReplacer( $replacePairs, 1 );
00432 
00433                 $text = preg_replace_callback(
00434                         '/<!--IWLINK (.*?)-->/',
00435                         $replacer->cb(),
00436                         $text );
00437                 wfProfileOut( __METHOD__ );
00438         }
00439 
00443         protected function doVariants( &$colours ) {
00444                 global $wgContLang;
00445                 $linkBatch = new LinkBatch();
00446                 $variantMap = array(); // maps $pdbkey_Variant => $keys (of link holders)
00447                 $output = $this->parent->getOutput();
00448                 $linkCache = LinkCache::singleton();
00449                 $threshold = $this->parent->getOptions()->getStubThreshold();
00450                 $titlesToBeConverted = '';
00451                 $titlesAttrs = array();
00452 
00453                 // Concatenate titles to a single string, thus we only need auto convert the
00454                 // single string to all variants. This would improve parser's performance
00455                 // significantly.
00456                 foreach ( $this->internals as $ns => $entries ) {
00457                         foreach ( $entries as $index => $entry ) {
00458                                 $pdbk = $entry['pdbk'];
00459                                 // we only deal with new links (in its first query)
00460                                 if ( !isset( $colours[$pdbk] ) || $colours[$pdbk] === 'new' ) {
00461                                         $title = $entry['title'];
00462                                         $titleText = $title->getText();
00463                                         $titlesAttrs[] = array(
00464                                                 'ns' => $ns,
00465                                                 'key' => "$ns:$index",
00466                                                 'titleText' => $titleText,
00467                                         );
00468                                         // separate titles with \0 because it would never appears
00469                                         // in a valid title
00470                                         $titlesToBeConverted .= $titleText . "\0";
00471                                 }
00472                         }
00473                 }
00474 
00475                 // Now do the conversion and explode string to text of titles
00476                 $titlesAllVariants = $wgContLang->autoConvertToAllVariants( rtrim( $titlesToBeConverted, "\0" ) );
00477                 $allVariantsName = array_keys( $titlesAllVariants );
00478                 foreach ( $titlesAllVariants as &$titlesVariant ) {
00479                         $titlesVariant = explode( "\0", $titlesVariant );
00480                 }
00481                 $l = count( $titlesAttrs );
00482                 // Then add variants of links to link batch
00483                 for ( $i = 0; $i < $l; $i ++ ) {
00484                         foreach ( $allVariantsName as $variantName ) {
00485                                 $textVariant = $titlesAllVariants[$variantName][$i];
00486                                 if ( $textVariant != $titlesAttrs[$i]['titleText'] ) {
00487                                         $variantTitle = Title::makeTitle( $titlesAttrs[$i]['ns'], $textVariant );
00488                                         if( is_null( $variantTitle ) ) {
00489                                                 continue;
00490                                         }
00491                                         $linkBatch->addObj( $variantTitle );
00492                                         $variantMap[$variantTitle->getPrefixedDBkey()][] = $titlesAttrs[$i]['key'];
00493                                 }
00494                         }
00495                 }
00496 
00497                 // process categories, check if a category exists in some variant
00498                 $categoryMap = array(); // maps $category_variant => $category (dbkeys)
00499                 $varCategories = array(); // category replacements oldDBkey => newDBkey
00500                 foreach( $output->getCategoryLinks() as $category ){
00501                         $variants = $wgContLang->autoConvertToAllVariants( $category );
00502                         foreach($variants as $variant){
00503                                 if($variant != $category){
00504                                         $variantTitle = Title::newFromDBkey( Title::makeName(NS_CATEGORY,$variant) );
00505                                         if(is_null($variantTitle)) continue;
00506                                         $linkBatch->addObj( $variantTitle );
00507                                         $categoryMap[$variant] = $category;
00508                                 }
00509                         }
00510                 }
00511 
00512 
00513                 if(!$linkBatch->isEmpty()){
00514                         // construct query
00515                         $dbr = wfGetDB( DB_SLAVE );
00516                         $varRes = $dbr->select( 'page',
00517                                 array( 'page_id', 'page_namespace', 'page_title', 'page_is_redirect', 'page_len', 'page_latest' ),
00518                                 $linkBatch->constructSet( 'page', $dbr ),
00519                                 __METHOD__
00520                         );
00521 
00522                         $linkcolour_ids = array();
00523 
00524                         // for each found variants, figure out link holders and replace
00525                         foreach ( $varRes as $s ) {
00526 
00527                                 $variantTitle = Title::makeTitle( $s->page_namespace, $s->page_title );
00528                                 $varPdbk = $variantTitle->getPrefixedDBkey();
00529                                 $vardbk = $variantTitle->getDBkey();
00530 
00531                                 $holderKeys = array();
00532                                 if( isset( $variantMap[$varPdbk] ) ) {
00533                                         $holderKeys = $variantMap[$varPdbk];
00534                                         $linkCache->addGoodLinkObjFromRow( $variantTitle, $s );
00535                                         $output->addLink( $variantTitle, $s->page_id );
00536                                 }
00537 
00538                                 // loop over link holders
00539                                 foreach( $holderKeys as $key ) {
00540                                         list( $ns, $index ) = explode( ':', $key, 2 );
00541                                         $entry =& $this->internals[$ns][$index];
00542                                         $pdbk = $entry['pdbk'];
00543 
00544                                         if ( !isset( $colours[$pdbk] ) || $colours[$pdbk] === 'new' ) {
00545                                                 // found link in some of the variants, replace the link holder data
00546                                                 $entry['title'] = $variantTitle;
00547                                                 $entry['pdbk'] = $varPdbk;
00548 
00549                                                 // set pdbk and colour
00550                                                 # @todo FIXME: Convoluted data flow
00551                                                 # The redirect status and length is passed to getLinkColour via the LinkCache
00552                                                 # Use formal parameters instead
00553                                                 $colours[$varPdbk] = Linker::getLinkColour( $variantTitle, $threshold );
00554                                                 $linkcolour_ids[$s->page_id] = $pdbk;
00555                                         }
00556                                 }
00557 
00558                                 // check if the object is a variant of a category
00559                                 if(isset($categoryMap[$vardbk])){
00560                                         $oldkey = $categoryMap[$vardbk];
00561                                         if($oldkey != $vardbk)
00562                                                 $varCategories[$oldkey]=$vardbk;
00563                                 }
00564                         }
00565                         wfRunHooks( 'GetLinkColours', array( $linkcolour_ids, &$colours ) );
00566 
00567                         // rebuild the categories in original order (if there are replacements)
00568                         if(count($varCategories)>0){
00569                                 $newCats = array();
00570                                 $originalCats = $output->getCategories();
00571                                 foreach($originalCats as $cat => $sortkey){
00572                                         // make the replacement
00573                                         if( array_key_exists($cat,$varCategories) )
00574                                                 $newCats[$varCategories[$cat]] = $sortkey;
00575                                         else $newCats[$cat] = $sortkey;
00576                                 }
00577                                 $output->setCategoryLinks($newCats);
00578                         }
00579                 }
00580         }
00581 
00589         function replaceText( $text ) {
00590                 wfProfileIn( __METHOD__ );
00591 
00592                 $text = preg_replace_callback(
00593                         '/<!--(LINK|IWLINK) (.*?)-->/',
00594                         array( &$this, 'replaceTextCallback' ),
00595                         $text );
00596 
00597                 wfProfileOut( __METHOD__ );
00598                 return $text;
00599         }
00600 
00608         function replaceTextCallback( $matches ) {
00609                 $type = $matches[1];
00610                 $key  = $matches[2];
00611                 if( $type == 'LINK' ) {
00612                         list( $ns, $index ) = explode( ':', $key, 2 );
00613                         if( isset( $this->internals[$ns][$index]['text'] ) ) {
00614                                 return $this->internals[$ns][$index]['text'];
00615                         }
00616                 } elseif( $type == 'IWLINK' ) {
00617                         if( isset( $this->interwikis[$key]['text'] ) ) {
00618                                 return $this->interwikis[$key]['text'];
00619                         }
00620                 }
00621                 return $matches[0];
00622         }
00623 }