3 class LinkHolderArray
{
6 var $internals = array(), $interwikis = array();
10 function __construct( $parent ) {
11 $this->parent
= $parent;
15 * Merge another LinkHolderArray into this one
17 function merge( $other ) {
18 foreach ( $other->internals
as $ns => $entries ) {
19 $this->size +
= count( $entries );
20 if ( !isset( $this->internals
[$ns] ) ) {
21 $this->internals
[$ns] = $entries;
23 $this->internals
[$ns] +
= $entries;
26 $this->interwikis +
= $other->interwikis
;
30 * Returns true if the memory requirements of this object are getting large
33 return $this->size
> $this->batchSize
;
37 * Clear all stored link holders.
38 * Make sure you don't have any text left using these link holders, before you call this
41 $this->internals
= array();
42 $this->interwikis
= array();
47 * Make a link placeholder. The text returned can be later resolved to a real link with
48 * replaceLinkHolders(). This is done for two reasons: firstly to avoid further
49 * parsing of interwiki links, and secondly to allow all existence checks and
50 * article length checks (for stub links) to be bundled into a single query.
53 function makeHolder( $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
54 wfProfileIn( __METHOD__
);
55 if ( ! is_object($nt) ) {
57 $retVal = "<!-- ERROR -->{$prefix}{$text}{$trail}";
59 # Separate the link trail from the rest of the link
60 list( $inside, $trail ) = Linker
::splitTrail( $trail );
64 'text' => $prefix.$text.$inside,
65 'pdbk' => $nt->getPrefixedDBkey(),
67 if ( $query !== '' ) {
68 $entry['query'] = $query;
71 if ( $nt->isExternal() ) {
72 // Use a globally unique ID to keep the objects mergable
73 $key = $this->parent
->nextLinkID();
74 $this->interwikis
['titles'][$key] = $entry;
75 $retVal = "<!--IWLINK $key-->{$trail}";
77 $key = $this->parent
->nextLinkID();
78 $ns = $nt->getNamespace();
79 $this->internals
[$ns][$key] = $entry;
80 $retVal = "<!--LINK $ns:$key-->{$trail}";
84 wfProfileOut( __METHOD__
);
89 * Replace <!--LINK--> link placeholders with actual links, in the buffer
90 * Placeholders created in Skin::makeLinkObj()
91 * Returns an array of link CSS classes, indexed by PDBK.
93 function replace( &$text ) {
94 wfProfileIn( __METHOD__
);
96 $colours = $this->replaceInternal( $text );
97 $this->replaceInterwiki( $text );
99 wfProfileOut( __METHOD__
);
104 * Replace internal links
106 protected function replaceInternal( &$text ) {
107 if ( !$this->internals
) {
111 wfProfileIn( __METHOD__
);
112 global $wgUser, $wgContLang;
116 $linkcolour_ids = array();
117 $sk = $this->parent
->getOptions()->getSkin();
118 $linkCache = LinkCache
::singleton();
119 $output = $this->parent
->getOutput();
121 wfProfileIn( __METHOD__
.'-check' );
122 $dbr = wfGetDB( DB_SLAVE
);
123 $page = $dbr->tableName( 'page' );
124 $threshold = $wgUser->getOption('stubthreshold');
127 ksort( $this->internals
);
132 foreach ( $this->internals
as $ns => $entries ) {
133 foreach ( $entries as $index => $entry ) {
135 $title = $entry['title'];
136 $pdbk = $entry['pdbk'];
138 # Skip invalid entries.
139 # Result will be ugly, but prevents crash.
140 if ( is_null( $title ) ) {
144 # Check if it's a static known link, e.g. interwiki
145 if ( $title->isAlwaysKnown() ) {
146 $colours[$pdbk] = '';
147 } elseif ( ( $id = $linkCache->getGoodLinkID( $pdbk ) ) != 0 ) {
148 $colours[$pdbk] = '';
149 $output->addLink( $title, $id );
150 } elseif ( $linkCache->isBadLink( $pdbk ) ) {
151 $colours[$pdbk] = 'new';
152 } elseif ( $title->getNamespace() == NS_SPECIAL
&& !SpecialPage
::exists( $pdbk ) ) {
153 $colours[$pdbk] = 'new';
155 # Not in the link cache, add it to the query
156 if ( !isset( $current ) ) {
158 $query = "SELECT page_id, page_namespace, page_title, page_is_redirect, page_len";
159 $query .= " FROM $page WHERE (page_namespace=$ns AND page_title IN(";
160 } elseif ( $current != $ns ) {
162 $query .= ")) OR (page_namespace=$ns AND page_title IN(";
167 $query .= $dbr->addQuotes( $title->getDBkey() );
174 $res = $dbr->query( $query, __METHOD__
);
176 # Fetch data and form into an associative array
177 # non-existent = broken
178 while ( $s = $dbr->fetchObject($res) ) {
179 $title = Title
::makeTitle( $s->page_namespace
, $s->page_title
);
180 $pdbk = $title->getPrefixedDBkey();
181 $linkCache->addGoodLinkObj( $s->page_id
, $title, $s->page_len
, $s->page_is_redirect
);
182 $output->addLink( $title, $s->page_id
);
183 $colours[$pdbk] = $sk->getLinkColour( $title, $threshold );
184 //add id to the extension todolist
185 $linkcolour_ids[$s->page_id
] = $pdbk;
188 //pass an array of page_ids to an extension
189 wfRunHooks( 'GetLinkColours', array( $linkcolour_ids, &$colours ) );
191 wfProfileOut( __METHOD__
.'-check' );
193 # Do a second query for different language variants of links and categories
194 if($wgContLang->hasVariants()){
195 $linkBatch = new LinkBatch();
196 $variantMap = array(); // maps $pdbkey_Variant => $keys (of link holders)
197 $categoryMap = array(); // maps $category_variant => $category (dbkeys)
198 $varCategories = array(); // category replacements oldDBkey => newDBkey
200 $categories = $output->getCategoryLinks();
202 // Add variants of links to link batch
203 foreach ( $this->internals
as $ns => $entries ) {
204 foreach ( $entries as $index => $entry ) {
206 $pdbk = $entry['pdbk'];
207 $title = $entry['title'];
208 $titleText = $title->getText();
210 // generate all variants of the link title text
211 $allTextVariants = $wgContLang->convertLinkToAllVariants($titleText);
213 // if link was not found (in first query), add all variants to query
214 if ( !isset($colours[$pdbk]) ){
215 foreach($allTextVariants as $textVariant){
216 if($textVariant != $titleText){
217 $variantTitle = Title
::makeTitle( $ns, $textVariant );
218 if(is_null($variantTitle)) continue;
219 $linkBatch->addObj( $variantTitle );
220 $variantMap[$variantTitle->getPrefixedDBkey()][] = $key;
227 // process categories, check if a category exists in some variant
228 foreach( $categories as $category ){
229 $variants = $wgContLang->convertLinkToAllVariants($category);
230 foreach($variants as $variant){
231 if($variant != $category){
232 $variantTitle = Title
::newFromDBkey( Title
::makeName(NS_CATEGORY
,$variant) );
233 if(is_null($variantTitle)) continue;
234 $linkBatch->addObj( $variantTitle );
235 $categoryMap[$variant] = $category;
241 if(!$linkBatch->isEmpty()){
243 $titleClause = $linkBatch->constructSet('page', $dbr);
245 $variantQuery = "SELECT page_id, page_namespace, page_title, page_is_redirect, page_len";
247 $variantQuery .= " FROM $page WHERE $titleClause";
249 $varRes = $dbr->query( $variantQuery, __METHOD__
);
251 // for each found variants, figure out link holders and replace
252 while ( $s = $dbr->fetchObject($varRes) ) {
254 $variantTitle = Title
::makeTitle( $s->page_namespace
, $s->page_title
);
255 $varPdbk = $variantTitle->getPrefixedDBkey();
256 $vardbk = $variantTitle->getDBkey();
258 $holderKeys = array();
259 if(isset($variantMap[$varPdbk])){
260 $holderKeys = $variantMap[$varPdbk];
261 $linkCache->addGoodLinkObj( $s->page_id
, $variantTitle, $s->page_len
, $s->page_is_redirect
);
262 $output->addLink( $variantTitle, $s->page_id
);
265 // loop over link holders
266 foreach($holderKeys as $key){
267 list( $ns, $index ) = explode( ':', $key, 2 );
268 $entry =& $this->internals
[$ns][$index];
269 $pdbk = $entry['pdbk'];
271 if(!isset($colours[$pdbk])){
272 // found link in some of the variants, replace the link holder data
273 $entry['title'] = $variantTitle;
274 $entry['pdbk'] = $varPdbk;
276 // set pdbk and colour
277 $colours[$varPdbk] = $sk->getLinkColour( $variantTitle, $threshold );
278 $linkcolour_ids[$s->page_id
] = $pdbk;
280 wfRunHooks( 'GetLinkColours', array( $linkcolour_ids, &$colours ) );
283 // check if the object is a variant of a category
284 if(isset($categoryMap[$vardbk])){
285 $oldkey = $categoryMap[$vardbk];
286 if($oldkey != $vardbk)
287 $varCategories[$oldkey]=$vardbk;
291 // rebuild the categories in original order (if there are replacements)
292 if(count($varCategories)>0){
294 $originalCats = $output->getCategories();
295 foreach($originalCats as $cat => $sortkey){
296 // make the replacement
297 if( array_key_exists($cat,$varCategories) )
298 $newCats[$varCategories[$cat]] = $sortkey;
299 else $newCats[$cat] = $sortkey;
301 $this->mOutput
->parent
->setCategoryLinks($newCats);
306 # Construct search and replace arrays
307 wfProfileIn( __METHOD__
.'-construct' );
308 $replacePairs = array();
309 foreach ( $this->internals
as $ns => $entries ) {
310 foreach ( $entries as $index => $entry ) {
311 $pdbk = $entry['pdbk'];
312 $title = $entry['title'];
313 $query = isset( $entry['query'] ) ?
$entry['query'] : '';
315 $searchkey = "<!--LINK $key-->";
316 if ( !isset( $colours[$pdbk] ) ||
$colours[$pdbk] == 'new' ) {
317 $linkCache->addBadLinkObj( $title );
318 $colours[$pdbk] = 'new';
319 $output->addLink( $title, 0 );
320 $replacePairs[$searchkey] = $sk->makeBrokenLinkObj( $title,
324 $replacePairs[$searchkey] = $sk->makeColouredLinkObj( $title, $colours[$pdbk],
330 $replacer = new HashtableReplacer( $replacePairs, 1 );
331 wfProfileOut( __METHOD__
.'-construct' );
334 wfProfileIn( __METHOD__
.'-replace' );
335 $text = preg_replace_callback(
336 '/(<!--LINK .*?-->)/',
340 wfProfileOut( __METHOD__
.'-replace' );
341 wfProfileOut( __METHOD__
);
345 * Replace interwiki links
347 protected function replaceInterwiki( &$text ) {
348 if ( empty( $this->mInterwikiLinkHolders
['texts'] ) ) {
352 wfProfileIn( __METHOD__
);
353 # Make interwiki link HTML
354 $replacePairs = array();
355 foreach( $this->mInterwikiLinkHolders
['texts'] as $key => $link ) {
356 $title = $this->mInterwikiLinkHolders
['titles'][$key];
357 $replacePairs[$key] = $sk->link( $title, $link );
359 $replacer = new HashtableReplacer( $replacePairs, 1 );
361 $text = preg_replace_callback(
362 '/<!--IWLINK (.*?)-->/',
365 wfProfileOut( __METHOD__
);
369 * Replace <!--LINK--> link placeholders with plain text of links
370 * (not HTML-formatted).
371 * @param string $text
374 function replaceText( $text ) {
375 wfProfileIn( __METHOD__
);
377 $text = preg_replace_callback(
378 '/<!--(LINK|IWLINK) (.*?)-->/',
379 array( &$this, 'replaceTextCallback' ),
382 wfProfileOut( __METHOD__
);
387 * @param array $matches
391 function replaceTextCallback( $matches ) {
394 if( $type == 'LINK' ) {
395 list( $ns, $index ) = explode( ':', $key, 2 );
396 if( isset( $this->internals
[$ns][$index]['text'] ) ) {
397 return $this->internals
[$ns][$index]['text'];
399 } elseif( $type == 'IWLINK' ) {
400 if( isset( $this->interwikis
[$key]['text'] ) ) {
401 return $this->interwikis
[$key]['text'];