]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - includes/parser/LinkHolderArray.php
MediaWiki 1.16.0
[autoinstallsdev/mediawiki.git] / includes / parser / LinkHolderArray.php
1 <?php
2
3 class LinkHolderArray {
4         var $internals = array(), $interwikis = array();
5         var $size = 0;
6         var $parent;
7
8         function __construct( $parent ) {
9                 $this->parent = $parent;
10         }
11
12         /**
13          * Reduce memory usage to reduce the impact of circular references
14          */
15         function __destruct() {
16                 foreach ( $this as $name => $value ) {
17                         unset( $this->$name );
18                 }
19         }
20
21         /**
22          * Merge another LinkHolderArray into this one
23          */
24         function merge( $other ) {
25                 foreach ( $other->internals as $ns => $entries ) {
26                         $this->size += count( $entries );
27                         if ( !isset( $this->internals[$ns] ) ) {
28                                 $this->internals[$ns] = $entries;
29                         } else {
30                                 $this->internals[$ns] += $entries;
31                         }
32                 }
33                 $this->interwikis += $other->interwikis;
34         }
35
36         /**
37          * Returns true if the memory requirements of this object are getting large
38          */
39         function isBig() {
40                 global $wgLinkHolderBatchSize;
41                 return $this->size > $wgLinkHolderBatchSize;
42         }
43
44         /**
45          * Clear all stored link holders.
46          * Make sure you don't have any text left using these link holders, before you call this
47          */
48         function clear() {
49                 $this->internals = array();
50                 $this->interwikis = array();
51                 $this->size = 0;
52         }
53
54         /**
55          * Make a link placeholder. The text returned can be later resolved to a real link with
56          * replaceLinkHolders(). This is done for two reasons: firstly to avoid further
57          * parsing of interwiki links, and secondly to allow all existence checks and
58          * article length checks (for stub links) to be bundled into a single query.
59          *
60          */
61         function makeHolder( $nt, $text = '', $query = '', $trail = '', $prefix = ''  ) {
62                 wfProfileIn( __METHOD__ );
63                 if ( ! is_object($nt) ) {
64                         # Fail gracefully
65                         $retVal = "<!-- ERROR -->{$prefix}{$text}{$trail}";
66                 } else {
67                         # Separate the link trail from the rest of the link
68                         list( $inside, $trail ) = Linker::splitTrail( $trail );
69
70                         $entry = array(
71                                 'title' => $nt,
72                                 'text' => $prefix.$text.$inside,
73                                 'pdbk' => $nt->getPrefixedDBkey(),
74                         );
75                         if ( $query !== '' ) {
76                                 $entry['query'] = $query;
77                         }
78
79                         if ( $nt->isExternal() ) {
80                                 // Use a globally unique ID to keep the objects mergable
81                                 $key = $this->parent->nextLinkID();
82                                 $this->interwikis[$key] = $entry;
83                                 $retVal = "<!--IWLINK $key-->{$trail}";
84                         } else {
85                                 $key = $this->parent->nextLinkID();
86                                 $ns = $nt->getNamespace();
87                                 $this->internals[$ns][$key] = $entry;
88                                 $retVal = "<!--LINK $ns:$key-->{$trail}";
89                         }
90                         $this->size++;
91                 }
92                 wfProfileOut( __METHOD__ );
93                 return $retVal;
94         }
95
96         /**
97          * Get the stub threshold
98          */
99         function getStubThreshold() {
100                 global $wgUser;
101                 if ( !isset( $this->stubThreshold ) ) {
102                         $this->stubThreshold = $wgUser->getOption('stubthreshold');
103                 }
104                 return $this->stubThreshold;
105         }
106
107         /**
108          * FIXME: update documentation. makeLinkObj() is deprecated.
109          * Replace <!--LINK--> link placeholders with actual links, in the buffer
110          * Placeholders created in Skin::makeLinkObj()
111          * Returns an array of link CSS classes, indexed by PDBK.
112          */
113         function replace( &$text ) {
114                 wfProfileIn( __METHOD__ );
115
116                 $colours = $this->replaceInternal( $text );
117                 $this->replaceInterwiki( $text );
118
119                 wfProfileOut( __METHOD__ );
120                 return $colours;
121         }
122
123         /**
124          * Replace internal links
125          */
126         protected function replaceInternal( &$text ) {
127                 if ( !$this->internals ) {
128                         return;
129                 }
130
131                 wfProfileIn( __METHOD__ );
132                 global $wgContLang;
133
134                 $colours = array();
135                 $sk = $this->parent->getOptions()->getSkin();
136                 $linkCache = LinkCache::singleton();
137                 $output = $this->parent->getOutput();
138
139                 wfProfileIn( __METHOD__.'-check' );
140                 $dbr = wfGetDB( DB_SLAVE );
141                 $page = $dbr->tableName( 'page' );
142                 $threshold = $this->getStubThreshold();
143
144                 # Sort by namespace
145                 ksort( $this->internals );
146
147                 # Generate query
148                 $query = false;
149                 $current = null;
150                 foreach ( $this->internals as $ns => $entries ) {
151                         foreach ( $entries as $index => $entry ) {
152                                 $key =  "$ns:$index";
153                                 $title = $entry['title'];
154                                 $pdbk = $entry['pdbk'];
155
156                                 # Skip invalid entries.
157                                 # Result will be ugly, but prevents crash.
158                                 if ( is_null( $title ) ) {
159                                         continue;
160                                 }
161
162                                 # Check if it's a static known link, e.g. interwiki
163                                 if ( $title->isAlwaysKnown() ) {
164                                         $colours[$pdbk] = '';
165                                 } elseif ( ( $id = $linkCache->getGoodLinkID( $pdbk ) ) != 0 ) {
166                                         $colours[$pdbk] = $sk->getLinkColour( $title, $threshold );
167                                         $output->addLink( $title, $id );
168                                 } elseif ( $linkCache->isBadLink( $pdbk ) ) {
169                                         $colours[$pdbk] = 'new';
170                                 } else {
171                                         # Not in the link cache, add it to the query
172                                         if ( !isset( $current ) ) {
173                                                 $current = $ns;
174                                                 $query =  "SELECT page_id, page_namespace, page_title, page_is_redirect, page_len";
175                                                 $query .= " FROM $page WHERE (page_namespace=$ns AND page_title IN(";
176                                         } elseif ( $current != $ns ) {
177                                                 $current = $ns;
178                                                 $query .= ")) OR (page_namespace=$ns AND page_title IN(";
179                                         } else {
180                                                 $query .= ', ';
181                                         }
182
183                                         $query .= $dbr->addQuotes( $title->getDBkey() );
184                                 }
185                         }
186                 }
187                 if ( $query ) {
188                         $query .= '))';
189
190                         $res = $dbr->query( $query, __METHOD__ );
191
192                         # Fetch data and form into an associative array
193                         # non-existent = broken
194                         $linkcolour_ids = array();
195                         while ( $s = $dbr->fetchObject($res) ) {
196                                 $title = Title::makeTitle( $s->page_namespace, $s->page_title );
197                                 $pdbk = $title->getPrefixedDBkey();
198                                 $linkCache->addGoodLinkObj( $s->page_id, $title, $s->page_len, $s->page_is_redirect );
199                                 $output->addLink( $title, $s->page_id );
200                                 # FIXME: convoluted data flow
201                                 # The redirect status and length is passed to getLinkColour via the LinkCache
202                                 # Use formal parameters instead
203                                 $colours[$pdbk] = $sk->getLinkColour( $title, $threshold );
204                                 //add id to the extension todolist
205                                 $linkcolour_ids[$s->page_id] = $pdbk;
206                         }
207                         unset( $res );
208                         //pass an array of page_ids to an extension
209                         wfRunHooks( 'GetLinkColours', array( $linkcolour_ids, &$colours ) );
210                 }
211                 wfProfileOut( __METHOD__.'-check' );
212
213                 # Do a second query for different language variants of links and categories
214                 if($wgContLang->hasVariants()) {
215                         $this->doVariants( $colours );
216                 }
217
218                 # Construct search and replace arrays
219                 wfProfileIn( __METHOD__.'-construct' );
220                 $replacePairs = array();
221                 foreach ( $this->internals as $ns => $entries ) {
222                         foreach ( $entries as $index => $entry ) {
223                                 $pdbk = $entry['pdbk'];
224                                 $title = $entry['title'];
225                                 $query = isset( $entry['query'] ) ? $entry['query'] : '';
226                                 $key = "$ns:$index";
227                                 $searchkey = "<!--LINK $key-->";
228                                 if ( !isset( $colours[$pdbk] ) || $colours[$pdbk] == 'new' ) {
229                                         $linkCache->addBadLinkObj( $title );
230                                         $colours[$pdbk] = 'new';
231                                         $output->addLink( $title, 0 );
232                                         // FIXME: replace deprecated makeBrokenLinkObj() by link()
233                                         $replacePairs[$searchkey] = $sk->makeBrokenLinkObj( $title,
234                                                                         $entry['text'],
235                                                                         $query );
236                                 } else {
237                                         // FIXME: replace deprecated makeColouredLinkObj() by link()
238                                         $replacePairs[$searchkey] = $sk->makeColouredLinkObj( $title, $colours[$pdbk],
239                                                                         $entry['text'],
240                                                                         $query );
241                                 }
242                         }
243                 }
244                 $replacer = new HashtableReplacer( $replacePairs, 1 );
245                 wfProfileOut( __METHOD__.'-construct' );
246
247                 # Do the thing
248                 wfProfileIn( __METHOD__.'-replace' );
249                 $text = preg_replace_callback(
250                         '/(<!--LINK .*?-->)/',
251                         $replacer->cb(),
252                         $text);
253
254                 wfProfileOut( __METHOD__.'-replace' );
255                 wfProfileOut( __METHOD__ );
256         }
257
258         /**
259          * Replace interwiki links
260          */
261         protected function replaceInterwiki( &$text ) {
262                 if ( empty( $this->interwikis ) ) {
263                         return;
264                 }
265
266                 wfProfileIn( __METHOD__ );
267                 # Make interwiki link HTML
268                 $sk = $this->parent->getOptions()->getSkin();
269                 $replacePairs = array();
270                 foreach( $this->interwikis as $key => $link ) {
271                         $replacePairs[$key] = $sk->link( $link['title'], $link['text'] );
272                 }
273                 $replacer = new HashtableReplacer( $replacePairs, 1 );
274
275                 $text = preg_replace_callback(
276                         '/<!--IWLINK (.*?)-->/',
277                         $replacer->cb(),
278                         $text );
279                 wfProfileOut( __METHOD__ );
280         }
281
282         /**
283          * Modify $this->internals and $colours according to language variant linking rules
284          */
285         protected function doVariants( &$colours ) {
286                 global $wgContLang;
287                 $linkBatch = new LinkBatch();
288                 $variantMap = array(); // maps $pdbkey_Variant => $keys (of link holders)
289                 $output = $this->parent->getOutput();
290                 $linkCache = LinkCache::singleton();
291                 $sk = $this->parent->getOptions()->getSkin();
292                 $threshold = $this->getStubThreshold();
293
294                 // Add variants of links to link batch
295                 foreach ( $this->internals as $ns => $entries ) {
296                         foreach ( $entries as $index => $entry ) {
297                                 $key = "$ns:$index";
298                                 $pdbk = $entry['pdbk'];
299                                 $title = $entry['title'];
300                                 $titleText = $title->getText();
301
302                                 // generate all variants of the link title text
303                                 $allTextVariants = $wgContLang->convertLinkToAllVariants($titleText);
304
305                                 // if link was not found (in first query), add all variants to query
306                                 if ( !isset($colours[$pdbk]) ){
307                                         foreach($allTextVariants as $textVariant){
308                                                 if($textVariant != $titleText){
309                                                         $variantTitle = Title::makeTitle( $ns, $textVariant );
310                                                         if(is_null($variantTitle)) continue;
311                                                         $linkBatch->addObj( $variantTitle );
312                                                         $variantMap[$variantTitle->getPrefixedDBkey()][] = $key;
313                                                 }
314                                         }
315                                 }
316                         }
317                 }
318
319                 // process categories, check if a category exists in some variant
320                 $categoryMap = array(); // maps $category_variant => $category (dbkeys)
321                 $varCategories = array(); // category replacements oldDBkey => newDBkey
322                 foreach( $output->getCategoryLinks() as $category ){
323                         $variants = $wgContLang->convertLinkToAllVariants($category);
324                         foreach($variants as $variant){
325                                 if($variant != $category){
326                                         $variantTitle = Title::newFromDBkey( Title::makeName(NS_CATEGORY,$variant) );
327                                         if(is_null($variantTitle)) continue;
328                                         $linkBatch->addObj( $variantTitle );
329                                         $categoryMap[$variant] = $category;
330                                 }
331                         }
332                 }
333
334
335                 if(!$linkBatch->isEmpty()){
336                         // construct query
337                         $dbr = wfGetDB( DB_SLAVE );
338                         $page = $dbr->tableName( 'page' );
339                         $titleClause = $linkBatch->constructSet('page', $dbr);
340                         $variantQuery =  "SELECT page_id, page_namespace, page_title, page_is_redirect, page_len";
341                         $variantQuery .= " FROM $page WHERE $titleClause";
342                         $varRes = $dbr->query( $variantQuery, __METHOD__ );
343                         $linkcolour_ids = array();
344
345                         // for each found variants, figure out link holders and replace
346                         while ( $s = $dbr->fetchObject($varRes) ) {
347
348                                 $variantTitle = Title::makeTitle( $s->page_namespace, $s->page_title );
349                                 $varPdbk = $variantTitle->getPrefixedDBkey();
350                                 $vardbk = $variantTitle->getDBkey();
351
352                                 $holderKeys = array();
353                                 if(isset($variantMap[$varPdbk])){
354                                         $holderKeys = $variantMap[$varPdbk];
355                                         $linkCache->addGoodLinkObj( $s->page_id, $variantTitle, $s->page_len, $s->page_is_redirect );
356                                         $output->addLink( $variantTitle, $s->page_id );
357                                 }
358
359                                 // loop over link holders
360                                 foreach($holderKeys as $key){
361                                         list( $ns, $index ) = explode( ':', $key, 2 );
362                                         $entry =& $this->internals[$ns][$index];
363                                         $pdbk = $entry['pdbk'];
364
365                                         if(!isset($colours[$pdbk])){
366                                                 // found link in some of the variants, replace the link holder data
367                                                 $entry['title'] = $variantTitle;
368                                                 $entry['pdbk'] = $varPdbk;
369
370                                                 // set pdbk and colour
371                                                 # FIXME: convoluted data flow
372                                                 # The redirect status and length is passed to getLinkColour via the LinkCache
373                                                 # Use formal parameters instead
374                                                 $colours[$varPdbk] = $sk->getLinkColour( $variantTitle, $threshold );
375                                                 $linkcolour_ids[$s->page_id] = $pdbk;
376                                         }
377                                 }
378
379                                 // check if the object is a variant of a category
380                                 if(isset($categoryMap[$vardbk])){
381                                         $oldkey = $categoryMap[$vardbk];
382                                         if($oldkey != $vardbk)
383                                                 $varCategories[$oldkey]=$vardbk;
384                                 }
385                         }
386                         wfRunHooks( 'GetLinkColours', array( $linkcolour_ids, &$colours ) );
387
388                         // rebuild the categories in original order (if there are replacements)
389                         if(count($varCategories)>0){
390                                 $newCats = array();
391                                 $originalCats = $output->getCategories();
392                                 foreach($originalCats as $cat => $sortkey){
393                                         // make the replacement
394                                         if( array_key_exists($cat,$varCategories) )
395                                                 $newCats[$varCategories[$cat]] = $sortkey;
396                                         else $newCats[$cat] = $sortkey;
397                                 }
398                                 $output->setCategoryLinks($newCats);
399                         }
400                 }
401         }
402
403         /**
404          * Replace <!--LINK--> link placeholders with plain text of links
405          * (not HTML-formatted).
406          * @param string $text
407          * @return string
408          */
409         function replaceText( $text ) {
410                 wfProfileIn( __METHOD__ );
411
412                 $text = preg_replace_callback(
413                         '/<!--(LINK|IWLINK) (.*?)-->/',
414                         array( &$this, 'replaceTextCallback' ),
415                         $text );
416
417                 wfProfileOut( __METHOD__ );
418                 return $text;
419         }
420
421         /**
422          * @param array $matches
423          * @return string
424          * @private
425          */
426         function replaceTextCallback( $matches ) {
427                 $type = $matches[1];
428                 $key  = $matches[2];
429                 if( $type == 'LINK' ) {
430                         list( $ns, $index ) = explode( ':', $key, 2 );
431                         if( isset( $this->internals[$ns][$index]['text'] ) ) {
432                                 return $this->internals[$ns][$index]['text'];
433                         }
434                 } elseif( $type == 'IWLINK' ) {
435                         if( isset( $this->interwikis[$key]['text'] ) ) {
436                                 return $this->interwikis[$key]['text'];
437                         }
438                 }
439                 return $matches[0];
440         }
441 }