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. |
5 | 4 | |
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 | | - |
10 | 5 | ------------------------------------------------------------------------------- |
11 | 6 | |
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 | | - |
31 | 7 | GNU GENERAL PUBLIC LICENSE |
32 | 8 | Version 2, June 1991 |
33 | 9 | |
Index: trunk/extensions/ParserFunctions/ParserFunctions.i18n.magic.php |
— | — | @@ -25,6 +25,13 @@ |
26 | 26 | 'timel' => array( 0, 'timel' ), |
27 | 27 | 'rel2abs' => array( 0, 'rel2abs' ), |
28 | 28 | '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' ), |
29 | 36 | ); |
30 | 37 | |
31 | 38 | /** |
Index: trunk/extensions/ParserFunctions/ParserFunctions.i18n.php |
— | — | @@ -26,6 +26,7 @@ |
27 | 27 | 'pfunc_expr_invalid_argument_ln' => 'Invalid argument for ln: <= 0', |
28 | 28 | 'pfunc_expr_unknown_error' => 'Expression error: Unknown error ($1)', |
29 | 29 | 'pfunc_expr_not_a_number' => 'In $1: result is not a number', |
| 30 | + 'pfunc_string_too_long' => 'Error: String exceeds $1 character limit', |
30 | 31 | ); |
31 | 32 | |
32 | 33 | /** Message documentation (Message documentation) |
Index: trunk/extensions/ParserFunctions/ParserFunctions.php |
— | — | @@ -8,24 +8,33 @@ |
9 | 9 | $wgExtensionCredits['parserhook'][] = array( |
10 | 10 | 'path' => __FILE__, |
11 | 11 | 'name' => 'ParserFunctions', |
12 | | - 'version' => '1.1.1', |
| 12 | + 'version' => '1.2.0', |
13 | 13 | 'url' => 'http://www.mediawiki.org/wiki/Extension:ParserFunctions', |
14 | | - 'author' => 'Tim Starling', |
| 14 | + 'author' => array('Tim Starling', 'Robert Rohde', 'Ross McClure', 'Juraj Simlovic'), |
15 | 15 | 'description' => 'Enhance parser with logical functions', |
16 | 16 | 'descriptionmsg' => 'pfunc_desc', |
17 | 17 | ); |
18 | 18 | |
19 | 19 | $wgExtensionMessagesFiles['ParserFunctions'] = dirname(__FILE__) . '/ParserFunctions.i18n.php'; |
20 | 20 | $wgHooks['LanguageGetMagic'][] = 'wfParserFunctionsLanguageGetMagic'; |
| 21 | +$wgHooks['ParserAfterStrip'][] = 'ExtParserFunctions::loadRegex'; |
21 | 22 | |
22 | 23 | $wgParserTestFiles[] = dirname( __FILE__ ) . "/funcsParserTests.txt"; |
23 | 24 | |
| 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 | + |
24 | 31 | class ExtParserFunctions { |
25 | 32 | var $mExprParser; |
26 | 33 | var $mTimeCache = array(); |
27 | 34 | var $mTimeChars = 0; |
28 | 35 | var $mMaxTimeChars = 6000; # ~10 seconds |
29 | 36 | |
| 37 | + static $markerRegex = false; |
| 38 | + |
30 | 39 | function registerParser( &$parser ) { |
31 | 40 | if ( defined( get_class( $parser ) . '::SFH_OBJECT_ARGS' ) ) { |
32 | 41 | // These functions accept DOM-style arguments |
— | — | @@ -50,6 +59,15 @@ |
51 | 60 | $parser->setFunctionHook( 'rel2abs', array( &$this, 'rel2abs' ) ); |
52 | 61 | $parser->setFunctionHook( 'titleparts', array( &$this, 'titleparts' ) ); |
53 | 62 | |
| 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 | + |
54 | 72 | return true; |
55 | 73 | } |
56 | 74 | |
— | — | @@ -59,6 +77,39 @@ |
60 | 78 | return true; |
61 | 79 | } |
62 | 80 | |
| 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 | + |
63 | 114 | function &getExprParser() { |
64 | 115 | if ( !isset( $this->mExprParser ) ) { |
65 | 116 | if ( !class_exists( 'ExprParser' ) ) { |
— | — | @@ -529,6 +580,253 @@ |
530 | 581 | return $title; |
531 | 582 | } |
532 | 583 | } |
| 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 | + } |
533 | 831 | } |
534 | 832 | |
535 | 833 | function wfSetupParserFunctions() { |
Index: trunk/extensions/ParserFunctions/README |
— | — | @@ -5,8 +5,7 @@ |
6 | 6 | 3. How to config |
7 | 7 | |
8 | 8 | 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. |
11 | 10 | |
12 | 11 | 2. How to install |
13 | 12 | a. Download this tarbell and extract the contents to $IP/extensions/ParserFunctions/ |