r50997 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r50996‎ | r50997 | r50998 >
Date:00:43, 26 May 2009
Author:rarohde
Status:deferred (Comments)
Tags:
Comment:
(Bug 6455) Add string function support to ParserFunctions
Modified paths:
  • /trunk/extensions/ParserFunctions/COPYING (modified) (history)
  • /trunk/extensions/ParserFunctions/ParserFunctions.i18n.magic.php (modified) (history)
  • /trunk/extensions/ParserFunctions/ParserFunctions.i18n.php (modified) (history)
  • /trunk/extensions/ParserFunctions/ParserFunctions.php (modified) (history)
  • /trunk/extensions/ParserFunctions/README (modified) (history)

Diff [purge]

Index: trunk/extensions/ParserFunctions/COPYING
@@ -1,33 +1,8 @@
2 -The ParserFunctions extension may be copied and redistributed under either the
3 -DWTFYWWI license or the GNU General Public License, at the option of the
4 -licensee. The text of both licenses is given below.
 2+The ParserFunctions extension may be copied and redistributed under the GNU
 3+General Public License.
54
6 -The majority of this extension is written by (and copyright) Tim Starling. Minor
7 -modifications have been made by various members of the MediaWiki development
8 -team.
9 -
105 -------------------------------------------------------------------------------
116
12 - DWTFYWWI LICENSE
13 - Version 1, January 2006
14 -
15 - Copyright (C) 2006 Ævar Arnfjörð Bjarmason
16 -
17 - Preamble
18 -
19 - The licenses for most software are designed to take away your
20 -freedom to share and change it. By contrast, the DWTFYWWI or Do
21 -Whatever The Fuck You Want With It license is intended to guarantee
22 -your freedom to share and change the software--to make sure the
23 -software is free for all its users.
24 -
25 - DWTFYWWI LICENSE
26 - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
27 -0. The author grants everyone permission to do whatever the fuck they
28 -want with the software, whatever the fuck that may be.
29 -
30 -
317 GNU GENERAL PUBLIC LICENSE
328 Version 2, June 1991
339
Index: trunk/extensions/ParserFunctions/ParserFunctions.i18n.magic.php
@@ -25,6 +25,13 @@
2626 'timel' => array( 0, 'timel' ),
2727 'rel2abs' => array( 0, 'rel2abs' ),
2828 'titleparts' => array( 0, 'titleparts' ),
 29+ 'len' => array( 0, 'len' ),
 30+ 'pos' => array( 0, 'pos' ),
 31+ 'rpos' => array( 0, 'rpos' ),
 32+ 'sub' => array( 0, 'sub' ),
 33+ 'count' => array( 0, 'count' ),
 34+ 'replace' => array( 0, 'replace' ),
 35+ 'explode' => array( 0, 'explode' ),
2936 );
3037
3138 /**
Index: trunk/extensions/ParserFunctions/ParserFunctions.i18n.php
@@ -26,6 +26,7 @@
2727 'pfunc_expr_invalid_argument_ln' => 'Invalid argument for ln: <= 0',
2828 'pfunc_expr_unknown_error' => 'Expression error: Unknown error ($1)',
2929 'pfunc_expr_not_a_number' => 'In $1: result is not a number',
 30+ 'pfunc_string_too_long' => 'Error: String exceeds $1 character limit',
3031 );
3132
3233 /** Message documentation (Message documentation)
Index: trunk/extensions/ParserFunctions/ParserFunctions.php
@@ -8,24 +8,33 @@
99 $wgExtensionCredits['parserhook'][] = array(
1010 'path' => __FILE__,
1111 'name' => 'ParserFunctions',
12 - 'version' => '1.1.1',
 12+ 'version' => '1.2.0',
1313 'url' => 'http://www.mediawiki.org/wiki/Extension:ParserFunctions',
14 - 'author' => 'Tim Starling',
 14+ 'author' => array('Tim Starling', 'Robert Rohde', 'Ross McClure', 'Juraj Simlovic'),
1515 'description' => 'Enhance parser with logical functions',
1616 'descriptionmsg' => 'pfunc_desc',
1717 );
1818
1919 $wgExtensionMessagesFiles['ParserFunctions'] = dirname(__FILE__) . '/ParserFunctions.i18n.php';
2020 $wgHooks['LanguageGetMagic'][] = 'wfParserFunctionsLanguageGetMagic';
 21+$wgHooks['ParserAfterStrip'][] = 'ExtParserFunctions::loadRegex';
2122
2223 $wgParserTestFiles[] = dirname( __FILE__ ) . "/funcsParserTests.txt";
2324
 25+//Defines the maximum length of a string that string functions are allowed to operate on
 26+//Prevention against denial of service by string function abuses.
 27+if( !isset($wgStringFunctionsLimit) ) {
 28+ $wgStringFunctionsLimit = 1000;
 29+}
 30+
2431 class ExtParserFunctions {
2532 var $mExprParser;
2633 var $mTimeCache = array();
2734 var $mTimeChars = 0;
2835 var $mMaxTimeChars = 6000; # ~10 seconds
2936
 37+ static $markerRegex = false;
 38+
3039 function registerParser( &$parser ) {
3140 if ( defined( get_class( $parser ) . '::SFH_OBJECT_ARGS' ) ) {
3241 // These functions accept DOM-style arguments
@@ -50,6 +59,15 @@
5160 $parser->setFunctionHook( 'rel2abs', array( &$this, 'rel2abs' ) );
5261 $parser->setFunctionHook( 'titleparts', array( &$this, 'titleparts' ) );
5362
 63+ //String Functions
 64+ $parser->setFunctionHook( 'len', array(&$this, 'runLen' ));
 65+ $parser->setFunctionHook( 'pos', array(&$this, 'runPos' ));
 66+ $parser->setFunctionHook( 'rpos', array(&$this, 'runRPos' ));
 67+ $parser->setFunctionHook( 'sub', array(&$this, 'runSub' ));
 68+ $parser->setFunctionHook( 'count', array(&$this, 'runCount' ));
 69+ $parser->setFunctionHook( 'replace', array(&$this, 'runReplace' ));
 70+ $parser->setFunctionHook( 'explode', array(&$this, 'runExplode' ));
 71+
5472 return true;
5573 }
5674
@@ -59,6 +77,39 @@
6078 return true;
6179 }
6280
 81+ /* Called by ParserAfterStrip. Preloads the syntax for unique markers
 82+ so that we can avoid reconstructing it on every operation. */
 83+ static function loadRegex( &$parser ) {
 84+ wfProfileIn( __METHOD__ );
 85+
 86+ $prefix = preg_quote( $parser->uniqPrefix(), '/' );
 87+
 88+ // The first line represents Parser from release 1.12 forward.
 89+ // subsequent lines are hacks to accomodate old Mediawiki versions.
 90+ if ( defined('Parser::MARKER_SUFFIX') )
 91+ $suffix = preg_quote( Parser::MARKER_SUFFIX, '/' );
 92+ elseif ( isset($parser->mMarkerSuffix) )
 93+ $suffix = preg_quote( $parser->mMarkerSuffix, '/' );
 94+ elseif ( defined('MW_PARSER_VERSION') &&
 95+ strcmp( MW_PARSER_VERSION, '1.6.1' ) > 0 )
 96+ $suffix = "QINU\x07";
 97+ else $suffix = 'QINU';
 98+
 99+ self::$markerRegex = '/' .$prefix. '(?:(?!' .$suffix. ').)*' . $suffix . '/us';
 100+
 101+ wfProfileOut( __METHOD__ );
 102+ return true;
 103+ }
 104+
 105+ // Removes unique markers from passed parameters, used by string functions.
 106+ private function killMarkers ( $text ) {
 107+ if( self::$markerRegex ) {
 108+ return preg_replace( self::$markerRegex , '' , $text );
 109+ } else {
 110+ return $text;
 111+ }
 112+ }
 113+
63114 function &getExprParser() {
64115 if ( !isset( $this->mExprParser ) ) {
65116 if ( !class_exists( 'ExprParser' ) ) {
@@ -529,6 +580,253 @@
530581 return $title;
531582 }
532583 }
 584+
 585+ // Verifies parameter is less than max string length.
 586+ private function checkLength( $text ) {
 587+ global $wgStringFunctionsLimit;
 588+ return ( mb_strlen( $text ) < $wgStringFunctionsLimit );
 589+ }
 590+
 591+ // Generates error message. Called when string is too long.
 592+ private function tooLongError() {
 593+ global $wgStringFunctionsLimit;
 594+ wfLoadExtensionMessages( 'ParserFunctions' );
 595+
 596+ return '<strong class="error">' .
 597+ wfMsgForContent( "pfunc_string_too_long",
 598+ htmlspecialchars( $wgStringFunctionsLimit ) ) .
 599+ '</strong>';
 600+ }
 601+
 602+ /**
 603+ * {{#len:string}}
 604+ *
 605+ * Reports number of characters in string.
 606+ */
 607+ function runLen ( &$parser, $inStr = '' ) {
 608+ wfProfileIn( __METHOD__ );
 609+
 610+ $inStr = $this->killMarkers( (string)$inStr );
 611+ $len = mb_strlen( $inStr );
 612+
 613+ wfProfileOut( __METHOD__ );
 614+ return $len;
 615+ }
 616+
 617+ /**
 618+ * {{#pos: string | needle | offset}}
 619+ *
 620+ * Finds first occurence of "needle" in "string" starting at "offset".
 621+ *
 622+ * Note: If the needle is an empty string, single space is used instead.
 623+ * Note: If the needle is not found, -1 is returned.
 624+ */
 625+ function runPos ( &$parser, $inStr = '', $inNeedle = '', $inOffset = 0 ) {
 626+ wfProfileIn( __METHOD__ );
 627+
 628+ $inStr = $this->killMarkers( (string)$inStr );
 629+ $inNeedle = $this->killMarkers( (string)$inNeedle );
 630+
 631+ if( !$this->checkLength( $inStr ) ||
 632+ !$this->checkLength( $inNeedle ) ) {
 633+ wfProfileOut( __METHOD__ );
 634+ return $this->tooLongError();
 635+ }
 636+
 637+ if( $inNeedle == '' ) { $inNeedle = ' '; }
 638+
 639+ $pos = mb_strpos( $inStr, $inNeedle, $inOffset );
 640+ if( $pos === false ) { $pos = -1; }
 641+
 642+ wfProfileOut( __METHOD__ );
 643+ return $pos;
 644+ }
 645+
 646+ /**
 647+ * {{#rpos: string | needle}}
 648+ *
 649+ * Finds last occurence of "needle" in "string".
 650+ *
 651+ * Note: If the needle is an empty string, single space is used instead.
 652+ * Note: If the needle is not found, -1 is returned.
 653+ */
 654+ function runRPos ( &$parser, $inStr = '', $inNeedle = '' ) {
 655+ wfProfileIn( __METHOD__ );
 656+
 657+ $inStr = $this->killMarkers( (string)$inStr );
 658+ $inNeedle = $this->killMarkers( (string)$inNeedle );
 659+
 660+ if( !$this->checkLength( $inStr ) ||
 661+ !$this->checkLength( $inNeedle ) ) {
 662+ wfProfileOut( __METHOD__ );
 663+ return $this->tooLongError();
 664+ }
 665+
 666+ if( $inNeedle == '' ) { $inNeedle = ' '; }
 667+
 668+ $pos = mb_strrpos( $inStr, $inNeedle );
 669+ if( $pos === false ) { $pos = -1; }
 670+
 671+ wfProfileOut( __METHOD__ );
 672+ return $pos;
 673+ }
 674+
 675+ /**
 676+ * {{#sub: string | start | length }}
 677+ *
 678+ * Returns substring of "string" starting at "start" and having
 679+ * "length" characters.
 680+ *
 681+ * Note: If length is zero, the rest of the input is returned.
 682+ * Note: A negative value for "start" operates from the end of the
 683+ * "string".
 684+ * Note: A negative value for "length" returns a string reduced in
 685+ * length by that amount.
 686+ */
 687+ function runSub ( &$parser, $inStr = '', $inStart = 0, $inLength = 0 ) {
 688+ wfProfileIn( __METHOD__ );
 689+
 690+ $inStr = $this->killMarkers( (string)$inStr );
 691+
 692+ if( !$this->checkLength( $inStr ) ) {
 693+ wfProfileOut( __METHOD__ );
 694+ return $this->tooLongError();
 695+ }
 696+
 697+ if ( intval($inLength) == 0 ) {
 698+ $result = mb_substr( $inStr, $inStart );
 699+ } else {
 700+ $result = mb_substr( $inStr, $inStart, $inLength );
 701+ }
 702+
 703+ wfProfileOut( __METHOD__ );
 704+ return $result;
 705+ }
 706+
 707+ /**
 708+ * {{#count: string | substr }}
 709+ *
 710+ * Returns number of occurences of "substr" in "string".
 711+ *
 712+ * Note: If "substr" is empty, a single space is used.
 713+ */
 714+ function runCount ( &$parser, $inStr = '', $inSubStr = '' ) {
 715+ wfProfileIn( __METHOD__ );
 716+
 717+ $inStr = $this->killMarkers( (string)$inStr );
 718+ $inSubStr = $this->killMarkers( (string)$inSubStr );
 719+
 720+ if( !$this->checkLength( $inStr ) ||
 721+ !$this->checkLength( $inSubStr ) ) {
 722+ wfProfileOut( __METHOD__ );
 723+ return $this->tooLongError();
 724+ }
 725+
 726+ if( $inSubStr == '' ) { $inSubStr = ' '; }
 727+
 728+ $result = mb_substr_count( $inStr, $inSubStr );
 729+
 730+ wfProfileOut( __METHOD__ );
 731+ return $result;
 732+ }
 733+
 734+ /**
 735+ * {{#replace:string | from | to | limit }}
 736+ *
 737+ * Replaces each occurence of "from" in "string" with "to".
 738+ * At most "limit" replacements are performed.
 739+ *
 740+ * Note: Armored against replacements that would generate huge strings.
 741+ * Note: If "from" is an empty string, single space is used instead.
 742+ */
 743+ function runReplace( &$parser, $inStr = '',
 744+ $inReplaceFrom = '', $inReplaceTo = '', $inLimit = -1 ) {
 745+ global $wgStringFunctionsLimit;
 746+ wfProfileIn( __METHOD__ );
 747+
 748+ $inStr = $this->killMarkers( (string)$inStr );
 749+ $inReplaceFrom = $this->killMarkers( (string)$inReplaceFrom );
 750+ $inReplaceTo = $this->killMarkers( (string)$inReplaceTo );
 751+
 752+ if( !$this->checkLength( $inStr ) ||
 753+ !$this->checkLength( $inReplaceFrom ) ||
 754+ !$this->checkLength( $inReplaceTo ) ) {
 755+ wfProfileOut( __METHOD__ );
 756+ return $this->tooLongError();
 757+ }
 758+
 759+ if( $inReplaceFrom == '' ) { $inReplaceFrom = ' '; }
 760+
 761+ // Precompute limit to avoid generating enormous string:
 762+ $diff = mb_strlen( $inReplaceTo ) - mb_strlen( $inReplaceFrom );
 763+ if( $diff > 0 ) {
 764+ $limit = ( ( $wgStringFunctionsLimit - mb_strlen( $inStr ) ) / $diff ) + 1;
 765+ } else {
 766+ $limit = -1;
 767+ }
 768+
 769+ $inLimit = intval($inLimit);
 770+ if( $inLimit >= 0 ) {
 771+ if( $limit > $inLimit || $limit == -1 ) { $limit = $inLimit; }
 772+ }
 773+
 774+ // Use regex to allow limit and handle UTF-8 correctly.
 775+ $inReplaceFrom = preg_quote( $inReplaceFrom, '/' );
 776+ $inReplaceTo = preg_quote( $inReplaceTo, '/' );
 777+
 778+ $result = preg_replace( '/' . $inReplaceFrom . '/u',
 779+ $inReplaceTo, $inStr, $limit);
 780+
 781+ if( !$this->checkLength( $result ) ) {
 782+ wfProfileOut( __METHOD__ );
 783+ return $this->tooLongError();
 784+ }
 785+
 786+ wfProfileOut( __METHOD__ );
 787+ return $result;
 788+ }
 789+
 790+
 791+ /**
 792+ * {{#explode:string | delimiter | position}}
 793+ *
 794+ * Breaks "string" into chunks seperated by "delimiter" and returns the
 795+ * chunk identified by "position".
 796+ *
 797+ * Note: Negative position can be used to specify tokens from the end.
 798+ * Note: If the divider is an empty string, single space is used instead.
 799+ * Note: Empty string is returned if there are not enough exploded chunks.
 800+ */
 801+ function runExplode ( &$parser, $inStr = '', $inDiv = '', $inPos = 0 ) {
 802+ wfProfileIn( __METHOD__ );
 803+
 804+ $inStr = $this->killMarkers( (string)$inStr );
 805+ $inDiv = $this->killMarkers( (string)$inDiv );
 806+
 807+ if( $inDiv == '' ) { $inDiv = ' '; }
 808+
 809+ if( !$this->checkLength( $inStr ) ||
 810+ !$this->checkLength( $inDiv ) ) {
 811+ wfProfileOut( __METHOD__ );
 812+ return $this->tooLongError();
 813+ }
 814+
 815+ $inStr = preg_quote( $inStr, '/' );
 816+ $inDiv = preg_quote( $inDiv, '/' );
 817+
 818+ $matches = preg_split( '/'.$inDiv.'/u', $inStr );
 819+
 820+ if( $inPos >= 0 && isset( $matches[$inPos] ) ) {
 821+ $result = $matches[$inPos];
 822+ } elseif ( $inPos < 0 && isset( $matches[count($matches) + $inPos] ) ) {
 823+ $result = $matches[count($matches) + $inPos];
 824+ } else {
 825+ $result = '';
 826+ }
 827+
 828+ wfProfileOut( __METHOD__ );
 829+ return $result;
 830+ }
533831 }
534832
535833 function wfSetupParserFunctions() {
Index: trunk/extensions/ParserFunctions/README
@@ -5,8 +5,7 @@
66 3. How to config
77
88 1. Licensing
9 -Dual licensed under GNU GPL and DWTFYWWI. See COPYING for more license
10 -information.
 9+Licensed under GNU GPL. See COPYING for more license information.
1110
1211 2. How to install
1312 a. Download this tarbell and extract the contents to $IP/extensions/ParserFunctions/

Follow-up revisions

RevisionCommit summaryAuthorDate
r51267fix spelling in code comments for r50997aude00:51, 1 June 2009
r51483(Bug 19078) Restore #pos default behavior unintentionally changed in r50997rarohde00:06, 5 June 2009

Past revisions this follows-up on

RevisionCommit summaryAuthorDate
r39618Per discussion for bug 6455, merged functionality of StringFunctions into the...krimpet22:19, 18 August 2008
r39653Revert r39618 "Per discussion for bug 6455, merged functionality of StringFun...brion18:53, 19 August 2008
r49043Adds fallback implementations of mb_strpos and mb_strrpos if native multi-byt...rarohde19:11, 30 March 2009

Comments

#Comment by Splarka (talk | contribs)   22:21, 4 June 2009

If this merge isn't reverted the issue here should probably be fixed? -> bugzilla:19078

#Comment by Tim Starling (talk | contribs)   10:03, 5 June 2009

Disabled the functions in question in r51497.

Status & tagging log