MediaWiki
REL1_21
|
00001 <?php 00028 class Parser_LinkHooks extends Parser { 00034 const VERSION = '1.6.4'; 00035 00036 # Flags for Parser::setLinkHook 00037 # Also available as global constants from Defines.php 00038 const SLH_PATTERN = 1; 00039 00040 # Constants needed for external link processing 00041 # Everything except bracket, space, or control characters 00042 const EXT_LINK_URL_CLASS = '[^][<>"\\x00-\\x20\\x7F]'; 00043 const EXT_IMAGE_REGEX = '/^(http:\/\/|https:\/\/)([^][<>"\\x00-\\x20\\x7F]+) 00044 \\/([A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF]+)\\.((?i)gif|png|jpg|jpeg)$/Sx'; 00045 00049 # Persistent: 00050 var $mLinkHooks; 00051 00057 public function __construct( $conf = array() ) { 00058 parent::__construct( $conf ); 00059 $this->mLinkHooks = array(); 00060 } 00061 00065 function firstCallInit() { 00066 parent::__construct(); 00067 if ( !$this->mFirstCall ) { 00068 return; 00069 } 00070 $this->mFirstCall = false; 00071 00072 wfProfileIn( __METHOD__ ); 00073 00074 $this->setHook( 'pre', array( $this, 'renderPreTag' ) ); 00075 CoreParserFunctions::register( $this ); 00076 CoreLinkFunctions::register( $this ); 00077 $this->initialiseVariables(); 00078 00079 wfRunHooks( 'ParserFirstCallInit', array( &$this ) ); 00080 wfProfileOut( __METHOD__ ); 00081 } 00082 00106 public function setLinkHook( $ns, $callback, $flags = 0 ) { 00107 if( $flags & SLH_PATTERN && !is_string($ns) ) 00108 throw new MWException( __METHOD__ . '() expecting a regex string pattern.' ); 00109 elseif( $flags | ~SLH_PATTERN && !is_int( $ns ) ) 00110 throw new MWException( __METHOD__ . '() expecting a namespace index.' ); 00111 $oldVal = isset( $this->mLinkHooks[$ns] ) ? $this->mLinkHooks[$ns][0] : null; 00112 $this->mLinkHooks[$ns] = array( $callback, $flags ); 00113 return $oldVal; 00114 } 00115 00121 function getLinkHooks() { 00122 return array_keys( $this->mLinkHooks ); 00123 } 00124 00133 function replaceInternalLinks2( &$s ) { 00134 wfProfileIn( __METHOD__ ); 00135 00136 wfProfileIn( __METHOD__ . '-setup' ); 00137 static $tc = false, $titleRegex; //$e1, $e1_img; 00138 if( !$tc ) { 00139 # the % is needed to support urlencoded titles as well 00140 $tc = Title::legalChars() . '#%'; 00141 # Match a link having the form [[namespace:link|alternate]]trail 00142 //$e1 = "/^([{$tc}]+)(?:\\|(.+?))?]](.*)\$/sD"; 00143 # Match cases where there is no "]]", which might still be images 00144 //$e1_img = "/^([{$tc}]+)\\|(.*)\$/sD"; 00145 # Match a valid plain title 00146 $titleRegex = "/^([{$tc}]+)$/sD"; 00147 } 00148 00149 $holders = new LinkHolderArray( $this ); 00150 00151 if( is_null( $this->mTitle ) ) { 00152 wfProfileOut( __METHOD__ . '-setup' ); 00153 wfProfileOut( __METHOD__ ); 00154 throw new MWException( __METHOD__ . ": \$this->mTitle is null\n" ); 00155 } 00156 00157 wfProfileOut( __METHOD__ . '-setup' ); 00158 00159 $offset = 0; 00160 $offsetStack = array(); 00161 $markers = new LinkMarkerReplacer( $this, $holders, array( &$this, 'replaceInternalLinksCallback' ) ); 00162 while( true ) { 00163 $startBracketOffset = strpos( $s, '[[', $offset ); 00164 $endBracketOffset = strpos( $s, ']]', $offset ); 00165 # Finish when there are no more brackets 00166 if( $startBracketOffset === false && $endBracketOffset === false ) break; 00167 # Determine if the bracket is a starting or ending bracket 00168 # When we find both, use the first one 00169 elseif( $startBracketOffset !== false && $endBracketOffset !== false ) 00170 $isStart = $startBracketOffset <= $endBracketOffset; 00171 # When we only found one, check which it is 00172 else $isStart = $startBracketOffset !== false; 00173 $bracketOffset = $isStart ? $startBracketOffset : $endBracketOffset; 00174 if( $isStart ) { 00176 # Just push our current offset in the string onto the stack 00177 $offsetStack[] = $startBracketOffset; 00178 } else { 00180 # Pop the start pos for our current link zone off the stack 00181 $startBracketOffset = array_pop( $offsetStack ); 00182 # Just to clean up the code, lets place offsets on the outer ends 00183 $endBracketOffset += 2; 00184 00185 # Only do logic if we actually have a opening bracket for this 00186 if( isset( $startBracketOffset ) ) { 00187 # Extract text inside the link 00188 @list( $titleText, $paramText ) = explode( '|', 00189 substr( $s, $startBracketOffset + 2, $endBracketOffset - $startBracketOffset - 4 ), 2 ); 00190 # Create markers only for valid links 00191 if( preg_match( $titleRegex, $titleText ) ) { 00192 # Store the text for the marker 00193 $marker = $markers->addMarker( $titleText, $paramText ); 00194 # Replace the current link with the marker 00195 $s = substr( $s, 0, $startBracketOffset ) . 00196 $marker . 00197 substr( $s, $endBracketOffset ); 00198 # We have modified $s, because of this we need to set the 00199 # offset manually since the end position is different now 00200 $offset = $startBracketOffset+strlen( $marker ); 00201 continue; 00202 } 00203 # ToDo: Some LinkHooks may allow recursive links inside of 00204 # the link text, create a regex that also matches our 00205 # <!-- LINKMARKER ### --> sequence in titles 00206 # ToDO: Some LinkHooks use patterns rather than namespaces 00207 # these need to be tested at this point here 00208 } 00209 } 00210 # Bump our offset to after our current bracket 00211 $offset = $bracketOffset+2; 00212 } 00213 00214 # Now expand our tree 00215 wfProfileIn( __METHOD__ . '-expand' ); 00216 $s = $markers->expand( $s ); 00217 wfProfileOut( __METHOD__ . '-expand' ); 00218 00219 wfProfileOut( __METHOD__ ); 00220 return $holders; 00221 } 00222 00223 function replaceInternalLinksCallback( $parser, $holders, $markers, $titleText, $paramText ) { 00224 wfProfileIn( __METHOD__ ); 00225 $wt = isset( $paramText ) ? "[[$titleText|$paramText]]" : "[[$titleText]]"; 00226 wfProfileIn( __METHOD__ . "-misc" ); 00227 00228 # Don't allow internal links to pages containing 00229 # PROTO: where PROTO is a valid URL protocol; these 00230 # should be external links. 00231 if( preg_match( '/^\b(?i:' . wfUrlProtocols() . ')/', $titleText ) ) { 00232 wfProfileOut( __METHOD__ . "-misc" ); 00233 wfProfileOut( __METHOD__ ); 00234 return $wt; 00235 } 00236 00237 # Make subpage if necessary 00238 if( $this->areSubpagesAllowed() ) { 00239 $titleText = $this->maybeDoSubpageLink( $titleText, $paramText ); 00240 } 00241 00242 # Check for a leading colon and strip it if it is there 00243 $leadingColon = $titleText[0] == ':'; 00244 if( $leadingColon ) $titleText = substr( $titleText, 1 ); 00245 00246 wfProfileOut( __METHOD__ . "-misc" ); 00247 # Make title object 00248 wfProfileIn( __METHOD__ . "-title" ); 00249 $title = Title::newFromText( $this->mStripState->unstripNoWiki( $titleText ) ); 00250 if( !$title ) { 00251 wfProfileOut( __METHOD__ . "-title" ); 00252 wfProfileOut( __METHOD__ ); 00253 return $wt; 00254 } 00255 $ns = $title->getNamespace(); 00256 wfProfileOut( __METHOD__ . "-title" ); 00257 00258 # Default for Namespaces is a default link 00259 # ToDo: Default for patterns is plain wikitext 00260 $return = true; 00261 if( isset( $this->mLinkHooks[$ns] ) ) { 00262 list( $callback, $flags ) = $this->mLinkHooks[$ns]; 00263 if( $flags & SLH_PATTERN ) { 00264 $args = array( $parser, $holders, $markers, $titleText, &$paramText, &$leadingColon ); 00265 } else { 00266 $args = array( $parser, $holders, $markers, $title, $titleText, &$paramText, &$leadingColon ); 00267 } 00268 # Workaround for PHP bug 35229 and similar 00269 if ( !is_callable( $callback ) ) { 00270 throw new MWException( "Tag hook for namespace $ns is not callable\n" ); 00271 } 00272 $return = call_user_func_array( $callback, $args ); 00273 } 00274 if( $return === true ) { 00275 # True (treat as plain link) was returned, call the defaultLinkHook 00276 $return = CoreLinkFunctions::defaultLinkHook( $parser, $holders, $markers, $title, 00277 $titleText, $paramText, $leadingColon ); 00278 } 00279 if( $return === false ) { 00280 # False (no link) was returned, output plain wikitext 00281 # Build it again as the hook is allowed to modify $paramText 00282 $return = isset( $paramText ) ? "[[$titleText|$paramText]]" : "[[$titleText]]"; 00283 } 00284 # Content was returned, return it 00285 wfProfileOut( __METHOD__ ); 00286 return $return; 00287 } 00288 00289 } 00290 00291 class LinkMarkerReplacer { 00292 00293 protected $markers, $nextId, $parser, $holders, $callback; 00294 00295 function __construct( $parser, $holders, $callback ) { 00296 $this->nextId = 0; 00297 $this->markers = array(); 00298 $this->parser = $parser; 00299 $this->holders = $holders; 00300 $this->callback = $callback; 00301 } 00302 00303 function addMarker( $titleText, $paramText ) { 00304 $id = $this->nextId++; 00305 $this->markers[$id] = array( $titleText, $paramText ); 00306 return "<!-- LINKMARKER $id -->"; 00307 } 00308 00309 function findMarker( $string ) { 00310 return (bool) preg_match( '/<!-- LINKMARKER [0-9]+ -->/', $string ); 00311 } 00312 00313 function expand( $string ) { 00314 return StringUtils::delimiterReplaceCallback( "<!-- LINKMARKER ", " -->", array( &$this, 'callback' ), $string ); 00315 } 00316 00317 function callback( $m ) { 00318 $id = intval( $m[1] ); 00319 if( !array_key_exists( $id, $this->markers ) ) return $m[0]; 00320 $args = $this->markers[$id]; 00321 array_unshift( $args, $this ); 00322 array_unshift( $args, $this->holders ); 00323 array_unshift( $args, $this->parser ); 00324 return call_user_func_array( $this->callback, $args ); 00325 } 00326 }