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