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