Parse the end tag that matches given BBCode name and suffix starting at current position

@return Tag|null
491109
protected function captureEndTag()
	{
		if (empty($this->uppercaseText))
		{
			$this->uppercaseText = strtoupper($this->text);
		}
		$match     = '[/' . $this->bbcodeName . $this->bbcodeSuffix . ']';
		$endTagPos = strpos($this->uppercaseText, $match, $this->pos);
		if ($endTagPos === false)
		{
			return;
		}

		return $this->parser->addEndTag($this->getTagName(), $endTagPos, strlen($match));
	}
988154
public function get_bbcodes()
	{
		$bbcode_end_length = utf8_strlen(':' . $this->bbcode_uid . ']');
		$quote_end_length = utf8_strlen('&quot;:' . $this->bbcode_uid . ']');

		$this->replace_square_brackets_in_smilies();

		$possible_bbcodes = explode('[', $this->message);
		$content_length = $this->get_content_length($possible_bbcodes[0]);
		if ($content_length >= $this->max_content_length)
		{
			$allowed_content_position = $this->get_content_position($possible_bbcodes[0], $this->max_content_length);
			$this->trim_position = $this->cur_position + $allowed_content_position;
			// As we did not touch any bbcodes yet, we can just skip all that.
			if (!$this->max_content_length || ($content_length > $this->max_content_length))
			{
				$this->is_trimmed = true;
			}
			return;
		}
		$this->cur_position += utf8_strlen($possible_bbcodes[0]) + 1;
		$this->cur_content_length += $content_length;

		// Skip the first one.
		array_shift($possible_bbcodes);
		$num_tested_bbcodes		= 0;

		$allow_close_quote = false;

		foreach ($possible_bbcodes as $part)
		{
			$num_tested_bbcodes++;
			$exploded_parts = explode(':' . $this->bbcode_uid . ']', $part);
			$num_parts = sizeof($exploded_parts);

			/**
			* One element means we do not match an end before the next opening:
			* String: [quote="[bbcode:uid]foobar[/bbcode:uid]":uid]
			* Keys:    ^^^^^^^ = 0
			*/
			if ($num_parts == 1)
			{
				// 1 means, we are in [quote="":uid] and found another bbcode here.
				if (utf8_strpos($exploded_parts[0], 'quote=&quot;') === 0)
				{
					$open_end_quote = utf8_strpos($this->message, '&quot;:' . $this->bbcode_uid . ']', $this->cur_position);
					if ($open_end_quote !== false)
					{
						$close_quote = utf8_strpos($this->message, '[/quote:' . $this->bbcode_uid . ']', $open_end_quote);
						if ($close_quote !== false)
						{
							$open_end_quote += $quote_end_length;
							$this->open_bbcode('quote', $this->cur_position);
							$this->bbcode_action('quote', 'open_end', $open_end_quote);
							$this->cur_position += utf8_strlen($exploded_parts[0]);

							// We allow the 3-keys special-case, when we have found a beginning before...
							$allow_close_quote = true;
						}
					}
				}
				// Or the user just used a normal [ in his post.
				else
				{
					$this->cur_content_length++;
					$content_length = $this->get_content_length($exploded_parts[0]);
					$max_content_allowed = ($this->max_content_length - $this->cur_content_length);
					if (($content_length >= $max_content_allowed) && !$this->trim_position)
					{
						$allowed_content_position = $this->get_content_position($exploded_parts[0], $max_content_allowed);
						$this->trim_position = $this->cur_position + $allowed_content_position;
					}
					$this->cur_content_length += $content_length;
					$this->cur_position += utf8_strlen($exploded_parts[0]);
				}
			}
			/**
			* Two element is the normal case:
			* String: [bbcode:uid]foobar
			* Keys:    ^^^^^^ = 0 ^^^^^^ = 1
			* String: [/bbcode:uid]foobar
			* Keys:    ^^^^^^^ = 0 ^^^^^^ = 1
			*/
			elseif ($num_parts == 2)
			{
				/**
				* We found an opening bracket in the quoted username which is not a bbcode
				* String: [quote="odd[name":uid]quote-text
				* Keys:               ^^^^^ = 0 ^^^^^^^^^^ = 1
				*/
				if ($allow_close_quote && (utf8_substr($exploded_parts[0], -6) == '&quot;'))
				{
					$this->cur_position += utf8_strlen($exploded_parts[0]) + $bbcode_end_length;

					$content_length = $this->get_content_length($exploded_parts[1]);
					$max_content_allowed = ($this->max_content_length - $this->cur_content_length);
					if (($content_length >= $max_content_allowed) && !$this->trim_position)
					{
						$allowed_content_position = $this->get_content_position($exploded_parts[1], $max_content_allowed);
						$this->trim_position = $this->cur_position + $allowed_content_position;
					}
					$this->cur_content_length += $content_length;
					$this->cur_position += utf8_strlen($exploded_parts[1]);
					$allow_close_quote = false;
				}
				// We matched something ;)
				else if ($exploded_parts[0][0] != '/')
				{
					// Open BBCode-tag
					$bbcode_tag = $this->filter_bbcode_tag($exploded_parts[0]);
					$bbcode_tag_extended = $this->filter_bbcode_tag($exploded_parts[0], false, false);
					if (in_array($bbcode_tag_extended, $this->sensitive_bbcodes))
					{
						$this->is_sensitive = true;
					}


					$this->open_bbcode($bbcode_tag, $this->cur_position);
					$this->cur_position += utf8_strlen($exploded_parts[0]) + $bbcode_end_length;
					$this->bbcode_action($bbcode_tag, 'open_end', $this->cur_position);

					if (!$allow_close_quote && !$this->is_sensitive)
					{
						// If we allow a closing quote, we are in the username.
						// We do not count that as content-length.
						$content_length = $this->get_content_length($exploded_parts[1]);
						$max_content_allowed = ($this->max_content_length - $this->cur_content_length);
						if (($content_length >= $max_content_allowed) && !$this->trim_position)
						{
							$allowed_content_position = $this->get_content_position($exploded_parts[1], $max_content_allowed);
							$this->trim_position = $this->cur_position + $allowed_content_position;
						}
						$this->cur_content_length += $content_length;
					}
					$this->cur_position += utf8_strlen($exploded_parts[1]);
				}
				else
				{
					// Close BBCode-tag
					$bbcode_tag = $this->filter_bbcode_tag($exploded_parts[0]);
					$bbcode_tag_extended = $this->filter_bbcode_tag($exploded_parts[0], false);
					if ($bbcode_tag_extended == $bbcode_tag)
					{
						$bbcode_tag_extended = '';
					}
					$this->is_sensitive = false;

					$this->bbcode_action($bbcode_tag, 'close_start', $this->cur_position);
					$this->cur_position += utf8_strlen($exploded_parts[0]) + $bbcode_end_length;
					$this->bbcode_action($bbcode_tag, 'close_end', $this->cur_position, $bbcode_tag_extended);

					if (!$allow_close_quote)
					{
						// If we allow a closing quote, we are in the username.
						// We do not count that as content-length.
						$content_length = $this->get_content_length($exploded_parts[1]);
						$max_content_allowed = ($this->max_content_length - $this->cur_content_length);
						if (($content_length >= $max_content_allowed) && !$this->trim_position)
						{
							$allowed_content_position = $this->get_content_position($exploded_parts[1], $max_content_allowed);
							$this->trim_position = $this->cur_position + $allowed_content_position;
						}
						$this->cur_content_length += $content_length;
					}
					$this->cur_position += utf8_strlen($exploded_parts[1]);
				}
			}
			/**
			* Three elements means we are closing the opening-quote and the BBCode from inside:
			* String: [quote="[bbcode:uid]foo[/bbcode:uid]bar":uid]quotehere
			* Keys:                           ^^^^^^^ = 0 ^^^^ = 1 ^^^^^^^^^ = 2
			*/
			elseif ($num_parts == 3)
			{
				if (($exploded_parts[0][0] == '/') && (utf8_substr($exploded_parts[1], -6) == '&quot;') && $allow_close_quote)
				{
					$bbcode_tag = $this->filter_bbcode_tag($exploded_parts[0]);

					$this->bbcode_action($bbcode_tag, 'close_start', $this->cur_position);
					$this->cur_position += utf8_strlen($exploded_parts[0]) + $bbcode_end_length;
					$this->bbcode_action($bbcode_tag, 'close_end', $this->cur_position);
					$this->cur_position += utf8_strlen($exploded_parts[1]) + $bbcode_end_length;

					$content_length = $this->get_content_length($exploded_parts[2]);
					$max_content_allowed = ($this->max_content_length - $this->cur_content_length);
					if (($content_length >= $max_content_allowed) && !$this->trim_position)
					{
						$allowed_content_position = $this->get_content_position($exploded_parts[2], $max_content_allowed);
						$this->trim_position = $this->cur_position + $allowed_content_position;
					}
					$this->cur_position += utf8_strlen($exploded_parts[2]);
					$this->cur_content_length += $content_length;

					$allow_close_quote = false;
				}
			}

			// Increase by one for the [ we explode on.
			$this->cur_position++;
		}

		if ($this->cur_content_length > $this->max_content_length)
		{
			$this->is_trimmed = true;
		}
	}
1176380
public function getBetween($content, $start, $end = null)
    {
        $startIndex = strpos($content, $start) + strlen($start);
        if ($startIndex === false) {
            return false;
        }

        if (!$end) {
            return substr($content, $startIndex);
        }

        $length = strpos($content, $end, $startIndex);
        if ($length === false) {
            return false;
        }

        return substr($content, $startIndex, $length - $startIndex);
    }
368696
private function detectFrameEnd()
    {
        $bodySize = null;
        if ($this->expectedBodyLength) {
            if (($this->bufferSize - $this->offset) >= $this->expectedBodyLength) {
                $bodySize = $this->expectedBodyLength;
            }
        } elseif (($frameEnd = strpos($this->buffer, self::FRAME_END, $this->offset)) !== false) {
            $bodySize = $frameEnd - $this->offset;
        }

        if ($bodySize !== null) {
            $this->setFrame($bodySize);
            $this->offset += $bodySize + strlen(self::FRAME_END); // x00
        }
        return $bodySize !== null;
    }
1144058
public function nextToken()
    {
        if ($this->_input === null) {
            return null;
        }

        do {
            if (! preg_match('/[\p{L}\p{N}]+/u', $this->_input, $match, PREG_OFFSET_CAPTURE, $this->_bytePosition)) {
                // It covers both cases a) there are no matches (preg_match(...) === 0)
                // b) error occured (preg_match(...) === FALSE)
                return null;
            }

            // matched string
            $matchedWord = $match[0][0];

            // binary position of the matched word in the input stream
            $binStartPos = $match[0][1];

            // character position of the matched word in the input stream
            $startPos = $this->_position +
                        iconv_strlen(
                            substr(
                                $this->_input,
                                $this->_bytePosition,
                                $binStartPos - $this->_bytePosition
                            ),
                            'UTF-8'
                        );
            // character postion of the end of matched word in the input stream
            $endPos = $startPos + iconv_strlen($matchedWord, 'UTF-8');

            $this->_bytePosition = $binStartPos + strlen($matchedWord);
            $this->_position     = $endPos;

            $token = $this->normalize(new Zend_Search_Lucene_Analysis_Token($matchedWord, $startPos, $endPos));
        } while ($token === null); // try again if token is skipped

        return $token;
    }
98812
private function _endElement($parser, $name) {
        // end tag was found...
        $parserid = (int) $parser;
        $this->_xmltree[$parserid] = substr($this->_xmltree[$parserid],0, strlen($this->_xmltree[$parserid]) - (strlen($name) + 1));
    }
704604
function getEnd() {
		$end = $this->getPageNum() * $this->limit;
		if ($end > $this->getTotal())
			return $this->getTotal();
		return $end;
	}
592561
private function parseNoContents(string $tag, string $html): string
    {
        $remainingHtml = \ltrim($html);
        $matchingResult = \preg_match(
            "/(<\/\s*" . $tag . "\s*>)/i",
            $html,
            $endOfScriptMatches
        );
        if ($matchingResult === 0) {
            return '';
        }

        $closingTag = $endOfScriptMatches[1];
        $this->value = \mb_substr($remainingHtml, 0, \mb_strpos($html, $closingTag));

        return \mb_substr(
            \mb_strstr($remainingHtml, $closingTag),
            \mb_strlen($closingTag)
        );
    }
512639
function tagMatcher($tag_name, $close_tags = null)
    {
        $expr = $this->_tag_expr;

        if ($close_tags) {
            $options = implode("|", array_merge(array($tag_name), $close_tags));
            $closer = sprintf("(?:%s)", $options);
        } else {
            $closer = $tag_name;
        }

        $expr = sprintf($expr, $tag_name, $closer);
        return sprintf("/%s/%s", $expr, $this->_re_flags);
    }
748504
public function end() {
        $containerEnd = $this->formatTemplate('containerEnd', []);
        $responsiveEnd = '';
        if ($this->_responsive) {
            $responsiveEnd = $this->formatTemplate('responsiveEnd', []);
        }
        return $this->formatTemplate('navbarEnd', [
            'containerEnd' => $containerEnd,
            'responsiveEnd' => $responsiveEnd
        ]);
    }
962509
public function getFullBbCode() : BbCode
    {
        $bbcode = new BbCode();

        $bold = new Bold();
        $bbcode->addTag($bold);

        $underline = new Underline();
        $bbcode->addTag($underline);

        $italic = new Italic();
        $bbcode->addTag($italic);

        $link = new Link;
        $bbcode->addTag($link);

        $img = new ImgTag();
        $bbcode->addTag($img);

        $unknownTag = new Tag\UnknownSimpleType();
        $bbcode->addTag($unknownTag);

        $youtubeTag = new YoutubeTag();
        $bbcode->addTag($youtubeTag);

        $tags = [
            $bold,
            $underline,
            $italic,
            $link,
            $unknownTag,
            $img,
            $youtubeTag,
        ];

        // link allowed
        $bbcode->addAllowedChildTag($link, $bold);
        $bbcode->addAllowedChildTag($link, $italic);
        $bbcode->addAllowedChildTag($link, $underline);
        $bbcode->addAllowedChildTag($link, $unknownTag);
        $bbcode->addAllowedChildTag($link, $img);

        $tagsAllowingAnyChild = [
            $bold,
            $italic,
            $underline,
            $unknownTag,
        ];

        foreach ($tagsAllowingAnyChild as $tagAllowingAnyTag) {
            foreach ($tags as $tag) {
                $bbcode->addAllowedChildTag($tagAllowingAnyTag, $tag);
            }
        }

        return $bbcode;
    }
1211547
private function parse()
    {
        $suffixes = implode("\.*|", $this->suffixes) . "\.*"; // each suffix gets a "\.*" behind it.
        $prefixes = implode(' |', $this->prefixes) . ' '; // each prefix gets a " " behind it.

        // The regex use is a bit tricky.  *Everything* matched by the regex will be replaced,
        //	but you can select a particular parenthesized submatch to be returned.
        //	Also, note that each regex requres that the preceding ones have been run, and matches chopped out.
        $nicknamesRegex = "/ ('|\"|\(\"*'*)(.+?)('|\"|\"*'*\)) /"; // names that starts or end w/ an apostrophe break this
        $suffixRegex = "/,* *($suffixes)$/";
        $lastRegex = "/(?!^)\b([^ ]+ y |$prefixes)*[^ ]+$/";
        $leadingInitRegex = "/^(.\.*)(?= \p{L}{2})/"; // note the lookahead, which isn't returned or replaced
        $firstRegex = '/^[^ ]+/'; //

        // get nickname, if there is one
        $this->nicknames = $this->name->chopWithRegex($nicknamesRegex, 2);

        // get suffix, if there is one
        $this->suffix = $this->name->chopWithRegex($suffixRegex, 1);

        // flip the before-comma and after-comma parts of the name
        $this->name->flip(',');

        // get the last name
        $this->last = $this->name->chopWithRegex($lastRegex, 0);
        if (!$this->last) {
            throw new Exception("Couldn't find a last name in '{$this->name->getStr()}'.");
        }

        // get the first initial, if there is one
        $this->leadingInit = $this->name->chopWithRegex($leadingInitRegex, 1);

        // get the first name
        $this->first = $this->name->chopWithRegex($firstRegex, 0);
        if (!$this->first) {
            throw new Exception("Couldn't find a first name in '{$this->name->getStr()}'");
        }

        // if anything's left, that's the middle name
        $this->middle = $this->name->getStr();

        return true;
    }
1320602
function nextNode() {
    if (empty($this->html)) {
      # we are done with parsing the html string
      return false;
    }
    static $skipWhitespace = true;
    if ($this->isStartTag && !$this->isEmptyTag) {
      array_push($this->openTags, $this->tagName);
      if (in_array($this->tagName, $this->preformattedTags)) {
        # dont truncate whitespaces for <code> or <pre> contents
        $this->keepWhitespace++;
      }
    }

    if ($this->html[0] == '<') {
      $token = substr($this->html, 0, 9);
      if (substr($token, 0, 2) == '<?') {
        # xml prolog or other pi's
        /** TODO **/
        #trigger_error('this might need some work', E_USER_NOTICE);
        $pos = strpos($this->html, '>');
        $this->setNode('pi', $pos + 1);
        return true;
      }
      if (substr($token, 0, 4) == '<!--') {
        # comment
        $pos = strpos($this->html, '-->');
        if ($pos === false) {
          # could not find a closing -->, use next gt instead
          # this is firefox' behaviour
          $pos = strpos($this->html, '>') + 1;
        } else {
          $pos += 3;
        }
        $this->setNode('comment', $pos);

        $skipWhitespace = true;
        return true;
      }
      if ($token == '<!DOCTYPE') {
        # doctype
        $this->setNode('doctype', strpos($this->html, '>')+1);

        $skipWhitespace = true;
        return true;
      }
      if ($token == '<![CDATA[') {
        # cdata, use text node

        # remove leading <![CDATA[
        $this->html = substr($this->html, 9);

        $this->setNode('text', strpos($this->html, ']]>')+3);

        # remove trailing ]]> and trim
        $this->node = substr($this->node, 0, -3);
        $this->handleWhitespaces();

        $skipWhitespace = true;
        return true;
      }
      if ($this->parseTag()) {
        # seems to be a tag
        # handle whitespaces
        if ($this->isBlockElement) {
          $skipWhitespace = true;
        } else {
          $skipWhitespace = false;
        }
        return true;
      }
    }
    if ($this->keepWhitespace) {
      $skipWhitespace = false;
    }
    # when we get here it seems to be a text node
    $pos = strpos($this->html, '<');
    if ($pos === false) {
      $pos = strlen($this->html);
    }
    $this->setNode('text', $pos);
    $this->handleWhitespaces();
    if ($skipWhitespace && $this->node == ' ') {
      return $this->nextNode();
    }
    $skipWhitespace = false;
    return true;
  }
113727
public function getBetweenReverse($content, $start, $end)
    {
        $endIndex = strrpos($content, $end);
        $startIndex = strrpos($content, $start, -1 * (strlen($content) - $endIndex)) + strlen($start);
        return substr($content, $startIndex, $endIndex - $startIndex);
    }
769581
public function getCurrentTag()
    {
        /**
         * @var Blog $dataRecord
         */
        $dataRecord = $this->dataRecord;
        $tag = $this->request->param('Tag');
        if ($tag) {
            $filter = URLSegmentFilter::create();
            // url encode unless it's multibyte (already pre-encoded in the database)
            // see https://github.com/silverstripe/silverstripe-cms/pull/2384
            if (!$filter->getAllowMultibyte()) {
                $tag = rawurlencode($tag);
            }

            return $dataRecord->Tags()
                ->filter('URLSegment', $tag)
                ->first();
        }
        return null;
    }
178174
public function getAsBBCode()
    {
        $str = "[".$this->tagName;
        if (!empty($this->attribute)) {
            if (isset($this->attribute[$this->tagName])) {
                $str .= "=".$this->attribute[$this->tagName];
            }

            foreach ($this->attribute as $key => $value) {
                if ($key == $this->tagName) {
                    continue;
                } else {
                    $str .= " ".$key."=" . $value;
                }
            }
        }
        $str .= "]";
        foreach ($this->getChildren() as $child) {
            $str .= $child->getAsBBCode();
        }
        $str .= "[/".$this->tagName."]";

        return $str;
    }
383060
protected function _getSlug()
    {
        return empty($this->_properties['tag']) ? null : strtolower(Text::slug($this->_properties['tag']));
    }