MediaWiki  REL1_19
Parser_LinkHooks.php
Go to the documentation of this file.
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 }