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