Skip to:
Content

BuddyPress.org

Ticket #6921: 6915.mustache.patch

File 6915.mustache.patch, 38.7 KB (added by DJPaul, 5 years ago)
  • src/bp-core/bp-core-filters.php

     
    10401040                        $user_obj = get_user_by( 'email', $tokens['recipient.email'] );
    10411041                }
    10421042
    1043                 if ( $user_obj ) {
     1043                if ( $user_obj && bp_is_user_active( $user_obj->ID ) ) {
    10441044                        // Unsubscribe link.
    10451045                        $tokens['unsubscribe'] = esc_url( sprintf(
    10461046                                '%s%s/notifications/',
  • src/bp-core/bp-core-functions.php

     
    30753075 * @return string
    30763076 */
    30773077function bp_core_replace_tokens_in_text( $text, $tokens ) {
    3078         $unescaped = array();
    3079         $escaped   = array();
    3080 
    3081         foreach ( $tokens as $token => $value ) {
    3082                 if ( ! is_string( $value ) && is_callable( $value ) ) {
    3083                         $value = call_user_func( $value );
     3078        // Mustache sections require a mustache library to parse.
     3079        if ( false !== strpos( $text, '{{#' ) ) {
     3080                if ( ! class_exists( 'MustacheParser' ) ) {
     3081                        require dirname( __FILE__ ) . '/lib/MustacheInterpreter.php';
    30843082                }
    30853083
    3086                 // Tokens could be objects or arrays.
    3087                 if ( ! is_scalar( $value ) ) {
    3088                         continue;
     3084                $parser = new MustacheParser( $text );
     3085                $parser->parse();
     3086
     3087                $mi = new MustacheInterpreter($parser);
     3088                $data = (object) $tokens;
     3089                $text = $mi->run( $data );
     3090
     3091        // Only tokens.
     3092        } else {
     3093                $unescaped = array();
     3094                $escaped   = array();
     3095
     3096                foreach ( $tokens as $token => $value ) {
     3097                        if ( ! is_string( $value ) && is_callable( $value ) ) {
     3098                                $value = call_user_func( $value );
     3099                        }
     3100
     3101                        // Tokens could be objects or arrays.
     3102                        if ( ! is_scalar( $value ) ) {
     3103                                continue;
     3104                        }
     3105
     3106                        $unescaped[ '{{{' . $token . '}}}' ] = $value;
     3107                        $escaped[ '{{' . $token . '}}' ]     = esc_html( $value );
    30893108                }
    30903109
    3091                 $unescaped[ '{{{' . $token . '}}}' ] = $value;
    3092                 $escaped[ '{{' . $token . '}}' ]     = esc_html( $value );
     3110                $text = strtr( $text, $unescaped );  // Do first.
     3111                $text = strtr( $text, $escaped );
    30933112        }
    30943113
    3095         $text = strtr( $text, $unescaped );  // Do first.
    3096         $text = strtr( $text, $escaped );
    3097 
    30983114        /**
    30993115         * Filters text that has had tokens replaced.
    31003116         *
  • new file src/bp-core/lib/MustacheInterpreter.php

    new file mode 100644
    - +  
     1<?php
     2/**
     3 * @package php-mustache
     4 * @subpackage interpreter
     5 * @author Ingmar Runge 2011 - https://github.com/KiNgMaR - BSD license
     6 **/
     7
     8
     9/**
     10 *
     11 * @package php-mustache
     12 * @subpackage interpreter
     13 **/
     14class MustacheInterpreter
     15{
     16        /**
     17         * Root section from MustacheParser's getTree().
     18         * @var MustacheParserSection
     19         **/
     20        protected $tree;
     21        /**
     22         * @var int
     23         **/
     24        protected $whitespace_mode;
     25
     26        /**
     27         * @param MustacheParser $parser Parser with the syntax tree.
     28         **/
     29        public function __construct(MustacheParser $parser)
     30        {
     31                $this->tree = $parser->getTree();
     32                $this->whitespace_mode = $parser->getWhitespaceMode();
     33        }
     34
     35        /**
     36         * Runs the previously assigned template tree against the view data in $view.
     37         * Returns false if the $parser from the constructor didn't provide a valid tree or if the $view is empty.
     38         * @param object|array $view
     39         * @return string Output or false.
     40         **/
     41        public function run($view)
     42        {
     43                if(!is_object($this->tree) || !is_array($view) && !is_object($view))
     44                {
     45                        return false;
     46                }
     47
     48                $mustache_stack = new MustacheRuntimeStack($view);
     49
     50                $result = $this->runOnStack($mustache_stack);
     51
     52                if($this->whitespace_mode == MUSTACHE_WHITESPACE_STRIP)
     53                {
     54                        // remove whitespace that accumulated from around gone conditionals and such.
     55                        $result = preg_replace('~\s+~', ' ', trim($result));
     56                }
     57
     58                return $result;
     59        }
     60
     61        /**
     62         * Runs the previously assigned template against an existing stack.
     63         * @param MustacheRuntimeStack $mustache_stack
     64         * @return string Output or false.
     65         **/
     66        public function runOnStack(MustacheRuntimeStack $mustache_stack)
     67        {
     68                if(!is_object($this->tree))
     69                {
     70                        return false;
     71                }
     72
     73                return $this->runInternal($mustache_stack, $this->tree);
     74        }
     75
     76        /**
     77         * Runs a parser object against a stack. Not more than a switch based on the type of $obj.
     78         * @param MustacheRuntimeStack $mustache_stack
     79         * @param MustacheParserObject $obj
     80         * @return string Output.
     81         **/
     82        protected function runInternal(MustacheRuntimeStack $mustache_stack, MustacheParserObject $obj)
     83        {
     84                if($obj instanceof MustacheParserSection)
     85                {
     86                        return $this->runSection($mustache_stack, $obj);
     87                }
     88                elseif($obj instanceof MustacheParserLiteral)
     89                {
     90                        return $obj->getContents();
     91                }
     92                elseif($obj instanceof MustacheParserVariable)
     93                {
     94                        return $this->runVar($mustache_stack, $obj);
     95                }
     96                elseif($obj instanceof MustacheParserRuntimeTemplate)
     97                {
     98                        return $this->runSubTemplate($mustache_stack, $obj);
     99                }
     100        }
     101
     102        /**
     103         * Runs a section, which could be the internal root section, an inverted section or a regular section.
     104         * @param MustacheRuntimeStack $mustache_stack
     105         * @param MustacheParserSection $section
     106         * @return string Output.
     107         **/
     108        protected function runSection(MustacheRuntimeStack $mustache_stack, MustacheParserSection $section)
     109        {
     110                $result = '';
     111
     112                // outline:
     113                // if it's a root, or a falsey inverted section, do_run = true will cause a simple pass,
     114                // otherwise if the section is not falsey or iterable, all values from the section variable
     115                // will be put onto the stack and executed with one pass each.
     116
     117                $is_root = ($section->getName() === '#ROOT#');
     118
     119                $do_run = $is_root;
     120
     121                // don't push the root context onto the stack, it's there already.
     122
     123                if(!$is_root)
     124                {
     125                        $secv = MustacheRuntime::lookUpVar($mustache_stack, $section->isDotNotation() ? $section->getNames() : $section->getName());
     126
     127                        if($section instanceof MustacheParserInvertedSection)
     128                        {
     129                                if(MustacheRuntime::sectionFalsey($secv))
     130                                {
     131                                        $mustache_stack->push($secv);
     132                                        $do_run = true;
     133                                }
     134                        }
     135                        elseif(MustacheRuntime::sectionIterable($secv))
     136                        {
     137                                foreach($secv as $v)
     138                                {
     139                                        $mustache_stack->push($v);
     140
     141                                        foreach($section as $child)
     142                                        {
     143                                                $result .= $this->runInternal($mustache_stack, $child);
     144                                        }
     145
     146                                        $mustache_stack->pop();
     147                                }
     148                                // don't use $do_run here, it's either done already or falsey-
     149                        }
     150                }
     151
     152                if($do_run)
     153                {
     154                        foreach($section as $child)
     155                        {
     156                                $result .= $this->runInternal($mustache_stack, $child);
     157                        }
     158
     159                        if(!$is_root)
     160                        {
     161                                // avoid popping the last entry, it's required to stay when working with
     162                                // recursive partials (because they don't have an explicit root, the current
     163                                // topmost element just remains on the stack).
     164                                $mustache_stack->pop();
     165                        }
     166                }
     167
     168                return $result;
     169        }
     170
     171        /**
     172         * "Runs" a template variable from the stack, returns their contents ready for output.
     173         * @param MustacheRuntimeStack $mustache_stack
     174         * @param MustacheParserVariable $var
     175         * @return string Output.
     176         **/
     177        protected function runVar(MustacheRuntimeStack $mustache_stack, MustacheParserVariable $var)
     178        {
     179                $v = MustacheRuntime::lookUpVar($mustache_stack, $var->isDotNotation() ? $var->getNames() : $var->getName());
     180
     181                if($var->escape())
     182                {
     183                        return htmlspecialchars($var->getIndent() . $v);
     184                }
     185                else
     186                {
     187                        return $var->getIndent() . $v;
     188                }
     189        }
     190
     191        /**
     192         * Runs a sub template, usually created by recursive partials that could (naturally) not be completely
     193         * resolved by the parser, so they have to be executed at runtime until the section containing the recursive
     194         * {>element} becomes falsey.
     195         * @param MustacheRuntimeStack $mustache_stack
     196         * @param MustacheParserRuntimeTemplate $tpl
     197         * @return string Output.
     198         **/
     199        protected function runSubTemplate(MustacheRuntimeStack $mustache_stack, MustacheParserRuntimeTemplate $tpl)
     200        {
     201                $parser = new MustacheParser($tpl->lookupSelf(), $this->whitespace_mode);
     202                $parser->addPartials($tpl->getPartials());
     203                $parser->parse();
     204                // :TODO: cache parsed template, pass down to new MustacheInterpreter instances.
     205
     206                $mi = new MustacheInterpreter($parser);
     207                $result = $mi->runOnStack($mustache_stack);
     208
     209                return preg_replace('~\r?\n~', '$0' . $tpl->getIndent(), $result);
     210        }
     211}
     212
     213
     214/**
     215 * Pull in parser classes...
     216 **/
     217require_once dirname(__FILE__) . '/MustacheParser.php';
     218/**
     219 * Pull in run-time classes for various references.
     220 **/
     221require_once dirname(__FILE__) . '/MustacheRuntime.php';
  • new file src/bp-core/lib/MustacheParser.php

    new file mode 100644
    - +  
     1<?php
     2/**
     3 * @package php-mustache
     4 * @subpackage shared
     5 * @author Ingmar Runge 2011 - https://github.com/KiNgMaR - BSD license
     6 **/
     7
     8
     9/**
     10 * Mustache whitespace handling: Don't spend extra CPU cycles on trying to be 100% conforming to the specs. This is the default mode.
     11 **/
     12define('MUSTACHE_WHITESPACE_LAZY', 1);
     13/**
     14 * Mustache whitespace handling: Try to be 100% conforming to the specs.
     15 **/
     16define('MUSTACHE_WHITESPACE_STRICT', 2);
     17/**
     18 * Mustache whitespace handling: Compact output, compact all superflous whitespace.
     19 **/
     20define('MUSTACHE_WHITESPACE_STRIP', 4);
     21
     22
     23/**
     24 * Very simple, but hopefully effective tokenizer for Mustache templates.
     25 * @package php-mustache
     26 * @subpackage shared
     27 **/
     28class MustacheTokenizer
     29{
     30        /**
     31         * Default opening delimiter.
     32         **/
     33        const DEFAULT_DELIMITER_OPEN = '{{';
     34        /**
     35         * Default closing delimiter.
     36         **/
     37        const DEFAULT_DELIMITER_CLOSE = '}}';
     38
     39        /**
     40         * List of special characters that denote a section.
     41         **/
     42        const SECTION_TYPES = '^#';
     43        /**
     44         * List of characters that denote the end of a section.
     45         **/
     46        const CLOSING_SECTION_TYPES = '/';
     47        /**
     48         * List of prefix characters that are specific to tags.
     49         * The difference between tags and sections is that tags can not contain
     50         * other tags and do not have a closing counter-part.
     51         **/
     52        const TAG_TYPES = '!>&';
     53        /**
     54         * Defines the tag type that denotes a comment.
     55         **/
     56        const COMMENT_TYPE = '!';
     57        /**
     58         * Defines the tag type that denotes a partial.
     59         **/
     60        const PARTIAL_TYPE = '>';
     61
     62        /**
     63         * Constant that denotes a literal token.
     64         **/
     65        const TKN_LITERAL = 'LITERAL';
     66        /**
     67         * Constant that denotes a section start token.
     68         **/
     69        const TKN_SECTION_START = 'SECTION_START';
     70        /**
     71         * Constant that denotes a section end token.
     72         **/
     73        const TKN_SECTION_END = 'SECTION_END';
     74        /**
     75         * Constant that denotes a tag token.
     76         **/
     77        const TKN_TAG = 'TAG';
     78        /**
     79         * Constant that denotes a comment tag token.
     80         **/
     81        const TKN_COMMENT = 'COMMENT';
     82        /**
     83         * Constant that denotes a partial tag token.
     84         **/
     85        const TKN_PARTIAL = 'PARTIAL';
     86        /**
     87         * Constant that denotes a tag token with escaping disabled.
     88         **/
     89        const TKN_TAG_NOESCAPE = 'TAG_NOESCAPE';
     90
     91        /**
     92         * Template string.
     93         * @var string
     94         **/
     95        protected $template = '';
     96
     97        /**
     98         * List of extracted tokens.
     99         * Example entry: array('t' => one of the TKN_ consts[, 'm' => modifier character from _TYPES], 'd' => data/contents)
     100         * @var array
     101         **/
     102        protected $tokens = array();
     103        /**
     104         * @var int
     105         **/
     106        protected $whitespace_mode;
     107
     108        /**
     109         * @param string $template
     110         **/
     111        public function __construct($template, $whitespace_mode = MUSTACHE_WHITESPACE_LAZY)
     112        {
     113                if(is_string($template))
     114                {
     115                        $this->template = $template;
     116                        $this->whitespace_mode = $whitespace_mode;
     117                }
     118        }
     119
     120        /**
     121         * This tokenizer basically ignores invalid syntax (thereby keeping it in the template output as literals).
     122         * @return boolean
     123         **/
     124        public function tokenize()
     125        {
     126                $dlm_o = self::DEFAULT_DELIMITER_OPEN;
     127                $dlm_c = self::DEFAULT_DELIMITER_CLOSE;
     128
     129                // radically compact whitespace in the template:
     130                if($this->whitespace_mode == MUSTACHE_WHITESPACE_STRIP)
     131                {
     132                        $this->template = preg_replace('~\s+~', ' ', $this->template);
     133                }
     134
     135                // start tokenizing:
     136                $pos = strpos($this->template, $dlm_o);
     137                $prev_pos = 0;
     138                $line = 0;
     139
     140                while($pos !== false)
     141                {
     142                        $end_pos = strpos($this->template, $dlm_c, $pos + strlen($dlm_o));
     143
     144                        if($end_pos === false)
     145                        {
     146                                break;
     147                        }
     148
     149                        if($pos > $prev_pos)
     150                        {
     151                                $this->tokens[] = array('t' => self::TKN_LITERAL, 'd' => substr($this->template, $prev_pos, $pos - $prev_pos));
     152                        }
     153
     154                        $new_token = NULL;
     155                        $skip = false;
     156                        $advance_extra = 0;
     157
     158                        $tag_contents = substr($this->template, $pos + strlen($dlm_o), $end_pos - $pos - strlen($dlm_o));
     159
     160                        // save this in case the modifiers changes:
     161                        $dlm_c_len = strlen($dlm_c);
     162
     163                        if(empty($tag_contents))
     164                        {
     165                                $skip = true;
     166                        }
     167                        elseif(strpos(self::SECTION_TYPES, $tag_contents[0]) !== false)
     168                        {
     169                                // t for token, m for modifier, d for data
     170                                $new_token = array('t' => self::TKN_SECTION_START, 'm' => $tag_contents[0], 'd' => trim(substr($tag_contents, 1)));
     171                        }
     172                        elseif(strpos(self::CLOSING_SECTION_TYPES, $tag_contents[0]) !== false)
     173                        {
     174                                $new_token = array('t' => self::TKN_SECTION_END, 'd' => trim(substr($tag_contents, 1)));
     175                        }
     176                        elseif(preg_match('~^=\s*(\S+)\s+(\S+)\s*=$~', $tag_contents, $match))
     177                        {
     178                                // delimiter change!
     179                                $dlm_o = $match[1];
     180                                $dlm_c = $match[2];
     181                        }
     182                        elseif($tag_contents[0] === self::COMMENT_TYPE)
     183                        {
     184                                $new_token = array('t' => self::TKN_COMMENT, 'd' => trim(substr($tag_contents, 1)));
     185                        }
     186                        elseif($tag_contents[0] === self::PARTIAL_TYPE)
     187                        {
     188                                $new_token = array('t' => self::TKN_PARTIAL, 'd' => trim(substr($tag_contents, 1)));
     189                        }
     190                        else
     191                        {
     192                                $t = self::TKN_TAG;
     193
     194                                // support {{{ / }}} for not-to-be-escaped tags
     195                                if($dlm_o == self::DEFAULT_DELIMITER_OPEN && $tag_contents[0] == substr(self::DEFAULT_DELIMITER_OPEN, -1))
     196                                {
     197                                        if(substr($this->template, $end_pos, $dlm_c_len + 1) == $dlm_c . substr(self::DEFAULT_DELIMITER_CLOSE, -1))
     198                                        {
     199                                                $tag_contents = substr($tag_contents, 1);
     200                                                $t = self::TKN_TAG_NOESCAPE;
     201                                                $advance_extra += 1; // get rid of extra } from closing delimiter
     202                                        }
     203                                }
     204
     205                                if(empty($tag_contents)) // re-check, may have changed
     206                                {
     207                                        $skip = true;
     208                                }
     209                                elseif(strpos(self::TAG_TYPES, $tag_contents[0]) !== false)
     210                                {
     211                                        $new_token = array('t' => $t, 'm' => $tag_contents[0], 'd' => trim(substr($tag_contents, 1)));
     212                                }
     213                                else
     214                                {
     215                                        $new_token = array('t' => $t, 'd' => trim($tag_contents));
     216                                }
     217                        }
     218
     219                        // beautiful code is over, here comes the fugly whitespacing fixing mess!
     220
     221                        $standalone = NULL;
     222                        $sa_indent = '';
     223                        if($this->whitespace_mode == MUSTACHE_WHITESPACE_STRICT)
     224                        {
     225                                if(count($this->tokens) > 0)
     226                                {
     227                                        $prev_token = &$this->tokens[count($this->tokens) - 1];
     228                                }
     229                                else
     230                                {
     231                                        $prev_token = NULL;
     232                                }
     233
     234                                // slowpoke is slow...
     235                                $line_index = substr_count($this->template, "\n", 0, ($pos > 0 ? $pos : strlen($this->template)));
     236
     237                                // let's dissect this a bit:
     238                                // condition A: there's no new token (=delimiter change, invalid stuff) or the new token is not a tag (so a section, partial, etc.)
     239                                // condition B: this is the first token or at least not preceded by a different token on the same line, or only preceded by whitespace (in a literal)
     240                                // condition C: there's nothing but a newline or the end of the template following this token
     241                                $standalone = (!$new_token || ($new_token['t'] != self::TKN_TAG && $new_token['t'] != self::TKN_TAG_NOESCAPE)) &&
     242                                        ($prev_token === NULL || ($prev_token['t'] !== self::TKN_LITERAL && $prev_token['line'] != $line_index) || (bool)preg_match('~(?:' . (count($this->tokens) == 1 ? '^|' : '') . '\r?\n)([\t ]*)$~D', $prev_token['d'], $match))
     243                                        && (bool)preg_match('~^(\r?\n|$)~D', substr($this->template, $end_pos + $dlm_c_len + $advance_extra), $match2);
     244
     245                                if($standalone)
     246                                {
     247                                        // capture indentation:
     248                                        $sa_indent = isset($match[1]) ? $match[1] : '';
     249
     250                                        // remove it from the preceding literal token, if necessary:
     251                                        if(strlen($sa_indent) > 0 && $prev_token['t'] === self::TKN_LITERAL)
     252                                        {
     253                                                $prev_token['d'] = substr($prev_token['d'], 0, -strlen($sa_indent));
     254                                        }
     255
     256                                        // skip trailing newline:
     257                                        $advance_extra += strlen($match2[1]);
     258
     259                                        // store token properties:
     260                                        if($new_token)
     261                                        {
     262                                                $new_token['sa'] = true;
     263                                                $new_token['ind'] = $sa_indent;
     264                                        }
     265                                }
     266                        }
     267                        else
     268                        {
     269                                unset($line_index);
     270                        }
     271
     272                        // end of whitespace fixing mess.
     273
     274                        if($new_token)
     275                        {
     276                                if(isset($line_index)) $new_token['line'] = $line_index;
     277                                $this->tokens[] = $new_token;
     278                        }
     279
     280                        if(!$skip)
     281                        {
     282                                $prev_pos = $end_pos + $dlm_c_len + $advance_extra;
     283                        }
     284
     285                        // find next opening delimiter:
     286                        $pos = strpos($this->template, $dlm_o, $end_pos + $dlm_c_len + $advance_extra);
     287                }
     288
     289                // append remainder (literal following the last section or tag), if there's any:
     290                if($prev_pos < strlen($this->template))
     291                {
     292                        $this->tokens[] = array('t' => self::TKN_LITERAL, 'd' => substr($this->template, $prev_pos));
     293                }
     294
     295                return true;
     296        }
     297
     298        /**
     299         * Use this method to retrieve the results from tokenize().
     300         * @return array
     301         **/
     302        public function getTokens()
     303        {
     304                return $this->tokens;
     305        }
     306}
     307
     308
     309/**
     310 * Mustache parser.
     311 *
     312 * @package php-mustache
     313 * @subpackage shared
     314 **/
     315class MustacheParser
     316{
     317        /**
     318         * @var array
     319         **/
     320        protected $tokens;
     321        /**
     322         * @var MustacheParserSection
     323         **/
     324        protected $tree = NULL;
     325        /**
     326         * @var array
     327         **/
     328        protected $partials = array();
     329        /**
     330         * @var array
     331         **/
     332        protected $partial_callbacks = array();
     333        /**
     334         * If this is a partial, its name is stored here.
     335         * @var string
     336         **/
     337        protected $this_partial_name = NULL;
     338        /**
     339         * @var int
     340         * @see MUSTACHE_WHITESPACE_LAZY
     341         * @see MUSTACHE_WHITESPACE_STRICT
     342         * @see MUSTACHE_WHITESPACE_STRIP
     343         **/
     344        protected $whitespace_mode;
     345
     346        /**
     347         * @param string $template
     348         * @param int $whitespace_mode
     349         **/
     350        public function __construct($template, $whitespace_mode = MUSTACHE_WHITESPACE_LAZY)
     351        {
     352                if(!is_string($template))
     353                {
     354                        throw new MustacheParserException(__CLASS__ . '\'s constructor expects a template string, ' . gettype($template) . ' given.');
     355                }
     356
     357                $this->whitespace_mode = $whitespace_mode;
     358
     359                $tokenizer = new MustacheTokenizer($template, $whitespace_mode);
     360
     361                if(!$tokenizer->tokenize())
     362                {
     363                        throw new MustacheParserException('The tokenizer failed miserably, please check your template syntax.');
     364                }
     365
     366                $this->tokens = $tokenizer->getTokens();
     367        }
     368
     369        /**
     370         * @return int
     371         **/
     372        public function getWhitespaceMode()
     373        {
     374                return $this->whitespace_mode;
     375        }
     376
     377        /**
     378         * Adds a partial with name $key and template contents $tpl.
     379         * @param string $key
     380         * @param string $tpl
     381         **/
     382        public function addPartial($key, $tpl)
     383        {
     384                if(is_scalar($key) && is_string($tpl))
     385                {
     386                        $this->partials[(string)$key] = $tpl;
     387                }
     388        }
     389
     390        /**
     391         * Adds multiple partials.
     392         * @see addPartial
     393         * @param array|object $partials
     394         **/
     395        public function addPartials($partials)
     396        {
     397                if(is_array($partials) || $partials instanceof Iterator)
     398                {
     399                        foreach($partials as $key => $tpl)
     400                        {
     401                                $this->addPartial($key, $tpl);
     402                        }
     403                }
     404        }
     405
     406        /**
     407         * Adds a callback that will be queried for unknown partials that occur during parsing.
     408         * The signature of the callback is: <code>string pcb($partial_name)</code>
     409         * @param callable $callback
     410         **/
     411        public function addPartialsCallback($callback)
     412        {
     413                if(is_callable($callback))
     414                {
     415                        $this->partial_callbacks[] = $callback;
     416                }
     417        }
     418
     419        /**
     420         * Empties the list of added partials and callbacks.
     421         **/
     422        public function clearPartials()
     423        {
     424                $this->partials = array();
     425                $this->partial_callbacks = array();
     426        }
     427
     428        /**
     429         * References all partials from $partials, usually from another MustacheParser instance.
     430         * @param array& $partials
     431         **/
     432        protected function refPartials($this_partial_name, array& $partials, array& $partial_callbacks)
     433        {
     434                $this->this_partial_name = $this_partial_name;
     435                $this->partials = &$partials;
     436                $this->partial_callbacks = &$partial_callbacks;
     437        }
     438
     439        /**
     440         * @throw MustacheParserException
     441         **/
     442        public function parse()
     443        {
     444                $open_sections = array();
     445
     446                // use a container section for the entire template:
     447                $root = new MustacheParserSection('#ROOT#');
     448
     449                // walk tokens, simultanously checking for invalidities (will throw)
     450                // and adding stuff into a tree under $root:
     451                $parent = $root;
     452                foreach($this->tokens as $token)
     453                {
     454                        if($token['t'] == MustacheTokenizer::TKN_LITERAL)
     455                        {
     456                                if(stripos($token['d'], '<?php') !== false)
     457                                {
     458                                        throw new MustacheParserException('Found PHP code start tag in literal!');
     459                                }
     460
     461                                $parent->addChild(new MustacheParserLiteral($token['d']));
     462                        }
     463                        elseif($token['t'] == MustacheTokenizer::TKN_SECTION_START)
     464                        {
     465                                if($token['m'] == '#')
     466                                {
     467                                        $new = new MustacheParserSection($token['d'], $parent);
     468                                }
     469                                elseif($token['m'] == '^')
     470                                {
     471                                        $new = new MustacheParserInvertedSection($token['d'], $parent);
     472                                }
     473                                else
     474                                {
     475                                        throw new MustacheParserException('Unknown section type \'' . $token['m'] . '\'.');
     476                                }
     477
     478                                $parent->addChild($new);
     479
     480                                $open_sections[] = $new;
     481                                $parent = $new; // descend
     482                        }
     483                        elseif($token['t'] == MustacheTokenizer::TKN_SECTION_END)
     484                        {
     485                                $top_sect = array_pop($open_sections);
     486
     487                                if($token['d'] != $top_sect->getName())
     488                                {
     489                                        throw new MustacheParserException('Found end tag for section \'' . $token['d'] . '\' which is not open.');
     490                                }
     491
     492                                $parent = $top_sect->getParent(); // restore parent
     493                        }
     494                        elseif($token['t'] == MustacheTokenizer::TKN_COMMENT)
     495                        {
     496                                // it's a comment, ignore it
     497                        }
     498                        elseif($token['t'] == MustacheTokenizer::TKN_PARTIAL)
     499                        {
     500                                // resolve partial, look for recursive partials first:
     501                                if(is_string($this->this_partial_name) && !empty($this->this_partial_name) && !strcmp($this->this_partial_name, $token['d']))
     502                                {
     503                                        // recursive partial
     504                                        $tag = new MustacheParserRuntimeTemplate($token['d'], $this->partials);
     505
     506                                        if(isset($token['ind'])) $tag->setIndent($token['ind']);
     507
     508                                        $parent->addChild($tag);
     509                                }
     510                                else
     511                                {
     512                                        // find template string from existing list or query callbacks
     513                                        $partial_tpl = NULL;
     514
     515                                        if(isset($this->partials[$token['d']]))
     516                                        {
     517                                                $partial_tpl = $this->partials[$token['d']];
     518                                        }
     519                                        else
     520                                        {
     521                                                foreach($this->partial_callbacks as $callback)
     522                                                {
     523                                                        $partial_tpl = $callback($token['d']);
     524
     525                                                        if(!empty($partial_tpl))
     526                                                        {
     527                                                                break;
     528                                                        }
     529                                                }
     530                                        }
     531
     532                                        if(!is_null($partial_tpl))
     533                                        {
     534                                                // replace partials at "compile time":
     535                                                $partial_parser = new self($partial_tpl, $this->whitespace_mode);
     536                                                $partial_parser->refPartials($token['d'], $this->partials, $this->partial_callbacks);
     537                                                $partial_parser->parse();
     538
     539                                                // :TODO: consider indentation
     540
     541                                                foreach($partial_parser->getTree() as $partial_child)
     542                                                {
     543                                                        $parent->addChild($partial_child);
     544                                                }
     545
     546                                                unset($partial_parser);
     547                                        }
     548                                }
     549                        }
     550                        elseif($token['t'] == MustacheTokenizer::TKN_TAG || $token['t'] == MustacheTokenizer::TKN_TAG_NOESCAPE)
     551                        {
     552                                $modifier = isset($token['m']) ? $token['m'] : '';
     553
     554                                if($modifier == '&' || $modifier == '')
     555                                {
     556                                        // boring interpolation...
     557                                        $tag = new MustacheParserVariable($token['d'], ($token['t'] != MustacheTokenizer::TKN_TAG_NOESCAPE) xor $modifier == '&');
     558
     559                                        if(isset($token['ind'])) $tag->setIndent($token['ind']);
     560
     561                                        $parent->addChild($tag);
     562                                }
     563                                else
     564                                {
     565                                        throw new MustacheParserException('Unknown tag type \'' . $modifier . '\'.');
     566                                }
     567                        }
     568                } // end of $token loop
     569
     570                if(count($open_sections) > 0)
     571                {
     572                        throw new MustacheParserException('Found unclosed section tag pairs.');
     573                }
     574
     575                $this->tree = $root;
     576
     577                return true;
     578        }
     579
     580        /**
     581         * Returns the tree formed by parse(), encapsulated in a root MustacheParserSection of name #ROOT#.
     582         * @see parse
     583         * @return MustacheParserSection
     584         **/
     585        public function getTree()
     586        {
     587                return $this->tree;
     588        }
     589}
     590
     591
     592/**
     593 * An object extracted by the parser. Used by code gens, interpreters and such.
     594 * Does not contain a lot of logic, mostly a data store with some utils.
     595 * The other parser object classes derive from this class.
     596 * @package php-mustache
     597 * @subpackage shared
     598 **/
     599abstract class MustacheParserObject
     600{
     601        /**
     602         * Parent element. Not all derived objects use this.
     603         * @var MustacheParserSection|NULL
     604         **/
     605        protected $parent = NULL;
     606        /**
     607         * Whitespace string that defines this object's indentation. Used by partials mostly.
     608         * @var string
     609         **/
     610        protected $indent = '';
     611
     612        /**
     613         * Constructor.
     614         * @param MustacheParserSection $parent Really only used for sections so far.
     615         **/
     616        public function __construct(MustacheParserSection $parent = NULL)
     617        {
     618                $this->parent = $parent;
     619        }
     620
     621        /**
     622         * @return MustacheParserSection|NULL
     623         **/
     624        public function getParent()
     625        {
     626                return $this->parent;
     627        }
     628
     629        /**
     630         * Corrects or sets this object's parent element. Used by addChild in section objects.
     631         * @see MustacheParserSection::addChild
     632         **/
     633        public function _setParent(MustacheParserSection $new_parent)
     634        {
     635                $this->parent = $new_parent;
     636        }
     637
     638        /**
     639         * Sets the whitespace/indentation to store with this element.
     640         * @param string $new Whitespace characters.
     641         **/
     642        public function setIndent($new)
     643        {
     644                $this->indent = $new;
     645        }
     646
     647        /**
     648         * Returns the indent string, usually empty or a number of whitespace characters.
     649         **/
     650        public function getIndent()
     651        {
     652                return $this->indent;
     653        }
     654}
     655
     656
     657/**
     658 * An object extracted by the parser, with an entity name. Provides helper methods
     659 * for dealing with dot-notation syntax.
     660 * @package php-mustache
     661 * @subpackage shared
     662 **/
     663abstract class MustacheParserObjectWithName extends MustacheParserObject
     664{
     665        /**
     666         * "Variable" name, e.g. "view" or "object.description".
     667         * @var string
     668         **/
     669        protected $name;
     670        /**
     671         * Dot-notation parts as an array.
     672         * @var array
     673         **/
     674        protected $dot_parts;
     675
     676        /**
     677         * Constructor.
     678         * @param string $name
     679         * @param MustacheParserSection|null $parent
     680         **/
     681        public function __construct($name, MustacheParserSection $parent = NULL)
     682        {
     683                parent::__construct($parent);
     684                $this->name = $name;
     685                $this->dot_parts = ($this->name == '.' ? array('.') : explode('.', $name));
     686        }
     687
     688        /**
     689         * Returns whether this object's name makes use of dot-notation.
     690         * @return boolean
     691         **/
     692        public function isDotNotation()
     693        {
     694                return (count($this->dot_parts) > 1);
     695        }
     696
     697        /**
     698         * Returns this object's name.
     699         * @return string
     700         **/
     701        public function getName()
     702        {
     703                return $this->name;
     704        }
     705
     706        /**
     707         * Returns this object's names, as an array. Useful with dot-notation.
     708         * @return array
     709         **/
     710        public function getNames()
     711        {
     712                return $this->dot_parts;
     713        }
     714}
     715
     716
     717/**
     718 * A section parser object.
     719 * @package php-mustache
     720 * @subpackage shared
     721 **/
     722class MustacheParserSection extends MustacheParserObjectWithName implements Iterator
     723{
     724        /**
     725         * @var array
     726         **/
     727        protected $children = array();
     728
     729        /**
     730         * Constructor.
     731         * @param string $name
     732         * @param MustacheParserSection|null $parent
     733         **/
     734        public function __construct($name, MustacheParserSection $parent = NULL)
     735        {
     736                parent::__construct($name, $parent);
     737                $this->name = $name;
     738        }
     739
     740        /**
     741         * Adds a child parser object to this section. Changes $child's parent to $this.
     742         * @param MustacheParserObject $child
     743         **/
     744        public function addChild(MustacheParserObject $child)
     745        {
     746                $child->_setParent($this);
     747                $this->children[] = $child;
     748        }
     749
     750        // Iterator interface implementation:
     751
     752        private $it_pos = 0;
     753        function rewind() { $this->it_pos = 0; }
     754        function current() { return $this->children[$this->it_pos]; }
     755        function key() { return $this->it_pos; }
     756        function next() { $this->it_pos++; }
     757        function valid() { return isset($this->children[$this->it_pos]); }
     758}
     759
     760
     761/**
     762 * An inverted section parser object. Exactly the same as MustacheParserSection,
     763 * however "$var isinstanceof MustacheParserInvertedSection" will be used to tell them apart.
     764 * @package php-mustache
     765 * @subpackage shared
     766 **/
     767class MustacheParserInvertedSection extends MustacheParserSection
     768{
     769
     770}
     771
     772
     773/**
     774 * Parser object that represents a literal string part of a template.
     775 * @package php-mustache
     776 * @subpackage shared
     777 **/
     778class MustacheParserLiteral extends MustacheParserObject
     779{
     780        /**
     781         * @var string
     782         **/
     783        protected $contents;
     784
     785        /**
     786         * Constructor...
     787         * @param string $contents
     788         **/
     789        public function __construct($contents)
     790        {
     791                $this->contents = $contents;
     792        }
     793
     794        /**
     795         * Damn, this is a boring class.
     796         * @return string
     797         **/
     798        public function getContents()
     799        {
     800                return $this->contents;
     801        }
     802}
     803
     804/**
     805 * This parser object represents a variable / {{interpolation}}.
     806 * @package php-mustache
     807 * @subpackage shared
     808 **/
     809class MustacheParserVariable extends MustacheParserObjectWithName
     810{
     811        /**
     812         * @var boolean
     813         **/
     814        protected $escape;
     815
     816        /**
     817         * @param string name
     818         * @param boolean escape
     819         **/
     820        public function __construct($name, $escape)
     821        {
     822                parent::__construct($name);
     823                $this->escape = $escape;
     824        }
     825
     826        /**
     827         * (HTML)escape this variable's contents?
     828         * @return boolean
     829         **/
     830        public function escape()
     831        {
     832                return $this->escape;
     833        }
     834}
     835
     836
     837/**
     838 * Represents a piece of mustache template that *must* be evaluated at runtime.
     839 * Currently only used for recursive partials.
     840 * @package php-mustache
     841 * @subpackage shared
     842 **/
     843class MustacheParserRuntimeTemplate extends MustacheParserObject
     844{
     845        /**
     846         * Partial's name
     847         * @var string
     848         **/
     849        protected $name;
     850        /**
     851         * List of all partials, required since they could be used by the "main" partial or other partials.
     852         * @var array
     853         **/
     854        protected $partials;
     855
     856        /**
     857         * @var string $name
     858         * @var array $partials
     859         **/
     860        public function __construct($name, array $partials)
     861        {
     862                $this->name = $name;
     863                $this->partials = $partials;
     864        }
     865
     866        /**
     867         * Returns the template contents of this partial.
     868         * @return string
     869         **/
     870        public function lookupSelf()
     871        {
     872                return $this->partials[$this->name];
     873        }
     874
     875        /**
     876         * Returns a copy of the list of all partials.
     877         * @return array
     878         **/
     879        public function getPartials()
     880        {
     881                return $this->partials;
     882        }
     883
     884        /**
     885         * Returns this partial's name.
     886         * @return string
     887         **/
     888        public function getName()
     889        {
     890                return $this->name;
     891        }
     892}
     893
     894
     895/**
     896 * Mustache parser exception class.
     897 **/
     898class MustacheParserException extends Exception
     899{
     900
     901}
  • new file src/bp-core/lib/MustacheRuntime.php

    new file mode 100644
    - +  
     1<?php
     2/**
     3 * @package php-mustache
     4 * @subpackage shared
     5 * @author Ingmar Runge 2011 - https://github.com/KiNgMaR - BSD license
     6 **/
     7
     8
     9/**
     10 *
     11 * @package php-mustache
     12 * @subpackage shared
     13 **/
     14class MustacheRuntime
     15{
     16        /**
     17         * Performs a variable lookup on the given stack. Returns the variable's contents.
     18         * @param array $stack
     19         * @param string|array $var_name
     20         * @return mixed
     21         **/
     22        public static function lookUpVar(MustacheRuntimeStack $stack, $var_name)
     23        {
     24                $item = NULL;
     25
     26                if($var_name === '.')
     27                {
     28                        // scalar value, hopefully.
     29                        $item = $stack->top();
     30                }
     31                else
     32                {
     33                        if(is_array($var_name)) // is this dot syntax?
     34                        {
     35                                // find first item on current stack level:
     36                                $item = self::lookUpVarFlat($stack, array_shift($var_name));
     37
     38                                // while the current var_name resolves to a new view context,
     39                                // walk that context with the next var_name part...
     40                                while(count($var_name) > 0 && $item)
     41                                {
     42                                        $item = self::lookUpVarInContext($item, array_shift($var_name));
     43                                }
     44                        }
     45                        else
     46                        {
     47                                // no dot syntax, do a simple lookup.
     48                                $item = self::lookUpVarFlat($stack, $var_name);
     49                        }
     50                }
     51
     52                return $item;
     53        }
     54
     55        /**
     56         * Performs a simple variable lookup against the given stack by walking it from
     57         * top to bottom and checking for the existence of a member with the given name.
     58         * @param MustacheRuntimeStack $stack
     59         * @param string $var_name
     60         * @return mixed Returns an empty string if there's no matching variable on any stack level.
     61         **/
     62        protected static function lookUpVarFlat(MustacheRuntimeStack $stack, $var_name)
     63        {
     64                foreach($stack as $ctx) // top2bottom
     65                {
     66                        $item = self::lookUpVarInContext($ctx, $var_name);
     67
     68                        if(!is_null($item))
     69                        {
     70                                return $item;
     71                        }
     72                }
     73
     74                return '';
     75        }
     76
     77        /**
     78         * Checks whether the stack member (context) $ctx contains an entity of the given name.
     79         * Behaves accordingly to the specs when it comes to lists, objects, member functions and such.
     80         * @param mixed $ctx
     81         * @param string $var_name
     82         * @return NULL|mixed
     83         **/
     84        protected static function lookUpVarInContext($ctx, $var_name)
     85        {
     86                if(is_array($ctx) && isset($ctx[$var_name]))
     87                {
     88                        return $ctx[$var_name];
     89                }
     90                elseif(is_object($ctx) && isset($ctx->$var_name))
     91                {
     92                        return $ctx->$var_name;
     93                }
     94                // :TODO: check for callable members
     95
     96                return NULL;
     97        }
     98
     99        /**
     100         * Returns true if $section_var (which should be something returned by lookUpVar)
     101         * is iterable, may modify $section_var's contents to be iterable when it makes sense.
     102         * Please note that while empty lists might still return true from this method,
     103         * they still won't yield any output since they are empty.
     104         * @see lookUpVar
     105         * @return boolean
     106         **/
     107        public static function sectionIterable(&$section_var)
     108        {
     109                // $section_var contains a result from lookUpVar
     110
     111                if(self::sectionFalsey($section_var))
     112                {
     113                        // falsey sections can not be iterated over.
     114                        return false;
     115                }
     116                elseif(is_array($section_var) || $section_var instanceof Iterator)
     117                {
     118                        // easy peasy iterable
     119                        return true;
     120                }
     121                elseif(is_scalar($section_var))
     122                {
     123                        // according to the specs, treat scalars as one-item-lists.
     124                        $section_var = array((string)$section_var);
     125                        return true;
     126                }
     127                elseif(is_object($section_var))
     128                {
     129                        // this must be pushed onto the context stack.
     130                        $section_var = array($section_var);
     131                        return true;
     132                }
     133
     134                return false;
     135        }
     136
     137        /**
     138         * Returns whether $section_var qualifies as "falsey" for an (inverted or normal) section.
     139         * @param mixed $section_var
     140         * @return boolean
     141         **/
     142        public static function sectionFalsey(&$section_var)
     143        {
     144                // this should be logically equal to is_section_falsey() in MustacheJavaScriptCodeGen's MustacheRuntime class.
     145                // see also this discussion: https://github.com/mustache/spec/issues/28
     146
     147                if(!isset($section_var))
     148                        return true;
     149
     150                if($section_var === false || $section_var === NULL || $section_var === '')
     151                        return true;
     152
     153                if((is_array($section_var) || $section_var instanceof ArrayAccess) && count($section_var) === 0)
     154                        return true;
     155
     156                return false;
     157        }
     158
     159        /**
     160         * Runtime-wrapper for partial evaluation.
     161         * @see MustacheInterpreter::runOnStack
     162         * @param MustacheRuntimeStack $mustache_stack
     163         * @param int $whitespace_mode
     164         * @param string $partial_name
     165         * @param array $partials ('name' => 'tpl code'), must contain $partial_name.
     166         * @return string
     167         **/
     168        public static function doRuntimeTemplate(MustacheRuntimeStack $mustache_stack, $whitespace_mode, $partial_name, array $partials)
     169        {
     170                $parser = new MustacheParser($partials[$partial_name], $whitespace_mode);
     171                $parser->addPartials($partials);
     172                $parser->parse(); // don't care about exceptions, syntax should have been validated at compile-time, at least for recursive partials
     173                $mi = new MustacheInterpreter($parser);
     174                return $mi->runOnStack($mustache_stack);
     175        }
     176}
     177
     178
     179/**
     180 * @package php-mustache
     181 * @subpackage shared
     182 **/
     183class MustacheRuntimeStack extends SplStack
     184{
     185        /**
     186         * @param $view Bottom-most item of the stack.
     187         **/
     188        public function __construct(&$view)
     189        {
     190                // MODE_LIFO for stack behaviour
     191                // MODE_KEEP for easier foreach() access, from top to bottom
     192                $this->setIteratorMode(SplDoublyLinkedList::IT_MODE_LIFO | SplDoublyLinkedList::IT_MODE_KEEP);
     193                $this->push($view);
     194        }
     195}
  • src/bp-members/bp-members-functions.php

     
    10791079                return false;
    10801080        }
    10811081
     1082        // Check if account is still pending.
     1083        $user = get_user_by( 'ID', $user_id );
     1084        if ( 2 === (int) $user->status ) {
     1085                return false;
     1086        }
     1087
    10821088        // Assume true if not spam or deleted.
    10831089        return true;
    10841090}
  • src/bp-templates/bp-legacy/buddypress/assets/emails/single-bp-email.php

     
    172172                                <tr>
    173173                                        <td style="padding: 20px; width: 100%; font-size: <?php echo esc_attr( $settings['footer_text_size'] . 'px' ); ?>; font-family: sans-serif; mso-height-rule: exactly; line-height: <?php echo esc_attr( floor( $settings['footer_text_size'] * 1.618 ) . 'px' ) ?>; text-align: left; color: <?php echo esc_attr( $settings['footer_text_color'] ); ?>;" class="footer_text_color footer_text_size">
    174174                                                <span class="footer_text"><?php echo nl2br( stripslashes( $settings['footer_text'] ) ); ?></span>
    175                                                 <br><br>
    176                                                 <a href="{{{unsubscribe}}}" style="text-decoration: underline;"><?php _ex( 'unsubscribe', 'email', 'buddypress' ); ?></a>
     175
     176                                                {{#unsubscribe}}
     177                                                        <br><br>
     178                                                        <a href="{{{unsubscribe}}}" style="text-decoration: underline;"><?php _ex( 'unsubscribe', 'email', 'buddypress' ); ?></a>
     179                                                {{/unsubscribe}}
    177180                                        </td>
    178181                                </tr>
    179182                        </table>
  • new file tests/phpunit/testcases/core/functions/bpCoreReplaceTokensInText.php

    new file mode 100644
    - +  
     1<?php
     2
     3/**
     4 * @group core
     5 * @group functions
     6 * @group bp_core_replace_tokens_in_text
     7 */
     8class BP_Tests_Core_Functions_BPCoreReplaceTokensInText extends BP_UnitTestCase {
     9        public function test_bp_core_replace_tokens_in_text_sections() {
     10                $text = '{{#person}}Show!{{/person}}';
     11
     12                $parsed = bp_core_replace_tokens_in_text( $text, array() );
     13                $this->assertEmpty( $parsed );
     14
     15                $parsed = bp_core_replace_tokens_in_text( $text, array( 'person' => 'I exist' ) );
     16                $this->assertEquals( 'Show!', $parsed );
     17        }
     18
     19        public function test_bp_core_replace_tokens_in_text_section_unescaped() {
     20                $text = '{{#person}}{{{person}}}{{/person}}';
     21
     22                $parsed = bp_core_replace_tokens_in_text( $text, array( 'person' => '<b>I am awesome</b>' ) );
     23                $this->assertEquals( '<b>I am awesome</b>', $parsed );
     24        }
     25
     26        public function test_bp_core_replace_tokens_in_text_section_escaped() {
     27                $text = '{{#person}}{{person}}{{/person}}';
     28
     29                $parsed = bp_core_replace_tokens_in_text( $text, array( 'person' => '<b>I am awesome</b>' ) );
     30                $this->assertEquals( '&lt;b&gt;I am awesome&lt;/b&gt;', $parsed );
     31        }
     32}