Advertisement
henke37

BBcode parser

Nov 9th, 2011
587
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 42.53 KB | None | 0 0
  1. <?php
  2. /* ubb.php
  3.     A safe Ubbcode libary
  4.     by Henrik Andersson aka henke37
  5.  
  6.     Free for noncommercial use as long as I get credit. I appertiate a notice about the code being used.
  7.     Comercial use includes, but is not limited to, using it on a site that has ads.
  8.     Exception, sites that I am an active member of have the rigths to use it,
  9.     even for comercial uses.
  10.  
  11.     No fee is to be charged for getting the code,
  12.     except a transfer fee to pay for the transfer, if needed.
  13.  
  14.     No waranties for quality, lack of defects or other problems.
  15.  
  16.     Classes:
  17.         BadUbbCodeException - Thrown if any invalid code is found
  18.         UbbDynData - Attached image filename, current time, userid of reader and things like that
  19.         UbbSmiley - A smiley
  20.         UbbTag - An ubbtag
  21.  
  22.     functions:
  23.         nextchar - strpos that takes an array of needles and returns both position and what was matched in an array
  24.         highlightPHPCode - A function that wraps the builtin PHP code highlighning
  25.  
  26. */
  27.  
  28. require dirname(__FILE__).'/function.nextchar.php';
  29.  
  30. define('UBBCODE_DYNDATACHAR','$');
  31. define('UBBCODE_SMILEYCHAR',':');
  32. define('UBBCODE_TAGBEGIN','[');
  33. define('UBBCODE_TAGEND',']');
  34.  
  35. class BadUbbCodeException extends Exception {
  36.     var $offset;
  37.     function __construct($error,$offset=0) {
  38.         parent::__construct($error);
  39.         $this->offset=$offset;
  40.     }
  41. };
  42. class InvalidDynDataException extends BadUbbCodeException {};
  43. class UbbDynData {
  44.  
  45.     public $type;
  46.     public static $callbacks=array();
  47.     public static $types=array('time'=>true,'author'=>true);
  48.  
  49.     function __construct($type) {
  50.         $this->type=$type;
  51.         if(!isset(self::$types[$type])) {
  52.             throw new InvalidDynDataException("There is no $type dyndata type");
  53.         }
  54.     }
  55.  
  56.     function getUbb() {
  57.         return UBBCODE_DYNDATACHAR.$this->type.UBBCODE_DYNDATACHAR;
  58.     }
  59.  
  60.     function getDynData() {
  61.  
  62.         //Do I have to say that you need to escape the input to any database queries?
  63.         if(isset(self::$callbacks[$this->type])) {
  64.             return call_user_func(self::$callbacks[$this->type]);
  65.         }
  66.  
  67.         switch($this->type) {
  68.             case 'time':
  69.                 return date('r');
  70.             break;
  71.  
  72.             case 'author':
  73.                 return 'henke37';
  74.             break;
  75.  
  76.             default:
  77.                 throw new Exception('getDynData somehow doesn\'t recognice the dyndata!');
  78.             break;
  79.         }
  80.     }
  81. } #end of class UbDynData
  82.  
  83. class UbbSmiley {
  84.  
  85.     public $type;
  86.     public $originaltype;
  87.     public $substMatch;
  88.     public static $substitutionmap=array(';)'=>'wink',';-)'=>'wink');
  89.     public static $smileymap=array('wink'=>'wink.png');
  90.     public static $smileypath='/smilies/';
  91.  
  92.     function __construct($type,$substMatch=false) {
  93.  
  94.         $this->originaltype=$type;
  95.  
  96.         if($substMatch) {
  97.             $type=self::$substitutionmap[$type];
  98.             $this->substMatch=true;
  99.         }
  100.  
  101.         if(!isset(self::$smileymap[$type])) {
  102.             throw new BadUbbcodeException(
  103.                 "Undefined smiley \"$type\" used!"
  104.             );
  105.         }
  106.         $this->type=$type;
  107.     }
  108.  
  109.     function getUbb() {
  110.  
  111.         if($this->substMatch) {
  112.             return $this->originaltype;
  113.         }
  114.  
  115.         if(defined('UBBCODE_COMPLEX_SMILIES')) {
  116.             return UBBCODE_SMILEYCHAR.$this->originaltype.UBBCODE_SMILEYCHAR;
  117.         } else {
  118.             return $this->originaltype;
  119.         }
  120.     }
  121.  
  122.     function getHtml() {
  123.         $image=self::$smileymap[$this->type];
  124.         if(substr($image,-4)=='.swf') {
  125.             return 'Flash smilies is not yet implemented, please goto line number '.__LINE__ .' in file '.__FILE__.' and add the proper html for it';
  126.         } else {
  127.             return sprintf(
  128.                 '<img src="%s%s" alt="Smiley: %s"/>',
  129.                 self::$smileypath,
  130.                 $image,
  131.                 htmlentities($this->type)
  132.             );
  133.         }
  134.     }
  135. }
  136.  
  137.  
  138. function highlightPHPCode($code,$language) {
  139.     return highlight_string($code,true);
  140. }
  141.  
  142. class UbbTag {
  143.  
  144.     public $type;
  145.     public $usedname;
  146.  
  147.     //nested arrays that need access methods to be used as one
  148.     public $contents;
  149.     public $atteributes;
  150.  
  151.  
  152.     //tag data
  153.     static public $tagaliases=array();
  154.     static protected $embedingtags=array();
  155.     static protected $noendtagtags=array();
  156.     static public $allowednesting=array();
  157.     static public $forbidendeepnesting=array();
  158.     static public $parserbypasstags=array();
  159.     static public $urltags=array();
  160.  
  161.     static public $customtagCallback=array();
  162.  
  163.     //misc related data
  164.     static public $allowedprotocols=array();
  165.     static public $allowedembedprotocols=array();
  166.     static public $highlightengines=array();
  167.  
  168.     //private stuff nobody should mess with
  169.     static protected $tagID;//a counter for id values
  170.  
  171.     public static function initdata($extrablock,$extrainline,$extranesting,$extrainteractive) {
  172.  
  173.         self::$urltags=array(
  174.             'img'=>array('href',0,'url','src'),
  175.             'flash'=>array('href',0,'url','src'),
  176.             'url'=>array('href',0,'url')
  177.         );
  178.  
  179.         self::$tagaliases=array(
  180.             'link'=>'url',
  181.             'image'=>'img',
  182.             'noparse'=>'noubb',
  183.             'raw'=>'noubb'
  184.         );
  185.         self::$embedingtags=array(
  186.             'img'=>true,
  187.             'flash'=>true,
  188.             'sound'=>true,
  189.             'video'=>true
  190.         );
  191.         self::$noendtagtags=array(
  192.             '*'=>true,
  193.             '_root'=>true
  194.         );
  195.  
  196.         $blocktags=array(
  197.             'list'=>true,
  198.             'table'=>true,
  199.             'quote'=>true,
  200.             'code'=>true,
  201.             'spoiler'=>true,
  202.             'sql'=>true,
  203.             'html'=>true,
  204.             'indent'=>true
  205.         );
  206.  
  207.         $inlinetags=array(
  208.             'url'=>true,
  209.             'google'=>true,
  210.             'img'=>true,
  211.             'b'=>true,
  212.             'i'=>true,
  213.             's'=>true,
  214.             'flash'=>true,
  215.             'noubb'=>true,
  216.             'comment'=>true,
  217.             'left'=>true,
  218.             'center'=>true,
  219.             'right'=>true,
  220.             'sub'=>true,
  221.             'sup'=>true,
  222.             'background'=>true,
  223.             'color'=>true,
  224.             'size'=>true,
  225.             'font'=>true,
  226.             'email'=>true,
  227.             'comment'=>true
  228.         );
  229.  
  230.         $commontags=array_merge($inlinetags,$blocktags);
  231.  
  232.         self::$highlightengines=array(
  233.             'php'=>'highlightPHPCode'
  234.         );
  235.  
  236.         self::$allowedprotocols=array(
  237.             'http',
  238.             'https',
  239.             'telnet',
  240.             'mailto',
  241.             'ftp',
  242.             'ftps',
  243.             'irc',
  244.             'ircs',
  245.             'news'
  246.         );
  247.  
  248.         self::$allowedembedprotocols=array(
  249.             'http',
  250.             'https',
  251.             'ftp',
  252.             'ftps'
  253.         );
  254.  
  255.         self::$allowednesting=array(
  256.             '_root'=>$commontags,
  257.             'list'=>array('*'=>true,'comment'=>true),
  258.             '*'=>$commontags,
  259.             'table'=>array('row'=>true,'comment'=>true),
  260.             'row'=>array('cell'=>true,'comment'=>true),
  261.             'cell'=>$commontags,
  262.             'quote'=>$commontags,
  263.             'spoiler'=>$commontags,
  264.             'b'=>$inlinetags,
  265.             'i'=>$inlinetags,
  266.             'sub'=>$inlinetags,
  267.             'google'=>$inlinetags,
  268.             'left'=>$inlinetags,
  269.             'center'=>$inlinetags,
  270.             'right'=>$inlinetags,
  271.             'sub'=>$inlinetags,
  272.             'sup'=>$inlinetags,
  273.             'background'=>$commontags,
  274.             'color'=>$inlinetags,
  275.             'size'=>$inlinetags,
  276.             'font'=>$inlinetags,
  277.             'indent'=>$commontags,
  278.             'email'=>$inlinetags
  279.         );
  280.  
  281.         $interactivetags=array('spoiler','flash','url','google','email');
  282.  
  283.         self::$forbidendeepnesting=array(
  284.             'url'=>$interactivetags,
  285.             'flash'=>$interactivetags,
  286.             'google'=>$interactivetags,
  287.             'sub'=>array('sup'=>true),
  288.             'sup'=>array('sub'=>true)
  289.         );
  290.  
  291.         self::$parserbypasstags=array(
  292.             'noubb'=>true,
  293.             'code'=>true
  294.         );
  295.  
  296.     }
  297.  
  298.     public function __construct($type,$contents,$atteributes,$usedname='') {
  299.         if(empty($type)) {
  300.             throw new Exception('Ubbtag can not be constructed with an empty tag type!');
  301.         }
  302.         $this->type = $type;
  303.         if(!is_array($contents)) {
  304.             var_dump($contents);
  305.             throw new Exception('Ubbtag can not be constructed when the content is not an array!');
  306.         }
  307.         $this->contents = $contents;
  308.         if(!is_array($atteributes)) {
  309.             throw new Exception('Ubbtag can not be constructed when the atteributes is not an array!');
  310.         }
  311.         $this->atteributes = $atteributes;
  312.         if($usedname=='') {
  313.             $this->usedname=$type;
  314.         } else {
  315.             $this->usedname=$usedname;
  316.         }
  317.  
  318.         if(isset(self::$urltags[$this->type])) {
  319.             if(!$this->hasatteribute(self::$urltags[$this->type])) {
  320.                 foreach($this->contents as $content) {
  321.                     if($content instanceof self or $content instanceof UbbSmiley) {
  322.                         throw new BadUbbCodeException(
  323.                             "{$this->type} tags that does not use an atteribute for the url may not contain any nested tags or smileys"
  324.                         );
  325.                     }
  326.                 }
  327.             }
  328.  
  329.             $embeding=(isset(self::$embedingtags[$this->type]) and self::$embedingtags[$this->type]);
  330.  
  331.             $url= (
  332.                 $this->hasatteribute(self::$urltags[$this->type]) ?
  333.                 $this->getatteribute(self::$urltags[$this->type]) :
  334.                 self::handleDynData($this->contents)
  335.             );
  336.  
  337.             self::checkvalidurl($url,$embeding);
  338.         }
  339.  
  340.         switch($this->type) {
  341.         }
  342.     }
  343.  
  344.     static private function checkvalidurl($url,$isembeding) {
  345.         //throws a BadUbbCodeException if the url is not ok
  346.         if(preg_match('#^([a-z]+):#i',$url,$matches)) {
  347.             $allowedprotocols=$isembeding ? self::$allowedembedprotocols :self::$allowedprotocols;
  348.             if(!in_array($matches[1],$allowedprotocols)) {
  349.                 throw new BadUbbCodeException("The url $url is not using one of the allowed protocols");
  350.             }
  351.         } else {
  352.             $url='http://'.$url;
  353.         }
  354.  
  355.     }
  356.  
  357.     static private function ubbparse_commonend(&$state,$char) {//used by the state switcher in the parser
  358.         switch($char) {
  359.             case UBBCODE_TAGBEGIN:
  360.                 $state='tagbegining';
  361.             break;
  362.  
  363.             case UBBCODE_DYNDATACHAR:
  364.                 $state='dyndatabegin';
  365.             break;
  366.  
  367.             case UBBCODE_SMILEYCHAR:
  368.                 if(defined('UBBCODE_COMPLEX_SMILIES')) {
  369.                     $state='smileybegin';
  370.                     break;
  371.                 }
  372.             //no break, fall trught
  373.  
  374.             default:
  375.                 $state='text';
  376.             break;
  377.         }
  378.     }
  379.  
  380.     static function parse($input) {
  381.  
  382.         //throw new BadUbbCodeException('Can\'t parse without a parser implemention!');
  383.  
  384.         $contents=array(0=>array());
  385.         $deep=0;
  386.         $tagstack=array();
  387.         $tagname='';
  388.         $attvalue=array();
  389.  
  390.         if(defined('UBBCODE_DEBUG') && UBBCODE_DEBUG>=1) {
  391.     ?><table class="ubbcodedebug">
  392.     <caption><?php echo htmlentities($input);?></caption>
  393.     <thead>
  394.     <tr><th>Prev state</th><th>Character</th><th>New state</th>
  395.     <?php if(UBBCODE_DEBUG>=2) { ?><th>$contents</th><th>$tagstack</th><th>$attvalue</th><th>$atts</th><th>$i</th><?php } ?></tr>
  396.     </thead>
  397.     <?php
  398.         }
  399.  
  400.         $state='text';
  401.         //Valid states:
  402.         //text
  403.         //tagbegining
  404.         //tagname
  405.         //tagend
  406.         //tagattspace
  407.         //taglongattname
  408.         //taglongattequ
  409.         //taglongattbeginquote
  410.         //taglongattvalue
  411.         //taglongattendquote
  412.         //taglongattescape
  413.         //taglongattescape2
  414.         //taglongattdyndataescape
  415.         //taglongattdyndatachar
  416.         //taglongattdyndataend
  417.         //dyndatabegin
  418.         //dyndatachar
  419.         //dyndataend
  420.         //smileybegin
  421.         //smileychar
  422.         //smileyend
  423.         //tagshortattequ
  424.         //tagshortattchar
  425.         //tagshortattcomma
  426.         //tagshortattbeginquote
  427.         //tagshortattquotechar
  428.         //tagshortattendquote
  429.         //tagshortattbegindyndata
  430.         //tagshortattdyndatachar
  431.         //tagshortattdyndataend
  432.         //tagshortattquotedyndbeginescape
  433.         //tagshortattdyndatachar
  434.         //tagshortattdyndataend
  435.         //tagshortattendquote
  436.  
  437.  
  438.         for($i=0;$i<strlen($input);++$i) {
  439.  
  440.             $char=$input[$i];
  441.  
  442.             if(defined('UBBCODE_DEBUG') && UBBCODE_DEBUG>=1) {
  443.                 echo '<tr><td>'.htmlentities($state).'</td><td>'.htmlentities($char).'</td>';
  444.             }
  445.  
  446.             //Chose the current state based on the previous state and the input
  447.  
  448.             switch($state) {
  449.  
  450.                 case 'text';
  451.                     self::ubbparse_commonend($state,$char);
  452.                 break;
  453.  
  454.                 case 'tagbegining':
  455.                     switch($char) {
  456.                         case UBBCODE_TAGEND:
  457.                             $state='tagend';
  458.                         break;
  459.  
  460.                         case UBBCODE_TAGBEGIN:
  461.                             throw new BadUbbCodeException(
  462.                                 'A "'.UBBCODE_TAGEND.'" is needed before another "'.UBBCODE_TAGBEGIN.'".',
  463.                                 $i
  464.                             );
  465.                         break;
  466.  
  467.                         default:
  468.                             $state='tagname';
  469.                         break;
  470.                     }
  471.                 break;
  472.  
  473.                 case 'tagname':
  474.                     switch($char) {
  475.                         case UBBCODE_TAGEND:
  476.                             $state='tagend';
  477.                         break;
  478.  
  479.                         case '=':
  480.                             $state='tagshortattequ';
  481.                         break;
  482.  
  483.                         case ' ':
  484.                             $state='tagattspace';
  485.                         break;
  486.  
  487.                         case UBBCODE_TAGBEGIN:
  488.                             throw new BadUbbCodeException(
  489.                                 'A "'.UBBCODE_TAGEND.'" is needed before another "'.UBBCODE_TAGBEGIN.'".',
  490.                                 $i
  491.                             );
  492.                         break;
  493.  
  494.                         default:
  495.                             //no state change
  496.                         break;
  497.                     }
  498.                 break;
  499.  
  500.                 case 'tagend':
  501.                     self::ubbparse_commonend($state,$char);
  502.                 break;
  503.  
  504.                 case 'tagattspace':
  505.                     switch($char) {
  506.                         case ' ':
  507.                             //no state change
  508.                         break;
  509.  
  510.                         case UBBCODE_TAGEND:
  511.                             $state='tagend';
  512.                         break;
  513.  
  514.                         case UBBCODE_TAGBEGIN:
  515.                             throw new BadUbbCodeException(
  516.                                 'It is invalid to begin a new tag inside the begining of another tag',
  517.                                 $i
  518.                             );
  519.                         break;
  520.  
  521.                         default:
  522.                             $state='taglongattname';
  523.                         break;
  524.                     }
  525.                 break;
  526.  
  527.                 case 'taglongattname':
  528.                     switch($char) {
  529.                         case UBBCODE_TAGEND:
  530.                             $state='tagend';
  531.                         break;
  532.  
  533.                         case '=':
  534.                             $state='taglongattequ';
  535.                         break;
  536.  
  537.                         default:
  538.                             //no state change
  539.                         break;
  540.                     }
  541.                 break;
  542.  
  543.                 case 'taglongattequ':
  544.                     switch($char) {
  545.                         case UBBCODE_TAGEND:
  546.                             $state='tagend';
  547.                         break;
  548.  
  549.                         case '"';
  550.                             $state='taglongattbeginquote';
  551.                         break;
  552.  
  553.                         default:
  554.                             $state='taglongattvalue';
  555.                         break;
  556.                     }
  557.                 break;
  558.  
  559.                 case 'taglongattbeginquote':
  560.                     switch($char) {
  561.                         case UBBCODE_DYNDATACHAR:
  562.                             $state='taglongattdyndataescape';
  563.                         break;
  564.  
  565.                         case '"':
  566.                             $state='taglongattendquote';
  567.                         break;
  568.  
  569.                         default:
  570.                             $state='taglongattvalue';
  571.                         break;
  572.                     }
  573.                 break;
  574.  
  575.                 case 'taglongattvalue':
  576.                     switch($char) {
  577.                         case '\\':
  578.                             $state='taglongescape';
  579.                         break;
  580.  
  581.                         case UBBCODE_DYNDATACHAR:
  582.                             $state='taglongattdyndataescape';
  583.                         break;
  584.  
  585.                         case '"':
  586.                             $state='taglongattendquote';
  587.                         break;
  588.  
  589.                         case UBBCODE_TAGEND:
  590.                             $state='tagend';
  591.                         break;
  592.  
  593.                         default:
  594.                             //no state change
  595.                         break;
  596.                     }
  597.                 break;
  598.  
  599.                 case 'taglongattendquote':
  600.                     switch($char) {
  601.                         case ' ':
  602.                             $state='tagattspace';
  603.                         break;
  604.  
  605.                         case UBBCODE_TAGEND:
  606.                             $state='tagend';
  607.                         break;
  608.  
  609.                         default:
  610.                             $state='taglongattname';
  611.                         break;
  612.                     }
  613.                 break;
  614.  
  615.                 case 'taglongattescape':
  616.                     $state='taglongattescape2';
  617.                 break;
  618.  
  619.                 case 'taglongattescape2':
  620.                     switch($char) {
  621.                         case UBBCODE_DYNDATACHAR:
  622.                             $state='taglongattdyndataescape';
  623.                         break;
  624.  
  625.                         case '\\':
  626.                             $state='taglongattescape';
  627.                         break;
  628.  
  629.                         default:
  630.                             $state='taglongattvalue';
  631.                         break;
  632.                     }
  633.                 break;
  634.  
  635.                 case 'taglongattdyndataescape':
  636.                     switch($char) {
  637.                         case UBBCODE_DYNDATACHAR:
  638.                             $state='taglongattvalue';
  639.                         break;
  640.  
  641.                         default:
  642.                             $state='taglongattdyndatachar';
  643.                         break;
  644.                     }
  645.                 break;
  646.  
  647.                 case 'taglongattdyndatachar':
  648.                     switch($char) {
  649.                         case UBBCODE_DYNDATACHAR:
  650.                             $state='taglongattdyndataend';
  651.                         break;
  652.  
  653.                         default:
  654.                             //no statechange
  655.                         break;
  656.                     }
  657.                 break;
  658.  
  659.                 case 'taglongattdyndataend';
  660.                     switch($char) {
  661.                         case UBBCODE_DYNDATACHAR:
  662.                             $state='taglongattdyndataescape';
  663.                         break;
  664.  
  665.                         case '\\':
  666.                             $state='taglongattescape';
  667.                         break;
  668.  
  669.                         case '"':
  670.                             $state='taglongattendquote';
  671.                         break;
  672.  
  673.                         default:
  674.                             $state='taglongattvalue';
  675.                         break;
  676.                     }
  677.                 break;
  678.  
  679.                 case 'dyndatabegin':
  680.                     switch($char) {
  681.                         case UBBCODE_DYNDATACHAR:
  682.                             $state='dyndataend';
  683.                         break;
  684.  
  685.                         default:
  686.                             $state='dyndatachar';
  687.                         break;
  688.                     }
  689.                 break;
  690.  
  691.                 case 'dyndatachar':
  692.                     switch($char) {
  693.                         case UBBCODE_DYNDATACHAR:
  694.                             $state='dyndataend';
  695.                         break;
  696.                         default:
  697.                             //no state change
  698.                         break;
  699.                     }
  700.                 break;
  701.  
  702.                 case 'dyndataend':
  703.                     self::ubbparse_commonend($state,$char);
  704.                 break;
  705.  
  706.                 case 'smileybegin':
  707.                     switch($char) {
  708.                         case UBBCODE_SMILEYCHAR:
  709.                             $state='smileyend';
  710.                         break;
  711.  
  712.                         default:
  713.                             $state='smileychar';
  714.                         break;
  715.                     }
  716.                 break;
  717.  
  718.                 case 'smileychar':
  719.                     switch($char) {
  720.                         case UBBCODE_SMILEYCHAR:
  721.                             $state='smileyend';
  722.                         break;
  723.  
  724.                         default:
  725.                             //no state change
  726.                         break;
  727.                     }
  728.                 break;
  729.  
  730.                 case 'smileyend':
  731.                     self::ubbparse_commonend($state,$char);
  732.                 break;
  733.  
  734.                 case 'tagshortattequ':
  735.                     switch($char) {
  736.                         case UBBCODE_TAGEND;
  737.                             $state='tagend';
  738.                         break;
  739.  
  740.                         case ',':
  741.                             $state='tagshortattcomma';
  742.                         break;
  743.  
  744.                         case UBBCODE_DYNDATACHAR:
  745.                             $state='tagshortattbegindyndata';
  746.                         break;
  747.  
  748.                         case '"':
  749.                             $state='tagshortattbeginquote';
  750.                         break;
  751.  
  752.                         default:
  753.                             $state='tagshortattchar';
  754.                         break;
  755.                     }
  756.                 break;
  757.  
  758.                 case 'tagshortattchar':
  759.                     switch($char) {
  760.                         case UBBCODE_TAGEND;
  761.                             $state='tagend';
  762.                         break;
  763.  
  764.                         case ',':
  765.                             $state='tagshortattcomma';
  766.                         break;
  767.  
  768.                         case UBBCODE_DYNDATACHAR:
  769.                             $state='tagshortattbegindyndata';
  770.                         break;
  771.  
  772.                         default:
  773.                             //no state change
  774.                         break;
  775.                     }
  776.                 break;
  777.  
  778.                 case 'tagshortattcomma':
  779.                     switch($char) {
  780.                         case UBBCODE_TAGEND;
  781.                             $state='tagend';
  782.                         break;
  783.  
  784.                         case UBBCODE_DYNDATACHAR:
  785.                             $state='tagshortattbegindyndata';
  786.                         break;
  787.  
  788.                         case '"':
  789.                             $state='tagshortattbeginquote';
  790.                         break;
  791.  
  792.                         case ',':
  793.                             //no state change
  794.                         break;
  795.  
  796.                         case ' ':
  797.                             $state='tagshortattspace';
  798.                         break;
  799.  
  800.                         default:
  801.                             $state='tagshortattchar';
  802.                         break;
  803.                     }
  804.                 break;
  805.  
  806.                 case 'tagshortattbeginquote':
  807.                     switch($char) {
  808.                         case '"':
  809.                             $state='tagshortattendquote';
  810.                         break;
  811.  
  812.                         case UBBCODE_DYNDATACHAR:
  813.                             $state='tagshortattquotedyndatabeginescape';
  814.                         break;
  815.  
  816.                         default:
  817.                             $state='tagshortattquotechar';
  818.                         break;
  819.                     }
  820.                 break;
  821.  
  822.                 case 'tagshortattquotedyndatabeginescape':
  823.                     switch($char) {
  824.                         case UBBCODE_DYNDATACHAR:
  825.                             $state='tagshortattquotedyndataend';
  826.                         break;
  827.  
  828.                         default:
  829.                             $state='tagshortattquotedyndatachar';
  830.                         break;
  831.                     }
  832.                 break;
  833.  
  834.                 case 'tagshortattdyndatachar':
  835.                     switch($char) {
  836.                         case UBBCODE_DYNDATACHAR:
  837.                             $state='tagshortattdyndataend';
  838.                         break;
  839.  
  840.                         default:
  841.                             //no state change
  842.                         break;
  843.                     }
  844.                 break;
  845.  
  846.  
  847.                 case 'tagshortattquotedyndataend':
  848.                     switch($char) {
  849.                         case UBBCODE_DYNDATACHAR:
  850.                             $state='tagshortattquotedyndatabeginescape';
  851.                         break;
  852.  
  853.                         case '"':
  854.                             $state='tagshortattendquote';
  855.                         break;
  856.  
  857.                         default:
  858.                             $state='tagshortattquotechar';
  859.                         break;
  860.                     }
  861.                 break;
  862.  
  863.                 case 'tagshortattbegindyndata':
  864.                     switch($char) {
  865.                         case UBBCODE_DYNDATACHAR:
  866.                             $state='tagshortattdyndataend';
  867.                         break;
  868.  
  869.                         default:
  870.                             $state='tagshortattdyndatachar';
  871.                         break;
  872.                     }
  873.                 break;
  874.  
  875.                 case 'tagshortattquotechar':
  876.                     switch($char) {
  877.                         case UBBCODE_DYNDATACHAR:
  878.                             $state='tagshortattquotedyndatabeginescape';
  879.                         break;
  880.  
  881.                         case '"':
  882.                             $state='tagshortattendquote';
  883.                         break;
  884.  
  885.                         default:
  886.                             //no state change
  887.                         break;
  888.                     }
  889.                 break;
  890.  
  891.                 case 'tagshortattendquote':
  892.                     switch($char) {
  893.                         case ' ':
  894.                             $state='tagshortattspace';
  895.                         break;
  896.  
  897.                         case UBBCODE_TAGEND:
  898.                             $state='tagend';
  899.                         break;
  900.  
  901.                         case ',':
  902.                             $state='tagshortattcomma';
  903.                         break;
  904.  
  905.                         default:
  906.                             throw new BadUbbCodeException(
  907.                                 "Parser too confused to continue at character $i,".
  908.                                 " expecting a space, a comma or a tagend",
  909.                                 $i
  910.                             );
  911.                         break;
  912.                     }
  913.                 break;
  914.  
  915.                 case 'tagshortattquotedyndatachar':
  916.                     switch($char) {
  917.                         case UBBCODE_DYNDATACHAR:
  918.                             $state='tagshortattquotedyndataend';
  919.                         break;
  920.  
  921.                         default:
  922.                             //no state change
  923.                         break;
  924.                     }
  925.                 break;
  926.  
  927.                 case 'tagshortattdyndataend':
  928.                     switch($char) {
  929.                         case ',':
  930.                             $state='tagshortattcomma';
  931.                         break;
  932.  
  933.                         case UBBCODE_TAGEND:
  934.                             $state='tagend';
  935.                         break;
  936.  
  937.                         default:
  938.                             $state='tagshortattchar';
  939.                         break;
  940.                     }
  941.                 break;
  942.  
  943.                 case 'tagshortattspace':
  944.                     switch($char) {
  945.                         case ' ':
  946.                             //no state change
  947.                         break;
  948.  
  949.                         case ',':
  950.                             $state='tagshortattcomma';
  951.                         break;
  952.  
  953.                         default:
  954.                             $state='taglongattname';
  955.                         break;
  956.                     }
  957.                 break;
  958.  
  959.                 default:
  960.                     throw new Exception("Unknown state, $state");
  961.                 break;
  962.  
  963.             }
  964.  
  965.  
  966.             if(defined('UBBCODE_DEBUG') && UBBCODE_DEBUG==true) {
  967.                 echo '<td>'.htmlentities($state)."</td>\r\n";
  968.             }
  969.  
  970.             ################################################################################################
  971.  
  972.             //Now do the stuff in each state
  973.  
  974.             switch($state) {
  975.  
  976.                 case 'text';
  977.                     $contents[$deep][]=$char;//we will merge the array later
  978.                 break;
  979.  
  980.                 case 'tagbegining':
  981.                     $atts=array();
  982.                     $tagname="";
  983.                 break;
  984.  
  985.                 case 'tagname':
  986.                     $tagname.=$char;
  987.                 break;
  988.  
  989.                 case 'tagend':
  990.                     $tagname=strtolower($tagname);
  991.  
  992.                     if($tagname=='') {
  993.                         throw new BadUbbcodeException(
  994.                             "A tag must have a name!",
  995.                             $i
  996.                         );
  997.                     } elseif($tagname=='_root') {
  998.                         throw new BadUbbcodeException(
  999.                             "The tag \"_root\" is a reservated tag, don't use it!",
  1000.                             $i
  1001.                         );
  1002.                     }
  1003.  
  1004.                     if(isset($attname) and ($attname or $attname===0) and count($attvalue)) {
  1005.                         //type is important due to not allowing zero lenght strings
  1006.                         $atts[$attname]=$attvalue;
  1007.                     }
  1008.  
  1009.                     $usedtagname=$tagname;
  1010.  
  1011.                     while(isset(self::$tagaliases[$tagname])) {
  1012.                         $tagname=self::$tagaliases[$tagname];
  1013.                     }
  1014.  
  1015.                     if(count($tagstack)) {
  1016.                         $parrentname=$tagstack[count($tagstack)-1]['tagname'];
  1017.                     } else {
  1018.                         $parrentname='_root';
  1019.                     }
  1020.  
  1021.                     if(substr($tagname,0,1)=='/') {
  1022.  
  1023.                         $stripedtagname=substr($tagname,1);
  1024.  
  1025.                         if($parrentname=='*' and $tagstack[count($tagstack)-2]['tagname']=='list' and $stripedtagname=='list') {
  1026.                             //close the * tag to allow for legacy syntax
  1027.                             $deep--;
  1028.                             $taginfo=array_pop($tagstack);
  1029.                             $contents[$deep][]=new self($taginfo['tagname'],array_pop($contents),$taginfo['atts'],$taginfo['usedtagname']);
  1030.                         }
  1031.  
  1032.                         $deep--;
  1033.                         if($deep<0) {
  1034.                             throw new BadUbbCodeException(
  1035.                                 'There is more closed tags than there was opened!',
  1036.                                 $i
  1037.                             );
  1038.                         }
  1039.  
  1040.                         $taginfo=array_pop($tagstack);
  1041.  
  1042.  
  1043.                         if(strlen($stripedtagname)>0) {
  1044.                             if($stripedtagname!=$taginfo['usedtagname']) {
  1045.                                 throw new BadUbbcodeException(
  1046.                                     "Tag \"{$taginfo['tagname']}\" was ended with a \"$tagname\" instead of a \"/{$taginfo['usedtagname']}\" tag",
  1047.                                     $i
  1048.                                 );
  1049.                             }
  1050.                         }
  1051.  
  1052.                         $contents[$deep][]=new self($taginfo['tagname'],array_pop($contents),$taginfo['atts'],$taginfo['usedtagname']);
  1053.                     } else {
  1054.  
  1055.                         if($tagname=='*' and $parrentname=='*') {
  1056.                             //close the old * tag to allow legacy syntax
  1057.                             $deep--;
  1058.                             $taginfo=array_pop($tagstack);
  1059.                             $contents[$deep][]=new self($taginfo['tagname'],array_pop($contents),$taginfo['atts'],$taginfo['usedtagname']);
  1060.  
  1061.                         }   elseif(!isset(self::$allowednesting[$parrentname][$tagname] ) ) {
  1062.                             throw new BadUbbCodeException(
  1063.                                 "A \"$tagname\" tag may not be nested in a \"$parrentname\" tag",
  1064.                                 $i
  1065.                             );
  1066.                         } elseif( isset(self::$forbidendeepnesting[$tagname]) ) {
  1067.                             foreach(array_slice($tagstack,0,-1) as $tagstackelement) {
  1068.                                 foreach(self::$forbidendeepnesting[$tagname] as $badtagname) {
  1069.                                     if($tagstackelement['tagname']==$badtagname) {
  1070.                                         throw new BadUbbcodeException(
  1071.                                             "A \"{$tagname}\" tag may not be nested in a \"{$tagstackelement['tagname']}\" tag!",
  1072.                                             $i
  1073.                                         );
  1074.                                     }
  1075.                                 }
  1076.                             }
  1077.                         }
  1078.  
  1079.                         if(isset(self::$parserbypasstags[$tagname]) and self::$parserbypasstags[$tagname]==true) {
  1080.                             $endtag=UBBCODE_TAGBEGIN.'/'.$usedtagname.UBBCODE_TAGEND;
  1081.                             $endpos=@stripos($input,$endtag,$i+1);
  1082.                             if($endpos===FALSE) {
  1083.                                 throw new BadUbbCodeException(
  1084.                                     'Parser disabled tag not terminated properly',
  1085.                                     $i
  1086.                                 );
  1087.                             }
  1088.  
  1089.                             $tagcontents=array( substr($input,$i+1,$endpos-($i+1)) );
  1090.                             $i=$endpos+strlen($endtag)-1;//the loop adds one at the end
  1091.                             $contents[$deep][]=new self($tagname,$tagcontents,$atts,$usedtagname);
  1092.                         } else {
  1093.  
  1094.                             $tagstack[]=array('tagname'=>$tagname,'atts'=>$atts,'usedtagname'=>$usedtagname);
  1095.                             $deep++;
  1096.                             $contents[$deep]=array();
  1097.  
  1098.                         }
  1099.                         $atts=array();
  1100.                         $attvalue=array();
  1101.                         $attname='';
  1102.                     } #end else (not / at beginging)
  1103.                 break;
  1104.  
  1105.                 case 'taglongattspace':
  1106.                     $attname='';
  1107.                     $attvalue=array();
  1108.                 break;
  1109.  
  1110.                 case 'taglongattname':
  1111.                     if(!isset($attname) or is_numeric($attname)) {
  1112.                         $attname='';
  1113.                     }
  1114.                     $attname.=$char;
  1115.                 break;
  1116.  
  1117.                 case 'taglongattequ':
  1118.                     $attvalue=array();
  1119.                 break;
  1120.  
  1121.                 case 'taglongattbeginquote':
  1122.                 break;
  1123.  
  1124.                 case 'taglongattvalue':
  1125.                     $attvalue[]=$char;
  1126.                 break;
  1127.  
  1128.                 case 'taglongattendquote':
  1129.                     $atts[$attname]=$attvalue;
  1130.                     $attvalue=array();
  1131.                     $attname='';
  1132.                 break;
  1133.  
  1134.                 case 'taglongattescape':
  1135.                 break;
  1136.  
  1137.                 case 'taglongattescape2':
  1138.                     switch($char) {
  1139.                         case '"';
  1140.                             $attvalue[]='"';
  1141.                         break;
  1142.  
  1143.                         case '\\':
  1144.                             $attvalue[]='\\';
  1145.                         break;
  1146.  
  1147.                         default:
  1148.                             throw new BadUbbcodeException(
  1149.                                 "Illegal escape character used $char",
  1150.                                 $i
  1151.                             );
  1152.                         break;
  1153.                     }
  1154.                 break;
  1155.  
  1156.                 case 'taglongattdyndataescape':
  1157.                     $dyndataname="";
  1158.                 break;
  1159.  
  1160.                 case 'taglongattdyndatachar':
  1161.                     $dyndataname.=$char;
  1162.                 break;
  1163.  
  1164.                 case 'taglongattdyndataend':
  1165.                     $attvalue[]=new UbbDynData($dyndataname);
  1166.                 break;
  1167.  
  1168.                 case 'dyndatabegin':
  1169.                     $dyndataname="";
  1170.                 break;
  1171.  
  1172.                 case 'dyndatachar':
  1173.                     $dyndataname.=$char;
  1174.                 break;
  1175.  
  1176.                 case 'dyndataend':
  1177.                     if($dyndataname=='') {
  1178.                         $contents[$deep][]=UBBCODE_DYNDATACHAR;
  1179.                     } else {
  1180.                         $contents[$deep][]=new UbbDynData($dyndataname);
  1181.                     }
  1182.                 break;
  1183.  
  1184.                 case 'smileybegin':
  1185.                     $smileyname="";
  1186.                 break;
  1187.  
  1188.                 case 'smileychar':
  1189.                     $smileyname.=$char;
  1190.                 break;
  1191.  
  1192.                 case 'smileyend':
  1193.                     if($smileyname=='') {
  1194.                         $contents[$deep][]=UBBCODE_SMILEYCHAR;
  1195.                     } else {
  1196.                         $contents[$deep][]=new UbbSmiley($smileyname);
  1197.                     }
  1198.                 break;
  1199.  
  1200.                 case 'tagshortattequ':
  1201.                     $attname=0;
  1202.                 break;
  1203.  
  1204.                 case 'tagshortattchar':
  1205.                     $attvalue[]=$char;
  1206.                 break;
  1207.  
  1208.                 case 'tagshortattcomma':
  1209.                     $atts[$attname]=$attvalue;
  1210.                     $attvalue=array();
  1211.                     ++$attname;
  1212.                 break;
  1213.  
  1214.                 case 'tagshortattbegindyndata':
  1215.                     $dyndataname='';
  1216.                 break;
  1217.  
  1218.                 case 'tagshortattdyndatachar':
  1219.                     $dyndataname.=$char;
  1220.                 break;
  1221.  
  1222.                 case 'tagshortattdyndataend':
  1223.                     $attvalue[]=new UbbDynData($dyndataname);
  1224.                 break;
  1225.  
  1226.                 case 'tagattspace':
  1227.                     if(isset($attname) and $attname) {
  1228.                         $atts[$attname]=$attvalue;
  1229.                     }
  1230.                     $attvalue=array();
  1231.                     //nothing to do
  1232.                 break;
  1233.  
  1234.                 case 'tagshortattbeginquote':
  1235.                     //already cleared the value before we got to this state
  1236.                 break;
  1237.  
  1238.                 case 'tagshortattquotechar':
  1239.                     $attvalue[]=$char;
  1240.                 break;
  1241.  
  1242.                 case 'tagshortattendquote':
  1243.                     $atts[$attname]=$attvalue;
  1244.                     $attvalue=array();
  1245.                     ++$attname;
  1246.  
  1247.                 break;
  1248.  
  1249.                 case 'tagshortattquotedyndatabeginescape':
  1250.                     $dyndataname='';
  1251.                 break;
  1252.  
  1253.                 case 'tagshortattquotedyndatachar':
  1254.                     $dyndataname.=$char;
  1255.                 break;
  1256.  
  1257.                 case 'tagshortattquotedyndataend':
  1258.                     $attvalue[]=new UbbDynData($dyndataname);
  1259.                 break;
  1260.  
  1261.                 case 'tagshortattspace':
  1262.                     //nothing to do?
  1263.                 break;
  1264.  
  1265.                 default:
  1266.                     throw new Exception("Unhandled state: $state");
  1267.                 break;
  1268.  
  1269.             }
  1270.  
  1271.             if(defined('UBBCODE_DEBUG') && ((UBBCODE_DEBUG&3)==3)) {//both flags are needed
  1272.                 echo '<td><pre>';
  1273.  
  1274.                 ob_start();
  1275.                 var_dump($contents);
  1276.                 $a=ob_get_contents();
  1277.                 ob_end_clean();
  1278.                 echo htmlspecialchars($a,ENT_QUOTES);
  1279.  
  1280.                 echo '</pre></td><td><pre>';
  1281.  
  1282.                 ob_start();
  1283.                 var_dump($tagstack);
  1284.                 $a=ob_get_contents();
  1285.                 ob_end_clean();
  1286.                 echo htmlspecialchars($a,ENT_QUOTES);
  1287.  
  1288.                 echo '</pre></td><td><pre>';
  1289.  
  1290.                 ob_start();
  1291.                 var_dump($attvalue);
  1292.                 $a=ob_get_contents();
  1293.                 ob_end_clean();
  1294.                 echo htmlspecialchars($a,ENT_QUOTES);
  1295.  
  1296.                 echo '</pre></td><td><pre>';
  1297.  
  1298.                 ob_start();
  1299.                 var_dump($atts);
  1300.                 $a=ob_get_contents();
  1301.                 ob_end_clean();
  1302.                 echo htmlspecialchars($a,ENT_QUOTES);
  1303.  
  1304.                 echo "</pre></td><td>$i</td></tr>";
  1305.             }
  1306.  
  1307.  
  1308.         }# end of for i<strlen(input)
  1309.  
  1310.         if(defined('UBBCODE_DEBUG') && UBBCODE_DEBUG==true) {
  1311.             echo '</table>';
  1312.         }
  1313.  
  1314.         switch($state) {
  1315.             case 'tagend':
  1316.             case 'text':
  1317.             case 'smileyend':
  1318.             case 'dyndataend':
  1319.                 $contents=array_pop($contents);
  1320.             break;
  1321.  
  1322.             default:
  1323.                 throw new BadUbbCodeException("Parser ended in invalid state, $state",$i);
  1324.             break;
  1325.         }
  1326.  
  1327.         if($deep>0) {
  1328.             throw new BadUbbCodeException("There is $deep unclosed tag".($deep!=1?'s':'')." still open!",$i);
  1329.         }
  1330.  
  1331.         $tag=new self('_root',$contents,array());
  1332.  
  1333.         $tag->mergestrings();
  1334.  
  1335.         $tag->convertsmilies();
  1336.  
  1337.         $tag->converturls();
  1338.  
  1339.         return $tag;
  1340.  
  1341.     }# end of static function parse
  1342.  
  1343.     public function hasContents() {
  1344.         return isset($this->contents) and count($this->contents)>0;
  1345.     }
  1346.  
  1347.     static protected function getNewTagID() {
  1348.         //used for spoiler tags and other tags that need a known unique id
  1349.  
  1350.         //Nah, they don't need an unique id, they can use relative paths
  1351.         user_error('Depraciated method, "getNewTagID", called',E_USER_WARNING);
  1352.  
  1353.         return self::$tagID++;
  1354.     }
  1355.  
  1356.     public function getHtml($issig=false) {
  1357.  
  1358.         if(isset(self::$customtagCallback[$this->type])) {
  1359.             return call_user_func(self::$customtagCallback[$this->type],$this);
  1360.         }
  1361.  
  1362.         switch($this->type) {
  1363.  
  1364.             case 'b':
  1365.                 return sprintf(
  1366.                     '<strong>%s</strong>',
  1367.                         $this->getHtmlForContents($issig)
  1368.                     );
  1369.             break;
  1370.  
  1371.             case 'i':
  1372.                 return sprintf(
  1373.                     '<em>%s</em>',
  1374.                         $this->getHtmlForContents($issig)
  1375.                     );
  1376.             break;
  1377.  
  1378.             case 's':
  1379.                 return sprintf(
  1380.                     '<span style="text-decoration: line-through">%s</span>',
  1381.                         $this->getHtmlForContents($issig)
  1382.                     );
  1383.             break;
  1384.  
  1385.             case 'url':
  1386.                 $url=$this->getatteribute(self::$urltags['url']);
  1387.                 if(!$url) {
  1388.                     $url=self::handleDynData($this->contents);
  1389.                 }
  1390.  
  1391.                 if(!preg_match('#^[a-z]+:#i',$url)) {
  1392.                     $url='http://'.$url;
  1393.                 }
  1394.  
  1395.                 return sprintf(
  1396.                     '<a href="%s">%s</a>',
  1397.                     $url,
  1398.                     (
  1399.                         $this->hasContents() ?
  1400.                         $this->getHtmlForContents($issig) :
  1401.                         htmlentities($url)
  1402.                     )
  1403.                 );
  1404.             break;
  1405.  
  1406.             case 'email':
  1407.                 $email=$this->getatteribute(array('email','to','address',0));
  1408.                 if(!$email) {
  1409.                     $email=$this->getHtmlForContents($issig);
  1410.                 } else {
  1411.                     $email=htmlentities($email);
  1412.                 }
  1413.  
  1414.                 return sprintf(
  1415.                     '<a href="mailto:%s">%s</a>',
  1416.                     urlencode($email),
  1417.                     (
  1418.                         $this->hasContents() ?
  1419.                         $this->getHtmlForContents($issig) :
  1420.                         htmlentities($email)
  1421.                     )
  1422.                 );
  1423.             break;
  1424.  
  1425.             case 'google':
  1426.                 $name=self::getatteributename(array('query',0));
  1427.                 //var_dump($name);
  1428.                 $query=($name!==false?$this->atteributes[$name]:$this->contents);
  1429.                 //var_dump($query);
  1430.                 $query=self::handleDynData($query);
  1431.                 //var_dump($query);
  1432.                 return sprintf(
  1433.                     '<a href="http://www.google.com/search?q=%s" class="google">%s</a>',
  1434.                     htmlentities(
  1435.                         urlencode(
  1436.                             $query
  1437.                         )
  1438.                     ),
  1439.                     (
  1440.                         $this->hasContents() ?
  1441.                         $this->getHtmlForContents($issig) :
  1442.                         'Google: '.htmlentities($query)
  1443.                     )
  1444.                 );
  1445.             break;
  1446.  
  1447.             case 'img':
  1448.                 if($this->hasatteribute(self::$urltags['img'])) {
  1449.                     return sprintf(
  1450.                         '<img src="%s" alt="%s" />',
  1451.                         htmlentities(
  1452.                             $this->getatteribute(self::$urltags['img'])
  1453.                         ),
  1454.                         (
  1455.                             $this->hasContents() ?
  1456.                             self::handleDynData($this->contents) :
  1457.                             'User posted image'
  1458.                         )
  1459.                     );
  1460.                 } else {
  1461.                     return sprintf(
  1462.                         '<img src="%s" alt="%s" />',
  1463.                         self::handleDynData($this->contents),
  1464.                         'User posted image'
  1465.                     );
  1466.                 }
  1467.             break;
  1468.  
  1469.             case 'flash':
  1470.                 //TODO: create non crappy html
  1471.                 return sprintf(
  1472.                     (
  1473.                         '<embed allowscripting="never" src="%s" heigth="%u" width="%u" params="%s"'.
  1474.                         ' loop="true" play="true" quality="%s" />'
  1475.                     ),
  1476.                     htmlentities(
  1477.                         $this->hasatteribute(self::$urltags['flash']) ?
  1478.                         $this->getatteribute(self::$urltags['flash']) :
  1479.                         self::handleDynData($this->contents)
  1480.                     ),
  1481.                     $this->getatteribute('heigth'),//safe since %u is used
  1482.                     $this->getatteribute('width'),
  1483.                     htmlentities(
  1484.                         $this->getatteribute('params')
  1485.                     ),
  1486.                     (
  1487.                         $issig ?//yes, flash can change it at runtime, but it is the thought that counts
  1488.                         'low':
  1489.                         'high'
  1490.                     )
  1491.                 );
  1492.             break;
  1493.  
  1494.             case 'list':
  1495.                 return sprintf(
  1496.                     '<%s>%s</%1$s>',
  1497.                     $this->hasatteribute(array('ordered',0)) ? 'ol' : 'ul',
  1498.                     $this->getHtmlForContents($issig)
  1499.                 );
  1500.             break;
  1501.  
  1502.             case '*':
  1503.                 return sprintf(
  1504.                     '<li>%s</li>',
  1505.                     $this->getHtmlForContents($issig)
  1506.                 );
  1507.             break;
  1508.  
  1509.             case 'code':
  1510.                 $language=$this->getatteribute(array('lang',0));
  1511.                 $gothighlighter=$language and
  1512.                 isset(self::$highlightengines[$language]);
  1513.  
  1514.                 if($gothighlighter) {
  1515.                     $highlighter=self::$highlightengines[$language];
  1516.                 }
  1517.  
  1518.                 //TODO: write better html for the head
  1519.  
  1520.                 return sprintf(
  1521.                     '<div class="code"><div class="codetop">%s</div><div class="codemain">%s</div></div>',
  1522.                     $language ? htmlentities("$language code") : 'code',
  1523.                     (
  1524.                         $gothighlighter ?
  1525.                         call_user_func($highlighter,self::handleDynData($this->contents),$language) :
  1526.                         htmlentities(self::handleDynData($this->contents))
  1527.                     )
  1528.                 );
  1529.             break;
  1530.  
  1531.             case 'table':
  1532.                 return sprintf(
  1533.                     '<table>%s</table>',
  1534.                     $this->getHtmlForContents($issig)
  1535.                 );
  1536.             break;
  1537.  
  1538.             case 'row':
  1539.                 return sprintf(
  1540.                     '<tr>%s</tr>',
  1541.                     $this->getHtmlForContents($issig)
  1542.                 );
  1543.             break;
  1544.  
  1545.             case 'cell':
  1546.                 return sprintf(
  1547.                     '<td>%s</td>',
  1548.                     $this->getHtmlForContents($issig)
  1549.                 );
  1550.             break;
  1551.  
  1552.             case 'noubb':
  1553.             case '_root':
  1554.                 return $this->getHtmlForContents($issig);
  1555.             break;
  1556.  
  1557.             case 'spoiler':
  1558.                 return 'Do not read this: '.$this->getHtmlForContents($issig);
  1559.             break;
  1560.  
  1561.             case 'comment':
  1562.                 return '';
  1563.             break;
  1564.  
  1565.             case 'left':
  1566.             case 'center':
  1567.             case 'right':
  1568.                 return sprintf(
  1569.                     '<div style="text-align: %s">%s</div>',
  1570.                     $this->type,
  1571.                     $this->getHtmlForContents($issig)
  1572.                 );
  1573.             break;
  1574.  
  1575.             case 'sub':
  1576.             case 'sup':
  1577.                 return sprintf(
  1578.                     '<%s>%s</%1$s>',
  1579.                     $this->type,
  1580.                     $this->getHtmlForContents($issig)
  1581.                 );
  1582.             break;
  1583.  
  1584.             case 'background':
  1585.                 $color=$this->getatteribute(array('color',0));
  1586.                 if(preg_match('/^([a-z]+|#[0-f]{6})$/i', $color)) {
  1587.                     return sprintf(
  1588.                         '<span style="background-color: %s">%s</span>',
  1589.                         $color,
  1590.                         $this->getHtmlForContents($issig)
  1591.                     );
  1592.                 } else {
  1593.                     return $this->getHtmlForContents($issig);
  1594.                 }
  1595.             break;
  1596.  
  1597.             case 'color':
  1598.                 $color=$this->getatteribute(array('color',0));
  1599.                 if(preg_match('/^([a-z]+|#[0-f]{6})$/i', $color)) {
  1600.                     return sprintf(
  1601.                         '<span style="color: %s">%s</span>',
  1602.                         $color,
  1603.                         $this->getHtmlForContents($issig)
  1604.                     );
  1605.                 } else {
  1606.                     return $this->getHtmlForContents($issig);
  1607.                 }
  1608.             break;
  1609.  
  1610.             case 'size':
  1611.                 $size=$this->getatteribute(array('size',0));
  1612.                 return sprintf(
  1613.                     '<span style="font-size: %u;">%s</span>',
  1614.                     $size,
  1615.                     $this->getHtmlForContents($issig)
  1616.                 );
  1617.             break;
  1618.  
  1619.             case 'font':
  1620.                 $font=$this->getatteribute(array('font',0));
  1621.                 if(preg_match('#^[a-z-]+$#i',$font)) {
  1622.                     return sprintf(
  1623.                         '<span style="font-family %s;">%s</span>',
  1624.                         $font,
  1625.                         $this->getHtmlForContents($issig)
  1626.                     );
  1627.                 } else {
  1628.                     return $this->getHtmlForContents($issig);
  1629.                 }
  1630.             break;
  1631.  
  1632.             case 'indent':
  1633.                 return sprintf(
  1634.                     '<div style="margin-left: 2em;">%s</div>',
  1635.                     $this->getHtmlForContents($issig)
  1636.                 );
  1637.             break;
  1638.  
  1639.             default:
  1640.                 throw new Exception("GetHtml somehow do not know the tag type {$this->type}");
  1641.             break;
  1642.         }
  1643.     }# end of function getHtml
  1644.  
  1645.     public function getHtmlForContents($isSig=false) {
  1646.  
  1647.         $o='';
  1648.  
  1649.         foreach($this->contents as $content) {
  1650.             if($content instanceof self) {
  1651.                 $o.=$content->getHtml($isSig);
  1652.             } elseif($content instanceof UbbDynData) {
  1653.                 $o.=htmlentities($content->getDynData());
  1654.             } elseif($content instanceof UbbSmiley) {
  1655.                 $o.=$content->getHtml();
  1656.             } else {//must be a plain old string
  1657.                 $o.=htmlentities($content);
  1658.             }
  1659.         }
  1660.  
  1661.         return $o;
  1662.     }# end of function getHtmlForContents
  1663.  
  1664.     protected function handleDynData($data) {
  1665.         //when smileys and tags wont be in it
  1666.  
  1667.         //var_dump($data);
  1668.  
  1669.         if(!is_array($data)) {
  1670.             throw new Exception('Handledyndata requires an array');
  1671.         }
  1672.         $o='';
  1673.         foreach($data as $chunk) {
  1674.             if($chunk instanceof UbbDynData) {
  1675.                 $o.=$chunk->getDynData();
  1676.             } else {
  1677.                 $o.=$chunk;
  1678.             }
  1679.         }
  1680.  
  1681.         return $o;
  1682.     }# end of function handleDynData
  1683.  
  1684.     public function getatteributename($names) {
  1685.         foreach($names as $candidate) {
  1686.             if(isset($this->atteributes[$candidate])) {
  1687.                 return $candidate;
  1688.             }
  1689.         }
  1690.         return false;
  1691.     }
  1692.  
  1693.     public function getatteribute($name) {
  1694.         if(is_array($name)) {
  1695.             $name=self::getatteributename($name);
  1696.             if($name===false) {
  1697.                 return $name;
  1698.             }
  1699.         } else {
  1700.             if(!isset($this->atteributes[$name])) {
  1701.                 return false;
  1702.             }
  1703.         }
  1704.         return self::handleDynData($this->atteributes[$name]);
  1705.     }
  1706.  
  1707.     public function hasatteribute($name) {
  1708.         if(is_array($name)) {
  1709.             return self::getatteributename($name)!==false;
  1710.         } else {
  1711.             return isset($this->atteributes[$name]);
  1712.         }
  1713.     }
  1714.  
  1715.     protected function usesshortstyleattirbutes() {
  1716.         return isset($this->atteributes[0]);
  1717.     }
  1718.  
  1719.     public function getUbb() {
  1720.  
  1721.         $o='';
  1722.  
  1723.         if($this->type!='_root') {
  1724.             $o='['.$this->usedname;
  1725.  
  1726.             if(isset($this->atteributes[0])) {
  1727.                 $o.='='.self::getUBBforatts($this->atteributes[0]);
  1728.                 for($i=1;isset($this->atteributes[$i]);++$i) {
  1729.                     $o.=','.self::getUBBforatts($this->atteributes[$i]);
  1730.                 }
  1731.             }
  1732.  
  1733.             foreach($this->atteributes as $atteribute=>$value) {
  1734.                 if(!is_numeric($atteribute)) {
  1735.                     $o.=' '.$atteribute.'="'.self::getUBBforatts($value).'"';
  1736.                 }
  1737.             }
  1738.             $o.=']';
  1739.         }
  1740.  
  1741.  
  1742.         foreach($this->contents as $content) {
  1743.             if($content instanceof self) {
  1744.                 $o.=$content->getUbb();
  1745.             } elseif($content instanceof UbbDyndata) {
  1746.                 $o.=$content->getUbb();
  1747.             } elseif($content instanceof UbbSmiley) {
  1748.                 $o.=$content->getUbb();
  1749.             } elseif(isset(self::$parserbypasstags[$this->type]) and self::$parserbypasstags[$this->type]==true) {
  1750.                 $o.=$content;
  1751.             } else {
  1752.                 $o.=self::escapetext($content);
  1753.             }
  1754.         }#end of foreach
  1755.  
  1756.  
  1757.         if(!(isset(self::$noendtagtags[$this->type]) and self::$noendtagtags[$this->type])) {
  1758.             $o.='[/'.$this->usedname.']';
  1759.         }
  1760.  
  1761.         return $o;
  1762.     }#end of getUbb
  1763.  
  1764.     protected function getUBBforatts($atts) {
  1765.         if(!is_array($atts)) {
  1766.             throw new Exception('getUBBforatts requires an array');
  1767.         }
  1768.         $o='';
  1769.  
  1770.         foreach($atts as $att) {
  1771.             if($att instanceof UbbDynData) {
  1772.                 $o.=$att->getUbb();
  1773.             } else {
  1774.                 $o.=self::escapeattvalue($att);
  1775.             }
  1776.         }
  1777.  
  1778.         return $o;
  1779.     }
  1780.  
  1781.     public static function escapeattvalue($in) {
  1782.         return str_replace(array('\\','"',UBBCODE_DYNDATACHAR),array('\\\\','\\"',UBBCODE_DYNDATACHAR.UBBCODE_DYNDATACHAR),$in);
  1783.     }
  1784.  
  1785.     public static function escapetext($in) {
  1786.         $in=str_replace(UBBCODE_DYNDATACHAR,UBBCODE_DYNDATACHAR.UBBCODE_DYNDATACHAR,$in);
  1787.         if(defined('UBBCODE_COMPLEX_SMILIES')) {
  1788.             return str_replace(UBBCODE_SMILEYCHAR,UBBCODE_SMILEYCHAR.UBBCODE_SMILEYCHAR,$in);
  1789.         } else {
  1790.             return $in;
  1791.         }
  1792.     }
  1793.  
  1794.     protected function mergestrings() {
  1795.         $strbuffer='';
  1796.         $o=array();
  1797.         foreach($this->contents as $element) {
  1798.             if(is_string($element)) {
  1799.                 $strbuffer.=$element;
  1800.             } else {
  1801.                 if(strlen($strbuffer)>0) {
  1802.                     $o[]=$strbuffer;
  1803.                     $strbuffer='';
  1804.                 }
  1805.  
  1806.                 if($element instanceof self) {
  1807.                     $element->mergestrings();
  1808.                 }
  1809.  
  1810.                 $o[]=$element;
  1811.             }
  1812.         }
  1813.         if(strlen($strbuffer)>0) {
  1814.             $o[]=$strbuffer;
  1815.         }
  1816.         $this->contents=$o;
  1817.  
  1818.         foreach($this->atteributes as $key=>$atteribute) {
  1819.             $o=array();
  1820.             $strbuffer='';
  1821.  
  1822.             foreach($atteribute as $element) {
  1823.                 if(is_string($element)) {
  1824.                     $strbuffer.=$element;
  1825.                 } else {
  1826.                     if(strlen($strbuffer)>0) {
  1827.                         $o[]=$strbuffer;
  1828.                         $strbuffer='';
  1829.                     }
  1830.  
  1831.                     $o[]=$element;
  1832.                 }
  1833.             }
  1834.  
  1835.             if(strlen($strbuffer)>0) {
  1836.                 $o[]=$strbuffer;
  1837.             }
  1838.  
  1839.             $this->atteributes[$key]=$o;
  1840.         }
  1841.     }
  1842.  
  1843.     protected function convertsmilies() {
  1844.  
  1845.         if(isset(self::$parserbypasstags[$this->type]) and self::$parserbypasstags[$this->type]) {
  1846.             return;
  1847.         }
  1848.  
  1849.         $substitutionneedles=array_keys(UbbSmiley::$substitutionmap);
  1850.  
  1851.         $o=array();
  1852.         foreach($this->contents as $content) {
  1853.             if(!is_string($content)) {
  1854.                 if($content instanceof self) {
  1855.                     $content->convertsmilies();
  1856.                 }
  1857.                 $o[]=$content;
  1858.                 continue;
  1859.             }
  1860.  
  1861.             $pos=0;
  1862.             while($find=nextchar($content,$substitutionneedles,$pos)) {
  1863.  
  1864.  
  1865.                 if($find['offset']>0) {
  1866.                     $pre=substr($content,$pos,$find['offset']-$pos);
  1867.  
  1868.                     $o[]=$pre;
  1869.                 }
  1870.  
  1871.                 $o[]=new UbbSmiley($find['needle'],true);
  1872.  
  1873.                 $pos=$find['offset']+strlen($find['needle']);
  1874.             }
  1875.  
  1876.             if($pos!=strlen($content)) {
  1877.                 $post=substr($content,$pos);
  1878.  
  1879.                 $o[]=$post;
  1880.             }
  1881.  
  1882.         }
  1883.  
  1884.         $this->contents=$o;
  1885.     }
  1886.  
  1887.     protected function converturls() {
  1888.  
  1889.         //print_r(self::$urltags[$this->type]);
  1890.  
  1891.         if(isset(self::$parserbypasstags[$this->type]) and self::$parserbypasstags[$this->type]) {
  1892.             return;
  1893.         }
  1894.  
  1895.         //don't convert urls that are already going to be interpreted as urls
  1896.         if(isset(self::$urltags[$this->type])) {
  1897.             if(!$this->hasatteribute(self::$urltags[$this->type])) {
  1898.                 return;
  1899.             }
  1900.         }
  1901.  
  1902.         $o=array();
  1903.         foreach($this->contents as $content) {
  1904.             if(!is_string($content)) {
  1905.                 if($content instanceof self) {
  1906.                     $content->converturls();
  1907.                 }
  1908.                 $o[]=$content;
  1909.                 continue;
  1910.             }
  1911.  
  1912.             preg_match_all(
  1913.                 '#'.
  1914.                 '(www\\.|(ht|f)tps?://)[a-z0-9-]+(\\.[a-z0-9-]+)*(:[0-9]{1,6})?(/[a-z0-9\\.~/&_\\?%=!-]*(\\#[a-z0-9-]*)?)?'
  1915.                 .'#iSXD',
  1916.                 $content,
  1917.                 $matches,
  1918.                 PREG_PATTERN_ORDER
  1919.             );
  1920.  
  1921.             $substitutionneedles=$matches[0];
  1922.             unset($matches);
  1923.  
  1924.             //print_r($substitutionneedles);
  1925.  
  1926.             $pos=0;//TODO: move from the string substitution into a more exact position based solution.
  1927.             while($find=nextchar($content,$substitutionneedles,$pos)) {
  1928.  
  1929.  
  1930.                 if($find['offset']>0) {
  1931.                     $pre=substr($content,$pos,$find['offset']-$pos);
  1932.  
  1933.                     $o[]=$pre;
  1934.                 }
  1935.  
  1936.                 $o[]=new self('url',array($find['needle']),array());
  1937.  
  1938.                 $pos=$find['offset']+strlen($find['needle']);
  1939.             }
  1940.  
  1941.             if($pos!=strlen($content)) {
  1942.                 $post=substr($content,$pos);
  1943.  
  1944.                 $o[]=$post;
  1945.             }
  1946.  
  1947.         }
  1948.         $this->contents=$o;
  1949.     }
  1950.  
  1951. } #end of UbbTag
  1952.  
  1953. UbbTag::initdata(array(),array(),array(),array());//Load the data
  1954.  
  1955. #end of ubb.php
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement