See the code below, and the changelog.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
<?php
/*
* Class for printing code in a HTML formatted block, complete with line numbers.
* It is also suited to be included in other UBB-like functionality.
*/
class CodeBlock
{
protected $options;
protected $output;
public function __construct($text, $options=array()) {
// Set options; first overwrites second (the defaults).
$this->options = $options + self::getDefaultOptions();
// Do we still need to strip tags?
if (!$this->options['tags_stripped'] && ini_get('magic_quotes_gpc')) {
$returnText = stripslashes($text);
} else {
$returnText = $text;
}
// Convert any tabs to spaces.
$returnText = str_replace("\t", str_repeat(' ', $this->options['tabspaces']), $returnText);
// Convert all linebreaks to "\n".
$returnText = str_replace("\r\n", "\n", $returnText);
// Remove all whitespace characters from the end of each line, this will also trim empty lines.
$lines = explode("\n", $returnText);
$returnText = implode("\n", array_map('rtrim', $lines));
// Do we want to apply PHP code highlighting?
if ($this->options['highlight']) {
$returnText = @highlight_string($returnText, true); // suppress possible errors
// Clean up some of the HTML generated by highlight_string():
// Replace all <br /> tags with "\n" and fix the start and end of the returned HTML.
$returnText = str_replace(
array('<br />', '<code><span style="color: #000000">'."\n", '</span>'."\n".'</code>'),
array("\n", '<span style="color: #000000">', '</span>'),
$returnText
);
} else {
// Just neutralize everything.
$returnText = htmlspecialchars($returnText, ENT_QUOTES, 'UTF-8');
}
// Create the code block, starting with linenumbers.
$nrs = implode("\n", range(1, count($lines))); // seems faster than a for-loop, alternatively: use <br /> instead of \n for implosion
ob_start();
?><div class="code-wrapper">
<div class="index"><pre><?php echo $nrs ?></pre></div>
<div class="code"><pre><?php echo $returnText ?></pre></div>
</div><?php
$this->output = ob_get_clean();
} // __construct
public static function getDefaultOptions() {
return array(
'tabspaces' => 4, // number of spaces that tab-characters are replaced with
'highlight' => true, // whether to use the PHP function highlight_string() for highlighting code
// The next default should not be changed here unless you use the CodeBlock class directly,
// or when magic_quotes_gpc is on but you already stripped these extra slashes before you
// passed the input to this class.
'tags_stripped' => false, // keeps track of whether we already stripped tags for when magic_quotes_gpc is on
);
} // getDefaultOptions
public function getOutput() {
return $this->output;
} // getOutput
} // class
/*
* Class for formatting output, codeblocks can be picked up as well.
*/
class Ubb
{
protected $options;
protected $output;
protected $codeBlocks; // stack (array) of CodeBlock objects
protected $tag; // shorthand for $this->options['code_block_tag'], used for delimiting blocks of code
public function __construct($text, $options=array()) {
$this->codeBlocks = array();
$this->options = $options + array(
'allow_html' => false, // allow HTML outside code blocks? be VERY careful with this
'use_code_block' => true, // use CodeBlock class for formatting blocks of code?
'code_block_tag' => 'code', // name of the UBB-tag for delimiting blocks of code, it will also enable us to print this code using this class :)
'code_block_options' => array(), // styling options for code blocks, see CodeBlock::getDefaultOptions()
);
$this->options['code_block_options'] = $this->options['code_block_options'] + CodeBlock::getDefaultOptions();
$this->tag = $this->options['code_block_tag'];
// Strip slashes if magic_quotes_gpc is on.
if (ini_get('magic_quotes_gpc')) {
$returnText = stripslashes($text);
} else {
$returnText = $text;
}
// We stripped tags; do not strip them again in CodeBlock calls.
$this->options['code_block_options']['tags_stripped'] = true;
if ($this->options['use_code_block']) {
// Backup code blocks, it needs separate treatment from the rest of the UBB-code).
// preg_replace() will become deprecated in PHP 5.5.0, we use preg_replace_callback instead.
$returnText = preg_replace_callback(
'#\['.$this->tag.'](.*)\[/'.$this->tag.']#sU',
array($this, 'backupCodeBlock'),
$returnText
);
}
// Perform the rest of the UBB formatting, starting with (dis)allowing HTML.
if ($this->options['allow_html'] == false) {
// No HTML allowed, neutralize all HTML and convert newline characters to linebreak-tags.
$returnText = nl2br(htmlspecialchars($returnText, ENT_QUOTES, 'UTF-8'));
if ($this->options['use_code_block']) {
// Some extra styling: remove the <br /> after the closing tag of a code block.
$returnText = str_replace('[/'.$this->tag.']<br />', '[/'.$this->tag.']', $returnText);
}
} // allow_html false
// Put the rest, if any, of your own UBB-styling here.
if ($this->options['use_code_block']) {
// Restore code blocks.
$returnText = preg_replace_callback(
'#\['.$this->tag.'](\d+)\[/'.$this->tag.']#sU',
array($this, 'restoreCodeBlock'),
$returnText
);
}
// Store the processed input in $this->output.
$this->output = $returnText;
} // __construct
// Callback function for storing blocks of code.
protected function backupCodeBlock($matches) {
$currentBlock = count($this->codeBlocks);
$this->codeBlocks[$currentBlock] = new CodeBlock(
// Note: strip backslash from escaped double quotes due to /e-switch is no longer necessary
// because we do not use preg_match anymore.
$matches[1],
$this->options['code_block_options']
);
// Return a HTML-safe placeholder to indicate where the codeblock should be placed back after
// processing the rest of the UBB-code.
return '['.$this->tag.']'.$currentBlock.'[/'.$this->tag.']';
} // backupCodeBlock
// Callback function for restoring code blocks after the rest of the UBB-code is applied.
protected function restoreCodeBlock($matches) {
return $this->codeBlocks[$matches[1]]->getOutput();
} // restoreCodeBlock
public function getOutput() {
return $this->output;
}
} // class
?>
And the CSS:
1 2 3 4 5 6 7
@CHARSET "UTF-8";
div.code-wrapper { display: block; margin: 0; padding: 0; border: 1px solid #cccccc; background-color: #eeeeee; position: relative; overflow-x: auto; overflow-y: hidden; }
div.code-wrapper pre { font-family: monospace; font-size: 13px; margin: 0; padding: 2px 5px; border: 0; line-height: 15px; }
div.code-wrapper div { margin: 0; padding: 0; border: 0; }
div.code-wrapper div.index { display: block; width: 40px; text-align: right; background-color: #cccccc; color: #333333; }
div.code-wrapper div.code { display: block; position: absolute; top: 0px; left: 40px; color: #000000; }
Simply include the PHP file and CSS file, and call your text-to-be-formatted-by-UBB like this:
1 2 3 4 5 6 7
<?php
// create an Ubb object
$ubb = new Ubb('your_text_with_codeblocks_here');
// print it!
echo $ubb->getOutput();
?>
Or, if you only want to print one codeblock, for when your entire input consists of code:
1 2 3 4 5 6 7
<?php
// create a CodeBlock object
$code = new CodeBlock('your_code_here');
// print it!
echo $code->getOutput();
?>
This article makes use of this code as well! It allows HTML outside codeblocks and was set up as follows:
1 2 3 4 5 6
<?php
// ...
$ubb = new Ubb('article_text_from_database', array('allow_html' => true));
// ...
echo $ubb->getOutput();
?>
If you do not use the UTF-8 character encoding, you might want to consider to start using that. Otherwise change the third parameter in the htmlspecialchars() calls to the (supported) character encoding you are using.