MediaWiki
master
|
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__ ); 00153 wfProfileOut( __METHOD__.'-setup' ); 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 # Don't allow internal links to pages containing 00228 # PROTO: where PROTO is a valid URL protocol; these 00229 # should be external links. 00230 if( preg_match('/^\b(?i:' . wfUrlProtocols() . ')/', $titleText) ) { 00231 wfProfileOut( __METHOD__ ); 00232 return $wt; 00233 } 00234 00235 # Make subpage if necessary 00236 if( $this->areSubpagesAllowed() ) { 00237 $titleText = $this->maybeDoSubpageLink( $titleText, $paramText ); 00238 } 00239 00240 # Check for a leading colon and strip it if it is there 00241 $leadingColon = $titleText[0] == ':'; 00242 if( $leadingColon ) $titleText = substr( $titleText, 1 ); 00243 00244 wfProfileOut( __METHOD__."-misc" ); 00245 # Make title object 00246 wfProfileIn( __METHOD__."-title" ); 00247 $title = Title::newFromText( $this->mStripState->unstripNoWiki( $titleText ) ); 00248 if( !$title ) { 00249 wfProfileOut( __METHOD__."-title" ); 00250 wfProfileOut( __METHOD__ ); 00251 return $wt; 00252 } 00253 $ns = $title->getNamespace(); 00254 wfProfileOut( __METHOD__."-title" ); 00255 00256 # Default for Namespaces is a default link 00257 # ToDo: Default for patterns is plain wikitext 00258 $return = true; 00259 if( isset( $this->mLinkHooks[$ns] ) ) { 00260 list( $callback, $flags ) = $this->mLinkHooks[$ns]; 00261 if( $flags & SLH_PATTERN ) { 00262 $args = array( $parser, $holders, $markers, $titleText, &$paramText, &$leadingColon ); 00263 } else { 00264 $args = array( $parser, $holders, $markers, $title, $titleText, &$paramText, &$leadingColon ); 00265 } 00266 # Workaround for PHP bug 35229 and similar 00267 if ( !is_callable( $callback ) ) { 00268 throw new MWException( "Tag hook for namespace $ns is not callable\n" ); 00269 } 00270 $return = call_user_func_array( $callback, $args ); 00271 } 00272 if( $return === true ) { 00273 # True (treat as plain link) was returned, call the defaultLinkHook 00274 $return = CoreLinkFunctions::defaultLinkHook( $parser, $holders, $markers, $title, 00275 $titleText, $paramText, $leadingColon ); 00276 } 00277 if( $return === false ) { 00278 # False (no link) was returned, output plain wikitext 00279 # Build it again as the hook is allowed to modify $paramText 00280 $return = isset($paramText) ? "[[$titleText|$paramText]]" : "[[$titleText]]"; 00281 } 00282 # Content was returned, return it 00283 wfProfileOut( __METHOD__ ); 00284 return $return; 00285 } 00286 00287 } 00288 00289 class LinkMarkerReplacer { 00290 00291 protected $markers, $nextId, $parser, $holders, $callback; 00292 00293 function __construct( $parser, $holders, $callback ) { 00294 $this->nextId = 0; 00295 $this->markers = array(); 00296 $this->parser = $parser; 00297 $this->holders = $holders; 00298 $this->callback = $callback; 00299 } 00300 00301 function addMarker($titleText, $paramText) { 00302 $id = $this->nextId++; 00303 $this->markers[$id] = array( $titleText, $paramText ); 00304 return "<!-- LINKMARKER $id -->"; 00305 } 00306 00307 function findMarker( $string ) { 00308 return (bool) preg_match('/<!-- LINKMARKER [0-9]+ -->/', $string ); 00309 } 00310 00311 function expand( $string ) { 00312 return StringUtils::delimiterReplaceCallback( "<!-- LINKMARKER ", " -->", array( &$this, 'callback' ), $string ); 00313 } 00314 00315 function callback( $m ) { 00316 $id = intval($m[1]); 00317 if( !array_key_exists($id, $this->markers) ) return $m[0]; 00318 $args = $this->markers[$id]; 00319 array_unshift( $args, $this ); 00320 array_unshift( $args, $this->holders ); 00321 array_unshift( $args, $this->parser ); 00322 return call_user_func_array( $this->callback, $args ); 00323 } 00324 }