bb code library - create your own bbcode system

Coding Critique is the place to post source code for peer review by other members of DevNetwork. Any kind of code can be posted. Code posted does not have to be limited to PHP. All members are invited to contribute constructive criticism with the goal of improving the code. Posted code should include some background information about it and what areas you specifically would like help with.

Popular code excerpts may be moved to "Code Snippets" by the moderators.

Moderator: General Moderators

User avatar
s.dot
Tranquility In Moderation
Posts: 5001
Joined: Sun Feb 06, 2005 7:18 pm
Location: Indiana

Re: bb code library - create your own bbcode system

Post by s.dot »

Thanks for the suggestion.
I'm also adding in emoticon support.

Dang, I'm embarassed to have the original version up there now :(
I've done so many improvements already but probably won't have the next version done for another week or so.
Set Search Time - A google chrome extension. When you search only results from the past year (or set time period) are displayed. Helps tremendously when using new technologies to avoid outdated results.
User avatar
s.dot
Tranquility In Moderation
Posts: 5001
Joined: Sun Feb 06, 2005 7:18 pm
Location: Indiana

Re: bb code library - create your own bbcode system

Post by s.dot »

josh wrote:This syntax also irks me:

Code: Select all

$img = new BB_Tag('img');
$img->setReplacements(array('<img src="', '" alt="Image">'));
(for example what if I wanted both src="" and alt="" to be sourced from the BB codes)
I suppose you could do it like this:

Code: Select all

$img = new BB_Tag('img');
$img->hasParameter();
$img->setReplacements(array(
    array('<img src="', '" alt="Image">'),
    array('<img alt="{placeholder}" src="', '">')
);
Set Search Time - A google chrome extension. When you search only results from the past year (or set time period) are displayed. Helps tremendously when using new technologies to avoid outdated results.
josh
DevNet Master
Posts: 4872
Joined: Wed Feb 11, 2004 3:23 pm
Location: Palm beach, Florida

Re: bb code library - create your own bbcode system

Post by josh »

s.dot wrote:Dang, I'm embarassed to have the original version up there now :(
If you were not, you'd be doing it wrong (not improving as time moves forward)
s.dot wrote: I suppose you could do it like this:

Code: Select all

$img = new BB_Tag('img');
$img->hasParameter();
$img->setReplacements(array(
    array('<img src="', '" alt="Image">'),
    array('<img alt="{placeholder}" src="', '">')
);
How about:

Code: Select all

$img = new BB_Tag('img');
$img->hasParameter();
$img->setReplacements(array(
    array('<img src="{img_src}" alt="{alt_txt}">'),
);
User avatar
Jonah Bron
DevNet Master
Posts: 2764
Joined: Thu Mar 15, 2007 6:28 pm
Location: Redding, California

Re: bb code library - create your own bbcode system

Post by Jonah Bron »

But how would it know what {img_src} and {alt_txt} are? With the method we discussed earlier in which you set the replacement text?

If you're working on a significantly improved version, I'll probably hold off on the paragraph thing. :cry:
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: bb code library - create your own bbcode system

Post by Christopher »

How about:

Code: Select all

$img->setParameters(array('url', 'alt_txt'));
// sprintf like character
$img->setReplacement('<img src="%" alt="%">');
// php template like tags using above names
$img->setReplacement('<img src="{url}" alt="{alt_txt}">') 
(#10850)
User avatar
Jonah Bron
DevNet Master
Posts: 2764
Joined: Thu Mar 15, 2007 6:28 pm
Location: Redding, California

Re: bb code library - create your own bbcode system

Post by Jonah Bron »

Okay, here's the results. Just one bug: there can be empty paragraphs at beginning and end (can, not always).

The tag class has a new setter and corresponding variable: setInside() and $inside.

The logic is very simple. It starts by wrapping the text with "<p>" and "</p>". In each makeTag loop, it wraps each tag that's has stated it can't be in a paragraph with "</p>" and "<p>". If parseContents is true, it also plasters "<p>" and "</p>" to the inside of the tag. Yes, the orders of <p> and </p> are correct.

Here are the files:

bb.class.php:

Code: Select all

<?php

/**
 * This package allows one to create their own bb code tags and define the
 * replacements for them.
 *
 * Package: BB
 * File: bb.class.php
 * Version: 1.0.0
 * Author: Scott Martin <sjm.dev1[at]gmail[dot]com>
 * Date: October 28, 2010
 * License: GNU GPL 3.0
 *
 * Features:
 *  - Create your own BB code style tags and define the HTML replacements for
 *    them.
 *  - Allows tags with parameters.. eg [ quote="name" ] text [ /quote ]
 *      - Allows text between tags to be used as a parameter..
 *    eg [ url ] http://www.example.com [ /url ]
 *  - Allows for text between tags to not be parsed for bb code style tags,
 *    useful for tags such as [ code ] text [ /code ]
 *  - Allows functions to be called on the text between tags
 *  - Correctly parses nested bb code style tags of the same kind (assuming
 *    input string is correctly nested, see Todo)
 *  - Correctly parses nested bb code style tags of any kind (assuming input
 *     string is correctly nested, see Todo)
 *
 * Todo:
 *  - Create a single regex to match all BB code style tags rather than
 *    recursively calling preg_match_all()
 *  - Iterate through the string to be parsed and check for (and fix)
 *    incorrect nesting prior to parsing
 *  - Cleanse parameters for URLs, or allow a custom cleansing function to be
 *    called on any parameter.
 *  - Add more error checking and verbosity
 *
 <BB creation and definition system>
    Copyright (C) <2010>  <Scott Martin>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
class BB
{
	//string holder
	private $str = '';
       
	//bbtags holder
	private $tags = array();
       
	/**
	 * Converts allowed BB code tags to HTML.
	 *
	 * @access public
	 * @param string $str
	 * @return string
	 */
	public function make($str)
	{
		$this->str = '<p>' . str_replace("\n\n", "</p>\n\n<p>", $str) . '</p>';
	       
	if ($matches = $this->pregMatchRecursive($str))
		{
			foreach ($matches AS $match)
			{
				$this->makeTag($match);
			}
		}
	       
		return $this->str;
	}
       
	/**
	 * Recursively parses $str for bb code tags.  This function would be entirely
	 * unnecessary if I could write a good enough regex.
	 *
	 * @access private
	 * @param string $str
	 * @return mixed - array of matches or boolean false when no matches are found
	 */
	private function pregMatchRecursive($str)
	{
		$ret = array();
	       
		if ((strpos($str, '[') !== false) && (strpos($str, ']') !== false))
		{
			if (preg_match_all('#\[(\w+)(=.+?)?\]((?:[^[]|\[(?!/?\\1(=.+?)?\])|(?R))+)\[/\\1\]#s', $str, $matches, PREG_SET_ORDER))
			{
				$ret = array_merge($ret, $matches);
			       
				foreach ($matches AS $match)
				{
					$match[1] = strtolower($match[1]);
					if (array_key_exists($match[1], $this->tags))
					{
						if  ($this->tags[$match[1]]->parseContents)
						{
							if ($newMatches = $this->pregMatchRecursive($match[3]))
							{
								$ret = array_merge($ret, $newMatches);
							}
						}
					}
				}
			       
				return $ret;
			}
		}
	       
		return false;
	}
       
	/**
	 * Adds a BB code tag to the list of allowed tags.
	 *
	 * @access public
	 * @param object $tag - object of type BB_Tag
	 * @return void
	 */
	public function addTag($tag)
	{
		$this->tags[$tag->tag] = $tag;
	}
       
	/**
	 * Does the replacing of BB code tags to their HTML counterparts.. or
	 * manipulation of content between BB code tags, depending on the tags'
	 * parameters.
	 *
	 * @access private
	 * @param array $match
	 * @return void
	 */
	private function makeTag($match)
	{
		//apply function to matched content
		if (count($this->tags[$match[1]]->applyFunction))
		{
			$params = $match[3];
		       
			if (!empty($this->tags[$match[1]]->applyFunction[1]))
			{
				$params = $this->tags[$match[1]]->applyFunction[1];
				array_unshift($params, $match[3]);
			}
		       
			$match[3] = call_user_func_array($this->tags[$match[1]]->applyFunction[0], $params);
		}
		
		$inside = array('', '');
		$parse = array('', '');
		
		if (isset($this->tags[$match[1]]) && $this->tags[$match[1]]->inside == false)
		{
			$inside = array("</p>\n\n", '<p>');
			if ($this->tags[$match[1]]->parseContents == true)
			{
				$parse = array('<p>', "</p>\n\n");
			}
		}
	       
		//simple tag (e.g. <span style="font-weight: bold">text</span>)
		if (!$this->tags[$match[1]]->parameter)
		{
			$this->str = str_replace(
				$match[0],
				$inside[0] . $this->tags[$match[1]]->replacements[0] . $parse[0] . $match[3] . $parse[1] . $this->tags[$match[1]]->replacements[1] . $inside[1],
				$this->str
			);
		       
			return;
		}
	       
		//tags with parameter (e.g. <blockquote class="uncited"><div>text</div></blockquote> or <blockquote><div><cite>name wrote:</cite>text</div></blockquote>)
		if ($this->tags[$match[1]]->parameter == 1)
		{
			if (empty($match[2]) === true)
			{
				if (empty($this->tags[$match[1]]->replacements[0]) !== true)
				{
					if ($this->tags[$match[1]]->useTextAsParameter)
					{      
						$this->str = str_replace(
							$match[0],
							$inside[0] . str_replace(
								'{placeholder}',
								$match[3],
								$this->tags[$match[1]]->replacements[0][0]
							) . $parse[0] . $match[3] . $parse[1] . $this->tags[$match[1]]->replacements[0][1] . $inside[1],
							$this->str
						);
					} else
					{
						$this->str = str_replace(
							$match[0],
							$inside[0] . $this->tags[$match[1]]->replacements[0][0] . $parse[0] . $match[3] . $parse[1] . $this->tags[$match[1]]->replacements[0][1] . $inside[1],
							$this->str
						);
					}
				}
			} else
			{
				//remove = and "" or '' in parameter supplied
				$match[2] = substr($match[2], 1);
		       
				if ((substr($match[2], 0, 1) == '"' && substr($match[2], -1) == '"')|| (substr($match[2], 0, 1) == '\'' && substr($match[2], -1) == '\''))
				{
					$match[2] = substr(substr($match[2], 0, strlen($match[2]) - 1), 1);
				}
			       
				$this->str = str_replace(
					$match[0],
					$inside[0] . str_replace(
						'{placeholder}',
						$match[2],
						$this->tags[$match[1]]->replacements[1][0]
					) . $parse[0] . $match[3] . $parse[1] . $this->tags[$match[1]]->replacements[1][1] . $inside[1],
					$this->str
				);
			}
		       
			return;
		}
	}	
}
bb_tag.class.php:

Code: Select all

<?php

/**
 * This package allows one to create their own bb code tags and define the
 * replacements for them.
 *
 * Package: BB
 * File: bb_tag.class.php
 * Version: 1.0.0
 * Author: Scott Martin <sjm.dev1[at]gmail[dot]com>
 * Date: October 28, 2010
 *
 * Features:
 *  - Create your own BB code style tags and define the HTML replacements for
 *    them.
 *  - Allows tags with parameters.. eg [ quote="name" ] text [ /quote ]
 *      - Allows text between tags to be used as a parameter..
 *    eg [ url ] http://www.example.com [ /url ]
 *  - Allows for text between tags to not be parsed for bb code style tags,
 *    useful for tags such as [ code ] text [ /code ]
 *  - Allows functions to be called on the text between tags
 *  - Correctly parses nested bb code style tags of the same kind (assuming
 *    input string is correctly nested, see Todo)
 *  - Correctly parses nested bb code style tags of any kind (assuming input
 *     string is correctly nested, see Todo)
 *
 * Todo:
 *  - Create a single regex to match all BB code style tags rather than
 *    recursively calling preg_match_all()
 *  - Iterate through the string to be parsed and check for (and fix)
 *    incorrect nesting prior to parsing
 *  - Cleanse parameters for URLs, or allow a custom cleansing function to be
 *    called on any parameter.
 *  - Add more error checking and verbosity
 *
 <BB creation and definition system>
    Copyright (C) <2010>  <Scott Martin>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
 
class BB_Tag
{
	//tag name
	private $tag;
       
	//tag has parameter?
	private $parameter = false;
       
	//use text between tags as parameter?
	private $useTextAsParameter = false;
       
	//html replacements for bb code tags
	private $replacements;
	
	//inside of <p> tag?
	private $inside = true;
	
	//parse the contents between bb code tags for additional bbcode tags?
	private $parseContents = true;
       
	//apply a function to contents between bb code tags?
	private $applyFunction = array();
       
	/**
	 * Sets the tag name
	 *
	 * @access public
	 * @param string $tag - the name of the BB code tag
	 * @return void
	 */
	public function __construct($tag)
	{
		$this->tag = $tag;
	}
       
	/**
	 * Sets the HTML replacement tags for the opening and closing BB code tags
	 *
	 * @access public
	 * @param array $replacement
	 * @return void
	 */
	public function setReplacements($replacement)
	{
		$this->replacements = $replacement;
	}
	
	/**
	 * Sets whether or not the tag can be inside of a <p> tag
	 * 
	 * @author Jonah Dahlquint <jonahbron.d[at]gmail[dot]com>
	 * @access public
	 * @param boolean $inside
	 * @return void
	 */
	public function setInside($inside)
	{
		$this->inside = (bool) $inside;
	}
	
	/**
	 * Notifies this object that this bb code tag has a parameter
	 *
	 * @access public
	 * @param void
	 * @access void
	 */
	public function hasParameter()
	{
		$this->parameter = true;
	}
       
	/**
	 * Notifies this object to use the text between the opening and closing BB
	 * code tags as a parameter.
	 *
	 * @access public
	 * @param void
	 * @return void
	 */
	public function useTextAsParameter()
	{
		$this->useTextAsParameter = true;
	}
       
	/**
	 * Tells this object whether or not to parse the contents between this tags'
	 * open and close for more BB code tags.
	 *
	 * @access public
	 * @param boolean $bool
	 * @return void
	 */
	public function parseContents($bool)
	{
		$this->parseContents = $bool;
	}
       
	/**
	 * Tells the object to apply a function to the contents between this tags'
	 * open and close.
	 *
	 * @access public
	 * @param mixed $function - string function name or array($objectInstance, 'methodName')
	 * @param array $params - parameters to pass to the function
	 * @return void
	 */
	public function applyFunction($function, $params=array())
	{
		$this->applyFunction = array($function, $params);
	}
       
	//accessor for object properties
	public function __get($var)
	{
		return $this->$var;
	}
}
Examples:

Code: Select all

$bold = new BB_Tag('b');
$bold->setReplacements(array('<strong>', '</strong>'));
$bold->setInside(true);

$div = new BB_Tag('div');
$div->setReplacements(array('<div style="border:solid 1px #000000;">', '</div>'));
$div->setInside(false);
I realize you're working on a new version. Just thought I'd say it might improve readability to reduce the number of If levels (especially in pregMatchRecursive()).
User avatar
s.dot
Tranquility In Moderation
Posts: 5001
Joined: Sun Feb 06, 2005 7:18 pm
Location: Indiana

Re: bb code library - create your own bbcode system

Post by s.dot »

Thanks Jonah

The new version has actually been quite restructured and factored down better. I'm very proud of it and realize it might be useful so I'm being very eager to work on it!

Thanks to your guys' suggestions I feel like this is going to be one awesome library.

by the way, i don't think leading/trailing empty <p></p>'s are a bug. If that's the way it is entered that is the way it should be parsed, right? I'll see if I can work the p into the new version
Set Search Time - A google chrome extension. When you search only results from the past year (or set time period) are displayed. Helps tremendously when using new technologies to avoid outdated results.
User avatar
Jonah Bron
DevNet Master
Posts: 2764
Joined: Thu Mar 15, 2007 6:28 pm
Location: Redding, California

Re: bb code library - create your own bbcode system

Post by Jonah Bron »

It only occurs if the very first and/or last thing in the input text is a tag that has $inside set to false. For example:

Code: Select all

$div = new BB_Tag('div');
$div->setReplacements(array('<div>', '</div>'));
$div->setInside(false);

$bb = new BB();
$bb->addTag($div);

echo $bb->make("[div] hello world [/div]");
/* outputs:
<p></p><div> hello world</div><p></p>
*/
As you can see, the user didn't really want those paragraphs there. I suppose that could be fixed with a few more IFs. Anticipating the new version! :D
User avatar
s.dot
Tranquility In Moderation
Posts: 5001
Joined: Sun Feb 06, 2005 7:18 pm
Location: Indiana

Re: bb code library - create your own bbcode system

Post by s.dot »

The paragraph thing is very interesting.

Not only because of the challenge of block/inline elements, but because of the need to set an additional parameter AND the margins it creates at the top of say, a TD or DIV.

I may leave the default set to use nl2br() but make an additional parameter for those that wish to use paragraph formatting. It will be challenging. I may define all block level elements and check the replacement for existence of a block level element. It will be interesting.

Another thing: Checking input for correct nesting and closing tags.

Is this really needed? The regexp checks for full matches (start tag, text, end tag) and only replaces if the whole match is found. I realize it may be beneficial to potentially create valid html output, but that doesn't stop someone from putting in [ b ][ quote ]text[ /quote ][ /b ] which would break the validity anyways.
Set Search Time - A google chrome extension. When you search only results from the past year (or set time period) are displayed. Helps tremendously when using new technologies to avoid outdated results.
josh
DevNet Master
Posts: 4872
Joined: Wed Feb 11, 2004 3:23 pm
Location: Palm beach, Florida

Re: bb code library - create your own bbcode system

Post by josh »

The level of nesting in the code is still horrendous. I would't be confident at all to modify your code as it is now. You may or may not be aware. We all know what clean code is, but some of us could use a few pointers on how to maintain clean code. I'd recommend trying out the testing thing. (or maybe you can do this with the tests you already have?). Once you have tests that allow you to detect breakages at the push of a button, you can then confidently refactor the code and confidentially say you didn't change any behavior. Here are the first 2 refactorings this code needs.

http://www.refactoring.com/catalog/deco ... ional.html
http://www.refactoring.com/catalog/repl ... auses.html

Work in very small changes, run the tests after every little changes & commit the code at any sign of progress or improvement (but never when the tests fail). Just follow these rules and apply refactorings the moment you think of them (but never while also working on functionality). You will realize great benefit. If you break the tests, revert the code after 10-20 minutes. Keep your changes smaller than that amount of time or keep the tests passing, if you can't keep the tests passing you aren't skilled enough to do a change that large. These are the rules I live by.
User avatar
Jonah Bron
DevNet Master
Posts: 2764
Joined: Thu Mar 15, 2007 6:28 pm
Location: Redding, California

Re: bb code library - create your own bbcode system

Post by Jonah Bron »

s.dot wrote:The paragraph thing is very interesting.

Not only because of the challenge of block/inline elements, but because of the need to set an additional parameter AND the margins it creates at the top of say, a TD or DIV.

I may leave the default set to use nl2br() but make an additional parameter for those that wish to use paragraph formatting. It will be challenging. I may define all block level elements and check the replacement for existence of a block level element. It will be interesting.

Another thing: Checking input for correct nesting and closing tags.

Is this really needed? The regexp checks for full matches (start tag, text, end tag) and only replaces if the whole match is found. I realize it may be beneficial to potentially create valid html output, but that doesn't stop someone from putting in [ b ][ quote ]text[ /quote ][ /b ] which would break the validity anyways.
Yes, it gets very complicated. There are two routes: the easy route, or the thorough route. I usually lean toward the thorough route, but that's not always a good thing.

You could try to come up with something more simple yet powerful, and do everything possible to keep it from being too complex. What I mean by that is, for example:

Each BB_Tag object knows what kind of tag it's in. Either a "super level" tag that can only be on top, or inside of another "super level" tag. Then, there is the other kind, "sub level" tags. Sub level tags cannot be in the top level: they must be inside of a paragraph. They can be inside of "super level" tags, but still inside of a <p> tag.

This system could be handled in a very similar way how it handles paragraphs now. If you don't feel like doing it, I just might. And yes, this probably should be optional.
User avatar
s.dot
Tranquility In Moderation
Posts: 5001
Joined: Sun Feb 06, 2005 7:18 pm
Location: Indiana

Re: bb code library - create your own bbcode system

Post by s.dot »

Those are sweet links, thanks.
I have refactored a lot in the newer version I'm working on, for example here is the makeTags() and makeSimpleTag() functions
I'll look for areas I can refactor further

Code: Select all

private function makeTags($matches)
{
	foreach ($matches AS $match)
	{	
		#each $match consists of the following
		#$match[0] = full match [tagname]contents[/tagname]
		#$match[1] = tag name
		#$match[2] = optional parameter (e.g. ="name")
		#$match[3] = the contents between the opening and closing bb code tag
		
		//applying a function to the tag contents
		if (count($this->tags[$match[1]]->applyFunction))
		{
			$match[3] = $this->applyFunction($match);
		}

		//tag with no parameter replacement (e.g. [b]text[/b])
		if (!$this->tags[$match[1]]->parameter)
		{
			$this->str = $this->makeSimpleTag($match);
			continue;
		}

		//tags with parameter (e.g. [quote]text[/quote] or [quote="name"]text[/quote])
		if ($this->tags[$match[1]]->parameter == 1)
		{
			$this->str = $this->makeParameterTag($match);
		}
	}
}
	
private function makeSimpleTag($match)
{
	return str_replace(
		$match[0],
		$this->tags[$match[1]]->replacements[0] . $match[3] . $this->tags[$match[1]]->replacements[1],
		$this->str
	);
}
Set Search Time - A google chrome extension. When you search only results from the past year (or set time period) are displayed. Helps tremendously when using new technologies to avoid outdated results.
User avatar
Jonah Bron
DevNet Master
Posts: 2764
Joined: Thu Mar 15, 2007 6:28 pm
Location: Redding, California

Re: bb code library - create your own bbcode system

Post by Jonah Bron »

8O Major improvement.

Um, it looks like it doesn't check if that tag exists in $this->tags, but I could be wrong. Seems like it might throw an "undefined index" error?
User avatar
s.dot
Tranquility In Moderation
Posts: 5001
Joined: Sun Feb 06, 2005 7:18 pm
Location: Indiana

Re: bb code library - create your own bbcode system

Post by s.dot »

Jonah Bron wrote:8O Major improvement.

Um, it looks like it doesn't check if that tag exists in $this->tags, but I could be wrong. Seems like it might throw an "undefined index" error?
The new regexp will only make matches for defined tags, if there are any. eg "/(b|u|i|etc)..../i", so any match that makes it to the makeTags() function will be present in the tag list
Set Search Time - A google chrome extension. When you search only results from the past year (or set time period) are displayed. Helps tremendously when using new technologies to avoid outdated results.
josh
DevNet Master
Posts: 4872
Joined: Wed Feb 11, 2004 3:23 pm
Location: Palm beach, Florida

Re: bb code library - create your own bbcode system

Post by josh »

Jonah Bron wrote: Seems like it might throw an "undefined index" error?
s.dot wrote:any match that makes it to the makeTags() function will be present in the tag list
Gotta love having tests. If you didn't have tests, you'd be writing 10x as much code out of paranoia ;-) (and therefore introducing more bugs. More code = more bugs.) Instead of asking yourself if it works, and writing code "just in case" - you can just press a button to "ask" the computer if it works. Nice. The only problem is your tests only touch the 'happy' path. If that method is not protected someone could maybe get it to execute under a different path by calling that function directly. You should ideally have a test that shows the thing handles 'undefined tags' by throwing an exception, escaping it, etc.. If the method is protected then I guess its not a problem right now
Post Reply