Index: trunk/phase3/includes/parser/LinkHolderArray.php |
— | — | @@ -0,0 +1,406 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +class LinkHolderArray { |
| 5 | + var $batchSize = 1000; |
| 6 | + |
| 7 | + var $internals = array(), $interwikis = array(); |
| 8 | + var $size = 0; |
| 9 | + var $parent; |
| 10 | + |
| 11 | + function __construct( $parent ) { |
| 12 | + $this->parent = $parent; |
| 13 | + } |
| 14 | + |
| 15 | + /** |
| 16 | + * Merge another LinkHolderArray into this one |
| 17 | + */ |
| 18 | + function merge( $other ) { |
| 19 | + foreach ( $other->internals as $ns => $entries ) { |
| 20 | + $this->size += count( $entries ); |
| 21 | + if ( !isset( $this->internals[$ns] ) ) { |
| 22 | + $this->internals[$ns] = $entries; |
| 23 | + } else { |
| 24 | + $this->internals[$ns] += $entries; |
| 25 | + } |
| 26 | + } |
| 27 | + $this->interwikis += $other->interwikis; |
| 28 | + } |
| 29 | + |
| 30 | + /** |
| 31 | + * Returns true if the memory requirements of this object are getting large |
| 32 | + */ |
| 33 | + function isBig() { |
| 34 | + return $this->size > $this->batchSize; |
| 35 | + } |
| 36 | + |
| 37 | + /** |
| 38 | + * Clear all stored link holders. |
| 39 | + * Make sure you don't have any text left using these link holders, before you call this |
| 40 | + */ |
| 41 | + function clear() { |
| 42 | + $this->internals = array(); |
| 43 | + $this->interwikis = array(); |
| 44 | + $this->size = 0; |
| 45 | + } |
| 46 | + |
| 47 | + /** |
| 48 | + * Make a link placeholder. The text returned can be later resolved to a real link with |
| 49 | + * replaceLinkHolders(). This is done for two reasons: firstly to avoid further |
| 50 | + * parsing of interwiki links, and secondly to allow all existence checks and |
| 51 | + * article length checks (for stub links) to be bundled into a single query. |
| 52 | + * |
| 53 | + */ |
| 54 | + function makeHolder( $nt, $text = '', $query = '', $trail = '', $prefix = '' ) { |
| 55 | + wfProfileIn( __METHOD__ ); |
| 56 | + if ( ! is_object($nt) ) { |
| 57 | + # Fail gracefully |
| 58 | + $retVal = "<!-- ERROR -->{$prefix}{$text}{$trail}"; |
| 59 | + } else { |
| 60 | + # Separate the link trail from the rest of the link |
| 61 | + list( $inside, $trail ) = Linker::splitTrail( $trail ); |
| 62 | + |
| 63 | + $entry = array( |
| 64 | + 'title' => $nt, |
| 65 | + 'text' => $prefix.$text.$inside, |
| 66 | + 'pdbk' => $nt->getPrefixedDBkey(), |
| 67 | + ); |
| 68 | + if ( $query !== '' ) { |
| 69 | + $entry['query'] = $query; |
| 70 | + } |
| 71 | + |
| 72 | + if ( $nt->isExternal() ) { |
| 73 | + // Use a globally unique ID to keep the objects mergable |
| 74 | + $key = $this->parent->nextLinkID(); |
| 75 | + $this->interwikis['titles'][$key] = $entry; |
| 76 | + $retVal = "<!--IWLINK $key-->{$trail}"; |
| 77 | + } else { |
| 78 | + $key = $this->parent->nextLinkID(); |
| 79 | + $ns = $nt->getNamespace(); |
| 80 | + $this->internals[$ns][$key] = $entry; |
| 81 | + $retVal = "<!--LINK $ns:$key-->{$trail}"; |
| 82 | + } |
| 83 | + $this->size++; |
| 84 | + } |
| 85 | + wfProfileOut( __METHOD__ ); |
| 86 | + return $retVal; |
| 87 | + } |
| 88 | + |
| 89 | + /** |
| 90 | + * Replace <!--LINK--> link placeholders with actual links, in the buffer |
| 91 | + * Placeholders created in Skin::makeLinkObj() |
| 92 | + * Returns an array of link CSS classes, indexed by PDBK. |
| 93 | + */ |
| 94 | + function replace( &$text ) { |
| 95 | + wfProfileIn( __METHOD__ ); |
| 96 | + |
| 97 | + $colours = $this->replaceInternal( $text ); |
| 98 | + $this->replaceInterwiki( $text ); |
| 99 | + |
| 100 | + wfProfileOut( __METHOD__ ); |
| 101 | + return $colours; |
| 102 | + } |
| 103 | + |
| 104 | + /** |
| 105 | + * Replace internal links |
| 106 | + */ |
| 107 | + protected function replaceInternal( &$text ) { |
| 108 | + if ( !$this->internals ) { |
| 109 | + return; |
| 110 | + } |
| 111 | + |
| 112 | + wfProfileIn( __METHOD__ ); |
| 113 | + global $wgUser, $wgContLang; |
| 114 | + |
| 115 | + $pdbks = array(); |
| 116 | + $colours = array(); |
| 117 | + $linkcolour_ids = array(); |
| 118 | + $sk = $this->parent->getOptions()->getSkin(); |
| 119 | + $linkCache = LinkCache::singleton(); |
| 120 | + $output = $this->parent->getOutput(); |
| 121 | + |
| 122 | + wfProfileIn( __METHOD__.'-check' ); |
| 123 | + $dbr = wfGetDB( DB_SLAVE ); |
| 124 | + $page = $dbr->tableName( 'page' ); |
| 125 | + $threshold = $wgUser->getOption('stubthreshold'); |
| 126 | + |
| 127 | + # Sort by namespace |
| 128 | + ksort( $this->internals ); |
| 129 | + |
| 130 | + # Generate query |
| 131 | + $query = false; |
| 132 | + $current = null; |
| 133 | + foreach ( $this->internals as $ns => $entries ) { |
| 134 | + foreach ( $entries as $index => $entry ) { |
| 135 | + $key = "$ns:$index"; |
| 136 | + $title = $entry['title']; |
| 137 | + $pdbk = $entry['pdbk']; |
| 138 | + |
| 139 | + # Skip invalid entries. |
| 140 | + # Result will be ugly, but prevents crash. |
| 141 | + if ( is_null( $title ) ) { |
| 142 | + continue; |
| 143 | + } |
| 144 | + |
| 145 | + # Check if it's a static known link, e.g. interwiki |
| 146 | + if ( $title->isAlwaysKnown() ) { |
| 147 | + $colours[$pdbk] = ''; |
| 148 | + } elseif ( ( $id = $linkCache->getGoodLinkID( $pdbk ) ) != 0 ) { |
| 149 | + $colours[$pdbk] = ''; |
| 150 | + $output->addLink( $title, $id ); |
| 151 | + } elseif ( $linkCache->isBadLink( $pdbk ) ) { |
| 152 | + $colours[$pdbk] = 'new'; |
| 153 | + } elseif ( $title->getNamespace() == NS_SPECIAL && !SpecialPage::exists( $pdbk ) ) { |
| 154 | + $colours[$pdbk] = 'new'; |
| 155 | + } else { |
| 156 | + # Not in the link cache, add it to the query |
| 157 | + if ( !isset( $current ) ) { |
| 158 | + $current = $ns; |
| 159 | + $query = "SELECT page_id, page_namespace, page_title, page_is_redirect, page_len"; |
| 160 | + $query .= " FROM $page WHERE (page_namespace=$ns AND page_title IN("; |
| 161 | + } elseif ( $current != $ns ) { |
| 162 | + $current = $ns; |
| 163 | + $query .= ")) OR (page_namespace=$ns AND page_title IN("; |
| 164 | + } else { |
| 165 | + $query .= ', '; |
| 166 | + } |
| 167 | + |
| 168 | + $query .= $dbr->addQuotes( $title->getDBkey() ); |
| 169 | + } |
| 170 | + } |
| 171 | + } |
| 172 | + if ( $query ) { |
| 173 | + $query .= '))'; |
| 174 | + |
| 175 | + $res = $dbr->query( $query, __METHOD__ ); |
| 176 | + |
| 177 | + # Fetch data and form into an associative array |
| 178 | + # non-existent = broken |
| 179 | + while ( $s = $dbr->fetchObject($res) ) { |
| 180 | + $title = Title::makeTitle( $s->page_namespace, $s->page_title ); |
| 181 | + $pdbk = $title->getPrefixedDBkey(); |
| 182 | + $linkCache->addGoodLinkObj( $s->page_id, $title, $s->page_len, $s->page_is_redirect ); |
| 183 | + $output->addLink( $title, $s->page_id ); |
| 184 | + $colours[$pdbk] = $sk->getLinkColour( $title, $threshold ); |
| 185 | + //add id to the extension todolist |
| 186 | + $linkcolour_ids[$s->page_id] = $pdbk; |
| 187 | + } |
| 188 | + unset( $res ); |
| 189 | + //pass an array of page_ids to an extension |
| 190 | + wfRunHooks( 'GetLinkColours', array( $linkcolour_ids, &$colours ) ); |
| 191 | + } |
| 192 | + wfProfileOut( __METHOD__.'-check' ); |
| 193 | + |
| 194 | + # Do a second query for different language variants of links and categories |
| 195 | + if($wgContLang->hasVariants()){ |
| 196 | + $linkBatch = new LinkBatch(); |
| 197 | + $variantMap = array(); // maps $pdbkey_Variant => $keys (of link holders) |
| 198 | + $categoryMap = array(); // maps $category_variant => $category (dbkeys) |
| 199 | + $varCategories = array(); // category replacements oldDBkey => newDBkey |
| 200 | + |
| 201 | + $categories = $output->getCategoryLinks(); |
| 202 | + |
| 203 | + // Add variants of links to link batch |
| 204 | + foreach ( $this->internals as $ns => $entries ) { |
| 205 | + foreach ( $entries as $index => $entry ) { |
| 206 | + $key = "$ns:$index"; |
| 207 | + $pdbk = $entry['pdbk']; |
| 208 | + $title = $entry['title']; |
| 209 | + $titleText = $title->getText(); |
| 210 | + |
| 211 | + // generate all variants of the link title text |
| 212 | + $allTextVariants = $wgContLang->convertLinkToAllVariants($titleText); |
| 213 | + |
| 214 | + // if link was not found (in first query), add all variants to query |
| 215 | + if ( !isset($colours[$pdbk]) ){ |
| 216 | + foreach($allTextVariants as $textVariant){ |
| 217 | + if($textVariant != $titleText){ |
| 218 | + $variantTitle = Title::makeTitle( $ns, $textVariant ); |
| 219 | + if(is_null($variantTitle)) continue; |
| 220 | + $linkBatch->addObj( $variantTitle ); |
| 221 | + $variantMap[$variantTitle->getPrefixedDBkey()][] = $key; |
| 222 | + } |
| 223 | + } |
| 224 | + } |
| 225 | + } |
| 226 | + } |
| 227 | + |
| 228 | + // process categories, check if a category exists in some variant |
| 229 | + foreach( $categories as $category ){ |
| 230 | + $variants = $wgContLang->convertLinkToAllVariants($category); |
| 231 | + foreach($variants as $variant){ |
| 232 | + if($variant != $category){ |
| 233 | + $variantTitle = Title::newFromDBkey( Title::makeName(NS_CATEGORY,$variant) ); |
| 234 | + if(is_null($variantTitle)) continue; |
| 235 | + $linkBatch->addObj( $variantTitle ); |
| 236 | + $categoryMap[$variant] = $category; |
| 237 | + } |
| 238 | + } |
| 239 | + } |
| 240 | + |
| 241 | + |
| 242 | + if(!$linkBatch->isEmpty()){ |
| 243 | + // construct query |
| 244 | + $titleClause = $linkBatch->constructSet('page', $dbr); |
| 245 | + |
| 246 | + $variantQuery = "SELECT page_id, page_namespace, page_title, page_is_redirect, page_len"; |
| 247 | + |
| 248 | + $variantQuery .= " FROM $page WHERE $titleClause"; |
| 249 | + |
| 250 | + $varRes = $dbr->query( $variantQuery, __METHOD__ ); |
| 251 | + |
| 252 | + // for each found variants, figure out link holders and replace |
| 253 | + while ( $s = $dbr->fetchObject($varRes) ) { |
| 254 | + |
| 255 | + $variantTitle = Title::makeTitle( $s->page_namespace, $s->page_title ); |
| 256 | + $varPdbk = $variantTitle->getPrefixedDBkey(); |
| 257 | + $vardbk = $variantTitle->getDBkey(); |
| 258 | + |
| 259 | + $holderKeys = array(); |
| 260 | + if(isset($variantMap[$varPdbk])){ |
| 261 | + $holderKeys = $variantMap[$varPdbk]; |
| 262 | + $linkCache->addGoodLinkObj( $s->page_id, $variantTitle, $s->page_len, $s->page_is_redirect ); |
| 263 | + $output->addLink( $variantTitle, $s->page_id ); |
| 264 | + } |
| 265 | + |
| 266 | + // loop over link holders |
| 267 | + foreach($holderKeys as $key){ |
| 268 | + list( $ns, $index ) = explode( ':', $key, 2 ); |
| 269 | + $entry =& $this->internals[$ns][$index]; |
| 270 | + $pdbk = $entry['pdbk']; |
| 271 | + |
| 272 | + if(!isset($colours[$pdbk])){ |
| 273 | + // found link in some of the variants, replace the link holder data |
| 274 | + $entry['title'] = $variantTitle; |
| 275 | + $entry['pdbk'] = $varPdbk; |
| 276 | + |
| 277 | + // set pdbk and colour |
| 278 | + $colours[$varPdbk] = $sk->getLinkColour( $variantTitle, $threshold ); |
| 279 | + $linkcolour_ids[$s->page_id] = $pdbk; |
| 280 | + } |
| 281 | + wfRunHooks( 'GetLinkColours', array( $linkcolour_ids, &$colours ) ); |
| 282 | + } |
| 283 | + |
| 284 | + // check if the object is a variant of a category |
| 285 | + if(isset($categoryMap[$vardbk])){ |
| 286 | + $oldkey = $categoryMap[$vardbk]; |
| 287 | + if($oldkey != $vardbk) |
| 288 | + $varCategories[$oldkey]=$vardbk; |
| 289 | + } |
| 290 | + } |
| 291 | + |
| 292 | + // rebuild the categories in original order (if there are replacements) |
| 293 | + if(count($varCategories)>0){ |
| 294 | + $newCats = array(); |
| 295 | + $originalCats = $output->getCategories(); |
| 296 | + foreach($originalCats as $cat => $sortkey){ |
| 297 | + // make the replacement |
| 298 | + if( array_key_exists($cat,$varCategories) ) |
| 299 | + $newCats[$varCategories[$cat]] = $sortkey; |
| 300 | + else $newCats[$cat] = $sortkey; |
| 301 | + } |
| 302 | + $this->mOutput->parent->setCategoryLinks($newCats); |
| 303 | + } |
| 304 | + } |
| 305 | + } |
| 306 | + |
| 307 | + # Construct search and replace arrays |
| 308 | + wfProfileIn( __METHOD__.'-construct' ); |
| 309 | + $replacePairs = array(); |
| 310 | + foreach ( $this->internals as $ns => $entries ) { |
| 311 | + foreach ( $entries as $index => $entry ) { |
| 312 | + $pdbk = $entry['pdbk']; |
| 313 | + $title = $entry['title']; |
| 314 | + $query = isset( $entry['query'] ) ? $entry['query'] : ''; |
| 315 | + $key = "$ns:$index"; |
| 316 | + $searchkey = "<!--LINK $key-->"; |
| 317 | + if ( !isset( $colours[$pdbk] ) || $colours[$pdbk] == 'new' ) { |
| 318 | + $linkCache->addBadLinkObj( $title ); |
| 319 | + $colours[$pdbk] = 'new'; |
| 320 | + $output->addLink( $title, 0 ); |
| 321 | + $replacePairs[$searchkey] = $sk->makeBrokenLinkObj( $title, |
| 322 | + $entry['text'], |
| 323 | + $query ); |
| 324 | + } else { |
| 325 | + $replacePairs[$searchkey] = $sk->makeColouredLinkObj( $title, $colours[$pdbk], |
| 326 | + $entry['text'], |
| 327 | + $query ); |
| 328 | + } |
| 329 | + } |
| 330 | + } |
| 331 | + $replacer = new HashtableReplacer( $replacePairs, 1 ); |
| 332 | + wfProfileOut( __METHOD__.'-construct' ); |
| 333 | + |
| 334 | + # Do the thing |
| 335 | + wfProfileIn( __METHOD__.'-replace' ); |
| 336 | + $text = preg_replace_callback( |
| 337 | + '/(<!--LINK .*?-->)/', |
| 338 | + $replacer->cb(), |
| 339 | + $text); |
| 340 | + |
| 341 | + wfProfileOut( __METHOD__.'-replace' ); |
| 342 | + wfProfileOut( __METHOD__ ); |
| 343 | + } |
| 344 | + |
| 345 | + /** |
| 346 | + * Replace interwiki links |
| 347 | + */ |
| 348 | + protected function replaceInterwiki( &$text ) { |
| 349 | + if ( empty( $this->mInterwikiLinkHolders['texts'] ) ) { |
| 350 | + return; |
| 351 | + } |
| 352 | + |
| 353 | + wfProfileIn( __METHOD__ ); |
| 354 | + # Make interwiki link HTML |
| 355 | + $replacePairs = array(); |
| 356 | + foreach( $this->mInterwikiLinkHolders['texts'] as $key => $link ) { |
| 357 | + $title = $this->mInterwikiLinkHolders['titles'][$key]; |
| 358 | + $replacePairs[$key] = $sk->link( $title, $link ); |
| 359 | + } |
| 360 | + $replacer = new HashtableReplacer( $replacePairs, 1 ); |
| 361 | + |
| 362 | + $text = preg_replace_callback( |
| 363 | + '/<!--IWLINK (.*?)-->/', |
| 364 | + $replacer->cb(), |
| 365 | + $text ); |
| 366 | + wfProfileOut( __METHOD__ ); |
| 367 | + } |
| 368 | + |
| 369 | + /** |
| 370 | + * Replace <!--LINK--> link placeholders with plain text of links |
| 371 | + * (not HTML-formatted). |
| 372 | + * @param string $text |
| 373 | + * @return string |
| 374 | + */ |
| 375 | + function replaceText( $text ) { |
| 376 | + wfProfileIn( __METHOD__ ); |
| 377 | + |
| 378 | + $text = preg_replace_callback( |
| 379 | + '/<!--(LINK|IWLINK) (.*?)-->/', |
| 380 | + array( &$this, 'replaceTextCallback' ), |
| 381 | + $text ); |
| 382 | + |
| 383 | + wfProfileOut( __METHOD__ ); |
| 384 | + return $text; |
| 385 | + } |
| 386 | + |
| 387 | + /** |
| 388 | + * @param array $matches |
| 389 | + * @return string |
| 390 | + * @private |
| 391 | + */ |
| 392 | + function replaceTextCallback( $matches ) { |
| 393 | + $type = $matches[1]; |
| 394 | + $key = $matches[2]; |
| 395 | + if( $type == 'LINK' ) { |
| 396 | + list( $ns, $index ) = explode( ':', $key, 2 ); |
| 397 | + if( isset( $this->internals[$ns][$index]['text'] ) ) { |
| 398 | + return $this->internals[$ns][$index]['text']; |
| 399 | + } |
| 400 | + } elseif( $type == 'IWLINK' ) { |
| 401 | + if( isset( $this->interwikis[$key]['text'] ) ) { |
| 402 | + return $this->interwikis[$key]['text']; |
| 403 | + } |
| 404 | + } |
| 405 | + return $matches[0]; |
| 406 | + } |
| 407 | +} |
Property changes on: trunk/phase3/includes/parser/LinkHolderArray.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 408 | + native |
Index: trunk/phase3/includes/parser/Parser.php |
— | — | @@ -98,7 +98,7 @@ |
99 | 99 | # Cleared with clearState(): |
100 | 100 | var $mOutput, $mAutonumber, $mDTopen, $mStripState; |
101 | 101 | var $mIncludeCount, $mArgStack, $mLastSection, $mInPre; |
102 | | - var $mInterwikiLinkHolders, $mLinkHolders; |
| 102 | + var $mInterwikiLinkHolders, $mLinkHolders, $mLinkID; |
103 | 103 | var $mIncludeSizes, $mPPNodeCount, $mDefaultSort; |
104 | 104 | var $mTplExpandCache; // empty-frame expansion cache |
105 | 105 | var $mTplRedirCache, $mTplDomCache, $mHeadings, $mDoubleUnderscores; |
— | — | @@ -179,17 +179,8 @@ |
180 | 180 | $this->mStripState = new StripState; |
181 | 181 | $this->mArgStack = false; |
182 | 182 | $this->mInPre = false; |
183 | | - $this->mInterwikiLinkHolders = array( |
184 | | - 'texts' => array(), |
185 | | - 'titles' => array() |
186 | | - ); |
187 | | - $this->mLinkHolders = array( |
188 | | - 'namespaces' => array(), |
189 | | - 'dbkeys' => array(), |
190 | | - 'queries' => array(), |
191 | | - 'texts' => array(), |
192 | | - 'titles' => array() |
193 | | - ); |
| 183 | + $this->mLinkHolders = new LinkHolderArray( $this ); |
| 184 | + $this->mLinkID = 0; |
194 | 185 | $this->mRevisionTimestamp = $this->mRevisionId = null; |
195 | 186 | |
196 | 187 | /** |
— | — | @@ -204,7 +195,7 @@ |
205 | 196 | */ |
206 | 197 | #$this->mUniqPrefix = "\x07UNIQ" . Parser::getRandomString(); |
207 | 198 | # Changed to \x7f to allow XML double-parsing -- TS |
208 | | - $this->mUniqPrefix = "\x7fUNIQ" . Parser::getRandomString(); |
| 199 | + $this->mUniqPrefix = "\x7fUNIQ" . self::getRandomString(); |
209 | 200 | |
210 | 201 | |
211 | 202 | # Clear these on every parse, bug 4549 |
— | — | @@ -294,7 +285,7 @@ |
295 | 286 | */ |
296 | 287 | |
297 | 288 | global $wgUseTidy, $wgAlwaysUseTidy, $wgContLang; |
298 | | - $fname = 'Parser::parse-' . wfGetCaller(); |
| 289 | + $fname = __METHOD__.'-' . wfGetCaller(); |
299 | 290 | wfProfileIn( __METHOD__ ); |
300 | 291 | wfProfileIn( $fname ); |
301 | 292 | |
— | — | @@ -328,7 +319,6 @@ |
329 | 320 | ); |
330 | 321 | $text = preg_replace( array_keys($fixtags), array_values($fixtags), $text ); |
331 | 322 | |
332 | | - # only once and last |
333 | 323 | $text = $this->doBlockLevels( $text, $linestart ); |
334 | 324 | |
335 | 325 | $this->replaceLinkHolders( $text ); |
— | — | @@ -348,7 +338,7 @@ |
349 | 339 | $uniq_prefix = $this->mUniqPrefix; |
350 | 340 | $matches = array(); |
351 | 341 | $elements = array_keys( $this->mTransparentTagHooks ); |
352 | | - $text = Parser::extractTagsAndParams( $elements, $text, $matches, $uniq_prefix ); |
| 342 | + $text = self::extractTagsAndParams( $elements, $text, $matches, $uniq_prefix ); |
353 | 343 | |
354 | 344 | foreach( $matches as $marker => $data ) { |
355 | 345 | list( $element, $content, $params, $tag ) = $data; |
— | — | @@ -366,7 +356,7 @@ |
367 | 357 | $text = Sanitizer::normalizeCharReferences( $text ); |
368 | 358 | |
369 | 359 | if (($wgUseTidy and $this->mOptions->mTidy) or $wgAlwaysUseTidy) { |
370 | | - $text = Parser::tidy($text); |
| 360 | + $text = self::tidy($text); |
371 | 361 | } else { |
372 | 362 | # attempt to sanitize at least some nesting problems |
373 | 363 | # (bug #2702 and quite a few others) |
— | — | @@ -471,6 +461,8 @@ |
472 | 462 | function &getTitle() { return $this->mTitle; } |
473 | 463 | function getOptions() { return $this->mOptions; } |
474 | 464 | function getRevisionId() { return $this->mRevisionId; } |
| 465 | + function getOutput() { return $this->mOutput; } |
| 466 | + function nextLinkID() { return $this->mLinkID++; } |
475 | 467 | |
476 | 468 | function getFunctionLang() { |
477 | 469 | global $wgLang, $wgContLang; |
— | — | @@ -658,9 +650,9 @@ |
659 | 651 | ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html>'. |
660 | 652 | '<head><title>test</title></head><body>'.$text.'</body></html>'; |
661 | 653 | if( $wgTidyInternal ) { |
662 | | - $correctedtext = Parser::internalTidy( $wrappedtext ); |
| 654 | + $correctedtext = self::internalTidy( $wrappedtext ); |
663 | 655 | } else { |
664 | | - $correctedtext = Parser::externalTidy( $wrappedtext ); |
| 656 | + $correctedtext = self::externalTidy( $wrappedtext ); |
665 | 657 | } |
666 | 658 | if( is_null( $correctedtext ) ) { |
667 | 659 | wfDebug( "Tidy error detected!\n" ); |
— | — | @@ -677,8 +669,7 @@ |
678 | 670 | */ |
679 | 671 | function externalTidy( $text ) { |
680 | 672 | global $wgTidyConf, $wgTidyBin, $wgTidyOpts; |
681 | | - $fname = 'Parser::externalTidy'; |
682 | | - wfProfileIn( $fname ); |
| 673 | + wfProfileIn( __METHOD__ ); |
683 | 674 | |
684 | 675 | $cleansource = ''; |
685 | 676 | $opts = ' -utf8'; |
— | — | @@ -707,7 +698,7 @@ |
708 | 699 | } |
709 | 700 | } |
710 | 701 | |
711 | | - wfProfileOut( $fname ); |
| 702 | + wfProfileOut( __METHOD__ ); |
712 | 703 | |
713 | 704 | if( $cleansource == '' && $text != '') { |
714 | 705 | // Some kind of error happened, so we couldn't get the corrected text. |
— | — | @@ -729,8 +720,7 @@ |
730 | 721 | */ |
731 | 722 | function internalTidy( $text ) { |
732 | 723 | global $wgTidyConf, $IP, $wgDebugTidy; |
733 | | - $fname = 'Parser::internalTidy'; |
734 | | - wfProfileIn( $fname ); |
| 724 | + wfProfileIn( __METHOD__ ); |
735 | 725 | |
736 | 726 | $tidy = new tidy; |
737 | 727 | $tidy->parseString( $text, $wgTidyConf, 'utf8' ); |
— | — | @@ -748,7 +738,7 @@ |
749 | 739 | "\n-->"; |
750 | 740 | } |
751 | 741 | |
752 | | - wfProfileOut( $fname ); |
| 742 | + wfProfileOut( __METHOD__ ); |
753 | 743 | return $cleansource; |
754 | 744 | } |
755 | 745 | |
— | — | @@ -758,34 +748,35 @@ |
759 | 749 | * @private |
760 | 750 | */ |
761 | 751 | function doTableStuff ( $text ) { |
762 | | - $fname = 'Parser::doTableStuff'; |
763 | | - wfProfileIn( $fname ); |
| 752 | + wfProfileIn( __METHOD__ ); |
764 | 753 | |
765 | | - $lines = explode ( "\n" , $text ); |
| 754 | + $lines = StringUtils::explode( "\n", $text ); |
| 755 | + $out = ''; |
766 | 756 | $td_history = array (); // Is currently a td tag open? |
767 | 757 | $last_tag_history = array (); // Save history of last lag activated (td, th or caption) |
768 | 758 | $tr_history = array (); // Is currently a tr tag open? |
769 | 759 | $tr_attributes = array (); // history of tr attributes |
770 | 760 | $has_opened_tr = array(); // Did this table open a <tr> element? |
771 | 761 | $indent_level = 0; // indent level of the table |
772 | | - foreach ( $lines as $key => $line ) |
773 | | - { |
774 | | - $line = trim ( $line ); |
775 | 762 | |
| 763 | + foreach ( $lines as $outLine ) { |
| 764 | + $line = trim( $outLine ); |
| 765 | + |
776 | 766 | if( $line == '' ) { // empty line, go to next line |
| 767 | + $out .= "\n"; |
777 | 768 | continue; |
778 | 769 | } |
779 | | - $first_character = $line{0}; |
| 770 | + $first_character = $line[0]; |
780 | 771 | $matches = array(); |
781 | 772 | |
782 | | - if ( preg_match( '/^(:*)\{\|(.*)$/' , $line , $matches ) ) { |
| 773 | + if ( preg_match( '/^(:*)\{\|(.*)$/', $line , $matches ) ) { |
783 | 774 | // First check if we are starting a new table |
784 | 775 | $indent_level = strlen( $matches[1] ); |
785 | 776 | |
786 | 777 | $attributes = $this->mStripState->unstripBoth( $matches[2] ); |
787 | 778 | $attributes = Sanitizer::fixTagAttributes ( $attributes , 'table' ); |
788 | 779 | |
789 | | - $lines[$key] = str_repeat( '<dl><dd>' , $indent_level ) . "<table{$attributes}>"; |
| 780 | + $outLine = str_repeat( '<dl><dd>' , $indent_level ) . "<table{$attributes}>"; |
790 | 781 | array_push ( $td_history , false ); |
791 | 782 | array_push ( $last_tag_history , '' ); |
792 | 783 | array_push ( $tr_history , false ); |
— | — | @@ -793,6 +784,7 @@ |
794 | 785 | array_push ( $has_opened_tr , false ); |
795 | 786 | } else if ( count ( $td_history ) == 0 ) { |
796 | 787 | // Don't do any of the following |
| 788 | + $out .= $outLine."\n"; |
797 | 789 | continue; |
798 | 790 | } else if ( substr ( $line , 0 , 2 ) == '|}' ) { |
799 | 791 | // We are ending a table |
— | — | @@ -811,7 +803,7 @@ |
812 | 804 | $line = "</{$last_tag}>{$line}"; |
813 | 805 | } |
814 | 806 | array_pop ( $tr_attributes ); |
815 | | - $lines[$key] = $line . str_repeat( '</dd></dl>' , $indent_level ); |
| 807 | + $outLine = $line . str_repeat( '</dd></dl>' , $indent_level ); |
816 | 808 | } else if ( substr ( $line , 0 , 2 ) == '|-' ) { |
817 | 809 | // Now we have a table row |
818 | 810 | $line = preg_replace( '#^\|-+#', '', $line ); |
— | — | @@ -835,7 +827,7 @@ |
836 | 828 | $line = "</{$last_tag}>{$line}"; |
837 | 829 | } |
838 | 830 | |
839 | | - $lines[$key] = $line; |
| 831 | + $outLine = $line; |
840 | 832 | array_push ( $tr_history , false ); |
841 | 833 | array_push ( $td_history , false ); |
842 | 834 | array_push ( $last_tag_history , '' ); |
— | — | @@ -859,7 +851,7 @@ |
860 | 852 | // attribute values containing literal "||". |
861 | 853 | $cells = StringUtils::explodeMarkup( '||' , $line ); |
862 | 854 | |
863 | | - $lines[$key] = ''; |
| 855 | + $outLine = ''; |
864 | 856 | |
865 | 857 | // Loop through each table cell |
866 | 858 | foreach ( $cells as $cell ) |
— | — | @@ -910,38 +902,42 @@ |
911 | 903 | $cell = "{$previous}<{$last_tag}{$attributes}>{$cell_data[1]}"; |
912 | 904 | } |
913 | 905 | |
914 | | - $lines[$key] .= $cell; |
| 906 | + $outLine .= $cell; |
915 | 907 | array_push ( $td_history , true ); |
916 | 908 | } |
917 | 909 | } |
| 910 | + $out .= $outLine . "\n"; |
918 | 911 | } |
919 | 912 | |
920 | 913 | // Closing open td, tr && table |
921 | 914 | while ( count ( $td_history ) > 0 ) |
922 | 915 | { |
923 | 916 | if ( array_pop ( $td_history ) ) { |
924 | | - $lines[] = '</td>' ; |
| 917 | + $out .= "</td>\n"; |
925 | 918 | } |
926 | 919 | if ( array_pop ( $tr_history ) ) { |
927 | | - $lines[] = '</tr>' ; |
| 920 | + $out .= "</tr>\n"; |
928 | 921 | } |
929 | 922 | if ( !array_pop ( $has_opened_tr ) ) { |
930 | | - $lines[] = "<tr><td></td></tr>" ; |
| 923 | + $out .= "<tr><td></td></tr>\n" ; |
931 | 924 | } |
932 | 925 | |
933 | | - $lines[] = '</table>' ; |
| 926 | + $out .= "</table>\n"; |
934 | 927 | } |
935 | 928 | |
936 | | - $output = implode ( "\n" , $lines ) ; |
| 929 | + // Remove trailing line-ending (b/c) |
| 930 | + if ( substr( $out, -1 ) == "\n" ) { |
| 931 | + $out = substr( $out, 0, -1 ); |
| 932 | + } |
937 | 933 | |
938 | 934 | // special case: don't return empty table |
939 | | - if( $output == "<table>\n<tr><td></td></tr>\n</table>" ) { |
940 | | - $output = ''; |
| 935 | + if( $out == "<table>\n<tr><td></td></tr>\n</table>" ) { |
| 936 | + $out = ''; |
941 | 937 | } |
942 | 938 | |
943 | | - wfProfileOut( $fname ); |
| 939 | + wfProfileOut( __METHOD__ ); |
944 | 940 | |
945 | | - return $output; |
| 941 | + return $out; |
946 | 942 | } |
947 | 943 | |
948 | 944 | /** |
— | — | @@ -952,12 +948,11 @@ |
953 | 949 | */ |
954 | 950 | function internalParse( $text ) { |
955 | 951 | $isMain = true; |
956 | | - $fname = 'Parser::internalParse'; |
957 | | - wfProfileIn( $fname ); |
| 952 | + wfProfileIn( __METHOD__ ); |
958 | 953 | |
959 | 954 | # Hook to suspend the parser in this state |
960 | 955 | if ( !wfRunHooks( 'ParserBeforeInternalParse', array( &$this, &$text, &$this->mStripState ) ) ) { |
961 | | - wfProfileOut( $fname ); |
| 956 | + wfProfileOut( __METHOD__ ); |
962 | 957 | return $text ; |
963 | 958 | } |
964 | 959 | |
— | — | @@ -990,7 +985,7 @@ |
991 | 986 | $text = $this->doMagicLinks( $text ); |
992 | 987 | $text = $this->formatHeadings( $text, $isMain ); |
993 | 988 | |
994 | | - wfProfileOut( $fname ); |
| 989 | + wfProfileOut( __METHOD__ ); |
995 | 990 | return $text; |
996 | 991 | } |
997 | 992 | |
— | — | @@ -1060,14 +1055,13 @@ |
1061 | 1056 | * @private |
1062 | 1057 | */ |
1063 | 1058 | function doHeadings( $text ) { |
1064 | | - $fname = 'Parser::doHeadings'; |
1065 | | - wfProfileIn( $fname ); |
| 1059 | + wfProfileIn( __METHOD__ ); |
1066 | 1060 | for ( $i = 6; $i >= 1; --$i ) { |
1067 | 1061 | $h = str_repeat( '=', $i ); |
1068 | 1062 | $text = preg_replace( "/^$h(.+)$h\\s*$/m", |
1069 | 1063 | "<h$i>\\1</h$i>", $text ); |
1070 | 1064 | } |
1071 | | - wfProfileOut( $fname ); |
| 1065 | + wfProfileOut( __METHOD__ ); |
1072 | 1066 | return $text; |
1073 | 1067 | } |
1074 | 1068 | |
— | — | @@ -1077,15 +1071,14 @@ |
1078 | 1072 | * @return string the altered text |
1079 | 1073 | */ |
1080 | 1074 | function doAllQuotes( $text ) { |
1081 | | - $fname = 'Parser::doAllQuotes'; |
1082 | | - wfProfileIn( $fname ); |
| 1075 | + wfProfileIn( __METHOD__ ); |
1083 | 1076 | $outtext = ''; |
1084 | | - $lines = explode( "\n", $text ); |
| 1077 | + $lines = StringUtils::explode( "\n", $text ); |
1085 | 1078 | foreach ( $lines as $line ) { |
1086 | | - $outtext .= $this->doQuotes ( $line ) . "\n"; |
| 1079 | + $outtext .= $this->doQuotes( $line ) . "\n"; |
1087 | 1080 | } |
1088 | 1081 | $outtext = substr($outtext, 0,-1); |
1089 | | - wfProfileOut( $fname ); |
| 1082 | + wfProfileOut( __METHOD__ ); |
1090 | 1083 | return $outtext; |
1091 | 1084 | } |
1092 | 1085 | |
— | — | @@ -1264,8 +1257,7 @@ |
1265 | 1258 | */ |
1266 | 1259 | function replaceExternalLinks( $text ) { |
1267 | 1260 | global $wgContLang; |
1268 | | - $fname = 'Parser::replaceExternalLinks'; |
1269 | | - wfProfileIn( $fname ); |
| 1261 | + wfProfileIn( __METHOD__ ); |
1270 | 1262 | |
1271 | 1263 | $sk = $this->mOptions->getSkin(); |
1272 | 1264 | |
— | — | @@ -1335,11 +1327,11 @@ |
1336 | 1328 | # Register link in the output object. |
1337 | 1329 | # Replace unnecessary URL escape codes with the referenced character |
1338 | 1330 | # This prevents spammers from hiding links from the filters |
1339 | | - $pasteurized = Parser::replaceUnusualEscapes( $url ); |
| 1331 | + $pasteurized = self::replaceUnusualEscapes( $url ); |
1340 | 1332 | $this->mOutput->addExternalLink( $pasteurized ); |
1341 | 1333 | } |
1342 | 1334 | |
1343 | | - wfProfileOut( $fname ); |
| 1335 | + wfProfileOut( __METHOD__ ); |
1344 | 1336 | return $s; |
1345 | 1337 | } |
1346 | 1338 | |
— | — | @@ -1349,8 +1341,7 @@ |
1350 | 1342 | */ |
1351 | 1343 | function replaceFreeExternalLinks( $text ) { |
1352 | 1344 | global $wgContLang; |
1353 | | - $fname = 'Parser::replaceFreeExternalLinks'; |
1354 | | - wfProfileIn( $fname ); |
| 1345 | + wfProfileIn( __METHOD__ ); |
1355 | 1346 | |
1356 | 1347 | $bits = preg_split( '/(\b(?:' . wfUrlProtocols() . '))/S', $text, -1, PREG_SPLIT_DELIM_CAPTURE ); |
1357 | 1348 | $s = array_shift( $bits ); |
— | — | @@ -1412,7 +1403,7 @@ |
1413 | 1404 | $text = $sk->makeExternalLink( $url, $wgContLang->markNoConversion($url), true, 'free', $this->mTitle->getNamespace() ); |
1414 | 1405 | # Register it in the output object... |
1415 | 1406 | # Replace unnecessary URL escape codes with their equivalent characters |
1416 | | - $pasteurized = Parser::replaceUnusualEscapes( $url ); |
| 1407 | + $pasteurized = self::replaceUnusualEscapes( $url ); |
1417 | 1408 | $this->mOutput->addExternalLink( $pasteurized ); |
1418 | 1409 | } |
1419 | 1410 | $s .= $text . $trail; |
— | — | @@ -1420,7 +1411,7 @@ |
1421 | 1412 | $s .= $protocol . $remainder; |
1422 | 1413 | } |
1423 | 1414 | } |
1424 | | - wfProfileOut( $fname ); |
| 1415 | + wfProfileOut( __METHOD__ ); |
1425 | 1416 | return $s; |
1426 | 1417 | } |
1427 | 1418 | |
— | — | @@ -1436,7 +1427,7 @@ |
1437 | 1428 | */ |
1438 | 1429 | static function replaceUnusualEscapes( $url ) { |
1439 | 1430 | return preg_replace_callback( '/%[0-9A-Fa-f]{2}/', |
1440 | | - array( 'Parser', 'replaceUnusualEscapesCallback' ), $url ); |
| 1431 | + array( __CLASS__, 'replaceUnusualEscapesCallback' ), $url ); |
1441 | 1432 | } |
1442 | 1433 | |
1443 | 1434 | /** |
— | — | @@ -1480,35 +1471,48 @@ |
1481 | 1472 | |
1482 | 1473 | /** |
1483 | 1474 | * Process [[ ]] wikilinks |
| 1475 | + * @return processed text |
1484 | 1476 | * |
1485 | 1477 | * @private |
1486 | 1478 | */ |
1487 | 1479 | function replaceInternalLinks( $s ) { |
| 1480 | + $this->mLinkHolders->merge( $this->replaceInternalLinks2( $s ) ); |
| 1481 | + return $s; |
| 1482 | + } |
| 1483 | + |
| 1484 | + /** |
| 1485 | + * Process [[ ]] wikilinks |
| 1486 | + * @return LinkHolderArray |
| 1487 | + * |
| 1488 | + * @private |
| 1489 | + */ |
| 1490 | + function replaceInternalLinks2( &$s ) { |
1488 | 1491 | global $wgContLang; |
1489 | | - static $fname = 'Parser::replaceInternalLinks' ; |
1490 | 1492 | |
1491 | | - wfProfileIn( $fname ); |
| 1493 | + wfProfileIn( __METHOD__ ); |
1492 | 1494 | |
1493 | | - wfProfileIn( $fname.'-setup' ); |
1494 | | - static $tc = FALSE; |
| 1495 | + wfProfileIn( __METHOD__.'-setup' ); |
| 1496 | + static $tc = FALSE, $e1, $e1_img; |
1495 | 1497 | # the % is needed to support urlencoded titles as well |
1496 | | - if ( !$tc ) { $tc = Title::legalChars() . '#%'; } |
| 1498 | + if ( !$tc ) { |
| 1499 | + $tc = Title::legalChars() . '#%'; |
| 1500 | + # Match a link having the form [[namespace:link|alternate]]trail |
| 1501 | + $e1 = "/^([{$tc}]+)(?:\\|(.+?))?]](.*)\$/sD"; |
| 1502 | + # Match cases where there is no "]]", which might still be images |
| 1503 | + $e1_img = "/^([{$tc}]+)\\|(.*)\$/sD"; |
| 1504 | + } |
1497 | 1505 | |
1498 | 1506 | $sk = $this->mOptions->getSkin(); |
| 1507 | + $holders = new LinkHolderArray( $this ); |
1499 | 1508 | |
1500 | 1509 | #split the entire text string on occurences of [[ |
1501 | | - $a = explode( '[[', ' ' . $s ); |
| 1510 | + $a = StringUtils::explode( '[[', ' ' . $s ); |
1502 | 1511 | #get the first element (all text up to first [[), and remove the space we added |
1503 | | - $s = array_shift( $a ); |
| 1512 | + $s = $a->current(); |
| 1513 | + $a->next(); |
| 1514 | + $line = $a->current(); # Workaround for broken ArrayIterator::next() that returns "void" |
1504 | 1515 | $s = substr( $s, 1 ); |
1505 | 1516 | |
1506 | | - # Match a link having the form [[namespace:link|alternate]]trail |
1507 | | - static $e1 = FALSE; |
1508 | | - if ( !$e1 ) { $e1 = "/^([{$tc}]+)(?:\\|(.+?))?]](.*)\$/sD"; } |
1509 | | - # Match cases where there is no "]]", which might still be images |
1510 | | - static $e1_img = FALSE; |
1511 | | - if ( !$e1_img ) { $e1_img = "/^([{$tc}]+)\\|(.*)\$/sD"; } |
1512 | | - |
1513 | 1517 | $useLinkPrefixExtension = $wgContLang->linkPrefixExtension(); |
1514 | 1518 | $e2 = null; |
1515 | 1519 | if ( $useLinkPrefixExtension ) { |
— | — | @@ -1518,8 +1522,8 @@ |
1519 | 1523 | } |
1520 | 1524 | |
1521 | 1525 | if( is_null( $this->mTitle ) ) { |
1522 | | - wfProfileOut( $fname ); |
1523 | | - wfProfileOut( $fname.'-setup' ); |
| 1526 | + wfProfileOut( __METHOD__ ); |
| 1527 | + wfProfileOut( __METHOD__.'-setup' ); |
1524 | 1528 | throw new MWException( __METHOD__.": \$this->mTitle is null\n" ); |
1525 | 1529 | } |
1526 | 1530 | $nottalk = !$this->mTitle->isTalkPage(); |
— | — | @@ -1541,13 +1545,20 @@ |
1542 | 1546 | $selflink = array($this->mTitle->getPrefixedText()); |
1543 | 1547 | } |
1544 | 1548 | $useSubpages = $this->areSubpagesAllowed(); |
1545 | | - wfProfileOut( $fname.'-setup' ); |
| 1549 | + wfProfileOut( __METHOD__.'-setup' ); |
1546 | 1550 | |
1547 | 1551 | # Loop for each link |
1548 | | - for ($k = 0; isset( $a[$k] ); $k++) { |
1549 | | - $line = $a[$k]; |
| 1552 | + for ( ; $line !== false && $line !== null ; $a->next(), $line = $a->current() ) { |
| 1553 | + # Check for excessive memory usage |
| 1554 | + if ( $holders->isBig() ) { |
| 1555 | + # Too big |
| 1556 | + # Do the existence check, replace the link holders and clear the array |
| 1557 | + $holders->replace( $s ); |
| 1558 | + $holders->clear(); |
| 1559 | + } |
| 1560 | + |
1550 | 1561 | if ( $useLinkPrefixExtension ) { |
1551 | | - wfProfileIn( $fname.'-prefixhandling' ); |
| 1562 | + wfProfileIn( __METHOD__.'-prefixhandling' ); |
1552 | 1563 | if ( preg_match( $e2, $s, $m ) ) { |
1553 | 1564 | $prefix = $m[2]; |
1554 | 1565 | $s = $m[1]; |
— | — | @@ -1559,12 +1570,12 @@ |
1560 | 1571 | $prefix = $first_prefix; |
1561 | 1572 | $first_prefix = false; |
1562 | 1573 | } |
1563 | | - wfProfileOut( $fname.'-prefixhandling' ); |
| 1574 | + wfProfileOut( __METHOD__.'-prefixhandling' ); |
1564 | 1575 | } |
1565 | 1576 | |
1566 | 1577 | $might_be_img = false; |
1567 | 1578 | |
1568 | | - wfProfileIn( "$fname-e1" ); |
| 1579 | + wfProfileIn( __METHOD__."-e1" ); |
1569 | 1580 | if ( preg_match( $e1, $line, $m ) ) { # page with normal text or alt |
1570 | 1581 | $text = $m[2]; |
1571 | 1582 | # If we get a ] at the beginning of $m[3] that means we have a link that's something like: |
— | — | @@ -1598,18 +1609,18 @@ |
1599 | 1610 | $trail = ""; |
1600 | 1611 | } else { # Invalid form; output directly |
1601 | 1612 | $s .= $prefix . '[[' . $line ; |
1602 | | - wfProfileOut( "$fname-e1" ); |
| 1613 | + wfProfileOut( __METHOD__."-e1" ); |
1603 | 1614 | continue; |
1604 | 1615 | } |
1605 | | - wfProfileOut( "$fname-e1" ); |
1606 | | - wfProfileIn( "$fname-misc" ); |
| 1616 | + wfProfileOut( __METHOD__."-e1" ); |
| 1617 | + wfProfileIn( __METHOD__."-misc" ); |
1607 | 1618 | |
1608 | 1619 | # Don't allow internal links to pages containing |
1609 | 1620 | # PROTO: where PROTO is a valid URL protocol; these |
1610 | 1621 | # should be external links. |
1611 | 1622 | if (preg_match('/^\b(?:' . wfUrlProtocols() . ')/', $m[1])) { |
1612 | 1623 | $s .= $prefix . '[[' . $line ; |
1613 | | - wfProfileOut( "$fname-misc" ); |
| 1624 | + wfProfileOut( __METHOD__."-misc" ); |
1614 | 1625 | continue; |
1615 | 1626 | } |
1616 | 1627 | |
— | — | @@ -1626,27 +1637,30 @@ |
1627 | 1638 | $link = substr($link, 1); |
1628 | 1639 | } |
1629 | 1640 | |
1630 | | - wfProfileOut( "$fname-misc" ); |
1631 | | - wfProfileIn( "$fname-title" ); |
| 1641 | + wfProfileOut( __METHOD__."-misc" ); |
| 1642 | + wfProfileIn( __METHOD__."-title" ); |
1632 | 1643 | $nt = Title::newFromText( $this->mStripState->unstripNoWiki($link) ); |
1633 | 1644 | if( !$nt ) { |
1634 | 1645 | $s .= $prefix . '[[' . $line; |
1635 | | - wfProfileOut( "$fname-title" ); |
| 1646 | + wfProfileOut( __METHOD__."-title" ); |
1636 | 1647 | continue; |
1637 | 1648 | } |
1638 | 1649 | |
1639 | 1650 | $ns = $nt->getNamespace(); |
1640 | 1651 | $iw = $nt->getInterWiki(); |
1641 | | - wfProfileOut( "$fname-title" ); |
| 1652 | + wfProfileOut( __METHOD__."-title" ); |
1642 | 1653 | |
1643 | 1654 | if ($might_be_img) { # if this is actually an invalid link |
1644 | | - wfProfileIn( "$fname-might_be_img" ); |
| 1655 | + wfProfileIn( __METHOD__."-might_be_img" ); |
1645 | 1656 | if ($ns == NS_IMAGE && $noforce) { #but might be an image |
1646 | 1657 | $found = false; |
1647 | | - while (isset ($a[$k+1]) ) { |
| 1658 | + while ( true ) { |
1648 | 1659 | #look at the next 'line' to see if we can close it there |
1649 | | - $spliced = array_splice( $a, $k + 1, 1 ); |
1650 | | - $next_line = array_shift( $spliced ); |
| 1660 | + $a->next(); |
| 1661 | + $next_line = $a->current(); |
| 1662 | + if ( $next_line === false || $next_line === null ) { |
| 1663 | + break; |
| 1664 | + } |
1651 | 1665 | $m = explode( ']]', $next_line, 3 ); |
1652 | 1666 | if ( count( $m ) == 3 ) { |
1653 | 1667 | # the first ]] closes the inner link, the second the image |
— | — | @@ -1666,19 +1680,19 @@ |
1667 | 1681 | if ( !$found ) { |
1668 | 1682 | # we couldn't find the end of this imageLink, so output it raw |
1669 | 1683 | #but don't ignore what might be perfectly normal links in the text we've examined |
1670 | | - $text = $this->replaceInternalLinks($text); |
| 1684 | + $holders->merge( $this->replaceInternalLinks2( $text ) ); |
1671 | 1685 | $s .= "{$prefix}[[$link|$text"; |
1672 | 1686 | # note: no $trail, because without an end, there *is* no trail |
1673 | | - wfProfileOut( "$fname-might_be_img" ); |
| 1687 | + wfProfileOut( __METHOD__."-might_be_img" ); |
1674 | 1688 | continue; |
1675 | 1689 | } |
1676 | 1690 | } else { #it's not an image, so output it raw |
1677 | 1691 | $s .= "{$prefix}[[$link|$text"; |
1678 | 1692 | # note: no $trail, because without an end, there *is* no trail |
1679 | | - wfProfileOut( "$fname-might_be_img" ); |
| 1693 | + wfProfileOut( __METHOD__."-might_be_img" ); |
1680 | 1694 | continue; |
1681 | 1695 | } |
1682 | | - wfProfileOut( "$fname-might_be_img" ); |
| 1696 | + wfProfileOut( __METHOD__."-might_be_img" ); |
1683 | 1697 | } |
1684 | 1698 | |
1685 | 1699 | $wasblank = ( '' == $text ); |
— | — | @@ -1688,41 +1702,38 @@ |
1689 | 1703 | if( $noforce ) { |
1690 | 1704 | |
1691 | 1705 | # Interwikis |
1692 | | - wfProfileIn( "$fname-interwiki" ); |
| 1706 | + wfProfileIn( __METHOD__."-interwiki" ); |
1693 | 1707 | if( $iw && $this->mOptions->getInterwikiMagic() && $nottalk && $wgContLang->getLanguageName( $iw ) ) { |
1694 | 1708 | $this->mOutput->addLanguageLink( $nt->getFullText() ); |
1695 | 1709 | $s = rtrim($s . $prefix); |
1696 | 1710 | $s .= trim($trail, "\n") == '' ? '': $prefix . $trail; |
1697 | | - wfProfileOut( "$fname-interwiki" ); |
| 1711 | + wfProfileOut( __METHOD__."-interwiki" ); |
1698 | 1712 | continue; |
1699 | 1713 | } |
1700 | | - wfProfileOut( "$fname-interwiki" ); |
| 1714 | + wfProfileOut( __METHOD__."-interwiki" ); |
1701 | 1715 | |
1702 | 1716 | if ( $ns == NS_IMAGE ) { |
1703 | | - wfProfileIn( "$fname-image" ); |
| 1717 | + wfProfileIn( __METHOD__."-image" ); |
1704 | 1718 | if ( !wfIsBadImage( $nt->getDBkey(), $this->mTitle ) ) { |
1705 | 1719 | # recursively parse links inside the image caption |
1706 | 1720 | # actually, this will parse them in any other parameters, too, |
1707 | 1721 | # but it might be hard to fix that, and it doesn't matter ATM |
1708 | 1722 | $text = $this->replaceExternalLinks($text); |
1709 | | - $text = $this->replaceInternalLinks($text); |
| 1723 | + $holders->merge( $this->replaceInternalLinks2( $text ) ); |
1710 | 1724 | |
1711 | 1725 | # cloak any absolute URLs inside the image markup, so replaceExternalLinks() won't touch them |
1712 | | - $s .= $prefix . $this->armorLinks( $this->makeImage( $nt, $text ) ) . $trail; |
1713 | | - $this->mOutput->addImage( $nt->getDBkey() ); |
| 1726 | + $s .= $prefix . $this->armorLinks( $this->makeImage( $nt, $text, $holders ) ) . $trail; |
1714 | 1727 | |
1715 | | - wfProfileOut( "$fname-image" ); |
| 1728 | + wfProfileOut( __METHOD__."-image" ); |
1716 | 1729 | continue; |
1717 | | - } else { |
1718 | | - # We still need to record the image's presence on the page |
1719 | | - $this->mOutput->addImage( $nt->getDBkey() ); |
1720 | 1730 | } |
1721 | | - wfProfileOut( "$fname-image" ); |
| 1731 | + $this->mOutput->addImage( $nt->getDBkey() ); |
| 1732 | + wfProfileOut( __METHOD__."-image" ); |
1722 | 1733 | |
1723 | 1734 | } |
1724 | 1735 | |
1725 | 1736 | if ( $ns == NS_CATEGORY ) { |
1726 | | - wfProfileIn( "$fname-category" ); |
| 1737 | + wfProfileIn( __METHOD__."-category" ); |
1727 | 1738 | $s = rtrim($s . "\n"); # bug 87 |
1728 | 1739 | |
1729 | 1740 | if ( $wasblank ) { |
— | — | @@ -1741,7 +1752,7 @@ |
1742 | 1753 | */ |
1743 | 1754 | $s .= trim($prefix . $trail, "\n") == '' ? '': $prefix . $trail; |
1744 | 1755 | |
1745 | | - wfProfileOut( "$fname-category" ); |
| 1756 | + wfProfileOut( __METHOD__."-category" ); |
1746 | 1757 | continue; |
1747 | 1758 | } |
1748 | 1759 | } |
— | — | @@ -1772,7 +1783,7 @@ |
1773 | 1784 | if( SpecialPage::exists( $nt->getDBkey() ) ) { |
1774 | 1785 | $s .= $this->makeKnownLinkHolder( $nt, $text, '', $trail, $prefix ); |
1775 | 1786 | } else { |
1776 | | - $s .= $this->makeLinkHolder( $nt, $text, '', $trail, $prefix ); |
| 1787 | + $s .= $holders->makeHolder( $nt, $text, '', $trail, $prefix ); |
1777 | 1788 | } |
1778 | 1789 | continue; |
1779 | 1790 | } elseif( $ns == NS_IMAGE ) { |
— | — | @@ -1786,10 +1797,10 @@ |
1787 | 1798 | continue; |
1788 | 1799 | } |
1789 | 1800 | } |
1790 | | - $s .= $this->makeLinkHolder( $nt, $text, '', $trail, $prefix ); |
| 1801 | + $s .= $holders->makeHolder( $nt, $text, '', $trail, $prefix ); |
1791 | 1802 | } |
1792 | | - wfProfileOut( $fname ); |
1793 | | - return $s; |
| 1803 | + wfProfileOut( __METHOD__ ); |
| 1804 | + return $holders; |
1794 | 1805 | } |
1795 | 1806 | |
1796 | 1807 | /** |
— | — | @@ -1798,32 +1809,10 @@ |
1799 | 1810 | * parsing of interwiki links, and secondly to allow all existence checks and |
1800 | 1811 | * article length checks (for stub links) to be bundled into a single query. |
1801 | 1812 | * |
| 1813 | + * @deprecated |
1802 | 1814 | */ |
1803 | 1815 | function makeLinkHolder( &$nt, $text = '', $query = '', $trail = '', $prefix = '' ) { |
1804 | | - wfProfileIn( __METHOD__ ); |
1805 | | - if ( ! is_object($nt) ) { |
1806 | | - # Fail gracefully |
1807 | | - $retVal = "<!-- ERROR -->{$prefix}{$text}{$trail}"; |
1808 | | - } else { |
1809 | | - # Separate the link trail from the rest of the link |
1810 | | - list( $inside, $trail ) = Linker::splitTrail( $trail ); |
1811 | | - |
1812 | | - if ( $nt->isExternal() ) { |
1813 | | - $nr = array_push( $this->mInterwikiLinkHolders['texts'], $prefix.$text.$inside ); |
1814 | | - $this->mInterwikiLinkHolders['titles'][] = $nt; |
1815 | | - $retVal = '<!--IWLINK '. ($nr-1) ."-->{$trail}"; |
1816 | | - } else { |
1817 | | - $nr = array_push( $this->mLinkHolders['namespaces'], $nt->getNamespace() ); |
1818 | | - $this->mLinkHolders['dbkeys'][] = $nt->getDBkey(); |
1819 | | - $this->mLinkHolders['queries'][] = $query; |
1820 | | - $this->mLinkHolders['texts'][] = $prefix.$text.$inside; |
1821 | | - $this->mLinkHolders['titles'][] = $nt; |
1822 | | - |
1823 | | - $retVal = '<!--LINK '. ($nr-1) ."-->{$trail}"; |
1824 | | - } |
1825 | | - } |
1826 | | - wfProfileOut( __METHOD__ ); |
1827 | | - return $retVal; |
| 1816 | + return $this->mLinkHolders->makeHolder( $nt, $text, $query, $trail, $prefix ); |
1828 | 1817 | } |
1829 | 1818 | |
1830 | 1819 | /** |
— | — | @@ -1889,8 +1878,7 @@ |
1890 | 1879 | # ../ -- convert to CurrentPage, from CurrentPage/CurrentSubPage |
1891 | 1880 | # ../Foobar -- convert to CurrentPage/Foobar, from CurrentPage/CurrentSubPage |
1892 | 1881 | |
1893 | | - $fname = 'Parser::maybeDoSubpageLink'; |
1894 | | - wfProfileIn( $fname ); |
| 1882 | + wfProfileIn( __METHOD__ ); |
1895 | 1883 | $ret = $target; # default return value is no change |
1896 | 1884 | |
1897 | 1885 | # Some namespaces don't allow subpages, |
— | — | @@ -1949,7 +1937,7 @@ |
1950 | 1938 | } |
1951 | 1939 | } |
1952 | 1940 | |
1953 | | - wfProfileOut( $fname ); |
| 1941 | + wfProfileOut( __METHOD__ ); |
1954 | 1942 | return $ret; |
1955 | 1943 | } |
1956 | 1944 | |
— | — | @@ -2036,50 +2024,53 @@ |
2037 | 2025 | * @return string the lists rendered as HTML |
2038 | 2026 | */ |
2039 | 2027 | function doBlockLevels( $text, $linestart ) { |
2040 | | - $fname = 'Parser::doBlockLevels'; |
2041 | | - wfProfileIn( $fname ); |
| 2028 | + wfProfileIn( __METHOD__ ); |
2042 | 2029 | |
2043 | 2030 | # Parsing through the text line by line. The main thing |
2044 | 2031 | # happening here is handling of block-level elements p, pre, |
2045 | 2032 | # and making lists from lines starting with * # : etc. |
2046 | 2033 | # |
2047 | | - $textLines = explode( "\n", $text ); |
| 2034 | + $textLines = StringUtils::explode( "\n", $text ); |
2048 | 2035 | |
2049 | 2036 | $lastPrefix = $output = ''; |
2050 | 2037 | $this->mDTopen = $inBlockElem = false; |
2051 | 2038 | $prefixLength = 0; |
2052 | 2039 | $paragraphStack = false; |
2053 | 2040 | |
2054 | | - if ( !$linestart ) { |
2055 | | - $output .= array_shift( $textLines ); |
2056 | | - } |
2057 | 2041 | foreach ( $textLines as $oLine ) { |
| 2042 | + # Fix up $linestart |
| 2043 | + if ( !$linestart ) { |
| 2044 | + $output .= $oLine; |
| 2045 | + $linestart = true; |
| 2046 | + continue; |
| 2047 | + } |
| 2048 | + |
2058 | 2049 | $lastPrefixLength = strlen( $lastPrefix ); |
2059 | 2050 | $preCloseMatch = preg_match('/<\\/pre/i', $oLine ); |
2060 | 2051 | $preOpenMatch = preg_match('/<pre/i', $oLine ); |
2061 | 2052 | if ( !$this->mInPre ) { |
2062 | 2053 | # Multiple prefixes may abut each other for nested lists. |
2063 | 2054 | $prefixLength = strspn( $oLine, '*#:;' ); |
2064 | | - $pref = substr( $oLine, 0, $prefixLength ); |
| 2055 | + $prefix = substr( $oLine, 0, $prefixLength ); |
2065 | 2056 | |
2066 | 2057 | # eh? |
2067 | | - $pref2 = str_replace( ';', ':', $pref ); |
| 2058 | + $prefix2 = str_replace( ';', ':', $prefix ); |
2068 | 2059 | $t = substr( $oLine, $prefixLength ); |
2069 | | - $this->mInPre = !empty($preOpenMatch); |
| 2060 | + $this->mInPre = (bool)$preOpenMatch; |
2070 | 2061 | } else { |
2071 | 2062 | # Don't interpret any other prefixes in preformatted text |
2072 | 2063 | $prefixLength = 0; |
2073 | | - $pref = $pref2 = ''; |
| 2064 | + $prefix = $prefix2 = ''; |
2074 | 2065 | $t = $oLine; |
2075 | 2066 | } |
2076 | 2067 | |
2077 | 2068 | # List generation |
2078 | | - if( $prefixLength && 0 == strcmp( $lastPrefix, $pref2 ) ) { |
| 2069 | + if( $prefixLength && $lastPrefix === $prefix2 ) { |
2079 | 2070 | # Same as the last item, so no need to deal with nesting or opening stuff |
2080 | | - $output .= $this->nextItem( substr( $pref, -1 ) ); |
| 2071 | + $output .= $this->nextItem( substr( $prefix, -1 ) ); |
2081 | 2072 | $paragraphStack = false; |
2082 | 2073 | |
2083 | | - if ( substr( $pref, -1 ) == ';') { |
| 2074 | + if ( substr( $prefix, -1 ) == ';') { |
2084 | 2075 | # The one nasty exception: definition lists work like this: |
2085 | 2076 | # ; title : definition text |
2086 | 2077 | # So we check for : in the remainder text to split up the |
— | — | @@ -2092,18 +2083,18 @@ |
2093 | 2084 | } |
2094 | 2085 | } elseif( $prefixLength || $lastPrefixLength ) { |
2095 | 2086 | # Either open or close a level... |
2096 | | - $commonPrefixLength = $this->getCommon( $pref, $lastPrefix ); |
| 2087 | + $commonPrefixLength = $this->getCommon( $prefix, $lastPrefix ); |
2097 | 2088 | $paragraphStack = false; |
2098 | 2089 | |
2099 | 2090 | while( $commonPrefixLength < $lastPrefixLength ) { |
2100 | | - $output .= $this->closeList( $lastPrefix{$lastPrefixLength-1} ); |
| 2091 | + $output .= $this->closeList( $lastPrefix[$lastPrefixLength-1] ); |
2101 | 2092 | --$lastPrefixLength; |
2102 | 2093 | } |
2103 | 2094 | if ( $prefixLength <= $commonPrefixLength && $commonPrefixLength > 0 ) { |
2104 | | - $output .= $this->nextItem( $pref{$commonPrefixLength-1} ); |
| 2095 | + $output .= $this->nextItem( $prefix[$commonPrefixLength-1] ); |
2105 | 2096 | } |
2106 | 2097 | while ( $prefixLength > $commonPrefixLength ) { |
2107 | | - $char = substr( $pref, $commonPrefixLength, 1 ); |
| 2098 | + $char = substr( $prefix, $commonPrefixLength, 1 ); |
2108 | 2099 | $output .= $this->openList( $char ); |
2109 | 2100 | |
2110 | 2101 | if ( ';' == $char ) { |
— | — | @@ -2115,10 +2106,10 @@ |
2116 | 2107 | } |
2117 | 2108 | ++$commonPrefixLength; |
2118 | 2109 | } |
2119 | | - $lastPrefix = $pref2; |
| 2110 | + $lastPrefix = $prefix2; |
2120 | 2111 | } |
2121 | 2112 | if( 0 == $prefixLength ) { |
2122 | | - wfProfileIn( "$fname-paragraph" ); |
| 2113 | + wfProfileIn( __METHOD__."-paragraph" ); |
2123 | 2114 | # No prefix (not in list)--go to paragraph mode |
2124 | 2115 | // XXX: use a stack for nestable elements like span, table and div |
2125 | 2116 | $openmatch = preg_match('/(?:<table|<blockquote|<h1|<h2|<h3|<h4|<h5|<h6|<pre|<tr|<p|<ul|<ol|<li|<\\/tr|<\\/td|<\\/th)/iS', $t ); |
— | — | @@ -2174,7 +2165,7 @@ |
2175 | 2166 | } |
2176 | 2167 | } |
2177 | 2168 | } |
2178 | | - wfProfileOut( "$fname-paragraph" ); |
| 2169 | + wfProfileOut( __METHOD__."-paragraph" ); |
2179 | 2170 | } |
2180 | 2171 | // somewhere above we forget to get out of pre block (bug 785) |
2181 | 2172 | if($preCloseMatch && $this->mInPre) { |
— | — | @@ -2185,7 +2176,7 @@ |
2186 | 2177 | } |
2187 | 2178 | } |
2188 | 2179 | while ( $prefixLength ) { |
2189 | | - $output .= $this->closeList( $pref2{$prefixLength-1} ); |
| 2180 | + $output .= $this->closeList( $prefix2[$prefixLength-1] ); |
2190 | 2181 | --$prefixLength; |
2191 | 2182 | } |
2192 | 2183 | if ( '' != $this->mLastSection ) { |
— | — | @@ -2193,7 +2184,7 @@ |
2194 | 2185 | $this->mLastSection = ''; |
2195 | 2186 | } |
2196 | 2187 | |
2197 | | - wfProfileOut( $fname ); |
| 2188 | + wfProfileOut( __METHOD__ ); |
2198 | 2189 | return $output; |
2199 | 2190 | } |
2200 | 2191 | |
— | — | @@ -2206,13 +2197,12 @@ |
2207 | 2198 | * return string the position of the ':', or false if none found |
2208 | 2199 | */ |
2209 | 2200 | function findColonNoLinks($str, &$before, &$after) { |
2210 | | - $fname = 'Parser::findColonNoLinks'; |
2211 | | - wfProfileIn( $fname ); |
| 2201 | + wfProfileIn( __METHOD__ ); |
2212 | 2202 | |
2213 | 2203 | $pos = strpos( $str, ':' ); |
2214 | 2204 | if( $pos === false ) { |
2215 | 2205 | // Nothing to find! |
2216 | | - wfProfileOut( $fname ); |
| 2206 | + wfProfileOut( __METHOD__ ); |
2217 | 2207 | return false; |
2218 | 2208 | } |
2219 | 2209 | |
— | — | @@ -2221,7 +2211,7 @@ |
2222 | 2212 | // Easy; no tag nesting to worry about |
2223 | 2213 | $before = substr( $str, 0, $pos ); |
2224 | 2214 | $after = substr( $str, $pos+1 ); |
2225 | | - wfProfileOut( $fname ); |
| 2215 | + wfProfileOut( __METHOD__ ); |
2226 | 2216 | return $pos; |
2227 | 2217 | } |
2228 | 2218 | |
— | — | @@ -2245,7 +2235,7 @@ |
2246 | 2236 | // We found it! |
2247 | 2237 | $before = substr( $str, 0, $i ); |
2248 | 2238 | $after = substr( $str, $i + 1 ); |
2249 | | - wfProfileOut( $fname ); |
| 2239 | + wfProfileOut( __METHOD__ ); |
2250 | 2240 | return $i; |
2251 | 2241 | } |
2252 | 2242 | // Embedded in a tag; don't break it. |
— | — | @@ -2255,7 +2245,7 @@ |
2256 | 2246 | $colon = strpos( $str, ':', $i ); |
2257 | 2247 | if( $colon === false ) { |
2258 | 2248 | // Nothing else interesting |
2259 | | - wfProfileOut( $fname ); |
| 2249 | + wfProfileOut( __METHOD__ ); |
2260 | 2250 | return false; |
2261 | 2251 | } |
2262 | 2252 | $lt = strpos( $str, '<', $i ); |
— | — | @@ -2264,7 +2254,7 @@ |
2265 | 2255 | // We found it! |
2266 | 2256 | $before = substr( $str, 0, $colon ); |
2267 | 2257 | $after = substr( $str, $colon + 1 ); |
2268 | | - wfProfileOut( $fname ); |
| 2258 | + wfProfileOut( __METHOD__ ); |
2269 | 2259 | return $i; |
2270 | 2260 | } |
2271 | 2261 | } |
— | — | @@ -2314,8 +2304,8 @@ |
2315 | 2305 | if( $c == ">" ) { |
2316 | 2306 | $stack--; |
2317 | 2307 | if( $stack < 0 ) { |
2318 | | - wfDebug( "Invalid input in $fname; too many close tags\n" ); |
2319 | | - wfProfileOut( $fname ); |
| 2308 | + wfDebug( __METHOD__.": Invalid input; too many close tags\n" ); |
| 2309 | + wfProfileOut( __METHOD__ ); |
2320 | 2310 | return false; |
2321 | 2311 | } |
2322 | 2312 | $state = self::COLON_STATE_TEXT; |
— | — | @@ -2350,14 +2340,14 @@ |
2351 | 2341 | } |
2352 | 2342 | break; |
2353 | 2343 | default: |
2354 | | - throw new MWException( "State machine error in $fname" ); |
| 2344 | + throw new MWException( "State machine error in " . __METHOD__ ); |
2355 | 2345 | } |
2356 | 2346 | } |
2357 | 2347 | if( $stack > 0 ) { |
2358 | | - wfDebug( "Invalid input in $fname; not enough close tags (stack $stack, state $state)\n" ); |
| 2348 | + wfDebug( __METHOD__.": Invalid input; not enough close tags (stack $stack, state $state)\n" ); |
2359 | 2349 | return false; |
2360 | 2350 | } |
2361 | | - wfProfileOut( $fname ); |
| 2351 | + wfProfileOut( __METHOD__ ); |
2362 | 2352 | return false; |
2363 | 2353 | } |
2364 | 2354 | |
— | — | @@ -2587,12 +2577,11 @@ |
2588 | 2578 | * @private |
2589 | 2579 | */ |
2590 | 2580 | function initialiseVariables() { |
2591 | | - $fname = 'Parser::initialiseVariables'; |
2592 | | - wfProfileIn( $fname ); |
| 2581 | + wfProfileIn( __METHOD__ ); |
2593 | 2582 | $variableIDs = MagicWord::getVariableIDs(); |
2594 | 2583 | |
2595 | 2584 | $this->mVariables = new MagicWordArray( $variableIDs ); |
2596 | | - wfProfileOut( $fname ); |
| 2585 | + wfProfileOut( __METHOD__ ); |
2597 | 2586 | } |
2598 | 2587 | |
2599 | 2588 | /** |
— | — | @@ -2661,8 +2650,7 @@ |
2662 | 2651 | return $text; |
2663 | 2652 | } |
2664 | 2653 | |
2665 | | - $fname = __METHOD__; |
2666 | | - wfProfileIn( $fname ); |
| 2654 | + wfProfileIn( __METHOD__ ); |
2667 | 2655 | |
2668 | 2656 | if ( $frame === false ) { |
2669 | 2657 | $frame = $this->getPreprocessor()->newFrame(); |
— | — | @@ -2675,7 +2663,7 @@ |
2676 | 2664 | $flags = $argsOnly ? PPFrame::NO_TEMPLATES : 0; |
2677 | 2665 | $text = $frame->expand( $dom, $flags ); |
2678 | 2666 | |
2679 | | - wfProfileOut( $fname ); |
| 2667 | + wfProfileOut( __METHOD__ ); |
2680 | 2668 | return $text; |
2681 | 2669 | } |
2682 | 2670 | |
— | — | @@ -2738,8 +2726,7 @@ |
2739 | 2727 | */ |
2740 | 2728 | function braceSubstitution( $piece, $frame ) { |
2741 | 2729 | global $wgContLang, $wgLang, $wgAllowDisplayTitle, $wgNonincludableNamespaces; |
2742 | | - $fname = __METHOD__; |
2743 | | - wfProfileIn( $fname ); |
| 2730 | + wfProfileIn( __METHOD__ ); |
2744 | 2731 | wfProfileIn( __METHOD__.'-setup' ); |
2745 | 2732 | |
2746 | 2733 | # Flags |
— | — | @@ -2926,7 +2913,7 @@ |
2927 | 2914 | } |
2928 | 2915 | } else if ( $wgNonincludableNamespaces && in_array( $title->getNamespace(), $wgNonincludableNamespaces ) ) { |
2929 | 2916 | $found = false; //access denied |
2930 | | - wfDebug( "$fname: template inclusion denied for " . $title->getPrefixedDBkey() ); |
| 2917 | + wfDebug( __METHOD__.": template inclusion denied for " . $title->getPrefixedDBkey() ); |
2931 | 2918 | } else { |
2932 | 2919 | list( $text, $title ) = $this->getTemplateDom( $title ); |
2933 | 2920 | if ( $text !== false ) { |
— | — | @@ -2960,7 +2947,7 @@ |
2961 | 2948 | # Recover the source wikitext and return it |
2962 | 2949 | if ( !$found ) { |
2963 | 2950 | $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args ); |
2964 | | - wfProfileOut( $fname ); |
| 2951 | + wfProfileOut( __METHOD__ ); |
2965 | 2952 | return array( 'object' => $text ); |
2966 | 2953 | } |
2967 | 2954 | |
— | — | @@ -3019,7 +3006,7 @@ |
3020 | 3007 | $ret = array( 'text' => $text ); |
3021 | 3008 | } |
3022 | 3009 | |
3023 | | - wfProfileOut( $fname ); |
| 3010 | + wfProfileOut( __METHOD__ ); |
3024 | 3011 | return $ret; |
3025 | 3012 | } |
3026 | 3013 | |
— | — | @@ -3562,12 +3549,7 @@ |
3563 | 3550 | # <!--LINK number--> |
3564 | 3551 | # turns into |
3565 | 3552 | # link text with suffix |
3566 | | - $safeHeadline = preg_replace( '/<!--LINK ([0-9]*)-->/e', |
3567 | | - "\$this->mLinkHolders['texts'][\$1]", |
3568 | | - $safeHeadline ); |
3569 | | - $safeHeadline = preg_replace( '/<!--IWLINK ([0-9]*)-->/e', |
3570 | | - "\$this->mInterwikiLinkHolders['texts'][\$1]", |
3571 | | - $safeHeadline ); |
| 3553 | + $safeHeadline = $this->replaceLinkHoldersText( $safeHeadline ); |
3572 | 3554 | |
3573 | 3555 | # Strip out HTML (other than plain <sup> and <sub>: bug 8393) |
3574 | 3556 | $tocline = preg_replace( |
— | — | @@ -3795,7 +3777,7 @@ |
3796 | 3778 | } else { |
3797 | 3779 | # Failed to validate; fall back to the default |
3798 | 3780 | $nickname = $username; |
3799 | | - wfDebug( "Parser::getUserSig: $username has bad XML tags in signature.\n" ); |
| 3781 | + wfDebug( __METHOD__.": $username has bad XML tags in signature.\n" ); |
3800 | 3782 | } |
3801 | 3783 | } |
3802 | 3784 | |
— | — | @@ -3901,19 +3883,17 @@ |
3902 | 3884 | global $wgTitle; |
3903 | 3885 | static $executing = false; |
3904 | 3886 | |
3905 | | - $fname = "Parser::transformMsg"; |
3906 | | - |
3907 | 3887 | # Guard against infinite recursion |
3908 | 3888 | if ( $executing ) { |
3909 | 3889 | return $text; |
3910 | 3890 | } |
3911 | 3891 | $executing = true; |
3912 | 3892 | |
3913 | | - wfProfileIn($fname); |
| 3893 | + wfProfileIn(__METHOD__); |
3914 | 3894 | $text = $this->preprocess( $text, $wgTitle, $options ); |
3915 | 3895 | |
3916 | 3896 | $executing = false; |
3917 | | - wfProfileOut($fname); |
| 3897 | + wfProfileOut(__METHOD__); |
3918 | 3898 | return $text; |
3919 | 3899 | } |
3920 | 3900 | |
— | — | @@ -4010,7 +3990,7 @@ |
4011 | 3991 | # Add to function cache |
4012 | 3992 | $mw = MagicWord::get( $id ); |
4013 | 3993 | if( !$mw ) |
4014 | | - throw new MWException( 'Parser::setFunctionHook() expecting a magic word identifier.' ); |
| 3994 | + throw new MWException( __METHOD__.'() expecting a magic word identifier.' ); |
4015 | 3995 | |
4016 | 3996 | $synonyms = $mw->getSynonyms(); |
4017 | 3997 | $sensitive = intval( $mw->isCaseSensitive() ); |
— | — | @@ -4046,266 +4026,9 @@ |
4047 | 4027 | * Replace <!--LINK--> link placeholders with actual links, in the buffer |
4048 | 4028 | * Placeholders created in Skin::makeLinkObj() |
4049 | 4029 | * Returns an array of link CSS classes, indexed by PDBK. |
4050 | | - * $options is a bit field, RLH_FOR_UPDATE to select for update |
4051 | 4030 | */ |
4052 | 4031 | function replaceLinkHolders( &$text, $options = 0 ) { |
4053 | | - global $wgUser; |
4054 | | - global $wgContLang; |
4055 | | - |
4056 | | - $fname = 'Parser::replaceLinkHolders'; |
4057 | | - wfProfileIn( $fname ); |
4058 | | - |
4059 | | - $pdbks = array(); |
4060 | | - $colours = array(); |
4061 | | - $linkcolour_ids = array(); |
4062 | | - $sk = $this->mOptions->getSkin(); |
4063 | | - $linkCache = LinkCache::singleton(); |
4064 | | - |
4065 | | - if ( !empty( $this->mLinkHolders['namespaces'] ) ) { |
4066 | | - wfProfileIn( $fname.'-check' ); |
4067 | | - $dbr = wfGetDB( DB_SLAVE ); |
4068 | | - $page = $dbr->tableName( 'page' ); |
4069 | | - $threshold = $wgUser->getOption('stubthreshold'); |
4070 | | - |
4071 | | - # Sort by namespace |
4072 | | - asort( $this->mLinkHolders['namespaces'] ); |
4073 | | - |
4074 | | - # Generate query |
4075 | | - $query = false; |
4076 | | - $current = null; |
4077 | | - foreach ( $this->mLinkHolders['namespaces'] as $key => $ns ) { |
4078 | | - # Make title object |
4079 | | - $title = $this->mLinkHolders['titles'][$key]; |
4080 | | - |
4081 | | - # Skip invalid entries. |
4082 | | - # Result will be ugly, but prevents crash. |
4083 | | - if ( is_null( $title ) ) { |
4084 | | - continue; |
4085 | | - } |
4086 | | - $pdbk = $pdbks[$key] = $title->getPrefixedDBkey(); |
4087 | | - |
4088 | | - # Check if it's a static known link, e.g. interwiki |
4089 | | - if ( $title->isAlwaysKnown() ) { |
4090 | | - $colours[$pdbk] = ''; |
4091 | | - } elseif ( ( $id = $linkCache->getGoodLinkID( $pdbk ) ) != 0 ) { |
4092 | | - $colours[$pdbk] = ''; |
4093 | | - $this->mOutput->addLink( $title, $id ); |
4094 | | - } elseif ( $linkCache->isBadLink( $pdbk ) ) { |
4095 | | - $colours[$pdbk] = 'new'; |
4096 | | - } elseif ( $title->getNamespace() == NS_SPECIAL && !SpecialPage::exists( $pdbk ) ) { |
4097 | | - $colours[$pdbk] = 'new'; |
4098 | | - } else { |
4099 | | - # Not in the link cache, add it to the query |
4100 | | - if ( !isset( $current ) ) { |
4101 | | - $current = $ns; |
4102 | | - $query = "SELECT page_id, page_namespace, page_title, page_is_redirect, page_len"; |
4103 | | - $query .= " FROM $page WHERE (page_namespace=$ns AND page_title IN("; |
4104 | | - } elseif ( $current != $ns ) { |
4105 | | - $current = $ns; |
4106 | | - $query .= ")) OR (page_namespace=$ns AND page_title IN("; |
4107 | | - } else { |
4108 | | - $query .= ', '; |
4109 | | - } |
4110 | | - |
4111 | | - $query .= $dbr->addQuotes( $this->mLinkHolders['dbkeys'][$key] ); |
4112 | | - } |
4113 | | - } |
4114 | | - if ( $query ) { |
4115 | | - $query .= '))'; |
4116 | | - if ( $options & RLH_FOR_UPDATE ) { |
4117 | | - $query .= ' FOR UPDATE'; |
4118 | | - } |
4119 | | - |
4120 | | - $res = $dbr->query( $query, $fname ); |
4121 | | - |
4122 | | - # Fetch data and form into an associative array |
4123 | | - # non-existent = broken |
4124 | | - while ( $s = $dbr->fetchObject($res) ) { |
4125 | | - $title = Title::makeTitle( $s->page_namespace, $s->page_title ); |
4126 | | - $pdbk = $title->getPrefixedDBkey(); |
4127 | | - $linkCache->addGoodLinkObj( $s->page_id, $title, $s->page_len, $s->page_is_redirect ); |
4128 | | - $this->mOutput->addLink( $title, $s->page_id ); |
4129 | | - $colours[$pdbk] = $sk->getLinkColour( $title, $threshold ); |
4130 | | - //add id to the extension todolist |
4131 | | - $linkcolour_ids[$s->page_id] = $pdbk; |
4132 | | - } |
4133 | | - //pass an array of page_ids to an extension |
4134 | | - wfRunHooks( 'GetLinkColours', array( $linkcolour_ids, &$colours ) ); |
4135 | | - } |
4136 | | - wfProfileOut( $fname.'-check' ); |
4137 | | - |
4138 | | - # Do a second query for different language variants of links and categories |
4139 | | - if($wgContLang->hasVariants()){ |
4140 | | - $linkBatch = new LinkBatch(); |
4141 | | - $variantMap = array(); // maps $pdbkey_Variant => $keys (of link holders) |
4142 | | - $categoryMap = array(); // maps $category_variant => $category (dbkeys) |
4143 | | - $varCategories = array(); // category replacements oldDBkey => newDBkey |
4144 | | - |
4145 | | - $categories = $this->mOutput->getCategoryLinks(); |
4146 | | - |
4147 | | - // Add variants of links to link batch |
4148 | | - foreach ( $this->mLinkHolders['namespaces'] as $key => $ns ) { |
4149 | | - $title = $this->mLinkHolders['titles'][$key]; |
4150 | | - if ( is_null( $title ) ) |
4151 | | - continue; |
4152 | | - |
4153 | | - $pdbk = $title->getPrefixedDBkey(); |
4154 | | - $titleText = $title->getText(); |
4155 | | - |
4156 | | - // generate all variants of the link title text |
4157 | | - $allTextVariants = $wgContLang->convertLinkToAllVariants($titleText); |
4158 | | - |
4159 | | - // if link was not found (in first query), add all variants to query |
4160 | | - if ( !isset($colours[$pdbk]) ){ |
4161 | | - foreach($allTextVariants as $textVariant){ |
4162 | | - if($textVariant != $titleText){ |
4163 | | - $variantTitle = Title::makeTitle( $ns, $textVariant ); |
4164 | | - if(is_null($variantTitle)) continue; |
4165 | | - $linkBatch->addObj( $variantTitle ); |
4166 | | - $variantMap[$variantTitle->getPrefixedDBkey()][] = $key; |
4167 | | - } |
4168 | | - } |
4169 | | - } |
4170 | | - } |
4171 | | - |
4172 | | - // process categories, check if a category exists in some variant |
4173 | | - foreach( $categories as $category ){ |
4174 | | - $variants = $wgContLang->convertLinkToAllVariants($category); |
4175 | | - foreach($variants as $variant){ |
4176 | | - if($variant != $category){ |
4177 | | - $variantTitle = Title::newFromDBkey( Title::makeName(NS_CATEGORY,$variant) ); |
4178 | | - if(is_null($variantTitle)) continue; |
4179 | | - $linkBatch->addObj( $variantTitle ); |
4180 | | - $categoryMap[$variant] = $category; |
4181 | | - } |
4182 | | - } |
4183 | | - } |
4184 | | - |
4185 | | - |
4186 | | - if(!$linkBatch->isEmpty()){ |
4187 | | - // construct query |
4188 | | - $titleClause = $linkBatch->constructSet('page', $dbr); |
4189 | | - |
4190 | | - $variantQuery = "SELECT page_id, page_namespace, page_title, page_is_redirect, page_len"; |
4191 | | - |
4192 | | - $variantQuery .= " FROM $page WHERE $titleClause"; |
4193 | | - if ( $options & RLH_FOR_UPDATE ) { |
4194 | | - $variantQuery .= ' FOR UPDATE'; |
4195 | | - } |
4196 | | - |
4197 | | - $varRes = $dbr->query( $variantQuery, $fname ); |
4198 | | - |
4199 | | - // for each found variants, figure out link holders and replace |
4200 | | - while ( $s = $dbr->fetchObject($varRes) ) { |
4201 | | - |
4202 | | - $variantTitle = Title::makeTitle( $s->page_namespace, $s->page_title ); |
4203 | | - $varPdbk = $variantTitle->getPrefixedDBkey(); |
4204 | | - $vardbk = $variantTitle->getDBkey(); |
4205 | | - |
4206 | | - $holderKeys = array(); |
4207 | | - if(isset($variantMap[$varPdbk])){ |
4208 | | - $holderKeys = $variantMap[$varPdbk]; |
4209 | | - $linkCache->addGoodLinkObj( $s->page_id, $variantTitle, $s->page_len, $s->page_is_redirect ); |
4210 | | - $this->mOutput->addLink( $variantTitle, $s->page_id ); |
4211 | | - } |
4212 | | - |
4213 | | - // loop over link holders |
4214 | | - foreach($holderKeys as $key){ |
4215 | | - $title = $this->mLinkHolders['titles'][$key]; |
4216 | | - if ( is_null( $title ) ) continue; |
4217 | | - |
4218 | | - $pdbk = $title->getPrefixedDBkey(); |
4219 | | - |
4220 | | - if(!isset($colours[$pdbk])){ |
4221 | | - // found link in some of the variants, replace the link holder data |
4222 | | - $this->mLinkHolders['titles'][$key] = $variantTitle; |
4223 | | - $this->mLinkHolders['dbkeys'][$key] = $variantTitle->getDBkey(); |
4224 | | - |
4225 | | - // set pdbk and colour |
4226 | | - $pdbks[$key] = $varPdbk; |
4227 | | - $colours[$varPdbk] = $sk->getLinkColour( $variantTitle, $threshold ); |
4228 | | - $linkcolour_ids[$s->page_id] = $pdbk; |
4229 | | - } |
4230 | | - wfRunHooks( 'GetLinkColours', array( $linkcolour_ids, &$colours ) ); |
4231 | | - } |
4232 | | - |
4233 | | - // check if the object is a variant of a category |
4234 | | - if(isset($categoryMap[$vardbk])){ |
4235 | | - $oldkey = $categoryMap[$vardbk]; |
4236 | | - if($oldkey != $vardbk) |
4237 | | - $varCategories[$oldkey]=$vardbk; |
4238 | | - } |
4239 | | - } |
4240 | | - |
4241 | | - // rebuild the categories in original order (if there are replacements) |
4242 | | - if(count($varCategories)>0){ |
4243 | | - $newCats = array(); |
4244 | | - $originalCats = $this->mOutput->getCategories(); |
4245 | | - foreach($originalCats as $cat => $sortkey){ |
4246 | | - // make the replacement |
4247 | | - if( array_key_exists($cat,$varCategories) ) |
4248 | | - $newCats[$varCategories[$cat]] = $sortkey; |
4249 | | - else $newCats[$cat] = $sortkey; |
4250 | | - } |
4251 | | - $this->mOutput->setCategoryLinks($newCats); |
4252 | | - } |
4253 | | - } |
4254 | | - } |
4255 | | - |
4256 | | - # Construct search and replace arrays |
4257 | | - wfProfileIn( $fname.'-construct' ); |
4258 | | - $replacePairs = array(); |
4259 | | - foreach ( $this->mLinkHolders['namespaces'] as $key => $ns ) { |
4260 | | - $pdbk = $pdbks[$key]; |
4261 | | - $searchkey = "<!--LINK $key-->"; |
4262 | | - $title = $this->mLinkHolders['titles'][$key]; |
4263 | | - if ( !isset( $colours[$pdbk] ) || $colours[$pdbk] == 'new' ) { |
4264 | | - $linkCache->addBadLinkObj( $title ); |
4265 | | - $colours[$pdbk] = 'new'; |
4266 | | - $this->mOutput->addLink( $title, 0 ); |
4267 | | - $replacePairs[$searchkey] = $sk->makeBrokenLinkObj( $title, |
4268 | | - $this->mLinkHolders['texts'][$key], |
4269 | | - $this->mLinkHolders['queries'][$key] ); |
4270 | | - } else { |
4271 | | - $replacePairs[$searchkey] = $sk->makeColouredLinkObj( $title, $colours[$pdbk], |
4272 | | - $this->mLinkHolders['texts'][$key], |
4273 | | - $this->mLinkHolders['queries'][$key] ); |
4274 | | - } |
4275 | | - } |
4276 | | - $replacer = new HashtableReplacer( $replacePairs, 1 ); |
4277 | | - wfProfileOut( $fname.'-construct' ); |
4278 | | - |
4279 | | - # Do the thing |
4280 | | - wfProfileIn( $fname.'-replace' ); |
4281 | | - $text = preg_replace_callback( |
4282 | | - '/(<!--LINK .*?-->)/', |
4283 | | - $replacer->cb(), |
4284 | | - $text); |
4285 | | - |
4286 | | - wfProfileOut( $fname.'-replace' ); |
4287 | | - } |
4288 | | - |
4289 | | - # Now process interwiki link holders |
4290 | | - # This is quite a bit simpler than internal links |
4291 | | - if ( !empty( $this->mInterwikiLinkHolders['texts'] ) ) { |
4292 | | - wfProfileIn( $fname.'-interwiki' ); |
4293 | | - # Make interwiki link HTML |
4294 | | - $replacePairs = array(); |
4295 | | - foreach( $this->mInterwikiLinkHolders['texts'] as $key => $link ) { |
4296 | | - $title = $this->mInterwikiLinkHolders['titles'][$key]; |
4297 | | - $replacePairs[$key] = $sk->link( $title, $link ); |
4298 | | - } |
4299 | | - $replacer = new HashtableReplacer( $replacePairs, 1 ); |
4300 | | - |
4301 | | - $text = preg_replace_callback( |
4302 | | - '/<!--IWLINK (.*?)-->/', |
4303 | | - $replacer->cb(), |
4304 | | - $text ); |
4305 | | - wfProfileOut( $fname.'-interwiki' ); |
4306 | | - } |
4307 | | - |
4308 | | - wfProfileOut( $fname ); |
4309 | | - return $colours; |
| 4032 | + return $this->mLinkHolders->replace( $text ); |
4310 | 4033 | } |
4311 | 4034 | |
4312 | 4035 | /** |
— | — | @@ -4315,39 +4038,10 @@ |
4316 | 4039 | * @return string |
4317 | 4040 | */ |
4318 | 4041 | function replaceLinkHoldersText( $text ) { |
4319 | | - $fname = 'Parser::replaceLinkHoldersText'; |
4320 | | - wfProfileIn( $fname ); |
4321 | | - |
4322 | | - $text = preg_replace_callback( |
4323 | | - '/<!--(LINK|IWLINK) (.*?)-->/', |
4324 | | - array( &$this, 'replaceLinkHoldersTextCallback' ), |
4325 | | - $text ); |
4326 | | - |
4327 | | - wfProfileOut( $fname ); |
4328 | | - return $text; |
| 4042 | + return $this->mLinkHolders->replaceText( $text ); |
4329 | 4043 | } |
4330 | 4044 | |
4331 | 4045 | /** |
4332 | | - * @param array $matches |
4333 | | - * @return string |
4334 | | - * @private |
4335 | | - */ |
4336 | | - function replaceLinkHoldersTextCallback( $matches ) { |
4337 | | - $type = $matches[1]; |
4338 | | - $key = $matches[2]; |
4339 | | - if( $type == 'LINK' ) { |
4340 | | - if( isset( $this->mLinkHolders['texts'][$key] ) ) { |
4341 | | - return $this->mLinkHolders['texts'][$key]; |
4342 | | - } |
4343 | | - } elseif( $type == 'IWLINK' ) { |
4344 | | - if( isset( $this->mInterwikiLinkHolders['texts'][$key] ) ) { |
4345 | | - return $this->mInterwikiLinkHolders['texts'][$key]; |
4346 | | - } |
4347 | | - } |
4348 | | - return $matches[0]; |
4349 | | - } |
4350 | | - |
4351 | | - /** |
4352 | 4046 | * Tag hook handler for 'pre'. |
4353 | 4047 | */ |
4354 | 4048 | function renderPreTag( $text, $attribs ) { |
— | — | @@ -4398,7 +4092,7 @@ |
4399 | 4093 | |
4400 | 4094 | wfRunHooks( 'BeforeParserrenderImageGallery', array( &$this, &$ig ) ); |
4401 | 4095 | |
4402 | | - $lines = explode( "\n", $text ); |
| 4096 | + $lines = StringUtils::explode( "\n", $text ); |
4403 | 4097 | foreach ( $lines as $line ) { |
4404 | 4098 | # match lines like these: |
4405 | 4099 | # Image:someimage.jpg|This is some image |
— | — | @@ -4411,7 +4105,7 @@ |
4412 | 4106 | |
4413 | 4107 | if ( strpos( $matches[0], '%' ) !== false ) |
4414 | 4108 | $matches[1] = urldecode( $matches[1] ); |
4415 | | - $tp = Title::newFromText( $matches[1] ); |
| 4109 | + $tp = Title::newFromText( $matches[1], NS_IMAGE ); |
4416 | 4110 | $nt =& $tp; |
4417 | 4111 | if( is_null( $nt ) ) { |
4418 | 4112 | # Bogus title. Ignore these so we don't bomb out later. |
— | — | @@ -4477,8 +4171,11 @@ |
4478 | 4172 | |
4479 | 4173 | /** |
4480 | 4174 | * Parse image options text and use it to make an image |
| 4175 | + * @param Title $title |
| 4176 | + * @param string $options |
| 4177 | + * @param LinkHolderArray $holders |
4481 | 4178 | */ |
4482 | | - function makeImage( $title, $options ) { |
| 4179 | + function makeImage( $title, $options, $holders = false ) { |
4483 | 4180 | # Check if the options text is of the form "options|alt text" |
4484 | 4181 | # Options are: |
4485 | 4182 | # * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang |
— | — | @@ -4501,7 +4198,7 @@ |
4502 | 4199 | # * bottom |
4503 | 4200 | # * text-bottom |
4504 | 4201 | |
4505 | | - $parts = array_map( 'trim', explode( '|', $options) ); |
| 4202 | + $parts = StringUtils::explode( "|", $options ); |
4506 | 4203 | $sk = $this->mOptions->getSkin(); |
4507 | 4204 | |
4508 | 4205 | # Give extensions a chance to select the file revision for us |
— | — | @@ -4588,7 +4285,13 @@ |
4589 | 4286 | } |
4590 | 4287 | |
4591 | 4288 | # Strip bad stuff out of the alt text |
4592 | | - $alt = $this->replaceLinkHoldersText( $caption ); |
| 4289 | + # We can't just use replaceLinkHoldersText() here, because if this function |
| 4290 | + # is called from replaceInternalLinks2(), mLinkHolders won't be up to date. |
| 4291 | + if ( $holders ) { |
| 4292 | + $alt = $holders->replaceText( $caption ); |
| 4293 | + } else { |
| 4294 | + $alt = $this->replaceLinkHoldersText( $caption ); |
| 4295 | + } |
4593 | 4296 | |
4594 | 4297 | # make sure there are no placeholders in thumbnail attributes |
4595 | 4298 | # that are later expanded to html- so expand them now and |
Index: trunk/phase3/includes/MessageCache.php |
— | — | @@ -44,7 +44,6 @@ |
45 | 45 | |
46 | 46 | /** |
47 | 47 | * ParserOptions is lazy initialised. |
48 | | - * Access should probably be protected. |
49 | 48 | */ |
50 | 49 | function getParserOptions() { |
51 | 50 | if ( !$this->mParserOptions ) { |
Index: trunk/phase3/includes/AutoLoader.php |
— | — | @@ -64,6 +64,7 @@ |
65 | 65 | 'EnotifNotifyJob' => 'includes/EnotifNotifyJob.php', |
66 | 66 | 'ErrorPageError' => 'includes/Exception.php', |
67 | 67 | 'Exif' => 'includes/Exif.php', |
| 68 | + 'ExplodeIterator' => 'includes/StringUtils.php', |
68 | 69 | 'ExternalEdit' => 'includes/ExternalEdit.php', |
69 | 70 | 'ExternalStoreDB' => 'includes/ExternalStoreDB.php', |
70 | 71 | 'ExternalStoreHttp' => 'includes/ExternalStoreHttp.php', |
— | — | @@ -351,6 +352,7 @@ |
352 | 353 | # includes/parser |
353 | 354 | 'CoreParserFunctions' => 'includes/parser/CoreParserFunctions.php', |
354 | 355 | 'DateFormatter' => 'includes/parser/DateFormatter.php', |
| 356 | + 'LinkHolderArray' => 'includes/parser/LinkHolderArray.php', |
355 | 357 | 'OnlyIncludeReplacer' => 'includes/parser/Parser.php', |
356 | 358 | 'PPDAccum_Hash' => 'includes/parser/Preprocessor_Hash.php', |
357 | 359 | 'PPDPart' => 'includes/parser/Preprocessor_DOM.php', |
Index: trunk/phase3/includes/Title.php |
— | — | @@ -10,12 +10,6 @@ |
11 | 11 | |
12 | 12 | define ( 'GAID_FOR_UPDATE', 1 ); |
13 | 13 | |
14 | | -/** |
15 | | - * Title::newFromText maintains a cache to avoid expensive re-normalization of |
16 | | - * commonly used titles. On a batch operation this can become a memory leak |
17 | | - * if not bounded. After hitting this many titles reset the cache. |
18 | | - */ |
19 | | -define( 'MW_TITLECACHE_MAX', 1000 ); |
20 | 14 | |
21 | 15 | /** |
22 | 16 | * Constants for pr_cascade bitfield |
— | — | @@ -36,6 +30,14 @@ |
37 | 31 | //@} |
38 | 32 | |
39 | 33 | /** |
| 34 | + * Title::newFromText maintains a cache to avoid expensive re-normalization of |
| 35 | + * commonly used titles. On a batch operation this can become a memory leak |
| 36 | + * if not bounded. After hitting this many titles reset the cache. |
| 37 | + */ |
| 38 | + const CACHE_MAX = 1000; |
| 39 | + |
| 40 | + |
| 41 | + /** |
40 | 42 | * @name Private member variables |
41 | 43 | * Please use the accessor functions instead. |
42 | 44 | * @private |
— | — | @@ -131,7 +133,7 @@ |
132 | 134 | static $cachedcount = 0 ; |
133 | 135 | if( $t->secureAndSplit() ) { |
134 | 136 | if( $defaultNamespace == NS_MAIN ) { |
135 | | - if( $cachedcount >= MW_TITLECACHE_MAX ) { |
| 137 | + if( $cachedcount >= self::CACHE_MAX ) { |
136 | 138 | # Avoid memory leaks on mass operations... |
137 | 139 | Title::$titleCache = array(); |
138 | 140 | $cachedcount=0; |
Index: trunk/phase3/includes/LinkCache.php |
— | — | @@ -9,7 +9,6 @@ |
10 | 10 | // becomes incompatible with the new version. |
11 | 11 | /* private */ var $mClassVer = 4; |
12 | 12 | |
13 | | - /* private */ var $mPageLinks; |
14 | 13 | /* private */ var $mGoodLinks, $mBadLinks; |
15 | 14 | /* private */ var $mForUpdate; |
16 | 15 | |
— | — | @@ -26,7 +25,6 @@ |
27 | 26 | |
28 | 27 | function __construct() { |
29 | 28 | $this->mForUpdate = false; |
30 | | - $this->mPageLinks = array(); |
31 | 29 | $this->mGoodLinks = array(); |
32 | 30 | $this->mGoodLinkFields = array(); |
33 | 31 | $this->mBadLinks = array(); |
— | — | @@ -78,14 +76,12 @@ |
79 | 77 | $dbkey = $title->getPrefixedDbKey(); |
80 | 78 | $this->mGoodLinks[$dbkey] = $id; |
81 | 79 | $this->mGoodLinkFields[$dbkey] = array( 'length' => $len, 'redirect' => $redir ); |
82 | | - $this->mPageLinks[$dbkey] = $title; |
83 | 80 | } |
84 | 81 | |
85 | 82 | public function addBadLinkObj( $title ) { |
86 | 83 | $dbkey = $title->getPrefixedDbKey(); |
87 | 84 | if ( ! $this->isBadLink( $dbkey ) ) { |
88 | 85 | $this->mBadLinks[$dbkey] = 1; |
89 | | - $this->mPageLinks[$dbkey] = $title; |
90 | 86 | } |
91 | 87 | } |
92 | 88 | |
— | — | @@ -96,7 +92,6 @@ |
97 | 93 | /* obsolete, for old $wgLinkCacheMemcached stuff */ |
98 | 94 | public function clearLink( $title ) {} |
99 | 95 | |
100 | | - public function getPageLinks() { return $this->mPageLinks; } |
101 | 96 | public function getGoodLinks() { return $this->mGoodLinks; } |
102 | 97 | public function getBadLinks() { return array_keys( $this->mBadLinks ); } |
103 | 98 | |
— | — | @@ -181,7 +176,6 @@ |
182 | 177 | * Clears cache |
183 | 178 | */ |
184 | 179 | public function clear() { |
185 | | - $this->mPageLinks = array(); |
186 | 180 | $this->mGoodLinks = array(); |
187 | 181 | $this->mGoodLinkFields = array(); |
188 | 182 | $this->mBadLinks = array(); |
Index: trunk/phase3/includes/StringUtils.php |
— | — | @@ -167,6 +167,18 @@ |
168 | 168 | $string = str_replace( '$', '\\$', $string ); |
169 | 169 | return $string; |
170 | 170 | } |
| 171 | + |
| 172 | + /** |
| 173 | + * Workalike for explode() with limited memory usage. |
| 174 | + * Returns an Iterator |
| 175 | + */ |
| 176 | + static function explode( $separator, $subject ) { |
| 177 | + if ( substr_count( $subject, $separator ) > 1000 ) { |
| 178 | + return new ExplodeIterator( $separator, $subject ); |
| 179 | + } else { |
| 180 | + return new ArrayIterator( explode( $separator, $subject ) ); |
| 181 | + } |
| 182 | + } |
171 | 183 | } |
172 | 184 | |
173 | 185 | /** |
— | — | @@ -310,3 +322,90 @@ |
311 | 323 | return $result; |
312 | 324 | } |
313 | 325 | } |
| 326 | + |
| 327 | +/** |
| 328 | + * An iterator which works exactly like: |
| 329 | + * |
| 330 | + * foreach ( explode( $delim, $s ) as $element ) { |
| 331 | + * ... |
| 332 | + * } |
| 333 | + * |
| 334 | + * Except it doesn't use 193 byte per element |
| 335 | + */ |
| 336 | +class ExplodeIterator implements Iterator { |
| 337 | + // The subject string |
| 338 | + var $subject, $subjectLength; |
| 339 | + |
| 340 | + // The delimiter |
| 341 | + var $delim, $delimLength; |
| 342 | + |
| 343 | + // The position of the start of the line |
| 344 | + var $curPos; |
| 345 | + |
| 346 | + // The position after the end of the next delimiter |
| 347 | + var $endPos; |
| 348 | + |
| 349 | + // The current token |
| 350 | + var $current; |
| 351 | + |
| 352 | + /** |
| 353 | + * Construct a DelimIterator |
| 354 | + */ |
| 355 | + function __construct( $delim, $s ) { |
| 356 | + $this->subject = $s; |
| 357 | + $this->delim = $delim; |
| 358 | + |
| 359 | + // Micro-optimisation (theoretical) |
| 360 | + $this->subjectLength = strlen( $s ); |
| 361 | + $this->delimLength = strlen( $delim ); |
| 362 | + |
| 363 | + $this->rewind(); |
| 364 | + } |
| 365 | + |
| 366 | + function rewind() { |
| 367 | + $this->curPos = 0; |
| 368 | + $this->endPos = strpos( $this->subject, $this->delim ); |
| 369 | + $this->refreshCurrent(); |
| 370 | + } |
| 371 | + |
| 372 | + |
| 373 | + function refreshCurrent() { |
| 374 | + if ( $this->curPos === false ) { |
| 375 | + $this->current = false; |
| 376 | + } elseif ( $this->curPos >= $this->subjectLength ) { |
| 377 | + $this->current = ''; |
| 378 | + } elseif ( $this->endPos === false ) { |
| 379 | + $this->current = substr( $this->subject, $this->curPos ); |
| 380 | + } else { |
| 381 | + $this->current = substr( $this->subject, $this->curPos, $this->endPos - $this->curPos ); |
| 382 | + } |
| 383 | + } |
| 384 | + |
| 385 | + function current() { |
| 386 | + return $this->current; |
| 387 | + } |
| 388 | + |
| 389 | + function key() { |
| 390 | + return $this->curPos; |
| 391 | + } |
| 392 | + |
| 393 | + function next() { |
| 394 | + if ( $this->endPos === false ) { |
| 395 | + $this->curPos = false; |
| 396 | + } else { |
| 397 | + $this->curPos = $this->endPos + $this->delimLength; |
| 398 | + if ( $this->curPos >= $this->subjectLength ) { |
| 399 | + $this->endPos = false; |
| 400 | + } else { |
| 401 | + $this->endPos = strpos( $this->subject, $this->delim, $this->curPos ); |
| 402 | + } |
| 403 | + } |
| 404 | + $this->refreshCurrent(); |
| 405 | + return $this->current; |
| 406 | + } |
| 407 | + |
| 408 | + function valid() { |
| 409 | + return $this->curPos !== false; |
| 410 | + } |
| 411 | +} |
| 412 | + |
Index: trunk/phase3/RELEASE-NOTES |
— | — | @@ -130,6 +130,7 @@ |
131 | 131 | gives results |
132 | 132 | * Avoid recursive crazy expansions in section edit comments for pages which |
133 | 133 | contain '/*' in the title |
| 134 | +* Fix excessive memory usage when parsing pages with lots of links |
134 | 135 | |
135 | 136 | === API changes in 1.14 === |
136 | 137 | |